第9章 Linux设备驱动中的异步通知与异步I/O之Linux异步通知编程

9.2 Linux异步通知编程

9.2.1 Linux信号

使用信号进行进程间通信(IPC)是UNIX中的一种传统机制,Linux也支持这种机制。在Linux中,异步通知使用信号来实现,Linux中可用的信号及其定义如表9.1所示。

表9.1 Linux信号及其定义


除SIGSTOP和SIGKILL两个信号外,进程能够忽略或捕获其他的全部信号。一个信号被捕获是当一个信号到达时有相应的代码处理它。如果一个信号没有被这个进程所捕获,内核将采用默认行为处理。

9.2.2 信号的接收

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

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

第一个参数指定信号的值,第二个参数指定针对前面信号值的处理函数,若为SIG_IGN,表示忽略该信号;若为SIG_DFL,表示采用系统默认方式处理信号;若为用户自定义的函数,则信号被捕获到后,该函数将被执行。
如果signal()调用成功,它返回最后一次为信号signum绑定的处理函数的handler值,失败则返回SIG_ERR。

在进程执行时,按下“Ctrl+C”将向其发出SIGINT信号,正在运行kill的进程将向其发出SIGTERM信号,代码清单9.1的进程可捕获这两个信号并输出信号值。

代码清单9.1 signal()捕获信号范例

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

void sigterm_handler(int signo)
{
printf("Have caught sig N.O. %d\n", signo);
_exit(0);
}

int main(void)
{

signal(SIGINT, sigterm_handler);
signal(SIGTERM, sigterm_handler);
while(1);

return 0;
}

除signal()函数外,sigaction()函数可用于改变进程接收到特定信号后的行为,它的原型为:

int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));

该函数的第一个参数为信号的值,可以是除SIGKILL及SIGSTOP外的任何一个特定有效的信号。第二个参数是指向结构体sigaction的一个实例的指针,在结构体sigaction的实例中,指定了对特定信号的处理函数,若为空,则进程会以缺省方式对信号处理;第三个参数oldact指向的对象用来保存原来对相应信号的处理函数,可指定oldact为NULL。如果把第二、第三个参数都设为NULL,那么该函数可用于检查信号的有效性。

使用信号实现异步通知的应用程序实例

/*
*
* #include <signal.h>
* typedef void (*sighandler_t)(int); // 函数指针类型
* sighandler_t signal(int signum, sighandler_t handler);
*
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>

#define MAX_LEN 100

// 信号处理函数
void input_handler(int signo)
{
char data[MAX_LEN];
int len;

/* 读取并输出STDIN_FILENO(标准输入的fd为0)上的输入 */
len = read(STDIN_FILENO, &data, MAX_LEN);
data[len] = 0;
printf("input available:%s\n", data);
}

void main(void)
{

int oflags;

/* 启动信号驱动机制 */
signal(SIGIO, input_handler);
fcntl(STDIN_FILENO, F_SETOWN, getpid()); // 设置本进程为STDIN_FILENO文件的拥有者
oflags = fcntl(STDIN_FILENO, F_GETFL);//F_GETFL Get the file access mode and the file status flags
fcntl(STDIN_FILENO, F_SETFL, oflags | FASYNC); // F_SETFL Set the file status flags to the value specified by arg.设置设备文件以支持异步通知

while (1);
}

为了能在用户空间中处理一个设备释放的信号,它必须完成3项工作。

1)通过F_SETOWN IO控制命令设置设备文件的拥有者为本进程,这样从设备驱动发出的信号才能被本进程接收到。

2)通过F_SETFL IO控制命令设置设备文件以支持FASYNC,即异步通知模式。

3)通过signal()函数连接信号和信号处理函数。

9.2.3 信号的释放

在设备驱动和应用程序的异步通知交互中,仅仅在应用程序端捕获信号是不够的,因为信号的源头在设备驱动端。所以,应该在合适的时机让设备驱动释放信号,在设备驱动程序中增加信号释放的相关代码。

为使设备支持异步通知机制,驱动程序中涉及3项工作。

1)支持F_SETOWN命令,能在这个控制命令处理中设置filp->f_owner为对应进程ID。此项工作已由内核完成,设备驱动无须处理。

2)支持F_SETFL命令的处理,每当FASYNC标志改变时,驱动程序中的fasync()函数将得以执行。因此,驱动中应该实现fasync()函数。

3)在设备资源可获得时,调用kill_fasync()函数激发相应的信号。

图9.2所示为异步通知处理过程中用户空间和设备驱动的交互。


图9.2 异步通知中设备驱动和异步通知的交互

设备驱动中异步通知编程,主要用到一项数据结构和两个函数。

数据结构是fasync_struct结构体,#include <linux/fs.h>

struct fasync_struct {
spinlock_t fa_lock;
int magic;
int fa_fd;
struct fasync_struct *fa_next; /* singly linked list */
struct file *fa_file;
struct rcu_head fa_rcu;
};

两个函数分别是:

1)处理FASYNC标志变更的函数。

int fasync_helper(int fd, struct file *filp, int mode, struct fasync_struct **fa);

/*
 * 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.

 */

fs/fcntl.c

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);
}


EXPORT_SYMBOL(fasync_helper);

2)释放信号用的函数。

void kill_fasync(struct fasync_struct **fa, int sig, int band);

fs/fcntl.c

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();
}
}
EXPORT_SYMBOL(kill_fasync);

代码清单9.3支持异步通知的设备结构体模板。

struct xxx_dev {
           struct cdev cdev;                    /* cdev结构体*/
            ...
            struct fasync_struct *async_queue;   /* 异步结构体指针 */
 };

代码清单9.4支持异步通知的设备驱动程序fasync()函数的模板。

static int xxx_fasync(int fd, struct file *filp, int mode)
 {
           struct xxx_dev *dev = filp->private_data; // 获取设备结构体指针
           return fasync_helper(fd, filp, mode, &dev->async_queue);
 }

在设备资源可以获得时,调用kill_fasync()释放SIGIO信号。可读时,第3个参数band设置为POLL_IN,可写时,第3个参数band设置为POLL_OUT。

代码清单9.5 支持异步通知的设备驱动信号释放函数的模板

static ssize_t xxx_write(struct file *filp, const char __user *buf, size_t count,
                      loff_t *f_pos)
 {
              struct xxx_dev *dev = filp->private_data;
              ...
              /* 产生异步读信号 */
              if (dev->async_queue)
                   kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
             ...

}

最后,在文件关闭时,即在设备驱动的release()函数中,调用设备驱动的fasync()函数将文件从异步通知的列表中删除。

代码清单9.6 支持异步通知的设备驱动release()函数模板

 static int xxx_release(struct inode *inode, struct file *filp)
 {
            /* 将文件从异步通知列表中删除 */
            xxx_fasync(-1, filp, 0);
            ...
            return 0;
 }






猜你喜欢

转载自blog.csdn.net/xiezhi123456/article/details/80399148
今日推荐