异步通知机制内核实现 及 驱动编写 (重要)
转载于: 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) 编辑 收藏 举报