异步通知机制内核实现 及 驱动编写 (重要)

 转载于:  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)
*/

 

 

 

 

 



 

posted on 2016-11-13 20:14  Red_Point  阅读(1521)  评论(0编辑  收藏  举报

导航