当前位置: 首页 > news >正文

异步通知实验

        在前面使用阻塞或者非阻塞的方式来读取驱动中按键值都是应用程序主动读取的,对于非

阻塞方式来说还需要应用程序通过poll函数不断的轮询。最好的方式就是驱动程序能主动向应

用程序发出通知,报告自己可以访问,然后应用程序在从驱动程序中读取或写入数据,类似于

我们在裸机例程中讲解的中断。Linux提供了异步通知这个机制来完成此功能,本章我们就来

学习一下异步通知以及如何在驱动中添加异步通知相关处理代码。

1 异步通知

1.1 异步通知简介

        我们首先来回顾一下“中断”,中断是处理器提供的一种异步机制,我们配置好中断以后就

可以让处理器去处理其他的事情了,当中断发生以后会触发我们事先设置好的中断服务函数,

在中断服务函数中做具体的处理。比如我们在裸机篇里面编写的GPIO按键中断实验,我们通

过按键去开关蜂鸣器,采用中断以后处理器就不需要时刻的去查看按键有没有被按下,因为按

键按下以后会自动触发中断。同样的,Linux应用程序可以通过阻塞或者非阻塞这两种方式来

访问驱动设备,通过阻塞方式访问的话应用程序会处于休眠态,等待驱动设备可以使用,非阻

塞方式的话会通过poll函数来不断的轮询,查看驱动设备文件是否可以使用。这两种方式都需

要应用程序主动的去查询设备的使用情况,如果能提供一种类似中断的机制,当驱动程序可以

访问的时候主动告诉应用程序那就最好了。

        “信号”为此应运而生,信号类似于我们硬件上使用的“中断”,只不过信号是软件层次上

的。算是在软件层次上对中断的一种模拟,驱动可以通过主动向应用程序发送信号的方式来报

告自己可以访问了,应用程序获取到信号以后就可以从驱动设备中读取或者写入数据了。整个

过程就相当于应用程序收到了驱动发送过来了的一个中断,然后应用程序去响应这个中断,在

整个处理过程中应用程序并没有去查询驱动设备是否可以访问,一切都是由驱动设备自己告诉

给应用程序的。

        阻塞、非阻塞、异步通知,这三种是针对不同的场合提出来的不同的解决方法,没有优劣

之分,在实际的工作和学习中,根据自己的实际需求选择合适的处理方法即可。

        异步通知的核心就是信号,在arch/xtensa/include/uapi/asm/signal.h文件中定义了Linux所支

持的所有信号,这些信号如下所示:

信号常量定义整理
#define SIGHUP      1
终端挂起或控制进程终止
#define SIGINT      2
终端中断(Ctrl+C组合键)
#define SIGQUIT     3
终端退出(Ctrl+\组合键)
#define SIGILL      4
非法指令
#define SIGTRAP     5
debug使用,有断点指令产生
#define SIGABRT     6
由abort(3)发出的退出指令
#define SIGIOT      6
IOT指令
#define SIGBUS      7
总线错误
#define SIGFPE      8
浮点运算错误
#define SIGKILL     9
杀死、终止进程
#define SIGUSR1    10
用户自定义信号1
#define SIGSEGV    11
段违例(无效的内存段)
#define SIGUSR2    12
用户自定义信号2
#define SIGPIPE    13
向非读管道写入数据
#define SIGALRM    14
闹钟
#define SIGTERM    15
软件终止
#define SIGSTKFLT  16
栈异常
#define SIGCHLD    17
子进程结束
#define SIGCONT    18
进程继续
#define SIGSTOP    19
停止进程的执行,只是暂停
#define SIGTSTP    20
停止进程的运行(Ctrl+Z组合键)
#define SIGTTIN    21
后台进程需要从终端读取数据
#define SIGTTOU    22
后台进程需要向终端写数据
#define SIGURG     23
有"紧急"数据
#define SIGXCPU    24
超过CPU资源限制
#define SIGXFSZ    25
文件大小超额
#define SIGVTALRM  26
虚拟时钟信号
#define SIGPROF    27
时钟信号描述
#define SIGWINCH   28
窗口大小改变
#define SIGIO      29
可以进行输入/输出操作
#define SIGPOLL    SIGIO
#define SIGPWR     30
断点重启
#define SIGSYS     31
非法的系统调用
#define SIGUNUSED  31
未使用信号

        在示例代码中的这些信号中,除了SIGKILL(9)和SIGSTOP(19)这两个信号不能被忽略外,其他的信号都可以忽略。这些信号就相当于中断号,不同的中断号代表了不同的中断,不同的中断所做的处理不同,因此,驱动程序可以通过向应用程序发送不同的信号来实现不同的功能。

        我们使用中断的时候需要设置中断处理函数,同样的,如果要在应用程序中使用信号,那么就必须设置信号所使用的信号处理函数,在应用程序中使用signal函数来设置指定信号的处理函数,signal函数原型如下所示:

