在很多應用中都需要應用程序主動去查詢驅動中是否有數據可讀或者是否可以向驅動寫入數據,對於單線程的應用,這可能會導致進程阻塞。當然,可以使用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
運行應用程序,效果如圖: