异步通知机制内核实现 及 驱动编写 (重要)
2016-11-24 16:36
411 查看
转载于: http://blog.csdn.net/wenqian1991/article/details/50333655 基本没有修改过,特此标注 /* *1.概念: 异步通知机制:一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,是一种“信号驱动的异步I/O”。 信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。 信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候会到达。 2. 我们试图通过两个方面来分析异步通知机制: 从用户程序的角度考虑: 为了启动文件的异步通知机制,用户程序必须执行两个步骤。首先,她们指定一个进程作为文件的“属主(owner)”。 当进程使用fcntl系统调用执行F_SETOWN命令时,属主进程的进程ID号就被保存在filp->f_owner中。这一步是必需的,目的是为了让内核知道应该通知哪个进程。 然后为了真正启动异步通知机制,用户程序还必须在设备中设置FASYNC标志,这通过fcntl的F_SETFL命令完成的。 执行完这两个步骤之后,输入文件就可以在新数据到达时请求发送一个SIGIO信号。该信号被发送到存放在filp->f_owner中的进程(如果是负值就是进程组)。 */ //下面先贴出用户程序代码: #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/select.h> #include <unistd.h> #include <signal.h> void input_handler(int signum) { printf("receive a signal from io,signalnum:%d\n", signum); } int main(void) { int fd, flag; fd_set r_fset, w_fset; fd = open("/dev/wqlkp", O_RDWR, S_IRUSR | S_IWUSR); if(fd < 0) { perror("open"); return -1; } /*启动信号驱动机制*/ signal(SIGIO, input_handler); //让input_handler()处理SIGIO信号 fcntl(fd, F_SETOWN, getpid()); //设置文件的所有权进程 flag = fcntl(fd, F_GETFL); //获取状态标志, 然后在下一行的语句中将得到的flag添加上FASYNC这个文件标志 fcntl(fd, F_SETFL, flag | FASYNC); //(添加上)设置FASYNC标志 while(1); return 0; } /*我们先通过内核源码,剖析上面的实现原理。 先看fcntl() 其调用步骤为:fcntl() ->sys_fcntl() ->do_fcntl() ->根据cmd调用相应操作函数。 */ /* 先看sys_fcntl() [Linux/fcntl.c] */ asmlinkage long sys_fcntl(unsigned int fd, unsigned int cmd, unsigned long arg) { struct file *filp; long err = -EBADF; filp = fget(fd); //通过文件描述符获得对应关联的文件指针 if (!filp) goto out; err = security_file_fcntl(filp, cmd, arg); if (err) { fput(filp); return err; } err = do_fcntl(fd, cmd, arg, filp);//调用do_fcntl函数 fput(filp); out: return err; }
//只保留与异步通知机制相关的部分代码 static long do_fcntl(int fd, unsigned int cmd, unsigned long arg, struct file *filp) { long err = -EINVAL; switch (cmd) { …… case F_GETFL: err = filp->f_flags; //返回文件标志 break; case F_SETFL: err = setfl(fd, filp, arg); //转调用setfl函数 break; …… case F_SETOWN: err = f_setown(filp, arg, 1); //转调用f_setown函数 break; …… default: break; } return err; }
//ok,来看看f_setown函数的内部实现:设置文件的属主进程 int f_setown(struct file *filp, unsigned long arg, int force) { int err; err = security_file_set_fowner(filp); if (err) return err; f_modown(filp, arg, current->uid, current->euid, force);//调用f_modown函数 return 0; } static void f_modown(struct file *filp, unsigned long pid, uid_t uid, uid_t euid, int force) { write_lock_irq(&filp->f_owner.lock); //设置对应的pid,uid,euid if (force || !filp->f_owner.pid) { filp->f_owner.pid = pid; filp->f_owner.uid = uid; filp->f_owner.euid = euid; } write_unlock_irq(&filp->f_owner.lock); } //再来看看setfl函数的内部实现: //只保留异步通知机制相关的代码 static int setfl(int fd, struct file * filp, unsigned long arg) { struct inode * inode = filp->f_dentry->d_inode; int error = 0; …… lock_kernel(); //下面这个判断语句有点意思,是一个边缘触发 //也就是说FASYNC标志从0变为1的时候,才为真。自己验证... if ((arg ^ filp->f_flags) & FASYNC) {//FASYNC标志发生了变化 if (filp->f_op && filp->f_op->fasync) { error = filp->f_op->fasync(fd, filp, (arg & FASYNC) != 0); //前面接触了这么多内核驱动接口,看到filp->f_op->fasync,应该知道这是调用我们注册的自定义fasync函数了 //实际上是调用fasync_helper() if (error < 0) goto out; } } filp->f_flags = (arg & SETFL_MASK) | (filp->f_flags & ~SETFL_MASK); out: unlock_kernel(); return error; }
/*好,有了前面的铺垫,我们在从驱动层次考虑:
从驱动程序角度考虑:
F_SETOWN被调用时对filp->f_owner赋值,此外什么也不做;
在执行F_SETFL启用FASYNC时,调用驱动程序的fasync方法。只要filp->f_flags中的FASYNC标识发生了变化,就会调用该方法,以便把这个变化通 知驱动程序,使其能正确响应。文件打开时,FASYNC标志被默认为是清除的。
当数据到达时,所有注册为异步通知的进程都会被发送一个SIGIO信号。
Linux的这种通用方法基于一个数据结构和两个函数:
*/
/* SMP safe fasync helpers: */ extern int fasync_helper(int, struct file *, int, struct fasync_struct **); /* can be called from interrupts */ extern void kill_fasync(struct fasync_struct **, int, int); /*当一个打开的文件的FASYNC标志被修改时,调用fasync_helper函数以便从相关的进程表中添加或删除文件。 当数据到达时,可使用kill_fasync函数通知所有的相关进程。(在UNIX语义中,kill这个词常用来向进程发送信号,而不是杀死某个进程,raise则用于向自身发送信号)。 基本上,所有工作都有内核提供的这两个函数完成了。 现在我们来看看驱动程序: */ #include <linux/module.h> #include <linux/types.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/wait.h> #include <linux/sched.h> #include <linux/errno.h> #include <asm/uaccess.h> #include <linux/poll.h> #include <linux/semaphore.h> #include <linux/fcntl.h> MODULE_LICENSE("Dual BSD/GPL"); #define DEV_SIZE 20 #define WQ_MAJOR 230 #define DEBUG_SWITCH 1 #if DEBUG_SWITCH #define P_DEBUG(fmt, args...) printk("<1>" "<kernel>[%s]"fmt,__FUNCTION__, ##args) #else #define P_DEBUG(fmt, args...) printk("<7>" "<kernel>[%s]"fmt,__FUNCTION__, ##args) #endif struct wq_dev{ char kbuf[DEV_SIZE]; //缓冲区 dev_t devno; //设备号 unsigned int major; struct cdev wq_cdev; unsigned int cur_size;//可读可写的数据量 struct semaphore sem;//信号量 wait_queue_head_t r_wait;//读等待队列 wait_queue_head_t w_wait;//写等待队列 struct fasync_struct *async_queue;//异步通知队列 }; struct wq_dev *wq_devp; //异步通知机制驱动函数 static int wq_fasync(int fd, struct file *filp, int mode) { struct wq_dev *dev = filp->private_data; return fasync_helper(fd, filp, mode, &dev->async_queue);//调用内核提供的函数 } int wq_open(struct inode *inodep, struct file *filp) { struct wq_dev *dev; dev = container_of(inodep->i_cdev, struct wq_dev, wq_cdev); filp->private_data = dev; printk(KERN_ALERT "open is ok!\n"); return 0; } int wq_release(struct inode *inodep, struct file *filp) { printk(KERN_ALERT "release is ok!\n"); wq_fasync(-1, filp, 0);//从异步通知队列中删除该filp return 0; } static ssize_t wq_read(struct file *filp, char __user *buf, size_t count, loff_t *offset) { struct wq_dev *dev = filp->private_data; P_DEBUG("read data...\n"); if(down_interruptible(&dev->sem))//获取信号量 { P_DEBUG("enter read down_interruptible\n"); return -ERESTARTSYS; } P_DEBUG("read first down\n"); while(dev->cur_size == 0){//无数据可读,进入休眠lon up(&dev->sem);//释放信号量,不然写进程没有机会来唤醒(没有获得锁) if(filp->f_flags & O_NONBLOCK)//检查是否是阻塞型I/O return -EAGAIN; P_DEBUG("%s reading:going to sleep\n", current->comm); if(wait_event_interruptible(dev->r_wait, dev->cur_size != 0))//休眠等待被唤醒 { P_DEBUG("read wait interruptible\n"); return -ERESTARTSYS; } P_DEBUG("wake up r_wait\n"); if(down_interruptible(&dev->sem))//获取信号量 return -ERESTARTSYS; } //数据已就绪 P_DEBUG("[2]dev->cur_size is %d\n", dev->cur_size); if(dev->cur_size > 0) count = min(count, dev->cur_size); //从内核缓冲区赋值数据到用户空间,复制成功返回0 if(copy_to_user(buf, dev->kbuf, count)) { up(&dev->sem); return -EFAULT; } dev->cur_size -= count;//可读数据量更新 up(&dev->sem); wake_up_interruptible(&dev->w_wait);//唤醒写进程 P_DEBUG("%s did read %d bytes\n", current->comm, (unsigned int)count); return count; } static ssize_t wq_write(struct file *filp,const char __user *buf,size_t count, loff_t *offset) { struct wq_dev *dev = filp->private_data; //wait_queue_t my_wait; P_DEBUG("write is doing\n"); if(down_interruptible(&dev->sem))//获取信号量 { P_DEBUG("enter write down_interruptible\n"); return -ERESTARTSYS; } P_DEBUG("write first down\n"); while(dev->cur_size == DEV_SIZE){//判断空间是否已满 up(&dev->sem);//释放信号量 if(filp->f_flags & O_NONBLOCK) return -EAGAIN; P_DEBUG("writing going to sleep\n"); if(wait_event_interruptible(dev->w_wait, dev->cur_size < DEV_SIZE)) return -ERESTARTSYS; if(down_interruptible(&dev->sem))//获取信号量 return -ERESTARTSYS; } if(count > DEV_SIZE - dev->cur_size) count = DEV_SIZE - dev->cur_size; if(copy_from_user(dev->kbuf, buf, count))//数据复制 return -EFAULT; dev->cur_size += count;//更新数据量 P_DEBUG("write %d bytes , cur_size:[%d]\n", count, dev->cur_size); P_DEBUG("kbuf is [%s]\n", dev->kbuf); up(&dev->sem); wake_up_interruptible(&dev->r_wait);//唤醒读进程队列 if(dev->async_queue) kill_fasync(&dev->async_queue, SIGIO, POLL_IN);//可写时发送信号 return count; } static unsigned int wq_poll(struct file *filp, poll_table *wait) { struct wq_dev *dev = filp->private_data; unsigned int mask = 0; if(down_interruptible(&dev->sem))//获取信号量 return -ERESTARTSYS; poll_wait(filp, &dev->w_wait, wait);//添加写等待队列 poll_wait(filp, &dev->r_wait, wait);//添加读等待队列 if(dev->cur_size != 0)//判断是否可读取 mask |= POLLIN | POLLRDNORM; if(dev->cur_size != DEV_SIZE)//判断是否可写入 mask |= POLLOUT | POLLWRNORM; up(&dev->sem);//释放信号量 return mask; } struct file_operations wq_fops = { .open = wq_open, .release = wq_release, .write = wq_write, .read = wq_read, .poll = wq_poll, .fasync = wq_fasync,//函数 }; struct wq_dev my_dev; static int __init wq_init(void) { int result = 0; my_dev.cur_size = 0; my_dev.devno = MKDEV(WQ_MAJOR, 0); //设备号分配 if(WQ_MAJOR) result = register_chrdev_region(my_dev.devno, 1, "wqlkp"); else { result = alloc_chrdev_region(&my_dev.devno, 0, 1, "wqlkp"); my_dev.major = MAJOR(my_dev.devno); } if(result < 0) return result; cdev_init(&my_dev.wq_cdev, &wq_fops);//设备初始化 my_dev.wq_cdev.owner = THIS_MODULE; sema_init(&my_dev.sem, 1);//信号量初始化 init_waitqueue_head(&my_dev.r_wait);//等待队列初始化 init_waitqueue_head(&my_dev.w_wait); result = cdev_add(&my_dev.wq_cdev, my_dev.devno, 1);//设备注册 if(result < 0) { P_DEBUG("cdev_add error!\n"); goto err; } printk(KERN_ALERT "hello kernel\n"); return 0; err: unregister_chrdev_region(my_dev.devno,1); return result; } static void __exit wq_exit(void) { cdev_del(&my_dev.wq_cdev); unregister_chrdev_region(my_dev.devno, 1); } module_init(wq_init); module_exit(wq_exit); //我们通过剖析这两个函数的内部实现来窥探异步通知机制原理 //异步通知机制驱动函数,我们自定义的驱动程序,可以看到主体是fasync_helper函数 static int wq_fasync(int fd, struct file *filp, int mode) { struct wq_dev *dev = filp->private_data; return fasync_helper(fd, filp, mode, &dev->async_queue);//调用内核提供的函数 } /* * fasync_helper() is used by some character device drivers (mainly mice) * to set up the fasync queue. It returns negative on error, 0 if it did * no changes and positive if it added/deleted the entry. */ int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp) { struct fasync_struct *fa, **fp; struct fasync_struct *new = NULL; int result = 0; if (on) { new = kmem_cache_alloc(fasync_cache, SLAB_KERNEL);//创建对象,slab分配器 if (!new) return -ENOMEM; } write_lock_irq(&fasync_lock); //遍历整个异步通知队列,看是否存在对应的文件指针 for (fp = fapp; (fa = *fp) != NULL; fp = &fa->fa_next) { if (fa->fa_file == filp) {//已存在 if(on) { fa->fa_fd = fd;//文件描述符赋值 kmem_cache_free(fasync_cache, new);//销毁刚创建的对象 } else { *fp = fa->fa_next;//继续遍历 kmem_cache_free(fasync_cache, fa);//删除非目标对象 result = 1; } goto out;//找到了 } } //看到下面可以得知,所谓的把进程添加到异步通知队列中 //实则是将文件指针关联到异步结构体对象,然后将该对象挂载在异步通知队列中(等待队列也是这个原理) //那么最后发送信号又是怎么知道是哪个进程的呢?我们看后面的kill_fasync函数。 if (on) {//不存在 new->magic = FASYNC_MAGIC; new->fa_file = filp;//指定文件指针 new->fa_fd = fd;//指定文件描述符 new->fa_next = *fapp;//挂载在异步通知队列中 *fapp = new;//挂载 result = 1; } out: write_unlock_irq(&fasync_lock); return result; } 看看kill_fasync函数是怎么将信号通知指定进程的: //我们自定义代码 if(dev->async_queue) kill_fasync(&dev->async_queue, SIGIO, POLL_IN);//可写时发送信号 void kill_fasync(struct fasync_struct **fp, int sig, int band) { /* First a quick test without locking: usually * the list is empty. */ if (*fp) { read_lock(&fasync_lock); /* reread *fp after obtaining the lock */ __kill_fasync(*fp, sig, band);//调用 read_unlock(&fasync_lock); } } void __kill_fasync(struct fasync_struct *fa, int sig, int band) { while (fa) { struct fown_struct * fown; if (fa->magic != FASYNC_MAGIC) { printk(KERN_ERR "kill_fasync: bad magic number in " "fasync_struct!\n"); return; } /* struct fown_struct { rwlock_t lock; // protects pid, uid, euid fields int pid; // pid or -pgrp where SIGIO should be sent uid_t uid, euid; // uid/euid of process setting the owner void *security; int signum; // posix.1b rt signal to be delivered on IO }; */ fown = &fa->fa_file->f_owner;//这里便是回答上面的问题,如果知道是哪个进程的,通过异步对象的文件指针知道其属主进程 /* Don't send SIGURG to processes which have not set a queued signum: SIGURG has its own default signalling mechanism. */ if (!(sig == SIGURG && fown->signum == 0)) send_sigio(fown, fa->fa_fd, band);//发送信号 fa = fa->fa_next; } } //ok,点到即止,发送信号的细节我们不深究了。 // //上面从用户态和驱动层两方面剖析,还深度分析了内核源码的实现,算是理清了异步通知机制的原理。 // //此外本文分析异步通知队列,如何将进程“添加”到异步通知队列中,对于理解等待队列原理有一定的帮助。实则是通过文件指针关联对象,将对象添加到队列中。定位进程则是反过来,根据文件指针查找,然后根据对应文件指针去找关联的进程,所以有时候会出现一个文件指针可以找到多个进程(多个进程打开了该文件)。这种情况下,应用程序仍然必须借助于poll/select来确定输入的来源。 // //测试: //代码都贴出来了,读者应该知道怎么测… // //参考文献: //《LDD》、《Linux设备驱动开发详解》、《Linux内核设计与实现》 //Linux kernel 2.6.18/3.13 SourceCode(开发内核源码树版本为3.13) // //异步通知机制:一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,是一种“信号驱动的异步I/O”。 // //信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候会到达。 // //我们试图通过两个方面来分析异步通知机制: //从用户程序的角度考虑: //为了启动文件的异步通知机制,用户程序必须执行两个步骤。首先,她们指定一个进程作为文件的“属主(owner)”。当进程使用fcntl系统调用执行F_SETOWN命令时,属主进程的进程ID号就被保存在filp->f_owner中。这一步是必需的,目的是为了让内核知道应该通知哪个进程。 //然后为了真正启动异步通知机制,用户程序还必须在设备中设置FASYNC标志,这通过fcntl的F_SETFL命令完成的。 //执行完这两个步骤之后,输入文件就可以在新数据到达时请求发送一个SIGIO信号。该信号被发送到存放在filp->f_owner中的进程(如果是负值就是进程组)。 // //下面先贴出用户程序代码: #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/select.h> #include <unistd.h> #include <signal.h> void input_handler(int signum) { printf("receive a signal from io,signalnum:%d\n", signum); } int main(void) { int fd, flag; fd_set r_fset, w_fset; fd = open("/dev/wqlkp", O_RDWR, S_IRUSR | S_IWUSR); if(fd < 0) { perror("open"); return -1; } //启动信号驱动机制 signal(SIGIO, input_handler);//让input_handler()处理SIGIO信号 fcntl(fd, F_SETOWN, getpid());//设置文件的所有权进程 flag = fcntl(fd, F_GETFL);//获取状态标志 fcntl(fd, F_SETFL, flag | FASYNC);//设置FASYNC标志 while(1); return 0; } //我们先通过内核源码,剖析上面的实现原理。 //先看fcntl() //其调用步骤为:fcntl() -> sys_fcntl() -> do_fcntl() -> 根据cmd调用相应操作函数。 //先看sys_fcntl() [Linux/fcntl.c] // asmlinkage long sys_fcntl(unsigned int fd, unsigned int cmd, unsigned long arg) { struct file *filp; long err = -EBADF; filp = fget(fd);//通过文件描述符获得对应关联的文件指针 if (!filp) goto out; err = security_file_fcntl(filp, cmd, arg); if (err) { fput(filp); return err; } err = do_fcntl(fd, cmd, arg, filp);//调用do_fcntl函数 fput(filp); out: return err; } //只保留与异步通知机制相关的部分代码 static long do_fcntl(int fd, unsigned int cmd, unsigned long arg, struct file *filp) { long err = -EINVAL; switch (cmd) { …… case F_GETFL: err = filp->f_flags;//返回文件标志 break; case F_SETFL: err = setfl(fd, filp, arg);//转调用setfl函数 break; …… case F_SETOWN: err = f_setown(filp, arg, 1);//转调用f_setown函数 break; …… default: break; } return err; } ok,来看看f_setown函数的内部实现:设置文件的属主进程 int f_setown(struct file *filp, unsigned long arg, int force) { int err; err = security_file_set_fowner(filp); if (err) return err; f_modown(filp, arg, current->uid, current->euid, force);//调用f_modown函数 return 0; } static void f_modown(struct file *filp, unsigned long pid, uid_t uid, uid_t euid, int force) { write_lock_irq(&filp->f_owner.lock); //设置对应的pid,uid,euid if (force || !filp->f_owner.pid) { filp->f_owner.pid = pid; filp->f_owner.uid = uid; filp->f_owner.euid = euid; } write_unlock_irq(&filp->f_owner.lock); } 再来看看setfl函数的内部实现: //只保留异步通知机制相关的代码 static int setfl(int fd, struct file * filp, unsigned long arg) { struct inode * inode = filp->f_dentry->d_inode; int error = 0; …… lock_kernel(); //下面这个判断语句有点意思,是一个边缘触发 //也就是说FASYNC标志从0变为1的时候,才为真。自己验证... if ((arg ^ filp->f_flags) & FASYNC) {//FASYNC标志发生了变化 if (filp->f_op && filp->f_op->fasync) { error = filp->f_op->fasync(fd, filp, (arg & FASYNC) != 0);//前面接触了这么多内核驱动接口,看到filp->f_op->fasync,应该知道这是调用我们注册的自定义fasync函数了 if (error < 0) goto out; } } filp->f_flags = (arg & SETFL_MASK) | (filp->f_flags & ~SETFL_MASK); out: unlock_kernel(); return error; } //好,有了前面的铺垫,我们在从驱动层次考虑: //从驱动程序角度考虑: // //F_SETOWN被调用时对filp->f_owner赋值,此外什么也不做; //在执行F_SETFL启用FASYNC时,调用驱动程序的fasync方法。只要filp->f_flags中的FASYNC标识发生了变化,就会调用该方法,以便把这个变化通知驱动程序,使其能正确响应。文件打开时,FASYNC标志被默认为是清除的。 //当数据到达时,所有注册为异步通知的进程都会被发送一个SIGIO信号。 //Linux的这种通用方法基于一个数据结构和两个函数: struct fasync_struct { int magic; int fa_fd;//文件描述符 struct fasync_struct *fa_next; /* singly linked list *///异步通知队列 struct file *fa_file;//文件指针 }; /* SMP safe fasync helpers: */ extern int fasync_helper(int, struct file *, int, struct fasync_struct **); /* can be called from interrupts */ extern void kill_fasync(struct fasync_struct **, int, int); //当一个打开的文件的FASYNC标志被修改时,调用fasync_helper函数以便从相关的进程表中添加或删除文件。 //当数据到达时,可使用kill_fasync函数通知所有的相关进程。(在UNIX语义中,kill这个词常用来向进程发送信号,而不是杀死某个进程,raise则用于向自身发送信号)。 // //基本上,所有工作都有内核提供的这两个函数完成了。 //现在我们来看看驱动程序: // #include <linux/module.h> #include <linux/types.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/wait.h> #include <linux/sched.h> #include <linux/errno.h> #include <asm/uaccess.h> #include <linux/poll.h> #include <linux/semaphore.h> #include <linux/fcntl.h> MODULE_LICENSE("Dual BSD/GPL"); #define DEV_SIZE 20 #define WQ_MAJOR 230 #define DEBUG_SWITCH 1 #if DEBUG_SWITCH #define P_DEBUG(fmt, args...) printk("<1>" "<kernel>[%s]"fmt,__FUNCTION__, ##args) #else #define P_DEBUG(fmt, args...) printk("<7>" "<kernel>[%s]"fmt,__FUNCTION__, ##args) #endif struct wq_dev{ char kbuf[DEV_SIZE];//缓冲区 dev_t devno;//设备号 unsigned int major; struct cdev wq_cdev; unsigned int cur_size;//可读可写的数据量 struct semaphore sem;//信号量 wait_queue_head_t r_wait;//读等待队列 wait_queue_head_t w_wait;//写等待队列 struct fasync_struct *async_queue;//异步通知队列 }; //struct wq_dev *wq_devp; //异步通知机制驱动函数 static int wq_fasync(int fd, struct file *filp, int mode) { struct wq_dev *dev = filp->private_data; return fasync_helper(fd, filp, mode, &dev->async_queue);//调用内核提供的函数 } int wq_open(struct inode *inodep, struct file *filp) { struct wq_dev *dev; dev = container_of(inodep->i_cdev, struct wq_dev, wq_cdev); filp->private_data = dev; printk(KERN_ALERT "open is ok!\n"); return 0; } int wq_release(struct inode *inodep, struct file *filp) { printk(KERN_ALERT "release is ok!\n"); wq_fasync(-1, filp, 0);//从异步通知队列中删除该filp return 0; } static ssize_t wq_read(struct file *filp, char __user *buf, size_t count, loff_t *offset) { struct wq_dev *dev = filp->private_data; P_DEBUG("read data...\n"); if(down_interruptible(&dev->sem))//获取信号量 { P_DEBUG("enter read down_interruptible\n"); return -ERESTARTSYS; } P_DEBUG("read first down\n"); while(dev->cur_size == 0){//无数据可读,进入休眠lon up(&dev->sem);//释放信号量,不然写进程没有机会来唤醒(没有获得锁) if(filp->f_flags & O_NONBLOCK)//检查是否是阻塞型I/O return -EAGAIN; P_DEBUG("%s reading:going to sleep\n", current->comm); if(wait_event_interruptible(dev->r_wait, dev->cur_size != 0))//休眠等待被唤醒 { P_DEBUG("read wait interruptible\n"); return -ERESTARTSYS; } P_DEBUG("wake up r_wait\n"); if(down_interruptible(&dev->sem))//获取信号量 return -ERESTARTSYS; } //数据已就绪 P_DEBUG("[2]dev->cur_size is %d\n", dev->cur_size); if(dev->cur_size > 0) count = min(count, dev->cur_size); //从内核缓冲区赋值数据到用户空间,复制成功返回0 if(copy_to_user(buf, dev->kbuf, count)) { up(&dev->sem); return -EFAULT; } dev->cur_size -= count;//可读数据量更新 up(&dev->sem); wake_up_interruptible(&dev->w_wait);//唤醒写进程 P_DEBUG("%s did read %d bytes\n", current->comm, (unsigned int)count); return count; } static ssize_t wq_write(struct file *filp,const char __user *buf,size_t count, loff_t *offset) { struct wq_dev *dev = filp->private_data; //wait_queue_t my_wait; P_DEBUG("write is doing\n"); if(down_interruptible(&dev->sem))//获取信号量 { P_DEBUG("enter write down_interruptible\n"); return -ERESTARTSYS; } P_DEBUG("write first down\n"); while(dev->cur_size == DEV_SIZE){//判断空间是否已满 up(&dev->sem);//释放信号量 if(filp->f_flags & O_NONBLOCK) return -EAGAIN; P_DEBUG("writing going to sleep\n"); if(wait_event_interruptible(dev->w_wait, dev->cur_size < DEV_SIZE)) return -ERESTARTSYS; if(down_interruptible(&dev->sem))//获取信号量 return -ERESTARTSYS; } if(count > DEV_SIZE - dev->cur_size) count = DEV_SIZE - dev->cur_size; if(copy_from_user(dev->kbuf, buf, count))//数据复制 return -EFAULT; dev->cur_size += count;//更新数据量 P_DEBUG("write %d bytes , cur_size:[%d]\n", count, dev->cur_size); P_DEBUG("kbuf is [%s]\n", dev->kbuf); up(&dev->sem); wake_up_interruptible(&dev->r_wait);//唤醒读进程队列 if(dev->async_queue) kill_fasync(&dev->async_queue, SIGIO, POLL_IN);//可写时发送信号 return count; } static unsigned int wq_poll(struct file *filp, poll_table *wait) { struct wq_dev *dev = filp->private_data; unsigned int mask = 0; if(down_interruptible(&dev->sem))//获取信号量 return -ERESTARTSYS; poll_wait(filp, &dev->w_wait, wait);//添加写等待队列 poll_wait(filp, &dev->r_wait, wait);//添加读等待队列 if(dev->cur_size != 0)//判断是否可读取 mask |= POLLIN | POLLRDNORM; if(dev->cur_size != DEV_SIZE)//判断是否可写入 mask |= POLLOUT | POLLWRNORM; up(&dev->sem);//释放信号量 return mask; } struct file_operations wq_fops = { .open = wq_open, .release = wq_release, .write = wq_write, .read = wq_read, .poll = wq_poll, .fasync = wq_fasync,//函数注册 }; struct wq_dev my_dev; static int __init wq_init(void) { int result = 0; my_dev.cur_size = 0; my_dev.devno = MKDEV(WQ_MAJOR, 0); //设备号分配 if(WQ_MAJOR) result = register_chrdev_region(my_dev.devno, 1, "wqlkp"); else { result = alloc_chrdev_region(&my_dev.devno, 0, 1, "wqlkp"); my_dev.major = MAJOR(my_dev.devno); } if(result < 0) return result; cdev_init(&my_dev.wq_cdev, &wq_fops);//设备初始化 my_dev.wq_cdev.owner = THIS_MODULE; sema_init(&my_dev.sem, 1);//信号量初始化 init_waitqueue_head(&my_dev.r_wait);//等待队列初始化 init_waitqueue_head(&my_dev.w_wait); result = cdev_add(&my_dev.wq_cdev, my_dev.devno, 1);//设备注册 if(result < 0) { P_DEBUG("cdev_add error!\n"); goto err; } printk(KERN_ALERT "hello kernel\n"); return 0; err: unregister_chrdev_region(my_dev.devno,1); return result; } static void __exit wq_exit(void) { cdev_del(&my_dev.wq_cdev); unregister_chrdev_region(my_dev.devno, 1); } module_init(wq_init); module_exit(wq_exit); 我们通过剖析这两个函数的内部实现来窥探异步通知机制原理 //异步通知机制驱动函数,我们自定义的驱动程序,可以看到主体是fasync_helper函数 static int wq_fasync(int fd, struct file *filp, int mode) { struct wq_dev *dev = filp->private_data; return fasync_helper(fd, filp, mode, &dev->async_queue);//调用内核提供的函数 } /* * fasync_helper() is used by some character device drivers (mainly mice) * to set up the fasync queue. It returns negative on error, 0 if it did * no changes and positive if it added/deleted the entry. */ int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp) { struct fasync_struct *fa, **fp; struct fasync_struct *new = NULL; int result = 0; if (on) { new = kmem_cache_alloc(fasync_cache, SLAB_KERNEL);//创建对象,slab分配器 if (!new) return -ENOMEM; } write_lock_irq(&fasync_lock); //遍历整个异步通知队列,看是否存在对应的文件指针 for (fp = fapp; (fa = *fp) != NULL; fp = &fa->fa_next) { if (fa->fa_file == filp) {//已存在 if(on) { fa->fa_fd = fd;//文件描述符赋值 kmem_cache_free(fasync_cache, new);//销毁刚创建的对象 } else { *fp = fa->fa_next;//继续遍历 kmem_cache_free(fasync_cache, fa);//删除非目标对象 result = 1; } goto out;//找到了 } } //看到下面可以得知,所谓的把进程添加到异步通知队列中 //实则是将文件指针关联到异步结构体对象,然后将该对象挂载在异步通知队列中(等待队列也是这个原理) //那么最后发送信号又是怎么知道是哪个进程的呢?我们看后面的kill_fasync函数。 if (on) {//不存在 new->magic = FASYNC_MAGIC; new->fa_file = filp;//指定文件指针 new->fa_fd = fd;//指定文件描述符 new->fa_next = *fapp;//挂载在异步通知队列中 *fapp = new;//挂载 result = 1; } out: write_unlock_irq(&fasync_lock); return result; } //看看kill_fasync函数是怎么将信号通知指定进程的: //我们自定义代码 if(dev->async_queue) kill_fasync(&dev->async_queue, SIGIO, POLL_IN);//可写时发送信号 void kill_fasync(struct fasync_struct **fp, int sig, int band) { /* First a quick test without locking: usually * the list is empty. */ if (*fp) { read_lock(&fasync_lock); /* reread *fp after obtaining the lock */ __kill_fasync(*fp, sig, band);//调用 read_unlock(&fasync_lock); } } void __kill_fasync(struct fasync_struct *fa, int sig, int band) { while (fa) { struct fown_struct * fown; if (fa->magic != FASYNC_MAGIC) { printk(KERN_ERR "kill_fasync: bad magic number in " "fasync_struct!\n"); return; } /* struct fown_struct { rwlock_t lock; // protects pid, uid, euid fields int pid; // pid or -pgrp where SIGIO should be sent uid_t uid, euid; // uid/euid of process setting the owner void *security; int signum; // posix.1b rt signal to be delivered on IO }; */ fown = &fa->fa_file->f_owner;//这里便是回答上面的问题,如果知道是哪个进程的,通过异步对象的文件指针知道其属主进程 /* Don't send SIGURG to processes which have not set a queued signum: SIGURG has its own default signalling mechanism. */ if (!(sig == SIGURG && fown->signum == 0)) send_sigio(fown, fa->fa_fd, band);//发送信号 fa = fa->fa_next; } } /*ok,点到即止,发送信号的细节我们不深究了。 上面从用户态和驱动层两方面剖析,还深度分析了内核源码的实现,算是理清了异步通知机制的原理。 此外本文分析异步通知队列,如何将进程“添加”到异步通知队列中,对于理解等待队列原理有一定的帮助。实则是通过文件指针关联对象,将对象添加到队列中。定位进程则是反过来,根据文件指针查找,然后根据对应文件指针去找关联的进程,所以有时候会出现一个文件指针可以找到多个进程(多个进程打开了该文件)。这种情况下,应用程序仍然必须借助于poll/select来确定输入的来源。 测试: 代码都贴出来了,读者应该知道怎么测… 参考文献: 《LDD》、《Linux设备驱动开发详解》、《Linux内核设计与实现》 Linux kernel 2.6.18/3.13 SourceCode(开发内核源码树版本为3.13)
相关文章推荐
- 编写字符设备驱动实现内核态与用户态通信
- 驱动中的异步通知机制
- 字符设备驱动学习笔记----异步通知机制
- Linux驱动开发之八-----按键驱动(异步通知机制)
- 学习笔记 --- LINNUX 使用异步通讯机制实现按键驱动代码分析
- linux驱动的异步同步通知机制
- 用Linux 2.6内核中的文件系统变化通知机制inotify可实现跨机文件同步
- 使用信号实现异步通知机制的例子
- fasync驱动异步通知机制
- 驱动中的异步通知机制
- 07-S3C2440驱动学习(一)嵌入式linux字符设备驱动-按键驱动程序之异步通知机制+原子操作+互斥信号量+阻塞与非阻塞+定时器去抖
- 使用信号实现异步通知机制的例子
- 驱动实现异步通知
- 使用信号实现异步通知机制的例子 http://blog.csdn.net/buaa_shang/article/details/9103155
- 【Linux 驱动】异步通知机制
- 【驱动】按键中断异步通知实现
- 设备驱动之异步通知机制
- 基于异步通知机制的按键驱动
- 按键驱动深化-异步通知机制
- 设备驱动的异步通知实现