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

转载于: 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>" “[%s]“fmt,FUNCTION, ##args)
#else
#define P_DEBUG(fmt, args…) printk(”<7>” "[%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>" “[%s]“fmt,FUNCTION, ##args)
#else
#define P_DEBUG(fmt, args…) printk(”<7>” "[%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来确定输入的来源。

测试:
代码都贴出来了,读者应该知道怎么测…

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值