Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1031149
  • 博文数量: 123
  • 博客积分: 5051
  • 博客等级: 大校
  • 技术积分: 1356
  • 用 户 组: 普通用户
  • 注册时间: 2008-07-14 10:56
文章分类
文章存档

2012年(1)

2011年(21)

2010年(13)

2009年(55)

2008年(33)

分类: LINUX

2008-08-06 12:04:10

L轮询函数
轮询的概念和作用
使用非阻塞I/O的应用程序通常会使用select()和poll(),poll和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函数。

当然2.25.45中还引入了epoll(),即拓展的poll();
select()和poll()系统调用的本质一样,前者在BSD UNIX中引入的,后者在System V
中引入的。
1、首先说什么是文件描述符,它有什么作用?
文件描述符是一个简单的整数,用以标明每一个被进程所打开的文件和socket。第一个打开的文件是0,第二个是
1,依此类推。Unix 操作系统通常给每个进程能打开的文件数量强加一个限制。更甚的是,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.正常情况下返回就绪的文件描述符个数;
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 {
  37        unsigned long fds_bits [__FDSET_LONGS];
  38} __kernel_fd_set;

typedef __kernel_fd_set         fd_set;

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


0

0

0

0

0

1

0

...

0

0

...

0

1

0

0

...

0

0

0

0

0                  3132                         63



下面操作用来设置、清除、判断文件描述符集合。
FD_ZERO(fd_set *set);//将文件描述符集fd_set中的值置0,如此以来对应所有位都被设置为0;
下面是对此函数的具体操作:

static __inline__ void __FD_ZERO(__kernel_fd_set *p)
  88{
  89        unsigned long *tmp = p->fds_bits;
  90        int i;
  91
  92        if (__builtin_constant_p(__FDSET_LONGS)) {
  93                switch (__FDSET_LONGS) {//__FDSET_LONGS的取值由系统决定
  94                      case 16:
  95                        tmp[ 0] = 0; tmp[ 1] = 0; tmp[ 2] = 0; tmp[ 3] = 0;
  96                        tmp[ 4] = 0; tmp[ 5] = 0; tmp[ 6] = 0; tmp[ 7] = 0;
  97                        tmp[ 8] = 0; tmp[ 9] = 0; tmp[10] = 0; tmp[11] = 0;
  98                        tmp[12] = 0; tmp[13] = 0; tmp[14] = 0; tmp[15] = 0;
  99                        return;
 100
 101                      case 8:
 102                        tmp[ 0] = 0; tmp[ 1] = 0; tmp[ 2] = 0; tmp[ 3] = 0;
 103                        tmp[ 4] = 0; tmp[ 5] = 0; tmp[ 6] = 0; tmp[ 7] = 0;
 104                        return;
 105
 106                      case 4:
 107                        tmp[ 0] = 0; tmp[ 1] = 0; tmp[ 2] = 0; tmp[ 3] = 0;
 108                        return;
 109                }
 110        }
 111        i = __FDSET_LONGS;
 112        while (i) {
 113                i--;
 114                *tmp = 0;
 115                tmp++;
 116        }//此循环是为了使得上面的判断操作变得更安全。
 117}
上面有内建函数 __builtin_constant_p ,这个函数用于判断一个值是否为编译时常数,假如参数__FDSET_LONGS的
值是常数,函数返回 1,否则返回 0。

FD_SET(int fd,fd_set *set);//将一个文件描述符加入文件描述集中 。
即将fd在文件描述符中所对应的位置1,*set为所操作的文件描述符集对象。
2static __inline__ void __FD_SET(unsigned long __fd, __kernel_fd_set *__fdsetp)
  53{
  54        unsigned long __tmp = __fd / __NFDBITS;
  55        unsigned long __rem = __fd % __NFDBITS;
  56        __fdsetp->fds_bits[__tmp] |= (1UL<<__rem);//将无符号长整型的1左移_rem位
  57}



FD_CLR(int fd,fd_set *set)//将一个文件描述符从文件描述符集中清除。

static __inline__ void __FD_CLR(unsigned long __fd, __kernel_fd_set *__fdsetp)
  61{
  62        unsigned long __tmp = __fd / __NFDBITS;
  63        unsigned long __rem = __fd % __NFDBITS;
  64        __fdsetp->fds_bits[__tmp] &= ~(1UL<<__rem);
  65}



FD_ISSET(int fd,fd_set *set)//判断文件描述符是否被置位。

9static __inline__ int __FD_ISSET(unsigned long __fd, const __kernel_fd_set *__p)
  70{
  71        unsigned long __tmp = __fd / __NFDBITS;
  72        unsigned long __rem = __fd % __NFDBITS;
  73        return (__p->fds_bits[__tmp] & (1UL<<__rem)) != 0;
  74}

设备驱动中的轮询操作:

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);
static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
  38{
  39        if (p && wait_address)
  40                p->qproc(filp, wait_address, p);//这个函数需要用户自己编写来实现。
  41}
下面是对轮询表的定义:
  33typedef struct poll_table_struct {
  34        poll_queue_proc qproc;
  35} poll_table;
  31typedef 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  {
struct * ;
;
* ;
};
poll_wait()函数的名称非常容易让人产生误会,以为它和wait_event()等一样,会阻塞地等待某件事情的发生,
这个函数不会引起阻塞.poll_wait()函数所作的 工作是把当前进程添加到wait参数指定的等待列表(poll_table)中。
驱动程序poll()函数应该返回设备资源的可获取状态,即POLLIN、POLLOUT、POLLPRI、POLLERR、POLLNVAL等宏的位“或“结果。
每个宏的含义都表明设备的一种状态,如POLLIN意味着设备可以无阻塞地读,POLLOUT意味着设备可以无阻塞地写。
下面对poll()函数的可获得状态的取值进行解析:
POLLIN
如果设备可被不阻塞地读, 这个位必须设置.
POLLRDNORM
这个位必须设置, 如果"正常"数据可用来读. 一个可读的设备返回( POLLIN|POLLRDNORM ).
POLLRDBAND
这个位指示带外数据可用来从设备中读取. 当前只用在 Linux 内核的一个地方( DECnet 代码 )并且通常对设备驱动不可用.
POLLPRI
高优先级数据(带外)可不阻塞地读取. 这个位使 select 报告在文件上遇到一个异常情况, 因为 selct 报告带外数据作为一个异常情况.
POLLHUP
当读这个设备的进程见到文件尾, 驱动必须设置 POLLUP(hang-up). 一个调用 select 的进程被告知设备是可读的, 如同 selcet 功能所规定的.
POLLERR
一个错误情况已在设备上发生. 当调用 poll, 设备被报告位可读可写, 因为读写都返回一个错误码而不阻塞.
POLLOUT
这个位在返回值中设置, 如果设备可被写入而不阻塞.
POLLWRNORM
这个位和 POLLOUT 有相同的含义, 并且有时它确实是相同的数. 一个可写的设备返回( POLLOUT|POLLWRNORM).
POLLWRBAND
    如同 POLLRDBAND , 这个位意思是带有零优先级的数据可写入设备. 只有 poll 的数据报实现    使用这个位,
因为一个数据报看传送带外数据.应当重复一下 POLLRDBAND 和 POLLWRBAND 仅仅对关联到 socket 的文件描述符有意义:
 通常设备驱动不使用这些标志.


阅读(4374) | 评论(7) | 转发(0) |
给主人留下些什么吧!~~

chinahhucai2008-08-06 21:29:20

__tmp是用于df_set类型中fds_bits数组成员的下标,之所以等于fd/__NFDBITS是因为要充分利用内存。这样说法应该不怎么具有说服力吧!因为大多数情况下,1024个文件描述符已经足够了,当然,可能会大于这个值。但是想想看,1024位在整个内存中又算得了什么呢?所以,上面的说法说服力不是很强。那么,如果是为了更容易操作,那结果又会是怎样的呢?

chinaunix网友2008-08-06 12:06:49

在static __inline__ int __FD_ISSET(unsigned long __fd, const __kernel_fd_set *__p) 函数中,__NFDBITS是8*sizeof(unsigned long),如果在32位系统下,就是32,__tmp是用于df_set类型中fds_bits数组成员的下标,之所以等于fd/__NFDBITS是因为要充分利用内存。例如__fd小于32的所有文件描述符都使用fds_bist[0]的相应位来标识其是否有变化,__fd等于0的使用fds_bits[0]的第零位,__fd等于1的使用fds_bits[0]的第一位,依次如此。这样fds_bits数组的每一个元素(一个unsigned long型)都可以检测32个文件描述符的变化情况,而不必要使用32个unsigned long型。