1.概念:
異步通知機制:一旦設備就緒,則主動通知應用程序,這樣應用程序根本就不需要查詢設備狀態,是一種“信號驅動的異步I/O”。信號是在軟件層次上對中斷機制的一種模擬,在原理上,一個進程收到一個信號與處理器收到一個中斷請求可以說是一樣的。信號是異步的,一個進程不必通過任何操作來等待信號的到達,事實上,進程也不知道信號到底什么時候會到達。
2.我們試圖通過兩個方面來分析異步通知機制:
從用戶程序的角度考慮:為了啟動文件的異步通知機制,用戶程序必須執行兩個步驟。首先,他們指定一個進程作為文件的“屬主(owner)”。當進程使用fcntl系統調用執行
F_SETOWN命令時,屬主進程的進程ID號就被保存在filp->f_owner中。這一步是必需的,目的是為了讓內核知道應該通知哪個進程。 然后為了真正啟動異步通知機制,用戶程序還必須
在設備中設置FASYNC標志,這通過fcntl的F_SETFL命令完成的。 執行完這兩個步驟之后,輸入文件就可以在新數據到達時請求發送一個SIGIO信號。該信號被發送到存放在filp-
>f_owner中的進程(如果是負值就是進程組)。
在用戶程序中,為了捕獲信號,可以使用signal()函數來設置對應信號的處理函數:
1 void (*signal(int signum, void (*handler))(int)))(int);
該函數原型較難理解, 它可以分解為:
1 typedef void (*sighandler_t)(int); //消息處理函數 2 sighandler_t signal(int signum, sighandler_t handler)); //連接信號與消息處理函數
第一個參數指定信號的值,第二個參數指定針對前面信號值的處理函數,若為SIG_IGN,表示忽略該信號;若為SIG_DFL,表示采用系統默認方式處理信號;若為用戶自定義的函
數,則信號被捕獲到后,該函數將被執行。如果signal()調用成功,它返回最后一次為信號signum綁定的處理函數的handler值,失敗則返回SIG_ERR。
1 fcntl(STDIN_FILENO, F_SETOWN, getpid()); //設置本進程為STDIN_FILENO文件的擁有者,沒有這一步,內核不會知道應該將信號發給哪個進程 2 oflags = fcntl(STDIN_FILENO, F_GETFL); //獲取設備文件的f_flags 3 fcntl(STDIN_FILENO, F_SETFL, oflags | FASYNC); //為了啟用異步通知機制,還需對設備設置FASYNC標志
我們先通過內核源碼,剖析上面的實現原理。
1 app:fcntl() 2 kernel:sys_fcntl() 3 do_fcntl() 4 switch (cmd) { 5 …… 6 case F_GETFL: 7 err = filp->f_flags; //返回文件標志 8 break; 9 case F_SETFL: 10 err = setfl(fd, filp, arg); //轉調用setfl函數 11 break; 12 …… 13 case F_SETOWN: 14 err = f_setown(filp, arg, 1); //轉調用f_setown函數 15 break; 16 …… 17 default: 18 break; 19 } 20 return err;
//來看看f_setown函數的內部實現:設置文件的屬主進程
1 int f_setown(struct file *filp, unsigned long arg, int force) 2 { 3 ... 4 pid = find_pid(who); //獲取當前進程的pid 5 result = __f_setown(filp, pid, type, force); //內部主要調用f_modown函數 6 ... 7 } 8 static void f_modown(struct file *filp, struct pid *pid, enum pid_type type,uid_t uid, uid_t euid, int force) 9 { 10 ... 11 if (force || !filp->f_owner.pid) { //設置對應的pid,uid,euid 12 put_pid(filp->f_owner.pid); 13 filp->f_owner.pid = get_pid(pid); 14 filp->f_owner.pid_type = type; 15 filp->f_owner.uid = uid; 16 filp->f_owner.euid = euid; 17 } 18 ... 19 }
//再來看看setfl函數的內部實現:
1 static int setfl(int fd, struct file * filp, unsigned long arg) 2 { 3 ... 4 if ((arg ^ filp->f_flags) & FASYNC) { //也就是說FASYNC標志從0變為1的時候,才為真。 5 if (filp->f_op && filp->f_op->fasync) { 6 error = filp->f_op->fasync(fd, filp, (arg & FASYNC) != 0); //調用的就是驅動程序的fasync()函數 7 if (error < 0) 8 goto out; 9 } 10 } 11 ... 12 }
從驅動程序角度考慮:
應用程序在執行F_SETFL啟用FASYNC時,調用驅動程序的fasync方法。只要filp->f_flags中的FASYNC標識發生了變化,就會調用該方法,以便把這個變化通知驅動程序,使其能
正確響應。文件打開時,FASYNC標志被默認為是清除的。當數據到達時,所有注冊為異步通知的進程都會被發送一個SIGIO信號。
Linux的這種通用方法基於一個數據結構和兩個函數:
1 extern int fasync_helper(int, struct file *, int, struct fasync_struct **); 2 //當一個打開的文件的FASYNC標志被修改時,調用驅動程序的fasync方法間接調用fasync_helper函數以便將當前進程加入到驅動程序的異步通知等待隊列中。 3 extern void kill_fasync(struct fasync_struct **, int, int); 4 //當設備可訪問時,可使用kill_fasync函數發信號所有的相關進程。進程進而調用綁定的消息處理函數。
//分析fasync_helper的內部實現
1 int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp) 2 { 3 struct fasync_struct *fa, **fp; 4 struct fasync_struct *new = NULL; 5 int result = 0; 6 if (on) { 7 new = kmem_cache_alloc(fasync_cache, SLAB_KERNEL);//創建對象,slab分配器 8 if (!new) 9 return -ENOMEM; 10 } 11 write_lock_irq(&fasync_lock); 12 //遍歷整個異步通知隊列,看是否存在對應的文件指針 13 for (fp = fapp; (fa = *fp) != NULL; fp = &fa->fa_next) { 14 if (fa->fa_file == filp) {//已存在 15 if(on) { 16 fa->fa_fd = fd;//文件描述符賦值 //注:不明白為什么這里只需要更新文件描述符,而不需要更新文件指針 17 kmem_cache_free(fasync_cache, new);//銷毀剛創建的對象 18 } else { 19 *fp = fa->fa_next;//繼續遍歷 20 kmem_cache_free(fasync_cache, fa);//刪除非目標對象 此用於應用程序屏蔽異步通知. 21 result = 1; 22 } 23 goto out;//找到了 24 } 25 } 26 //看到下面可以得知,所謂的把進程添加到異步通知隊列中 27 //實則是將文件指針關聯到異步結構體對象,然后將該對象掛載在異步通知隊列中(等待隊列也是這個原理) 28 //那么最后發送信號又是怎么知道是哪個進程的呢?我們看后面的kill_fasync函數。 29 if (on) {//不存在 30 new->magic = FASYNC_MAGIC; 31 new->fa_file = filp;//指定文件指針 32 new->fa_fd = fd;//指定文件描述符 33 new->fa_next = *fapp;//掛載在異步通知隊列中 34 *fapp = new;//掛載 35 result = 1; 36 } 37 out: 38 write_unlock_irq(&fasync_lock); 39 return result; 40 }
//看看kill_fasync函數是怎么將信號通知指定進程的:
1 void __kill_fasync(struct fasync_struct *fa, int sig, int band) 2 { 3 while (fa) { 4 ... 5 fown = &fa->fa_file->f_owner;//這里便是回答上面的問題,如果知道是哪個進程的,通過異步對象的文件指針知道其屬主進程 6 /* Don't send SIGURG to processes which have not set a queued signum: SIGURG has its own default signallingmechanism. */ 7 if (!(sig == SIGURG && fown->signum == 0)) 8 send_sigio(fown, fa->fa_fd, band);//發送信號 9 fa = fa->fa_next; 10 ... 11 } 12 }
總結:應用程序使用fcntl()設置當前進程的pid和FASYNC標志。進而調用驅動程序的fasync(),即fasync_helper()。然后申請和設置fasync_struct結構,將此結構掛載到驅動程序
的fasync_struct結構鏈表中。當設備可用時,驅動程序會使用kill_fasync(),從fasync_struct鏈表中,查找所有的等待進程,然后調用send_sigio發送相應的消息給進程。進程接收到
消息,就會跳轉到與消息綁定的消息處理函數中。
實例:基於<<Linux設備驅動開發詳解:基於最新的Linux4.0內核.pdf>>第9.3章節
驅動程序源碼.

1 #include <linux/module.h> 2 #include <linux/fs.h> 3 #include <linux/init.h> 4 #include <linux/cdev.h> 5 #include <linux/slab.h> 6 #include <linux/uaccess.h> 7 #include <linux/poll.h> 8 9 #define GLOBALMEM_SIZE 0x1000 10 //#define GLOBALMEM_SIZE 0x10 11 #define GLOBALMEM_MAJOR 230 12 #define GLOBALMEM_MAGIC 'g' 13 //#define MEM_CLEAR _IO(GLOBALMEM_MAGIC,0) 14 #define MEM_CLEAR (0x01) 15 static int globalfifo_major = GLOBALMEM_MAJOR; 16 module_param(globalfifo_major, int, S_IRUGO); 17 18 struct globalfifo_dev { 19 struct cdev cdev; 20 unsigned int current_len; 21 unsigned char mem[GLOBALMEM_SIZE]; 22 struct mutex mutex; 23 wait_queue_head_t r_wait; 24 wait_queue_head_t w_wait; 25 struct fasync_struct *queue; 26 }; 27 28 struct globalfifo_dev *globalfifo_devp; 29 30 static int globalfifo_open(struct inode *inode, struct file *filp) 31 { 32 filp->private_data = globalfifo_devp; 33 return 0; 34 } 35 36 static ssize_t globalfifo_read(struct file *filp, char __user * buf, size_t size, 37 loff_t * ppos) 38 { 39 unsigned int count = size; 40 int ret = 0; 41 struct globalfifo_dev *dev = filp->private_data; 42 DECLARE_WAITQUEUE(wait, current); 43 44 mutex_lock(&dev->mutex); 45 add_wait_queue(&dev->r_wait, &wait); 46 47 while(dev->current_len ==0){ 48 if(filp->f_flags & O_NONBLOCK){ 49 ret = -EAGAIN; 50 goto out; 51 } 52 53 set_current_state(TASK_INTERRUPTIBLE); 54 mutex_unlock(&dev->mutex); 55 56 schedule(); 57 if(signal_pending(current)){ 58 ret = -ERESTARTSYS; 59 goto out2; 60 } 61 mutex_lock(&dev->mutex); 62 63 } 64 65 if (count > dev->current_len) 66 count = dev->current_len; 67 68 if (copy_to_user(buf, dev->mem, count)) { 69 ret = -EFAULT; 70 goto out; 71 } else { 72 memcpy(dev->mem, dev->mem+count, dev->current_len - count); 73 dev->current_len -=count; 74 printk(KERN_INFO "read %d bytes(s) current_len %d\n", count, dev->current_len); 75 wake_up_interruptible(&dev->w_wait); 76 77 if(dev->queue){ 78 kill_fasync(&dev->queue, SIGIO, POLL_OUT); 79 printk(KERN_DEBUG "%s kill SIGIO\n", __func__); 80 } 81 ret = count; 82 } 83 out: 84 mutex_unlock(&dev->mutex); 85 out2: 86 remove_wait_queue(&dev->r_wait, &wait); 87 set_current_state(TASK_RUNNING); 88 89 return ret; 90 } 91 92 static ssize_t globalfifo_write(struct file *filp, const char __user * buf, 93 size_t size, loff_t * ppos) 94 { 95 unsigned int count = size; 96 int ret = 0; 97 struct globalfifo_dev *dev = filp->private_data; 98 DECLARE_WAITQUEUE(wait, current); 99 100 mutex_lock(&dev->mutex); 101 add_wait_queue(&dev->w_wait, &wait); 102 103 while(dev->current_len == GLOBALMEM_SIZE){ 104 if(filp->f_flags & O_NONBLOCK){ 105 ret = -EAGAIN; 106 goto out; 107 } 108 109 set_current_state(TASK_INTERRUPTIBLE); 110 mutex_unlock(&dev->mutex); 111 schedule(); 112 if(signal_pending(current)){ 113 ret = -ERESTARTSYS; 114 goto out2; 115 } 116 mutex_lock(&dev->mutex); 117 118 } 119 120 if (count > (GLOBALMEM_SIZE - dev->current_len)) 121 count = (GLOBALMEM_SIZE - dev->current_len); 122 123 if (copy_from_user(dev->mem + dev->current_len, buf, count)){ 124 ret = -EFAULT; 125 goto out; 126 } 127 else { 128 dev->current_len += count; 129 wake_up_interruptible(&dev->r_wait); 130 ret = count; 131 printk(KERN_INFO "written %d bytes(s) current_len %d\n", count, dev->current_len); 132 133 if(dev->queue){ 134 kill_fasync(&dev->queue, SIGIO, POLL_IN); 135 printk(KERN_DEBUG "%s kill SIGIO\n", __func__); 136 } 137 } 138 out: 139 mutex_unlock(&dev->mutex); 140 out2: 141 remove_wait_queue(&dev->w_wait, &wait); 142 set_current_state(TASK_RUNNING); 143 return ret; 144 } 145 static loff_t globalfifo_llseek(struct file *filp, loff_t offset, int orig) 146 { 147 loff_t ret = 0; 148 switch (orig) { 149 case 0: /* ´ÓÎļþ¿ªÍ·Î»ÖÃseek */ 150 if (offset< 0) { 151 ret = -EINVAL; 152 break; 153 } 154 if ((unsigned int)offset > GLOBALMEM_SIZE) { 155 ret = -EINVAL; 156 break; 157 } 158 filp->f_pos = (unsigned int)offset; 159 ret = filp->f_pos; 160 break; 161 case 1: /* ´ÓÎļþµ±Ç°Î»ÖÿªÊ¼seek */ 162 if ((filp->f_pos + offset) > GLOBALMEM_SIZE) { 163 ret = -EINVAL; 164 break; 165 } 166 if ((filp->f_pos + offset) < 0) { 167 ret = -EINVAL; 168 break; 169 } 170 filp->f_pos += offset; 171 ret = filp->f_pos; 172 break; 173 default: 174 ret = -EINVAL; 175 break; 176 } 177 return ret; 178 } 179 static long globalfifo_ioctl(struct file *filp, unsigned int cmd, 180 unsigned long arg) 181 { 182 struct globalfifo_dev *dev = filp->private_data; 183 switch (cmd) { 184 case MEM_CLEAR: 185 mutex_lock(&dev->mutex); 186 memset(dev->mem, 0, GLOBALMEM_SIZE); 187 dev->current_len =0; 188 printk(KERN_INFO "globalfifo is set to zero\n"); 189 mutex_unlock(&dev->mutex); 190 break; 191 default: 192 return -EINVAL; 193 } 194 195 return 0; 196 } 197 static unsigned int globalfifo_poll(struct file * filp, poll_table * wait) 198 { 199 unsigned int mask =0; 200 struct globalfifo_dev *dev = filp->private_data; 201 202 mutex_lock(&dev->mutex); 203 204 poll_wait(filp, &dev->w_wait, wait); 205 poll_wait(filp, &dev->r_wait, wait); 206 207 if(dev->current_len != 0) 208 mask |=POLLIN | POLLRDNORM; 209 if(dev->current_len != GLOBALMEM_SIZE) 210 mask |=POLLOUT | POLLWRNORM; 211 212 mutex_unlock(&dev->mutex); 213 return mask; 214 } 215 216 static int globalfifo_fasync(int fd, struct file *filp, int on) 217 { 218 struct globalfifo_dev *dev = filp->private_data; 219 220 return fasync_helper(fd, filp, on, &dev->queue); 221 } 222 223 static int globalfifo_release(struct inode *inode, struct file *filp) 224 { 225 globalfifo_fasync(-1, filp, 0); 226 return 0; 227 } 228 static const struct file_operations globalfifo_fops = { 229 .owner = THIS_MODULE, 230 .llseek = globalfifo_llseek, 231 .read = globalfifo_read, 232 .write = globalfifo_write, 233 .unlocked_ioctl = globalfifo_ioctl, 234 .open = globalfifo_open, 235 .poll = globalfifo_poll, 236 .release = globalfifo_release, 237 .fasync = globalfifo_fasync, 238 }; 239 static void globalfifo_setup_cdev(struct globalfifo_dev *dev, int index) 240 { 241 int err, devno = MKDEV(globalfifo_major, index); 242 cdev_init(&dev->cdev, &globalfifo_fops); 243 dev->cdev.owner = THIS_MODULE; 244 err = cdev_add(&dev->cdev, devno, 1); 245 if (err) 246 printk(KERN_NOTICE "Error %d adding globalfifo%d", err, index); 247 } 248 static int __init globalfifo_init(void) 249 { 250 int ret; 251 dev_t devno = MKDEV(globalfifo_major, 0); 252 253 if (globalfifo_major) 254 ret = register_chrdev_region(devno, 1, "globalfifo"); 255 else { 256 ret = alloc_chrdev_region(&devno, 0, 1, "globalfifo"); 257 globalfifo_major = MAJOR(devno); 258 } 259 if (ret < 0) 260 return ret; 261 262 globalfifo_devp = kzalloc(sizeof(struct globalfifo_dev), GFP_KERNEL); 263 if (!globalfifo_devp) { 264 ret = -ENOMEM; 265 goto fail_malloc; 266 } 267 globalfifo_setup_cdev(globalfifo_devp, 0); 268 269 mutex_init(&globalfifo_devp->mutex); 270 init_waitqueue_head(&globalfifo_devp->r_wait); 271 init_waitqueue_head(&globalfifo_devp->w_wait); 272 return 0; 273 274 fail_malloc: 275 unregister_chrdev_region(devno, 1); 276 return ret; 277 } 278 279 static void __exit globalfifo_exit(void) 280 { 281 cdev_del(&globalfifo_devp->cdev); 282 kfree(globalfifo_devp); 283 unregister_chrdev_region(MKDEV(globalfifo_major, 0), 1); 284 } 285 module_init(globalfifo_init); 286 module_exit(globalfifo_exit); 287 288 MODULE_LICENSE("GPL v2");
應用程序源碼.

1 #include <stdio.h> 2 #include <fcntl.h> 3 #include <errno.h> 4 #include <signal.h> 5 #include <sys/types.h> 6 #include <unistd.h> 7 8 9 void signal_handler(int signalNum) 10 { 11 printf("signalNum:0x%x\n",signalNum); 12 } 13 14 int main(int argc, char **argv) 15 { 16 int fd; 17 int err; 18 19 fd = open("/dev/globalfifo", O_RDONLY); 20 if(fd == -1) 21 printf("open fail\n"); 22 else{ 23 signal(SIGIO, signal_handler); 24 fcntl(fd, F_SETOWN, getpid()); 25 int oflags = fcntl(fd, F_GETFL); 26 fcntl(fd, F_SETFL, oflags | FASYNC); 27 while(1) 28 sleep(100); 29 } 30 return 0; 31 }
結果:
當echo 1 >/dev/globalfifo時
此文源碼基於內核源碼版本為linux-2.6.22.6