sighandler_t signal(int signum, sighandler_t handler) 

信号处理函数原型如下所示:

typedef void (*sighandler_t)(int) 

        我们前面讲解的使用“kill -9 PID”杀死指定进程的方法就是向指定的进程(PID)发送SIGKILL这个信号。当按下键盘上的CTRL+C组合键以后会向当前正在占用终端的应用程序发出SIGINT信号,SIGINT信号默认的动作是关闭当前应用程序。这里我们修改一下SIGINT信号的默认处理函数,当按下CTRL+C组合键以后先在终端上打印出“SIGINT signal!”这行字符串,然后再关闭当前应用程序。新建signaltest.c文件,然后输入如下所示内容:

1  #include "stdlib.h" 
2  #include "stdio.h" 
3  #include "signal.h" 
4   
5  void sigint_handler(int num) 
6  { 
7      printf("\r\nSIGINT signal!\r\n"); 
8      exit(0); 
9  } 
10  
11 int main(void) 
12 { 
13     signal(SIGINT, sigint_handler); 
14     while(1); 
15     return 0; 
16 } 

}

        在示例代码中我们设置SIGINT信号的处理函数为sigint_handler,当按下CTRL+C向signaltest发送SIGINT信号以后sigint_handler函数就会执行,此函数先输出一行“SIGINTsignal!”字符串,然后调用exit函数关闭signaltest应用程序。

        使用如下命令编译signaltest.c:

gcc signaltest.c -o signaltest 

        然后输入“./signaltest”命令打开signaltest这个应用程序,然后按下键盘上的CTRL+C组

合键,结果如图所示:

        从图可以看出,当按下CTRL+C组合键以后sigint_handler这个SIGINT信号处理函数执行了,并且输出了“SIGINT signal!”这行字符串。

1.2 驱动中的信号处理

1、fasync_struct结构体

        首先我们需要在驱动程序中定义一个fasync_struct结构体指针变量,fasync_struct结构体内容如下:

struct fasync_struct {spinlock_t      fa_lock;int             magic;int             fa_fd;struct fasync_struct *fa_next;struct file     *fa_file;struct rcu_head fa_rcu;
};

2、fasync函数

        如果要使用异步通知,需要在设备驱动中实现file_operations操作集中的fasync函数,此函数格式如下所示:

int (*fasync) (int fd, struct file *filp, int on) 

        fasync函数里面一般通过调用fasync_helper函数来初始化前面定义的fasync_struct结构体指针,fasync_helper函数原型如下:

int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp) 

        fasync_helper函数的前三个参数就是fasync函数的那三个参数,第四个参数就是要初始化

的fasync_struct结构体指针变量。当应用程序通过“fcntl(fd, F_SETFL, flags | FASYNC)”改变

fasync标记的时候,驱动程序file_operations操作集中的fasync函数就会执行。

        驱动程序中的fasync函数参考示例如下:

