轮询操作

一、轮询的概念:
    使用非阻塞I/O的应用程序通常会使用select()和poll()系统调用查询是否可对设备进行无阻塞的访问,select() 和 poll() 系统调用都需要设备驱动程序中的poll函数支持,也就是说,poll 函数为最终执行体。
二、Linux下 select 调用的过程:
    1.用户层应用程序调用 select() ,底层调用 poll()
    2.核心层调用 sys_select() --> do_select()
    最终调用文件描述符fd 对应的 struct file 类型变量的 struct file_operations *f_op 的 poll 函数。
    ## 解释 ##
    【1】select() 和 poll()系统调用的本质一样,前者在 BSD UNIX 中引入,后者在 System V 中引入。
    【2】文件描述符是一个简单的整数,用以标明每一个被进程所打开的文件和 socket 。第一个打开的文件是0,第二个是1,依此类推。Unix 操作系统通常给每个进程能打开的文件数量强加一个限制。大多数情况下,1024 个文件描述符足够了。非常忙的cache可能需要4096或更多。当然,你可以自己配置文件描述符限制,推荐设置系统级限制的数量为每个进程限制的2 倍。
三、应用程序中的轮询编程
    应用程序中最广泛用到的是 BSD UNIX 中引入的 select() 系统调用,其原型如下:
            int select(int numfds,fd_set *readfme,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);
    select 的第一个参数是文件描述符集中要被检测的数目,这个值必须至少比待检测的最大文件描述符大1;参数 readfds 指定了被读监控的文件描述符集;参数 writefds 指定了被写监控的文件描述符集;而参数 exceptfds 指定了异常处理的文件描述符集。timeout 参数是一个指向 struct timeval 类型的指针,它可以使 select() 在等待 timeout 时间后若没有文件描述符准备好则返回, struct timeval 数据结构的定义如下:
struct timeval
{
int tv_sec;//表示几秒
int tv_usec;//表示几微秒
};
timeout 取不同的值,该调用就表现不同的性质: 
1.timeout为0,调用立即返回; 
2.timeout为 NULL,select()调用就阻塞,直到知道有文件描述符就绪;(当有文件描述符就绪时,会向这个函数发送信号,以唤醒此函数。) 
3.timeout为正整数,就是一般的定时器。
select 的返回值有如下情况: 
1.正常情况下返回就绪的文件描述符个数 numfds; 
2.经过了 timeout 时长后仍无设备准备好,返回值为0; 
3.如果 select 被某个信号中断,它将返回-1,并设置 errno 为 EINTR ;
4.如果出错,返回-1并设置相应的 errno 。
select() 函数的接口主要是建立在一种叫“fd_set”类型的基础上。这个类型是一组文件描述符(fd)的集合。因为 fd_set 类型的长度在不同平台上不同,因此应该用一组标准的宏定义来处理这个类变量:
首先我们来了解 fd_set 这个结构的定义:
        #define FD_ZERO(fdsetp) __FD_ZERO(fdsetp)
(注意了,下面定义的常量是很有用的,因为在了解 fd_set 这个结构中会使用到,当然,这些值并不是规定死的,在不同的程序中有不同的值,不过这些值只是取少数的几个规定值)
        #define __FDSET_LONGS   (__FD_SETSIZE/__NFDBITS)    //计算得到的值为32
        #define __FD_SETSIZE    1024
        #define __NFDBITS       (8 * sizeof(unsigned long))        //8*4=32
        typedef struct {
              unsigned long fds_bits [__FDSET_LONGS];
              __kernel_fd_set;
        typedef __kernel_fd_set    fd_set;

以上就是对文件描述符集 fd_set 的定义,如果你能够有下图,那么我想,你更高兴地去学习文件描述符:

000001...00...0100...0000
第0位                                                                                                          31位    32位                                                                          63位
    下面操作用来设置、清除、判断文件描述符集合。
            FD_ZERO(fd_set *set);                //清除一个文件描述集;
            FD_SET(int fd,fd_set *set);         //将一个文件描述符加入文件描述集中;
            FD_CLR(int fd,fd_set *set);         //将一个文教描述符从文件描述集中清除;
            FD_ISSET(int fd,fd_set *set);      //判断文件描述符是否被置位。
四、设备驱动中的轮询编程
    设备驱动中 poll() 函数的原型是:
            unsigned int (*poll)(struct file  *filp,struct  poll_table  *wait);
    第一个参数为file结构体指针,第二个参数为轮询表指针。这个函数应该进行以下两项工作:
    1、对可能引起设备文件状态变化的等待队列调用 poll_wait() 函数,将对应的等待队列头添加到 poll_table;
    2、返回表示是否对设备进行无阻塞读、写访问的掩码。
    关键的用于向poll_table注册等待队列的 poll_wait() 函数的原型如下:
            void poll_wait(struct file *filp,wait_queue_heat_t *queue,poll_table *wait);
    下面是对轮询表的定义:
            typedef struct poll_table_struct {
                  poll_queue_proc qproc;
            } poll_table;
            typedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *);
    由上面的代码可以知道 poll_table 结构只是对一个函数的封装,更有趣的是,这个函数建立了实际的数据结构。那个数据结构,对于 poll 和 select , 是一个内存页的链表, 其中包含 poll_table_entry 结构。每个 poll_table_entry 持有被传递给 poll_wait 的 struct file 和wait_queue_head_t 指针,以及一个关联的等待队列入口.
    下面是poll_table_entry结构体的源代码:
            struct poll_table_entry {
                struct file * filp;
                wait_queue_t wait;
                wait_queue_head_t * wait_address;
            };
    poll_wait() 函数所作的工作是把当前进程添加到 wait 参数指定的等待列表(poll_table)中。
    驱动程序 poll() 函数应该返回设备资源的可获取状态,即POLLIN、POLLOUT、POLLPRI、POLLERR、POLLNVAL等宏的位“或“结果。
每个宏的含义都表明设备的一种状态。
    下面对poll()函数的可获得状态的取值进行解析:
    
     
posted @ 2013-10-04 21:04  Jan5  阅读(1428)  评论(0编辑  收藏  举报