炼狱,是为追逐光芒
分类: LINUX
2012-03-02 15:12:39
1.select
能够监听多个阻塞的文件描述符,这样,不需要fork和多进程就可以实现并发服务(网络中常用来监听多个网络连接)。
原型
maxfdp1:select中监视的文件句柄数,一般设为要监视的文件中的最大文件号加一。
readfds:select()监视的可读文件句柄集合,当readfds映象的文件句柄状态变成可读时系统诉select函数返回。这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值,可以传入NULL值,表示不关心任何文件的读变化;
writefds:监视的可写文件句柄集合,当writefds映象的文件句柄状态变成可写时系统告诉select函数返回。如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值,可以传入NULL值,表示不关心任何文件的写变化。
exceptfds:select()监视的异常文件句柄集合,当exceptfds映象的文件句柄上有特殊情况发生时系统会告诉select函数返回。
tvptr:select()的超时结束时间。
这个参数它使select处于三种状态,
第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;
第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;
第三,timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。
返回值:
负值:select错误对文件句柄操作函数
实例
2.驱动中轮询的实现
第一个参数file结构体指针,第二个参数为轮询表指针。
这个函数完成两项工作。
①对可能引起设备文件状态变化的等待队列调用poll_wait()函数,将对应的等待队列拖添加到poll_table,若没有文件描述符可用来执行I/O,则内核将使进程在传递到该系统调用的所有文件描述符对应的等待队列上等待。
②返回表示是否能对设备进行无阻塞读写访问的掩码。
关键的用于向poll_table注册等待队列的poll_wait()函数原型
功能:吧当前进程添加到wait参数指定的等待列表poll_table中,无阻塞执行。
驱动poll()应该返回设备资源的可获取状态,即
#define POLLIN 0x0001
#define POLLPRI 0x0002
#define POLLOUT 0x0004
#define POLLERR 0x0008
#define POLLHUP 0x0010
#define POLLNVAL 0x0020
Poll函数典型的模板
3.与read 和write 的交互
正确实现poll调用的规则:
① 如果在输入缓冲中有数据,read 调用应当立刻返回,即便数据少于应用程序要求的,并确保其他的数据会很快到达。 如果方便,可一直返回小于请求的数据,但至少返回一个字节。在这个情况下,poll 应当返回 POLLIN|POLLRDNORM。
②如果在输入缓冲中无数据,read默认必须阻塞直到有一个字节。若O_NONBLOCK 被置位,read 立刻返回 -EAGIN 。在这个情况下,poll 必须报告这个设备是不可读(清零POLLIN|POLLRDNORM)的直到至少一个字节到达。
③若处于文件尾,不管是否阻塞,read 应当立刻返回0,且poll 应该返回POLLHUP。
向设备写数据
①若输出缓冲有空间,write 应立即返回。它可接受小于调用所请求的数据,但至少必须接受一个字节。在这个情况下,poll应返回 POLLOUT|POLLWRNORM。
②若输出缓冲是满的,write默认阻塞直到一些空间被释放。若 O_NOBLOCK 被设置,write 立刻返回一个 -EAGAIN。在这些情况下, poll 应当报告文件是不可写的(清零POLLOUT|POLLWRNORM). 若设备不能接受任何多余数据, 不管是否设置了 O_NONBLOCK,write 应返回 -ENOSPC("设备上没有空间")。
③永远不要让write在返回前等待数据的传输结束,即使O_NONBLOCK 被清除。若程序想保证它加入到输出缓冲中的数据被真正传送, 驱动必须提供一个 fsync 方法。
刷新待处理输出
若一些应用程序需要确保数据被发送到设备,就必须实现fsync方法。对 fsync的调用只在设备被完全刷新时(即输出缓冲为空)才返回,即便这需要一些时间,不管 O_NONBLOCK 是否被设置对此没有影响。其原型是:
参数datasync用于区分fsync和fdatasync两个系统调用,只与文件系统有挂,驱动程序可以忽略。fsync方法对时间没有严格要求,大部分时候,字符驱动中只给个NULL指针,而快设备总是用通用的block_fsync来实现这个方法,block_fsync会依次刷新设备的所有缓冲块,并等待所有I/O结束。
底层数据结构
只要用户应用程序调用 poll、select、或epoll_ctl,内核就会调用这个系统调用所引用的所有文件的 poll 方法,并向他们传递同一个poll_table。 poll_table 结构只是构成实际数据结构的简单封装:
对于 poll和 select系统调用,poll_table 是一个包含 poll_table_entry 结构内存页链表
对 poll_wait 的调用有时还会将进程添加到给定的等待队列。整个的结构必须由内核维护,在 poll 或者 select 返回前,进程可从所有的队列中去除, .
如果被轮询的驱动没有一个驱动程序指明可进行非阻塞I/O,poll 调用会简单地睡眠,直到一个它所在的等待队列(可能许多)唤醒它.
当 poll 调用完成,poll_table 结构被重新分配, 所有的之前加入到 poll 表的等待队列入口都会从表和它们的等待队列中移出.
4.异步通知
通过使用异步通知,应用程序可以在数据可用时收到一个信号,而无需不停地轮询。
启用步骤:
①它们指定一个进程作为文件的拥有者:使用 fcntl 系统调用发出 F_SETOWN 命令,这个拥有者进程的 ID 被保存在 filp->f_owner。目的:让内核知道信号到达时该通知哪个进程。
②使用 fcntl 系统调用,通过 F_SETFL 命令设置 FASYNC 标志。
内核操作过程
①.F_SETOWN被调用时filp->f_owner被赋值。
②. 当 F_SETFL 被执行来打开 FASYNC, 驱动的 fasync 方法被调用.这个标志在文件被打开时缺省地被清除。
③. 当数据到达时,所有的注册异步通知的进程都会被发送一个 SIGIO 信号。
Linux 提供的通用方法是基于一个数据结构和两个函数,定义在
数据结构:
驱动调用的两个函数的原型:
当一个打开的文件的FASYNC标志被修改时,调用fasync_helper 来从相关的进程列表中添加或去除文件。除了最后一个参数, 其他所有参数都时被提供给 fasync 方法的相同参数并被直接传递。 当数据到达时,kill_fasync 被用来通知相关的进程,它的参数是被传递的信号(常常是 SIGIO)和 band(几乎都是 POLL_IN)。
这是 scullpipe 实现 fasync 方法的:
当数据到达, 下面的语句必须被执行来通知异步读者. 因为对 sucllpipe 读者的新数据通过一个发出 write 的进程被产生, 这个语句出现在 scullpipe 的 write 方法中:
当文件被关闭时必须调用fasync 方法,来从活动的异步读取进程列表中删除该文件。尽管这个调用仅当 filp->f_flags 被设置为 FASYNC 时才需要,但不管什么情况,调用这个函数不会有问题,并且是普遍的实现方法。 以下是 scullpipe 的 release 方法的一部分:
异步通知使用的数据结构和 struct wait_queue 几乎相同,因为他们都涉及等待事件。区别异步通知用 struct file 替代 struct task_struct. 队列中的 file 用获取 f_owner, 一边给进程发送信号。
应用实例: