linux驱动开发学习笔记二十一:异步通知

一、异步通知简介

我们首先来回顾一下“中断”,中断是处理器提供的一种异步机制,我们配置好中断以后就可以让处理器去处理其他的事情了,当中断发生以后会触发我们事先设置好的中断服务函数,在中断服务函数中做具体的处理。比如我们在裸机篇里面编写的 GPIO 按键中断实验,我们通过按键去开关蜂鸣器,采用中断以后处理器就不需要时刻的去查看按键有没有被按下,因为按键按下以后会自动触发中断。同样的,Linux 应用程序可以通过阻塞或者非阻塞这两种方式来访问驱动设备,通过阻塞方式访问的话应用程序会处于休眠态,等待驱动设备可以使用,非阻塞方式的话会通过 poll 函数来不断的轮询,查看驱动设备文件是否可以使用。这两种方式都需要应用程序主动的去查询设备的使用情况,如果能提供一种类似中断的机制,当驱动程序可以访问的时候主动告诉应用程序那就最好了。

“信号”为此应运而生,信号类似于我们硬件上使用的“中断”,只不过信号是软件层次上的。算是在软件层次上对中断的一种模拟,驱动可以通过主动向应用程序发送信号的方式来报告自己可以访问了,应用程序获取到信号以后就可以从驱动设备中读取或者写入数据了。整个过程就相当于应用程序收到了驱动发送过来了的一个中断,然后应用程序去响应这个中断,在整个处理过程中应用程序并没有去查询驱动设备是否可以访问,一切都是由驱动设备自己告诉给应用程序的。

异步通知的核心就是信号,在 arch/xtensa/include/uapi/asm/signal.h文件中定义了 Linux 所支持的所有信号,这些信号如下所示:

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

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

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

sighandler_t signal(int signum, sighandler_t handler)

函数参数和返回值含义如下:
signum:要设置处理函数的信号。
handler:信号的处理函数。
返回值:设置成功的话返回信号的前一个处理函数,设置失败的话返回 SIG_ERR。

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

typedef void (*sighandler_t)(int)

我们前面讲解的使用“kill -9 PID”杀死指定进程的方法就是向指定的进程(PID)发送SIGKILL 这个信号。当按下键盘上的 CTRL+C 组合键以后会向当前正在占用终端的应用程序发出 SIGINT 信号,SIGINT 信号默认的动作是关闭当前应用程序。

二、驱动中的信号处理

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;
};

一般将 fasync_struct 结构体指针变量定义到设备结构体中:

1 struct imx6uirq_dev { 
2 			struct device *dev; 
3 			struct class *cls;
4 			struct cdev cdev;
......
14 			struct fasync_struct *async_queue; /* 异步相关结构体 */
15 };

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 函数就会执行。

1 struct xxx_dev { 
2 			......
3 			struct fasync_struct *async_queue; /* 异步相关结构体 */
4 };
5
6 static int xxx_fasync(int fd, struct file *filp, int on) 
7 { 
8 			struct xxx_dev *dev = (xxx_dev)filp->private_data; 
9 
10			if (fasync_helper(fd, filp, on, &dev->async_queue) < 0)
11 				return -EIO;
12 			return 0;
13 }
14
15 static struct file_operations xxx_ops = {
16 			......
17 			.fasync = xxx_fasync,
18 			......
19 };

在关闭驱动文件的时候需要在 file_operations 操作集中的 release 函数中释放 fasync_struct,fasync_struct 的释放函数同样为 fasync_helper,release 函数参数参考实例如下:

1 static int xxx_release(struct inode *inode, struct file *filp) 
2 { 
3 			return xxx_fasync(-1, filp, 0); /* 删除异步通知 */
4 }
5
6 static struct file_operations xxx_ops = { 
7 			......
8 			.release = xxx_release, 9 };

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

3、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、注册信号处理函数

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

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

使用如下语句将本应用程序的进程号告诉给内核。

fcntl(fd, F_SETOWN, getpid())

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

flags = fcntl(fd, F_GETFL); /* 获取当前的进程状态 */
fcntl(fd, F_SETFL, flags | FASYNC); /* 开启当前进程异步通知功能 */

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

四、实例程序(伪代码)

  • 驱动程序
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
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 }
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 }
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 };
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 }
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");

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

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

第 269-273 行,imx6uirq_fasync 函数,为 file_operations 操作集中的 fasync 函数,此函数内容很简单,就是调用一下 fasync_helper。 第 281~284 行,release 函数,应用程序调用 close 函数关闭驱动设备文件的时候此函数就会执行,在此函数中释放掉fasync_struct 指针变量。

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

  • 应用程序
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 }
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 }

第 32~43 行,sigio_signal_func 函数,SIGIO 信号的处理函数,当驱动程序有效按键按下以后就会发送 SIGIO 信号,此函数就会执行。此函数通过 read 函数读取按键值,然后通过printf 函数打印在终端上。

第 69 行,通过 signal 函数设置 SIGIO 信号的处理函数为 sigio_signal_func。 第 71~73 行,设置当前进程的状态,开启异步通知的功能。

第 75~77 行,while 循环,等待信号产生。

猜你喜欢

转载自blog.csdn.net/qq_39507748/article/details/106026950