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