2.1. 怎样管理多个连接?
“我想同一时候监控一个以上的文件描写叙述符(fd)/连接(connection)/流(stream),应该怎么办?”
使用 select() 或 poll() 函数。
注 意:select() 在BSD中被引入,而poll()是SysV STREAM流控制的产物。因此,这里就有了平台移植上的考虑:纯粹的BSD系统可 能仍然缺少poll(),而早一些的SVR3系统中可能没有select(),虽然在SVR4中将其增加。眼下两者都是POSIX. 1g标准,(译者 注:因此在Linux上两者都存在)
select()和poll()本质上来讲做的是同一件事,仅仅是完毕的方法不一样。两者都通过检验一组文件描写叙述符来检測是否有特定的时间将在上面发生并在一定的时间内等待其发生。
[重要事项:不管select()还是poll()都不正确普通文件起非常大效用,它们着重用于套接口(socket)、管道(pipe)、伪终端(pty)、终端设备(tty)和其它一些字符设备,可是这些操作都是系统相关(system-dependent)的。]
2.1.1. 我怎样使用select()函数?
select()函数的接口主要是建立在一种叫’fd_set’类型的基础上。它(‘fd_set’) 是一组文件描写叙述符(fd)的集合。由于fd_set类型的长度在不同平台上不同,因此应该用一组标准的宏定义来处理此类变量:
fd_set set;
FD_ZERO(&set); /* 将set清零 */
FD_SET(fd, &set); /* 将fd增加set */
FD_CLR(fd, &set); /* 将fd从set中清除 */
FD_ISSET(fd, &set); /* 假设fd在set中则真 */
在 过去,一个fd_set通常仅仅能包含少于等于32个文件描写叙述符,由于fd_set事实上仅仅用了一个int的比特矢量来实现,在大多数情况下,检查 fd_set能包含随意值的文件描写叙述符是系统的责任,但确定你的fd_set究竟能放多少有时你应该检查/改动宏FD_SETSIZE的值。*这个值是系 统相关的*,同一时候检查你的系统中的select() 的man手冊。有一些系统对多于1024个文件描写叙述符的支持有问题。[译者注: Linux就是这样 的系统!你会发现sizeof(fd_set)的结果是128(*8 = FD_SETSIZE=1024) 虽然非常少你会遇到这样的情况。]
select的基本接口十分简单:
int select(int nfds, fd_set *readset, fd_set *writeset,
fd_set *exceptset, struct timeval *timeout);
当中:
nfds
须要检查的文件描写叙述符个数,数值应该比是三组fd_set中最大数
更大,而不是实际文件描写叙述符的总数。
readset
用来检查可读性的一组文件描写叙述符。
writeset
用来检查可写性的一组文件描写叙述符。
exceptset
用来检查意外状态的文件描写叙述符。(注:错误并非意外状态)
timeout
NULL指针代表无限等待,否则是指向timeval结构的指针,代表最
长等待时间。(假设当中tv_sec和tv_usec都等于0, 则文件描写叙述符
的状态不被影响,但函数并不挂起)
函数将返回响应操作的相应操作文件描写叙述符的总数,且三组数据均在恰当位置被改动,仅仅有响应操作的那一些没有改动。接着应该用FD_ISSET宏来查找返回的文件描写叙述符组。
这里是一个简单的測试单个文件描写叙述符可读性的样例:
int isready(int fd)
{
int rc;
fd_set fds;
struct timeval tv;
FD_ZERO(&fds);
FD_SET(fd,&fds);
tv.tv_sec = tv.tv_usec = 0;
rc = select(fd+1, &fds, NULL, NULL, &tv);
if (rc < 0)
return -1;
return FD_ISSET(fd,&fds) ? 1 : 0;
}
当然假设我们把NULL指针作为fd_set传入的话,这就表示我们对这样的操作的发生不感兴趣,但select() 还是会等待直到其发生或者超过等待时间。
[译 者注:在Linux中,timeout指的是程序在非sleep状态中度过的时间,而不是实际上过去的时间,这就会引起和非Linux平台移植上的时间不 等问题。移植问题还包含在System V风格中select()在函数退出前会把timeout设为没有定义的 NULL状态,而在BSD中则不是这样, Linux在这点上遵从System V,因此在反复利用timeout指针问题上也应该注意。]
2.1.2. 我怎样使用poll()?
poll ()接受一个指向结构’struct pollfd’列表的指针,当中包含了你想測试的文件描写叙述符和事件。事件由一个在结构中事件域的比特掩码确定。当前 的结构在调用后将被填写并在事件发生后返回。在SVR4(可能更早的一些版本号)中的 “poll.h”文件里包含了用于确定事件的一些宏定义。事件的等待 时间精确到毫秒 (但令人困惑的是等待时间的类型却是int),当等待时间为0时,poll()函数马上返回,-1则使poll()一直挂起直到一个指定 事件发生。以下是pollfd的结构。
struct pollfd {
int fd; /* 文件描写叙述符 */
short events; /* 等待的事件 */
short revents; /* 实际发生了的事件 */
};
于select()十分类似,当返回正值时,代表满足响应事件的文件描写叙述符的个数,假设返回0则代表在规定事件内没有事件发生。如发现返回为负则应该马上查看 errno,由于这代表有发生错误。
假设没有事件发生,revents会被清空,所以你不必多此一举。
这里是一个样例
/* 检測两个文件描写叙述符,分别为一般数据和高优先数据。假设事件发生
则用相关描写叙述符和优先度调用函数handler(),无时间限制等待,直到
发生错误或描写叙述符挂起。*/
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <stropts.h>
#include <poll.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#define NORMAL_DATA 1
#define HIPRI_DATA 2
int poll_two_normal(int fd1,int fd2)
{
struct pollfd poll_list[2];
int retval;
poll_list[0].fd = fd1;
poll_list[1].fd = fd2;
poll_list[0].events = POLLIN|POLLPRI;
poll_list[1].events = POLLIN|POLLPRI;
while(1)
{
retval = poll(poll_list,(unsigned long)2,-1);
/* retval 总是大于0或为-1,由于我们在堵塞中工作 */
if(retval < 0)
{
fprintf(stderr,”poll错误: %s/n”,strerror(errno));
return -1;
}
if(((poll_list[0].revents&POLLHUP) == POLLHUP) ||
((poll_list[0].revents&POLLERR) == POLLERR) ||
((poll_list[0].revents&POLLNVAL) == POLLNVAL) ||
((poll_list[1].revents&POLLHUP) == POLLHUP) ||
((poll_list[1].revents&POLLERR) == POLLERR) ||
((poll_list[1].revents&POLLNVAL) == POLLNVAL))
return 0;
if((poll_list[0].revents&POLLIN) == POLLIN)
handle(poll_list[0].fd,NORMAL_DATA);
if((poll_list[0].revents&POLLPRI) == POLLPRI)
handle(poll_list[0].fd,HIPRI_DATA);
if((poll_list[1].revents&POLLIN) == POLLIN)
handle(poll_list[1].fd,NORMAL_DATA);
if((poll_list[1].revents&POLLPRI) == POLLPRI)
handle(poll_list[1].fd,HIPRI_DATA);
}
}
2.1.3. 我能否够同一时候使用SysV IPC和select()/poll()?
*不能。* (除非在AIX上,由于它用一个无比奇怪的方法来实现这样的组合)
一般来说,同一时候使用select()或poll()和SysV 消息队列会带来很多麻烦。SysV IPC的对象并非用文件描写叙述符来处理的,所以它们不能被传递给select()和 poll()。这里有几种解决方法,其粗暴程度各不同样:
全然放弃使用SysV IPC。 :-)
用fork(),然后让子进程来处理SysV IPC,然后用管道或套接口和父进程 说话。父进程则使用select()。
同上,但让子进程用select(),然后和父亲用消息队列交流。
安排进程发送消息给你,在发送消息后再发送一个信号。*警告*:要做好 这个并不简单,非常easy写出会丢失消息或引起死锁的程序。
另外还有其它方法。
poll :
|
驱动中poll_wait()函数的疑问
应用程序的select()系统调用,调用驱动中的poll()方法。 不理解的是在以下的poll()方法实现中,首先调用poll_wait将等待队列加入到wait结构中,接下来是个推断语句 if (dev->rp != dev->wp) mask |= POLLIN | POLLRDNORM; /* readable */ 仅仅 考虑可读情况。假设这个if语句的条件不满足,那么就不会返回可读,也就是返回0。那么在这里怎么实现堵塞的呢?也就是说假设在应用的select()系 统中,指定一个等待时间,在这个等待时间里假设没有描写叙述符可读,就一直堵塞。那个这个等待时间是怎么和驱动中的poll()方法联系起来的呢?假设要改动 这个poll()方法怎么改动呢?还有在poll()方法中,怎么指定描写叙述符集中的哪一个是可读的呢?简单的返回POLLIN | POLLRDNORM,是无法指定是哪一个描写叙述符可读的呀? 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; } |
|
在调用驱动程序的poll之前,实现调用VFS相关的poll接口的(比方sys_poll等),堵塞、等待时间等的实现是在那个里面完毕的 |
|
是立马返回的,那么假设有一个描写叙述符集当前不可读,也就返回0。但等了一段时间后可读,那么怎么返回mask |= POLLIN | POLLRDNORM;。我的意思是怎么指示给应用程序可读的呢? 那么驱动的poll()方法的作用是什么呢? |
|
假设当前不可读,那么在sys_poll->do_poll中当 前进程就会睡眠在等待队列上,这个等待队列是由驱动程序提供的(就是poll_wait中传入的那个)。当可读的时候,驱动程序可能有一部分代码执行了 (比方驱动的中断服务程序),那么在这部分代码中,就会唤醒等待队列上的进程,也就是之前睡眠的那个,当那个进程被唤醒后do_poll会再一次的调用驱 动程序的poll函数,这个时候应用程序就知道是可读的了。
不知道有没有解释清楚啊,呵呵 |
snow_insky |
2006-7-19 04:30 |
|
POOL方法就是用来支持非堵塞式的訪问,当然是马上返回,可是它会把这次请求放入一个等待队列中,当某个条件满足时,内核会通知应用程序(应用程序的select函数会感知),然后就会接着select操作 |