1.概念:

异步通知机制:一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,是一种“信号驱动的异步I/O”。信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候会到达。

2.我们试图通过两个方面来分析异步通知机制:

从用户程序的角度考虑:为了启动文件的异步通知机制,用户程序必须执行两个步骤。首先,他们指定一个进程作为文件的“属主(owner)”。当进程使用fcntl系统调用执行

F_SETOWN命令时,属主进程的进程ID号就被保存在filp->f_owner中。这一步是必需的,目的是为了让内核知道应该通知哪个进程。 然后为了真正启动异步通知机制,用户程序还必须

在设备中设置FASYNC标志,这通过fcntl的F_SETFL命令完成的。 执行完这两个步骤之后,输入文件就可以在新数据到达时请求发送一个SIGIO信号。该信号被发送到存放在filp-

>f_owner中的进程(如果是负值就是进程组)。

在用户程序中,为了捕获信号,可以使用signal()函数来设置对应信号的处理函数:

 void (*signal(int signum, void (*handler))(int)))(int);

该函数原型较难理解, 它可以分解为:

 typedef void (*sighandler_t)(int);                        //消息处理函数
sighandler_t signal(int signum, sighandler_t handler)); //连接信号与消息处理函数

第一个参数指定信号的值,第二个参数指定针对前面信号值的处理函数,若为SIG_IGN,表示忽略该信号;若为SIG_DFL,表示采用系统默认方式处理信号;若为用户自定义的函

数,则信号被捕获到后,该函数将被执行。如果signal()调用成功,它返回最后一次为信号signum绑定的处理函数的handler值,失败则返回SIG_ERR。

 fcntl(STDIN_FILENO, F_SETOWN, getpid()); //设置本进程为STDIN_FILENO文件的拥有者,没有这一步,内核不会知道应该将信号发给哪个进程
oflags = fcntl(STDIN_FILENO, F_GETFL); //获取设备文件的f_flags
fcntl(STDIN_FILENO, F_SETFL, oflags | FASYNC); //为了启用异步通知机制,还需对设备设置FASYNC标志

我们先通过内核源码,剖析上面的实现原理。

 app:fcntl()
kernel:sys_fcntl()
do_fcntl()
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, );  //转调用f_setown函数
break;
……
default:
break;
}
return err;

//来看看f_setown函数的内部实现:设置文件的属主进程

 int f_setown(struct file *filp, unsigned long arg, int force)
{
...
pid = find_pid(who); //获取当前进程的pid
result = __f_setown(filp, pid, type, force); //内部主要调用f_modown函数
...
}
static void f_modown(struct file *filp, struct pid *pid, enum pid_type type,uid_t uid, uid_t euid, int force)
{
...
if (force || !filp->f_owner.pid) { //设置对应的pid,uid,euid
put_pid(filp->f_owner.pid);
filp->f_owner.pid = get_pid(pid);
filp->f_owner.pid_type = type;
filp->f_owner.uid = uid;
filp->f_owner.euid = euid;
}
...
}

//再来看看setfl函数的内部实现:

 static int setfl(int fd, struct file * filp, unsigned long arg)
{
...
if ((arg ^ filp->f_flags) & FASYNC) { //也就是说FASYNC标志从0变为1的时候,才为真。
if (filp->f_op && filp->f_op->fasync) {
error = filp->f_op->fasync(fd, filp, (arg & FASYNC) != ); //调用的就是驱动程序的fasync()函数
if (error < )
goto out;
}
}
...
}

从驱动程序角度考虑:

应用程序在执行F_SETFL启用FASYNC时,调用驱动程序的fasync方法。只要filp->f_flags中的FASYNC标识发生了变化,就会调用该方法,以便把这个变化通知驱动程序,使其能

正确响应。文件打开时,FASYNC标志被默认为是清除的。当数据到达时,所有注册为异步通知的进程都会被发送一个SIGIO信号。

Linux的这种通用方法基于一个数据结构和两个函数:

 extern int fasync_helper(int, struct file *, int, struct fasync_struct **);
//当一个打开的文件的FASYNC标志被修改时,调用驱动程序的fasync方法间接调用fasync_helper函数以便将当前进程加入到驱动程序的异步通知等待队列中。
extern void kill_fasync(struct fasync_struct **, int, int);
//当设备可访问时,可使用kill_fasync函数发信号所有的相关进程。进程进而调用绑定的消息处理函数。

//分析fasync_helper的内部实现

 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 = ;
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 = ;
}
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 = ;
}
out:
write_unlock_irq(&fasync_lock);
return result;
}

//看看kill_fasync函数是怎么将信号通知指定进程的:

 void __kill_fasync(struct fasync_struct *fa, int sig, int band)
{
while (fa) {
...
fown = &fa->fa_file->f_owner;//这里便是回答上面的问题,如果知道是哪个进程的,通过异步对象的文件指针知道其属主进程
/* Don't send SIGURG to processes which have not set a queued signum: SIGURG has its own default signallingmechanism. */
if (!(sig == SIGURG && fown->signum == ))
send_sigio(fown, fa->fa_fd, band);//发送信号
fa = fa->fa_next;
...
}
}

总结:应用程序使用fcntl()设置当前进程的pid和FASYNC标志。进而调用驱动程序的fasync(),即fasync_helper()。然后申请和设置fasync_struct结构,将此结构挂载到驱动程序

的fasync_struct结构链表中。当设备可用时,驱动程序会使用kill_fasync(),从fasync_struct链表中,查找所有的等待进程,然后调用send_sigio发送相应的消息给进程。进程接收到

消息,就会跳转到与消息绑定的消息处理函数中。

实例:基于<<Linux设备驱动开发详解:基于最新的Linux4.0内核.pdf>>第9.3章节

驱动程序源码.

 #include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/poll.h> #define GLOBALMEM_SIZE 0x1000
//#define GLOBALMEM_SIZE 0x10
#define GLOBALMEM_MAJOR 230
#define GLOBALMEM_MAGIC 'g'
//#define MEM_CLEAR _IO(GLOBALMEM_MAGIC,0)
#define MEM_CLEAR (0x01)
static int globalfifo_major = GLOBALMEM_MAJOR;
module_param(globalfifo_major, int, S_IRUGO); struct globalfifo_dev {
struct cdev cdev;
unsigned int current_len;
unsigned char mem[GLOBALMEM_SIZE];
struct mutex mutex;
wait_queue_head_t r_wait;
wait_queue_head_t w_wait;
struct fasync_struct *queue;
}; struct globalfifo_dev *globalfifo_devp; static int globalfifo_open(struct inode *inode, struct file *filp)
{
filp->private_data = globalfifo_devp;
return ;
} static ssize_t globalfifo_read(struct file *filp, char __user * buf, size_t size,
loff_t * ppos)
{
unsigned int count = size;
int ret = ;
struct globalfifo_dev *dev = filp->private_data;
DECLARE_WAITQUEUE(wait, current); mutex_lock(&dev->mutex);
add_wait_queue(&dev->r_wait, &wait); while(dev->current_len ==){
if(filp->f_flags & O_NONBLOCK){
ret = -EAGAIN;
goto out;
} set_current_state(TASK_INTERRUPTIBLE);
mutex_unlock(&dev->mutex); schedule();
if(signal_pending(current)){
ret = -ERESTARTSYS;
goto out2;
}
mutex_lock(&dev->mutex); } if (count > dev->current_len)
count = dev->current_len; if (copy_to_user(buf, dev->mem, count)) {
ret = -EFAULT;
goto out;
} else {
memcpy(dev->mem, dev->mem+count, dev->current_len - count);
dev->current_len -=count;
printk(KERN_INFO "read %d bytes(s) current_len %d\n", count, dev->current_len);
wake_up_interruptible(&dev->w_wait); if(dev->queue){
kill_fasync(&dev->queue, SIGIO, POLL_OUT);
printk(KERN_DEBUG "%s kill SIGIO\n", __func__);
}
ret = count;
}
out:
mutex_unlock(&dev->mutex);
out2:
remove_wait_queue(&dev->r_wait, &wait);
set_current_state(TASK_RUNNING); return ret;
} static ssize_t globalfifo_write(struct file *filp, const char __user * buf,
size_t size, loff_t * ppos)
{
unsigned int count = size;
int ret = ;
struct globalfifo_dev *dev = filp->private_data;
DECLARE_WAITQUEUE(wait, current); mutex_lock(&dev->mutex);
add_wait_queue(&dev->w_wait, &wait); while(dev->current_len == GLOBALMEM_SIZE){
if(filp->f_flags & O_NONBLOCK){
ret = -EAGAIN;
goto out;
} set_current_state(TASK_INTERRUPTIBLE);
mutex_unlock(&dev->mutex);
schedule();
if(signal_pending(current)){
ret = -ERESTARTSYS;
goto out2;
}
mutex_lock(&dev->mutex); } if (count > (GLOBALMEM_SIZE - dev->current_len))
count = (GLOBALMEM_SIZE - dev->current_len); if (copy_from_user(dev->mem + dev->current_len, buf, count)){
ret = -EFAULT;
goto out;
}
else {
dev->current_len += count;
wake_up_interruptible(&dev->r_wait);
ret = count;
printk(KERN_INFO "written %d bytes(s) current_len %d\n", count, dev->current_len); if(dev->queue){
kill_fasync(&dev->queue, SIGIO, POLL_IN);
printk(KERN_DEBUG "%s kill SIGIO\n", __func__);
}
}
out:
mutex_unlock(&dev->mutex);
out2:
remove_wait_queue(&dev->w_wait, &wait);
set_current_state(TASK_RUNNING);
return ret;
}
static loff_t globalfifo_llseek(struct file *filp, loff_t offset, int orig)
{
loff_t ret = ;
switch (orig) {
case : /* ´ÓÎļþ¿ªÍ·Î»ÖÃseek */
if (offset< ) {
ret = -EINVAL;
break;
}
if ((unsigned int)offset > GLOBALMEM_SIZE) {
ret = -EINVAL;
break;
}
filp->f_pos = (unsigned int)offset;
ret = filp->f_pos;
break;
case : /* ´ÓÎļþµ±Ç°Î»ÖÿªÊ¼seek */
if ((filp->f_pos + offset) > GLOBALMEM_SIZE) {
ret = -EINVAL;
break;
}
if ((filp->f_pos + offset) < ) {
ret = -EINVAL;
break;
}
filp->f_pos += offset;
ret = filp->f_pos;
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static long globalfifo_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
struct globalfifo_dev *dev = filp->private_data;
switch (cmd) {
case MEM_CLEAR:
mutex_lock(&dev->mutex);
memset(dev->mem, , GLOBALMEM_SIZE);
dev->current_len =;
printk(KERN_INFO "globalfifo is set to zero\n");
mutex_unlock(&dev->mutex);
break;
default:
return -EINVAL;
} return ;
}
static unsigned int globalfifo_poll(struct file * filp, poll_table * wait)
{
unsigned int mask =;
struct globalfifo_dev *dev = filp->private_data; mutex_lock(&dev->mutex); poll_wait(filp, &dev->w_wait, wait);
poll_wait(filp, &dev->r_wait, wait); if(dev->current_len != )
mask |=POLLIN | POLLRDNORM;
if(dev->current_len != GLOBALMEM_SIZE)
mask |=POLLOUT | POLLWRNORM; mutex_unlock(&dev->mutex);
return mask;
} static int globalfifo_fasync(int fd, struct file *filp, int on)
{
struct globalfifo_dev *dev = filp->private_data; return fasync_helper(fd, filp, on, &dev->queue);
} static int globalfifo_release(struct inode *inode, struct file *filp)
{
globalfifo_fasync(-, filp, );
return ;
}
static const struct file_operations globalfifo_fops = {
.owner = THIS_MODULE,
.llseek = globalfifo_llseek,
.read = globalfifo_read,
.write = globalfifo_write,
.unlocked_ioctl = globalfifo_ioctl,
.open = globalfifo_open,
.poll = globalfifo_poll,
.release = globalfifo_release,
.fasync = globalfifo_fasync,
};
static void globalfifo_setup_cdev(struct globalfifo_dev *dev, int index)
{
int err, devno = MKDEV(globalfifo_major, index);
cdev_init(&dev->cdev, &globalfifo_fops);
dev->cdev.owner = THIS_MODULE;
err = cdev_add(&dev->cdev, devno, );
if (err)
printk(KERN_NOTICE "Error %d adding globalfifo%d", err, index);
}
static int __init globalfifo_init(void)
{
int ret;
dev_t devno = MKDEV(globalfifo_major, ); if (globalfifo_major)
ret = register_chrdev_region(devno, , "globalfifo");
else {
ret = alloc_chrdev_region(&devno, , , "globalfifo");
globalfifo_major = MAJOR(devno);
}
if (ret < )
return ret; globalfifo_devp = kzalloc(sizeof(struct globalfifo_dev), GFP_KERNEL);
if (!globalfifo_devp) {
ret = -ENOMEM;
goto fail_malloc;
}
globalfifo_setup_cdev(globalfifo_devp, ); mutex_init(&globalfifo_devp->mutex);
init_waitqueue_head(&globalfifo_devp->r_wait);
init_waitqueue_head(&globalfifo_devp->w_wait);
return ; fail_malloc:
unregister_chrdev_region(devno, );
return ret;
} static void __exit globalfifo_exit(void)
{
cdev_del(&globalfifo_devp->cdev);
kfree(globalfifo_devp);
unregister_chrdev_region(MKDEV(globalfifo_major, ), );
}
module_init(globalfifo_init);
module_exit(globalfifo_exit); MODULE_LICENSE("GPL v2");

应用程序源码.

 #include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h> void signal_handler(int signalNum)
{
printf("signalNum:0x%x\n",signalNum);
} int main(int argc, char **argv)
{
int fd;
int err; fd = open("/dev/globalfifo", O_RDONLY);
if(fd == -)
printf("open fail\n");
else{
signal(SIGIO, signal_handler);
fcntl(fd, F_SETOWN, getpid());
int oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, oflags | FASYNC);
while()
sleep();
}
return ;
}

结果:

当echo 1 >/dev/globalfifo时

此文源码基于内核源码版本为linux-2.6.22.6

参考:https://www.cnblogs.com/tureno/articles/6059711.html

Linux之异步通知机制分析的更多相关文章

  1. Linux内核中断引入用户空间(异步通知机制)【转】

    转自:http://blog.csdn.net/kingdragonfly120/article/details/10858647 版权声明:本文为博主原创文章,未经博主允许不得转载. 当Linux内 ...

  2. Linux信号(signal) 机制分析

    Linux信号(signal) 机制分析 [摘要]本文分析了Linux内核对于信号的实现机制和应用层的相关处理.首先介绍了软中断信号的本质及信号的两种不同分类方法尤其是不可靠信号的原理.接着分析了内核 ...

  3. Linux内核态抢占机制分析(转)

    Linux内核态抢占机制分析  http://blog.sina.com.cn/s/blog_502c8cc401012pxj.html 摘 要]本文首先介绍非抢占式内核(Non-Preemptive ...

  4. Linux之异步通知20160702

    异步通知,主要说的是使用信号的方式,同时使用信号也是实现进程之间通信的一种方式. 多的不说,我们直接看代码: 首先应用程序的: #include <sys/types.h> #includ ...

  5. Linux进程组调度机制分析【转】

    转自:http://oenhan.com/task-group-sched 又碰到一个神奇的进程调度问题,在系统重启过程中,发现系统挂住了,过了30s后才重新复位,真正系统复位的原因是硬件看门狗重启的 ...

  6. Linux内核抢占实现机制分析【转】

    Linux内核抢占实现机制分析 转自:http://blog.chinaunix.net/uid-24227137-id-3050754.html [摘要]本文详解了Linux内核抢占实现机制.首先介 ...

  7. linux驱动程序之电源管理之标准linux休眠和唤醒机制分析(二)

    三.pm_test属性文件读写 int pm_test_level = TEST_NONE; static const char * const  pm_tests[__TEST_AFTER_LAST ...

  8. Android应用程序组件Content Provider的共享数据更新通知机制分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6985171 在Android系统中,应用程序组 ...

  9. c++ 网络编程(十) LINUX/windows 异步通知I/O模型与重叠I/O模型 附带示例代码

    原文作者:aircraft 原文链接:https://www.cnblogs.com/DOMLX/p/9662931.html 一.异步IO模型(asynchronous IO) (1)什么是异步I/ ...

随机推荐

  1. freeswitch编译mod_av模块

    需要先编译libav库 编译libav下载:git clone https://freeswitch.org/stash/scm/sd/libav.git cd libav ./configure - ...

  2. Eclipse远程调试Tomcat

    1.Linux服务器中在Tomcat的catalina.sh文件添加如下内容: CATALINA_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,ad ...

  3. CompletableFuture

    若你的意图是并发,而非并行,或者你的主要目标是在同一个CPU上执行几个松耦合的任务,充分利用CPU的核,让其足够忙碌,从而最大化程序的吞吐量,那么其实真正想做的避免因为等待远程服务的返回,或对数据库的 ...

  4. Python学习笔记第二十五周(Django补充)

    1.render_to_reponse() 不同于render,render_to_response()不用包含request,直接写template中文件 2.locals() 如果views文件中 ...

  5. jQuery对标签、类样式、值、文档、DOM对象的操作

    jquery的标签属性操作 使用attr()方法对html标签属性进行操作,attr如果参数是一个参数,表示获取html标签的属性值,如果是两个参数则是设置标签属性名以及对象的属性值 .prop()适 ...

  6. bootstrap 4 移除Glyphicons

    /********************************************************************** * bootstrap 4 移除Glyphicons * ...

  7. 百练6376-二维数组右上左下遍历-2015正式C题

    C:二维数组右上左下遍历 总时间限制:  1000ms 内存限制:  65536kB 描述 给定一个row行col列的整数数组array,要求从array[0][0]元素开始,按从左上到右下的对角线顺 ...

  8. 在django中进行MySQL入库

    在django中进行mysql 入库 需要导入 : from django.db import models   在添加主键时,需要使用:  primary_key=True id = models. ...

  9. 外部javascript

  10. 牛客G-指纹锁【一题三解】

    链接:https://www.nowcoder.com/acm/contest/136/G来源:牛客网 题目描述     HA实验有一套非常严密的安全保障体系,在HA实验基地的大门,有一个指纹锁.   ...