分析第一个代码:
代码第50行 struct aiocb aiow, aior;
定义了两个分别用于读和写的异步I/O控制块
代码第56行到76行初始化了这二个控制块。
memset(&aiow, 0, sizeof(aiow));
memset(&aior, 0, sizeof(aior));
aiow.aio_fildes = fd;
aiow.aio_buf = malloc(32);
strcpy((char *)aiow.aio_buf, "aio test");
aiow.aio_nbytes = strlen((char *)aiow.aio_buf) + 1;
aiow.aio_offset = 0;
aiow.aio_sigevent.sigev_notify = SIGEV_THREAD;
aiow.aio_sigevent.sigev_notify_function = aiow_completion_handler;
aiow.aio_sigevent.sigev_notify_attributes = NULL;
aiow.aio_sigevent.sigev_value.sival_ptr = &aiow;
aior.aio_fildes = fd;
aior.aio_buf = malloc(32);
aior.aio_nbytes = 32;
aior.aio_offset = 0;
aior.aio_sigevent.sigev_notify = SIGEV_THREAD;
aior.aio_sigevent.sigev_notify_function = aior_completion_handler;
aior.aio_sigevent.sigev_notify_attributes = NULL;
aior.aio_sigevent.sigev_value.sival_ptr = &aior;
主要是文件描述符,用于读写的缓冲区,读写的字节数和异步I/O完成后的回调函数。
代码第79行发起一个异步写操作,该函数会立即返回, 具体的写操作在底层的驱动完成。
while (1) {
if (aio_write(&aiow) == -1)
goto fail;
然后代码81行异步读操作,该函数会立即返回, 具体的写操作在底层的驱动完成。
if (aio_read(&aior) == -1)
goto fail;
写完成后,注册的aiow_competion_handler写完成函数将被自动自动调用,该函数通过aio_error及aio_return获取了I/O操作的错误码及实际的写操作的返回值。给sigval.sival_ptr赋值,指向了I/O控制块aiow。
sugval.sival是第67行赋值的
aiow.aio_sigevent.sigev_value.sival_ptr = &aiow;
同样,读完成后,注册的aior_completion_handler读完成函数将会被自动调用。
除了像写完成操作中可以获取完成状态,还可以从aio_buf中获取读取的数据。
sleep()是为了模拟其他操作所消耗的时间。
在一次异步操作中,可以将多个i/o请求合并,从而完成一系列的读写操作,其对应的接口函数是
lio_listio
。
第二次代码的编译实现:
编译加载
异步通知
关于代码1的分析:
代码41到46行对应步骤1——注册信号处理函数(注册中断处理函数)
//阻塞了SIGIO,防止信号处理函数的嵌套调用
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask, SIGIO);
act.sa_flags = SA_SIGINFO;
act.sa_sigaction = sigio_handler;
if (sigaction(SIGIO, &act, &oldact) == -1)
goto fail;
sigaction 比signal更高级,主要是信号阻塞和提供信号信息两方面。
使用sigaction 注册的信号处理函数的参数有三个,而第二个参数act就是关于信号的一些信息,我们随后会用到里面的内容。
另外,代码第41行和第42行
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask, SIGIO);
阻塞了SIGIO自己,防止信号处理函数的嵌套调用。
//设置文件属主
fd = open("/dev/vser0", O_RDWR | O_NONBLOCK);
if (fd == -1)
goto fail;
if (fcntl(fd, F_SETOWN, getpid()) == -1)
goto fail;
代码第48行至第53行对应步骤(2)——打开设备文件,设置文件属主(目的是是驱动根据打开文件的file结构,找到相应的进程,从而向该进程发送信号)
即设置文件属主驱动在发信号时,处于一个所谓的任意进程上下文,即不知道当前运行的进程,要给一个特定的进程发信号,则需要一些额外的信息,可以通过fcntl将所属的进程信息保存在file 结构中,从而驱动可以根据file结构来找到对应的进程。
代码第54行和第55行对应步骤(3)——设置设备资源可用可用时驱动向进程发送的信号(非必须,但要用sigaction的高级特性需要)
//设置当前资源可用时,向进程发送SIGIO信号
if (fcntl(fd, F_SETSIG, SIGIO) == -1)
goto fail;
设置了当设备资源可用时,向进程发送SIGIO信号,虽然这是默认发送的信号,但是为了使用信号的更多信息(主要是发送信号的原因,或者说是具体资源的情况),需要显式地进行这一步操作 。
代码第56行和第59行对应步骤(4)——设置文件的FASYNC标志,使能异步通知机制(相当于中断使能位)
//打开异步通知机制
if ((flag = fcntl(fd, F_GETFL)) == -1)
goto fail;
if (fcntl(fd, F_SETFL, flag | FASYNC) == -1)
goto fail;
首先获取了文件的标志,然后再添加FASYNC标志,这就打开了异步通知的机制。
之后主函数初始化后一直休眠,等待驱动发来的信号。当进程收到驱动发来的信号后,注册的信号处理函数 sigio_handler
自动被调用,函数的第一个参数是信号值,第二个参数是信号的附带信息(ID号、发送时间等)。si_band成员
将记录资源是可读还是可写,从而进行操作。
补充异步通知方面的内核代码
fs/fcnl.c
static int setfl(int fd, struct file * filp, unsigned long arg)
{
........
if (((arg ^ filp->f_flags) & FASYNC) && filp->f_op->fasync) {
error = filp->f_op->fasync(fd, filp, (arg & FASYNC) != 0);
........
}
static long do_fcntl(int fd, unsigned int cmd, unsigned long arg,
struct file *filp)
{
long err = -EINVAL;
switch (cmd) {
.......
case F_SETFL:
err = setfl(fd, filp, arg);
break;
......
}
.......
SYSCALL_DEFINE3(fcntl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)
{
.......
err = do_fcntl(fd, cmd, arg, f.file);
.......
}
.......
static int fasync_add_entry(int fd, struct file *filp, struct fasync_struct **fapp)
{
struct fasync_struct *new;
new = fasync_alloc();
if (!new)
return -ENOMEM;
/*
* fasync_insert_entry() returns the old (update) entry if
* it existed.
*
* So free the (unused) new entry and return 0 to let the
* caller know that we didn't add any new fasync entries.
*/
if (fasync_insert_entry(fd, filp, fapp, new)) {
fasync_free(new);
return 0;
}
return 1;
}
.......
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);
}
.......
static void kill_fasync_rcu(struct fasync_struct *fa, int sig, int band)
{
while (fa) {
struct fown_struct *fown;
unsigned long flags;
if (fa->magic != FASYNC_MAGIC) {
printk(KERN_ERR "kill_fasync: bad magic number in "
"fasync_struct!\n");
return;
}
spin_lock_irqsave(&fa->fa_lock, flags);
if (fa->fa_file) {
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);
}
spin_unlock_irqrestore(&fa->fa_lock, flags);
fa = rcu_dereference(fa->fa_next);
}
}
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();
}
}
分析:
代码
SYSCALL_DEFINE3(fcntl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)
是fcntl系统调用对应的代码,它调用了do_fcntl来完成具体的操作。
代码
case F_SETFL:
err = setfl(fd, filp, arg);
break;
代码判断如果是F_ SETFL 则调用setfl 函数,setfl 会调用驱动代码中的fasync接口函数(代码第68行)error = filp->f_op->fasync(fd, filp, (arg & FASYNC) != 0);
,并传递FASYNC标志是否被设置。驱动中的fasync接口函数会调用fasync_ helper 函数(代码第680行)
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
, fasync_helper 函数根据FASYNC标志是否设置来决定在链表中添加一个struct fasync_ struct 节点还是删除一个节点,而这个结构中最主要的成员就是fa_ file, 它是一个打开文件的结构,还包含了进程信息(前面设置的文件属主)。当资源可用时,驱动调用kill fasync函数发送信号,该函数会遍历struct fasync_struct 链表,从而找到所有要接收信号的进程,并调用send_sigio 依次发送信号(代码第692行至第714行)。如下:
static void kill_fasync_rcu(struct fasync_struct *fa, int sig, int band)
{
while (fa) {
struct fown_struct *fown;
unsigned long flags;
if (fa->magic != FASYNC_MAGIC) {
printk(KERN_ERR "kill_fasync: bad magic number in "
"fasync_struct!\n");
return;
}
spin_lock_irqsave(&fa->fa_lock, flags);
if (fa->fa_file) {
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);
}
spin_unlock_irqrestore(&fa->fa_lock, flags);
fa = rcu_dereference(fa->fa_next);
}
}
从上面了解后,总结出驱动代码的完成以下几个操作:
- 构造struct fasync_ struct 链表的头。
- 实现fasync接口函数,调用fasync_ helper 函数来构造struct fasyne_struct 节点,并加入到链表。
- 在资源可用时,调用kill fasync发送信号,并设置资源的可用类型是可读还是可写。
- 在文件最后一次关闭时,即在release接口中,需要显式调用驱动实现的fasyne接口函数,将节点从链表中删除,这样进程就不会再收到信号。
实现了众多接口的虛拟串口驱动的完整代码如下:
看顶头博客
分析:
代码第30行定义了链表的指针,实现了步骤(1);代码第170行至第173行和代码第185行,实现了步骤(2); 代码第66行和第90行发送信号,实现了步骤(3),注意此时资源状态为POLL_ IN和POLL_OUT, 在信号发送函数中会转换成POLLIN和POLLOUT; 代码第45行完成了步骤(4)。
编译和测试的命令如下:
注意:在编译应用程序时,需要在命令行中加入-D_GNU_SOURCE来定义 GNU_SOURCE, 因为F SETSIG不是POSIX标准。
这里就是gcc -o test test.c -D_GNU_SOURCE