struct xxx_dev {struct fasync_struct *async_queue;  /* 异步相关结构体 */
};static int xxx_fasync(int fd, struct file *filp, int on)
{struct xxx_dev *dev = (xxx_dev)filp->private_data;if (fasync_helper(fd, filp, on, &dev->async_queue) < 0)return -EIO;return 0;
}static struct file_operations xxx_ops = {.fasync = xxx_fasync,
};

        在关闭驱动文件的时候需要在file_operations操作集中的release函数中释放fasync_struct,

fasync_struct的释放函数同样为fasync_helper,release函数参数参考实例如下:

static int xxx_release(struct inode *inode, struct file *filp)
{return xxx_fasync(-1, filp, 0); /* 删除异步通知 */
}static struct file_operations xxx_ops = {.......release = xxx_release,
};

        第3行通过调用xxx_fasync函数来完成fasync_struct的释放工作,但是,其最终还是通过fasync_helper函数完成释放工作。

1、kill_fasync函数

        当设备可以访问的时候,驱动程序需要向应用程序发出信号,相当于产生“中断”。kill_fasync

函数负责发送指定的信号,kill_fasync函数原型如下所示:

void kill_fasync(struct fasync_struct **fp, int sig, int band) 

函数参数和返回值含义如下:

fp:要操作的fasync_struct。

sig:要发送的信号。

band:可读时设置为POLL_IN,可写时设置为POLL_OUT。

返回值:无。

1.3 应用程序对异步通知的处理

应用程序对异步通知的处理包括以下三步:

1、注册信号处理函数

应用程序根据驱动程序所使用的信号来设置信号的处理函数,应用程序使用signal函数来

设置信号的处理函数。前面已经详细的讲过了,这里就不细讲了。

2、将本应用程序的进程号告诉给内核

使用fcntl(fd, F_SETOWN, getpid())将本应用程序的进程号告诉给内核。

3、开启异步通知

使用如下两行程序开启异步通知:

flags = fcntl(fd, F_GETFL);
/* 获取文件描述符的当前状态标志 */fcntl(fd, F_SETFL, flags | FASYNC);
/* 启用文件描述符的异步通知功能(FASYNC标志) */

        重点就是通过fcntl函数设置进程状态为FASYNC,经过这一步,驱动程序中的fasync函

数就会执行。

2.实验程序编写

