十三、Linux驱动程序开发(8) - 高级字符驱动程序(2)
三、poll 和 select 当应用程序需要进行对多文件读写时,若某个文件没有准备好,则系统会处于读
写阻塞的状态,并影响了其他文件的读写。为了避免这种情况,在必须使用多
输入输出流又不想阻塞在它们任何一个上的应用程序常将非阻塞 I/O 和 poll(System V)、select(BSD Unix)、
epoll(linux unsigned int (*poll) (struct file *filp, poll_table *wait); 实现这个设备方法分两步: 1. 在
一个或多个可指示查询状态变化的等待队列上调用 poll_wait. 如果没有文件描述符可用来执行 I/O, 内核使这个进程在等待队列上等待所有的传递给系统调用的文件描述符. 驱动通过调用函数 poll_wait增加一个等待队列到 poll_table 结构,原型: void poll_wait (struct file *,
wait_queue_head_t *, poll_table *); 2. 返
回一个位掩码:描述可能不必阻塞就立刻进行的操作,几个标志(通过
<linux/poll.h> 定义)用来指示可能的操作:
应当重复一下
POLLRDBAND 和
POLLWRBAND 仅仅对关
联到 socket 的文件描述符有意义: 通常设备驱动不使用这些标志. poll 的描述使用了大量在实际使用中相对简单的东西. 考虑
poll 方法的
scullpipe 实现: static unsigned int scull_p_poll(struct file *filp, poll_table *wait)
{
struct scull_pipe *dev = filp->private_data;
unsigned int mask = 0;
/*
* The buffer is circular; it is considered full
* if "wp" is right behind "rp" and empty if the
* two are equal.
*/
down(&dev->sem);
poll_wait(filp, &dev->inq, wait);
poll_wait(filp, &dev->outq, wait);
if (dev->rp != dev->wp)
mask |= POLLIN | POLLRDNORM; /* readable */
if (spacefree(dev))
mask |= POLLOUT | POLLWRNORM; /* writable */
up(&dev->sem);
return mask;
}
这个代码简单地增加了
2 个
scullpipe 等待队列到
poll_table, 接着设
置正确的掩码位, 根据数据是否可以读或写. 所示的 poll 代码缺乏文件尾支持, 因为 scullpipe 不支持文件 尾情况. 对大部分真实的设备, poll 方法应当返回 POLLHUP 如果没有更多数 据(或者将)可用. 如果调用者使用 select 系统调用, 文件被报告为可读. 不管是使用 poll 还是 select, 应用程序知道它 能够调用 read 而不必永远等待, 并且 read 方法返回 0 来指示文件尾.
四、异步通知与异步IO 在设备驱动中使用异步通知可以使得对设备的访问
可进行时,由驱动主动通知应用程序进行访问,这样,使用无阻塞IO的应用程序无须轮询设备是否可访问,而阻塞访问
也可被类似“中断”的异步通知所取代。 异步通知 概念:一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,这一点非常类似硬件上
“中断”的概念,可称之为“信号驱使的异步IO”。 阻塞IO意味着一直等待设备可访问后再访问。 非阻塞IO中使用poll意味着查询设备是否可访问。 异步通知则意味着设备通知自身可访问,实现了异步IO。 Linux异步通知编程 讲linux异
步通知编程就要说到Linux信号(可参考linux其他中的linux信号)。 使用信号进行进程间通信(IPC)是UNIX系统中的一种传统机制,当然,Linux也支持这种机制,并且在Linux系统中,异步通知使用信号来实现。 在Linux信
号中,除了SIGSTOP(停止执行)和SIGKILL(强行终止)两个信号外,进程能够忽略或捕获其它的全部信号,一个信号被捕获意味着当一个信号到达时有相应的代码去处理它,如果一个信号没有被
这个进程捕获,内核将采用默认的处理。 信号的接收 系统调用signal用来设定某个信号的处理方法。该调用声明的格式如下: 信号的释放 设备驱动中异步通知编程比较简单,主要用到一项数据结果和两个函数,数据结构是fasync_struct结构体,两个函数分别如下: 处理FASYNC标志变更的函数 int
fasync_helper(int fd, struct file *filp, int mode, struct fasync_struct
**fa); 释放信号用的函数 void
kill_fasync(struct fasync_struct *fa, int sig,int band);
和其它的设备驱动一样,将fasync_struct结构体指针放在设备结构体中仍然是最佳选择。 Linux2.6异步IO AIO的引入 输入输出模型是Linux系统中最常见的同步IO,在这个模型中,当请求发出之后,应用
程序就会阻塞,直到请求满足为止。这是一种很好的解决方案,因为调用应用程序在等待IO请求完成时不需要使用任何CPU,
但是在某些情况下,IO请求可能需要与其他进程产生交叠,可移植操
作系统接口(POSIX)异步IO(AIO)应用程序接口就提供了这种功能。 AIO就基本思想是允许进程发起很多IO操作,而不用阻塞或等待任何操作完成,稍后或在接收到IO操作完成的通知时,进程就可以检索IO操作的结果。 Select()函数所提供的功能(异步阻塞IO)与AIO类似,它对通知事件进行阻塞,而不是对IO调用进行阻塞。 在异步非阻塞IO中,我们可以同时发起多个传输操作,这需要每个传输操作都有唯一的上下
文,这样才能在它们完成时区分到底是哪个传输操作完成了,在AIO中,通过aiocb(AIO IO Control Block)结构体进行区分,这个结构体包含了有关传输的所有信息,包括为数据准
备的用户缓冲区,在产生IO(称为完成)通知时,aiocb结构就被用来唯一标识所完成的IO操作。 AIO系列API被GUN库函数所包含,它被POSIX.1b所要求。主要包括如下函数: aio_read aio_read 函数请求对一个有效的文件描述符进行异步读操作。这个文件描述符可以表示一个文件、套接字甚至管道。aio_read 函数的原型如下: int aio_read( struct aiocb *aiocbp ); aio_read 函数在请求进行排队之后会立即返回。如果执行成功,返回值就为 0;
如果出现错误,返回值就为 -1,并设置 errno 的值。 要执行读操作,应用程序必须
对 aiocb 结构进行初始化。下面这个简短的例子就展示了如何填充 aiocb 请求结构,并使用 aio_read 来执行异步读请求(现在暂时忽略通知)操作。它还展示了 aio_error 的用法,不过我们将稍后再作解释。 aio_write aio_write 函数用来请求一个异步写操作。其函数原型如下: int aio_write( struct aiocb *aiocbp ); aio_write 函数会立即返回,说明请求已经进行排队(成功时返回值为 0,
失败时返回值为
-1,并相应地设置 errno)。 这与 read 系统调用类似,但是有一点不一样的行为需要注意。回想一下对于 read 调用来说,要使用的偏移量是非常重要的。然而,对于 write 来说,这个偏移量只有在没有设置 O_APPEND 选项的文件上下文中才会非常重要。如果设置了 O_APPEND,那么这个偏移量就会被忽略,数据都会被附加到文件的末尾。否则,aio_offset 域就确定了数据在要写入的文件中的偏移量。 aio_error aio_error 函数被用来确定请求的状态。其原型如下: int aio_error( struct aiocb *aiocbp ); 这个函数可以返回以下内容:
aio_return 异步 I/O 和标准块 I/O 之
间的另外一个区别是我们不能立即访问这个函数的返回状态,因为我们并没有阻塞在 read 调用上。在标准的 read 调用中,返回状态是在该函数返
回时提供的。但是在异步 I/O 中,我们要使用 aio_return 函数。这个函数的原型如下: ssize_t aio_return( struct aiocb *aiocbp ) 只有在 aio_error 调用确定请求已经完成(可能成功,也可能发生了错误)之后,才会调用这个函数。aio_return 的返回值就等价于同步情况中 read 或 write 系统调用的返回值(所传输的字节数,如果发生错误,返回值就为 -1)。 aio_suspend 我们可以使用 aio_suspend 函数来挂起(或阻塞)调用进程,直到异步请求完成为止,此时会产生一个信号,或者发生其他超时操
作。调用者提供了一个 aiocb 引用列表,其中任何一个完成
都会导致
aio_suspend 返回。 aio_suspend 的函数原型如下: int aio_suspend( const struct aiocb *const
cblist[], int n,
const struct timespec *timeout ); aio_suspend 的使用非常简单。我们要提供一个 aiocb 引用列表。如果任何一个完成了,这个调用就会返回 0。否则就会返回 -1,
说明发生了错误。 aio_cancel aio_cancel 函数允许我们取消对某个文件描述符执行的一个或所有 I/O 请求。其原型如下: int aio_cancel( int fd, struct aiocb *aiocbp ); 要取消一个请求,我们需要提
供文件描述符和
aiocb 引用。如果这个请求被成功取消了,那么这个函
数就会返回
AIO_CANCELED。如果请求完成了,这个函数就会
返回
AIO_NOTCANCELED。 要取消对某个给定文件描述符
的所有请求,我们需要提供这个文件的描述符,以及一个对 aiocbp 的
NULL 引用。如果所有的请求都取消了,这个函数就会返
回
AIO_CANCELED;如果至少有一个请求没有被取
消,那么这个函数就会返回 AIO_NOT_CANCELED;如果
没有一个请求可以被取消,那么这个函数就会返回 AIO_ALLDONE。我们然后可以使用 aio_error 来验证每个 AIO 请
求。如果这个请求已经被取消了,那么 aio_error 就会返回 -1,并且 errno 会被设置为 ECANCELED。 使用信号作为IO的通知 Linux信号作为异步通知的机制在AIO中
仍然是适用的,为使用信号,使用AIO的应用程序同样需要定义信号处理程
序,在指定的信号被产生时会触发调用这个处理程序,作为信号上下文的一部分,特定的aiocb请求被提供给信号处理函数用来区分AIO请求。 使用回调函数作为AIO的通知 除了信号以外,应用程序还可提供一个回调(callback)函数给内核,以便AIO的
请求完成后内核调用这个函数。 总结 本节所将的异步IO可以使得应用程序在等待IO操作的同时进行其他操作。 使用信号可以实现驱动程序与用户程序之间的异步通知,总体而言,设备驱动和用户空间要完成以下工作:用户空间设置文件的拥有者、FASYNC标志及捕获信号,内核空间响应对文件的拥有者,FASYNC标志的设置,并在资源可获得时释放信号。 Linux2.6内核包含对AIO的支持为用户空间提供统一的异步IO接口,在AIO中,信号和回调函数是实现内核空间对用户空间应用程序通知的两种机制。 五、移位一个设备(llseek) llseek是修改文件中的当前读写位置的系统调用。内核中的缺省的实现进行移位通过修改 filp->f_pos, 这是文件中的当前读写位置。对于 lseek 系统调用要正确工作,读和写方法必须通过更新它们收到的偏移量来配合。 如果设备是不允许移位的,你
不能只制止声明
llseek 操作,因为缺省的方法允许移位。应当在你的 open 方法中,通过调用 nonseekable_open 通知内核你的设备不支持 llseek : int nonseekable_open(struct inode *inode; struct file *filp); |
|