Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2242071
  • 博文数量: 395
  • 博客积分: 10994
  • 博客等级: 上将
  • 技术积分: 5586
  • 用 户 组: 普通用户
  • 注册时间: 2010-12-17 19:49
文章存档

2014年(1)

2013年(10)

2012年(74)

2011年(303)

2010年(7)

分类: 嵌入式

2011-05-09 17:03:25

转自:

 

http://blogold.chinaunix.net/u2/73521/showart_1110536.html

(注:一下博文中的一些函数是在2.6低版本中才有的,如果你的内核是2.6.30版本的话,这些函数就没有了,他们是使用其他的方法来实现的,我用的2.6.18版本中就有这些函数)

 

轮询函数

轮询的概念和作用

使用非阻塞I/O的应用程序通常会使用select()poll()pollselect用于查询设备的状态,以便用户程序获知是

否能对设备进行非阻塞的访问,它们都需要设备驱动程序中的poll函数支持。

Select()poll()系统调用最终会引发设备驱动设备中的poll()函数被执行。poll()函数为最终执行体)

Linuxselect调用的过程:

1.用户层应用程序调用select(),底层调用poll())

2.核心层调用sys_select() --> do_select()

最终调用文件描述符fd对应的struct file类型变量的struct file_operations *f_oppoll函数。

 

当然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取不同的值,该调用就表现不同的性质:

1timeout0,调用立即返回;

2timeoutNULLselect()调用就阻塞,直到知道有文件描述符就绪;(当有文件描述符就绪时,会向这个函数

发送信号,以唤醒此函数。)

3timeout为正整数,就是一般的定时器。

select的返回值有如下情况:

1.正常情况下返回就绪的文件描述符个数;

2.经过了timeout时长后仍无设备准备好,返回值为0

3.如果select被某个信号中断,它将返回-1并设置errnoEINTR

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的定义,如果你能够有下图,那么我想,你更高兴地去学习文件描述符:

 

 

 

下面操作用来设置、清除、判断文件描述符集合。

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}(注:这个地方的return,你可以看看它的用法,实际上它返回的是一个0,或者1,很奇怪,呵呵)

 

设备驱动中的轮询操作:

 

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结构体的源代码:

 

  48struct poll_table_entry {  49        struct file * filp;  50        wait_queue_t wait;  51        wait_queue_head_t * wait_address;  52};

poll_wait()函数的名称非常容易让人产生误会,以为它和wait_event()等一样,会阻塞地等待某件事情的发生,

这个函数不会引起阻塞.poll_wait()函数所作的 工作是把当前进程添加到wait参数指定的等待列表(poll_table)中。

驱动程序poll()函数应该返回设备资源的可获取状态,即POLLINPOLLOUTPOLLPRIPOLLERRPOLLNVAL等宏的位“或“结果。

每个宏的含义都表明设备的一种状态,如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 的文件描述符有意义:

 通常设备驱动不使用这些标志.

 

 

 

 

 

发表于: 2008-08-06 ,修改于: 2008-08-07 16:11,已浏览1333次,有评论5 推荐 投诉 

 

  网友评论

 

内容: static __inline__ int __FD_ISSET(unsigned long __fd, const __kernel_fd_set *__p)

函数中,__NFDBITS8*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个文件描述符的变化情况,而不必要使用32unsigned long型。

 

Niu Tao评论于:2008-08-06 12:06:49 221.11.22.★) 

 

 

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

 

chinahhucai 评论于:2008-08-06 21:29:20 221.11.22.★) 

 

 

内容: static __inline__ void __FD_ZERO(__kernel_fd_set *p)

在这个函数中,为什么不直接用循环来完成了,这样代码还简单些,因为循环效率低吗?

 

chenjifeng 评论于:2008-08-07 10:29:24 221.11.22.★) 

 

 

内容: 首先说说上面说的节省内存?在32位系统下,如果使用一位去监测一个文件描述符,按默认监测1024个文件描述符需要__FDSET_LONGSlong,也就是128字节,而如果使用一个long型去监测一个文件描述符,那就需要1024long,也就是4096字节。也许对于现在的大内存而言没什么,但在像arm那样的板上会是如何?要知道每一个字节都很宝贵,都不能浪费。况且一个好的操作系统就是要很好的管理系统资源,用最小的代价提供最好的性能为应用程序服务,如果每个数据结构的设计都出于“浪费几个字节无所谓”的态度,那这样下来内核里的N多个浪费的几个字节加起来会是一个什么概念?恐怕要用MB去度量吧。当然对于你说的更容易操作,昨天的确是没考虑,第一感觉就是节省内存。今天你提起觉得的确是有这样的目的,比如__FD_SET()等都是用了位操作。

下来说说select具体是如何和我们写的poll联系起来的:

select(用户态调用select)  (注:这个时候应用层调用的是select (),你也可以追踪内核源代码,看看应用层调用

-->sys_select                                        poll时是如何一级一级用下去的,呵呵)

---->core_sys_select

------>do_select

-------->poll(驱动程序里的poll)

可以看到一层层调用下来,就到了我们写的poll了。其中select的第一个参数n最终是用在了do_select里控制循环了,后面三个在core_sys_select里被“加工”也是被传到do_select里了,而最后的参数timeout是被换算为HZ最中在do_select里被用做schedule_timeout()的参数了。

 

niutao.linux 评论于:2008-08-07 10:36:23 221.11.22.★) 

 

 

内容: 对于chenjifeng的问题我是这样理解的:

如果使用循环代码的确是会简单一些,但如果编译成汇编的话,循环必定涉及到指令的跳转,而简单的赋值操作却只是一个mov就搞定了,况且是顺序执行,效率显然比循环高多了

 

 

 

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