1   #include <linux/types.h> 
...... 
20  #include <linux/fcntl.h> 
21  #include <asm/mach/map.h> 
22  #include <asm/uaccess.h> 
23  #include <asm/io.h> 
24  /*************************************************************** 
25  Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved. 
26  文件名   : asyncnoti.c 
27  作者    : 左忠凯 
28  版本    : V1.0 
29  描述    : 非阻塞IO访问 
30  其他     : 无 
31  论坛    : www.openedv.com 
32  日志    : 初版V1.0 2019/8/13 左忠凯创建 
33  ***************************************************************/ 
34  #define IMX6UIRQ_CNT     1              
/* 设备号个数     
*/ 
35  #define IMX6UIRQ_NAME    "asyncnoti"   /* 名字           
*/ 
36  #define KEY0VALUE         0X01          
/* KEY0按键值      */ 
37  #define INVAKEY           0XFF          
/* 无效的按键值    
*/ 
38  #define KEY_NUM           1              
/* 按键数量        
*/ 
......  
49  /* imx6uirq设备结构体 */ 
50  struct imx6uirq_dev{ 
...... 
64      struct fasync_struct *async_queue;      /* 异步相关结构体 */ 
65  }; 
66   
67  struct imx6uirq_dev imx6uirq;   /* irq设备 */ 
68   
...... 
84   
85  /* @description   : 定时器服务函数,用于按键消抖,定时器到了以后 
86   *                   再次读取按键值,如果按键还是处于按下状态就表示按键有效。 
87   * @param - arg   : 设备结构变量 
88   * @return        : 无 
89   */ 
90  void timer_function(unsigned long arg) 
91  { 
92      unsigned char value; 
93      unsigned char num; 
94      struct irq_keydesc *keydesc; 
95      struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg; 
......              
109     if(atomic_read(&dev->releasekey)) {     /* 一次完整的按键过程 */ 
110         if(dev->async_queue)  
111             kill_fasync(&dev->async_queue, SIGIO, POLL_IN);  
112     } 
113  
114 #if 0 
115     /* 唤醒进程 */ 
116     if(atomic_read(&dev->releasekey)) {   /* 完成一次按键过程  */ 
117         /* wake_up(&dev->r_wait); */ 
118         wake_up_interruptible(&dev->r_wait); 
119     } 
120 #endif 
121 } 
...... 
262 /* 
263  * @description   : fasync函数,用于处理异步通知 
264  * @param - fd    : 文件描述符 
265  * @param - filp  : 要打开的设备文件(文件描述符) 
266  * @param - on    : 模式 
267  * @return         : 负数表示函数执行失败 
268  */ 
269 static int imx6uirq_fasync(int fd, struct file *filp, int on) 
270 { 
271     struct imx6uirq_dev *dev = (struct imx6uirq_dev *) 
filp->private_data; 
272     return fasync_helper(fd, filp, on, &dev->async_queue); 
273 } 
274  
275 /* 
276  * @description  : release函数,应用程序调用close关闭驱动文件的时候会执行 
277  * @param – inode : inode节点 
278  * @param – filp : 要打开的设备文件(文件描述符) 
279  * @return         : 负数表示函数执行失败 
280  */ 
281 static int imx6uirq_release(struct inode *inode, struct file *filp) 
282 { 
283     return imx6uirq_fasync(-1, filp, 0); 
284 } 
285  
286 /* 设备操作函数 */ 
287 static struct file_operations imx6uirq_fops = { 
288     .owner = THIS_MODULE, 
289     .open = imx6uirq_open, 
290     .read = imx6uirq_read, 
291     .poll = imx6uirq_poll, 
292     .fasync = imx6uirq_fasync, 
293     .release = imx6uirq_release, 
294 }; 
295  
296 /* 
297  * @description   : 驱动入口函数 
298  * @param         : 无 
299  * @return        : 无 
300  */ 
301 static int __init imx6uirq_init(void) 
302 { 
...... 
328          
329     /* 5、始化按键 */ 
330     atomic_set(&imx6uirq.keyvalue, INVAKEY); 
331     atomic_set(&imx6uirq.releasekey, 0); 
332     keyio_init(); 
333     return 0; 
334 } 
335  
336 /* 
337  * @description   : 驱动出口函数 
338  * @param         : 无 
339  * @return        : 无 
340  */ 
341 static void __exit imx6uirq_exit(void) 
342 { 
343     unsigned i = 0; 
...... 
354     class_destroy(imx6uirq.class); 
355 }    
356      
357 module_init(imx6uirq_init); 
358 module_exit(imx6uirq_exit); 
359 MODULE_LICENSE("GPL"); 

第20行,添加fcntl.h头文件,因为要用到相关的API函数。

第64行,在设备结构体imx6uirq_dev中添加fasync_struct指针变量。

第109~112行,如果是一次完整的按键过程,那么就通过kill_fasync函数发送SIGIO信号。

第114~120行,屏蔽掉以前的唤醒进程相关程序。

第269~273行,imx6uirq_fasync 函数,为file_operations操作集中的fasync函数,此函数内容很简单,就是调用一下fasync_helper。

第281~284行,release函数,应用程序调用close函数关闭驱动设备文件的时候此函数就会执行,在此函数中释放掉fasync_struct指针变量。

第292~293行,设置file_operations操作集中的fasync和release这两个成员变量。

测试APP编写

