在很多应用中都需要应用程序主动去查询驱动中是否有数据可读或者是否可以向驱动写入数据,对于单线程的应用,这可能会导致进程阻塞。当然,可以使用select来不断轮询驱动是否可读或可写,但是这并不是很好的解决方法,更好的解决方式是由驱动主动通知应用程序其状态,而不是应用程序主动去查询驱动的状态。
异步通知就类似于回调,应用程序首先向驱动注册一个回调函数,然后应用程序就可以无阻塞地去做其他事情,当驱动认为其已经可以被写入或者被读取时就会调用应用程序之前注册的回调函数,从而实现异步通知。下面以一个简单的例子说明驱动中异步通知的用法。
新建eclipse工程,具体过程见前面的文章,编写异步通知驱动程序,为了方便说明,这里使用内核定时器的超时操作作为触发异步通知的行为(实际使用中应根据具体的时机来触发),驱动程序比较简单,直接上代码:
1 #include <linux/miscdevice.h> 2 #include <linux/delay.h> 3 #include <linux/kernel.h> 4 #include <linux/module.h> 5 #include <linux/init.h> 6 #include <linux/mm.h> 7 #include <linux/fs.h> 8 #include <linux/types.h> 9 #include <linux/delay.h> 10 #include <linux/moduleparam.h> 11 #include <linux/errno.h> 12 #include <linux/ioctl.h> 13 #include <linux/cdev.h> 14 #include <linux/string.h> 15 #include <linux/list.h> 16 #include <linux/poll.h> 17 #include <linux/fcntl.h> 18 #include <linux/signal.h> //for SIGIO,POLL_IN 19 #include <linux/timer.h> 20 #include <linux/jiffies.h> 21 22 //设备名 23 #define DEVICE_NAME "sync" 24 //主设备号 25 #define DEVICE_MAJOR 228 26 27 //异步通知结构变量 28 struct fasync_struct *async_queue; 29 //内核定时器 30 struct timer_list my_timer; 31 32 33 //定时器超时函数 34 static void timer_function(unsigned long arg) 35 { 36 //发送信号到用户空间,POLL_IN表示可写 37 kill_fasync(&async_queue, SIGIO, POLL_IN); 38 } 39 40 //用户空间调用fcntl函数时会调用这个函数 41 static int async_fasync(int fd, struct file *filp, int mode) 42 { 43 //根据用户空间的需要,获取或设置相应的属性 44 return fasync_helper(fd, filp, mode, &async_queue); 45 } 46 47 static int async_open(struct inode *node,struct file *flip) 48 { 49 //初始化定时器 50 init_timer(&my_timer); 51 my_timer.data = 0; 52 //定时器超时函数 53 my_timer.function = &timer_function; 54 //定时器超时时间(当前时间的后1000个时钟滴答) 55 my_timer.expires = jiffies + 5*200; 56 //启动定时器 57 add_timer(&my_timer); 58 59 return 0; 60 } 61 62 63 static struct file_operations async_fops = 64 { 65 .owner = THIS_MODULE, 66 .fasync = async_fasync, 67 .open = async_open, 68 }; 69 70 71 static int __init dev_init(void) 72 { 73 int ret; 74 75 //注册字符设备 76 ret = register_chrdev(DEVICE_MAJOR , DEVICE_NAME, &async_fops); 77 if (ret < 0) 78 { 79 printk(DEVICE_NAME " can't register\n"); 80 return ret; 81 } 82 else 83 { 84 printk(DEVICE_NAME " register successful\n"); 85 return 0; 86 } 87 88 } 89 90 91 static void __exit dev_exit(void) 92 { 93 //撤销字符设备 94 unregister_chrdev(DEVICE_MAJOR , DEVICE_NAME); 95 printk("unregister success \n"); 96 } 97 98 99 module_init(dev_init); 100 module_exit(dev_exit); 101 MODULE_LICENSE("GPL"); 102 MODULE_AUTHOR("LKN@SCUT");
编译成功后会生成fasync.ko文件,先加载该驱动:
#insmod fasync.ko
然后,在/dev目录下创建设备文件:
#mknod sync c 228 1
接着,编写应用程序:
1 #include <unistd.h> 2 #include <sys/stat.h> 3 #include <fcntl.h> 4 #include <stdlib.h> 5 #include <stdio.h> 6 #include <signal.h> 7 #include <string.h> 8 9 //是否已接收到信号标志 10 int flag = 0; 11 12 //信号处理函数定义,由内核调用执行 13 void sig_handler(int signo) 14 { 15 if (signo==SIGIO) 16 { 17 printf("receive successful!!!\n"); 18 flag = 1; 19 } 20 } 21 22 int main() 23 { 24 struct sigaction action; 25 int async_fd; 26 27 memset(&action, 0, sizeof(action)); 28 //信号处理函数 29 action.sa_handler = sig_handler; 30 action.sa_flags = 0; 31 //注册信号类型 32 sigaction(SIGIO, &action, NULL); 33 34 async_fd = open("/dev/sync", O_RDONLY); 35 if(async_fd < 0) 36 { 37 printf("can not open /dev/sync \n"); 38 return -1; 39 } 40 41 //告诉驱动当前进程的PID 42 fcntl(async_fd, F_SETOWN, getpid()); 43 //设置驱动的FASYNC属性,支持异步通知 44 fcntl(async_fd, F_SETFL, fcntl(async_fd, F_GETFL) | FASYNC); 45 46 printf("waiting for receive...\n"); 47 48 while(!flag) 49 { 50 } 51 52 close(async_fd); 53 54 return 0; 55 }
编译之:
gcc -o fasync_test fasync_test.c
运行应用程序,效果如图: