linux下使用异步通知

阻塞式I/O是一直等待直到设备可以访问,非阻塞式I/O是定期轮询设备是否可以访问。

异步通知则是当设备可以访问时才主动通知应用程序,有点像设备的硬中断。

 

并不是所有的设备都支持异步通知,应用程序通常假设只有套接字和终端才有异步通知的能力。

异步通知存在一个问题,当进程收到SIGIO信号时,它并不知道是哪个文件有了新的输入,如果

有多于一个的文件可以异步通知同一个进程,那么应用进程还需要借助于poll或select来确定输入

的来源。

 

应用层

 

fcntl - manipulate file descriptor

fcntl(fd, F_SETFL, flags | O_ASYNC);

效果:

If you set the O_ASYNC status flag on a file descriptor by using the F_SETFL command of fcntl(),

a SIGIO signal is sent whenever input or output becomes possible on that file descriptor.

 

例子

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <fcntl.h>

#define MAX_LEN 100

void my_handler(int signum)
{
    char data[MAX_LEN];
    int len;

    len = read(STDIN_FILENO, &data, MAX_LEN);
    data[len] = 0;
    
    printf("Input message: %s\n", data);
    exit(0);
}

int main(void)
{
    int oflags;

    /* set new SIGIO handler */
    signal(SIGIO, my_handler);
    /* set fd's owner process */
    fcntl(STDIN_FILENO, F_SETOWN, getpid());
    /* get old fd flags */
    oflags = fcntl(STDIN_FILENO, F_GETFL);
    /* set new fd flags */
    fcntl(STDIN_FILENO, F_SETFL, oflags | O_ASYNC);

    /* infinitely wait until recv SIGIO */
    while(1);
    return 0;
}

为了能处理设备发出的SIGIO信号,用户程序需要做:

1. 设置设备文件的拥有者为本进程,这样一来才能收到设备驱动发出的SIGIO信号。

2. 使设备文件支持异步通知,即设置O_ASYNC标志。

3. 通过signal()指定SIGIO的处理函数。

 

设备驱动

 

struct fasync_struct {
    spinlock_t fa_lock;
    int magic;
    int fa_fd; /* 文件描述符 */
    struct fasync_struct *fa_next; /* 用于链入单向链表 */
    struct file *fa_file; /* fa_file->f_owner记录接收信号的进程 */
    struct rcu_head fa_rcu;
};

 

(1) 设备的异步通知链

struct xxx_dev {
    struct cdev cdev;
    ...
    struct fasync_struct *async_queue; /* 异步通知链 */
};

 

(2) 插入到异步通知链

static int xxx_fasync(int fd, struct file *filp, int on)
{
    struct xxx_dev *dev = filp->private_data;
    return fasync_helper(fd, filp, on, &dev->async_queue);
}

 

(3) 发送信号通知进程

设备使用kill_fasync()来发送信号给用户进程,一般sig为SIGIO,可读时band为POLL_IN,

可写时band为POLL_OUT。

static ssize_t xxx_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_ops)
{
    struct xxx_dev *dev = filp->private_data;
    ...
    if (dev->async_queue) {
        kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
    }
    ...
}

 

(4) 从异步通知链中删除

在关闭文件时,需要把对应的异步通知结构从链表中删除。

static xxx_release(struct inode *inode, struct file *filp)
{
    struct xxx_dev *dev = filp->private_data;
    ...
    fasync_helper(-1, filp, 0, &dev->async_queue);
    ...
}

 

内核API

 

异步通知结构体的插入和删除,失败返回负值,没有变化返回0,有变化返回正值。

/* fasync_helper() is used by almost all character device drivers to set up the 
 * fasync queue, and for regular files by the file lease code. 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)
{
    if (!on) /* 删除 */
        return fasync_remove_entry(filp, fapp);
    return fasync_add_entry(fd, filp, fapp); /* 插入 */
}


发送信号,通知用户进程,sig一般设为SIGIO,可读时band为POLL_IN,可写时band为POLL_OUT。

void kill_fasync(struct fasync_struct **fp, int sig, int band)
{
    /* First a quick test without locking: usually the list is empty. */
    if (*fp) {
        rcu_read_lock();
        kill_fasync_rcu(rcu_dereference(*fp), sig, band);
        rcu_read_unlock();
    }
}

 

Reference

 

[1]. http://www.cnblogs.com/hanyan225/archive/2010/10/20/1857040.html

[2]. 《设备驱动程序》

 

posted on 2015-05-23 22:47  张大大123  阅读(209)  评论(0编辑  收藏  举报

导航