1  #include "stdio.h" 
2  #include "unistd.h" 
3  #include "sys/types.h" 
4  #include "sys/stat.h" 
5  #include "fcntl.h" 
6  #include "stdlib.h" 
7  #include "string.h" 
8  #include "poll.h" 
9  #include "sys/select.h" 
10 #include "sys/time.h" 
11 #include "linux/ioctl.h" 
12 #include "signal.h" 
13 /*************************************************************** 
14 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved. 
15 文件名       : asyncnotiApp.c 
16 作者         : 左忠凯 
17 版本         : V1.0 
18 描述         : 异步通知测试APP 
19 其他         : 无 
20 使用方法    :./asyncnotiApp /dev/asyncnoti 打开测试App 
21 论坛         : www.openedv.com 
22 日志         : 初版V1.0 2019/8/13 左忠凯创建 
23 ***************************************************************/ 
24  
25 static int fd = 0;   /* 文件描述符 */ 
26  
27 /* 
28  * SIGIO信号处理函数 
29  * @param - signum    : 信号值 
30  * @return             : 无 
31  */ 
32 static void sigio_signal_func(int signum) 
33 { 
34     int err = 0; 
35     unsigned int keyvalue = 0; 
36  
37     err = read(fd, &keyvalue, sizeof(keyvalue)); 
38     if(err < 0) { 
39         /* 读取错误 */ 
40     } else { 
41         printf("sigio signal! key value=%d\r\n", keyvalue); 
42     } 
43 } 
44  
45 /* 
46  * @description    : main主程序 
47  * @param - argc   : argv数组元素个数 
48  * @param - argv   : 具体参数 
49  * @return          : 0 成功;其他 失败 
50  */ 
51 int main(int argc, char *argv[]) 
52 { 
53     int flags = 0; 
54     char *filename; 
55  
56     if (argc != 2) { 
57         printf("Error Usage!\r\n"); 
58         return -1; 
59     } 
60  
61     filename = argv[1]; 
62     fd = open(filename, O_RDWR); 
63     if (fd < 0) { 
64         printf("Can't open file %s\r\n", filename); 
65         return -1; 
66     } 
67  
68     /* 设置信号SIGIO的处理函数 */ 
69     signal(SIGIO, sigio_signal_func); 
70   
71     fcntl(fd, F_SETOWN, getpid());  /* 将当前进程的进程号告诉给内核  */ 
72     flags = fcntl(fd, F_GETFD);     /* 获取当前的进程状态          */ 
73     fcntl(fd, F_SETFL, flags | FASYNC);/* 设置进程启用异步通知功能  */   
74  
75     while(1) { 
76         sleep(2); 
77     } 
78  
79     close(fd); 
80     return 0; 
81 }

http://www.dtcms.com/a/393098.html

相关文章:

  • 用 C 语言模拟面向对象编程
  • 联邦学习论文分享:FedKTL
  • 智能体分类:从反应式到混合式的架构演进与实践
  • 【面板数据】上市公司企业ZF连接度数据集(1991-2024年)
  • 让codex像 cladue code一样 自动牛马
  • NeurIPS 2025 spotlight Autonomous Driving VLA World Model FSDrive
  • 多线程JUC
  • Qwen3技术之模型后训练
  • 服务端实现
  • 深入AQS源码:解密Condition的await与signal
  • ceph存储配置大全
  • 数据库造神计划第十六天---索引(1)
  • 【软件推荐】免费图片视频管理工具,让灵感库告别混乱
  • C语言入门教程 | 阶段二:循环语句详解(while、do...while、for)
  • GEO(Generative Engine Optimization)完全指南:从原理到实践
  • Msyql日期时间总结
  • IP地址入门基础
  • 【ROS2】Beginner: CLI tools
  • LeetCode刷题记录----279.完全平方数(Medium)
  • H7-TOOL的250M示波器模组采集CANFD标准波形效果,开口逻辑0,闭口逻辑1
  • 打工人日报#20250920
  • 详解C/C++内存管理
  • SSM(springboot部分)
  • C++ std:string和Qt的QString有哪些差异?
  • FunASR开源项目实战:解锁语音识别新姿势
  • (华为杯)数学建模比赛编程助手
  • 通义千问对postgresql wire协议的连接和执行SQL过程的解释
  • 钣金折弯机被远程锁机了怎么办
  • 基于陌讯AIGC检测算法的高性能部署实践:FastAPI与多进程并发设计详解
  • 群晖 NAS 远程访问痛点解决:神卓 N600 公网 IP 盒实战体验