File I/O Multiplexed I/O
select()
#include <sys/select.h>
int select(int n, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
FD_CLR(int fd, fs_set *set);
FD_ISSET(int fd, fs_set *set);
FD_SET(int fd, fs_set *set);
FD_ZERO(int fd, fs_set *set);
select()的调用会一直阻塞,直到给定的文件描述符可以执行I/O,或是已过了可选指定的超时。
readfds中列出的文件描述符,以查看数据是否可用于读取。
writefds中列出的文件描述符,以查看写入操作是否将在不阻塞的情况下完成。
exceptfds中列出的文件描述符,以查看是否发生异常,或是否带外数据可用(这些状态仅适用于sockets)。
上述这些可以设置为NULL, 表示select不监视这个事件。
第一个参数n等于任何集合中值最高的文件描述符的值,再加上1。
timeout参数是指向timeval结构的指针,定义如下:
#include <sys/time.h>
struct timeval{
long tv_sec;
long tv_usec;
};
如果这个参数不是NULL, select将会返回超时返回,即使没有任何的文件描述符准备好执行IO。
**因为这个结构体的状态在各个UNIX系统中都是未定的,所以在每次调用select前,都要重新初始化。连同文件描述符sets。
如果timeout的两个参数都设置为0,那么调用将立即返回,报告调用时尚未处理的任何事件,但不等待任何后续事件。
文件描述符集不是直接操作的,而是通过宏进行管理。
- FD_ZERO
移除指定sets中的所有文件描述符,它应该在每次调用select前被调用。
fd_set writefds;
FD_ZERO(&writefds);
- FD_SET
在一个给定的sets中增加一个文件描述符 - FD_CLR
在一个给定的sets中移除一个文件描述符 - FD_SET(fd, &writesfds);
- FD_CLR(fd, &writesfds);
FD_SET(fd, &writefds); /* add 'fd' to the set */
FD_CLR(fd, &writefds); /* oops,
remove 'fd' from the set */
设计良好的代码不应该使用FD_CLR,而且很少使用它。
- FD_ISSET
测试文件描述符是否是给定set的一部分。
如果文件描述符在集合中,则返回一个非零整数,如果不返回,则返回0。fd_ISSET是在SELECT()调用返回以测试给定的文件描述符是否已准备好操作后使用的:
if(FD_ISSET(fd, &writefds))
/*'fd' is readable without blocking*/
在成功的情况下,select()返回所有三个集合中为I/O准备的文件描述符的数量。如果提供了超时,则返回值可能为0。如果出现错误,则调用返回−1,并设置errno。 对于以下值之一:
- EBADF
在其中一个集合中提供了一个无效的文件描述符。 - EINTR
等待时捕捉到信号,可以重新发出呼叫。 - EINVAL
参数n是负的。 或者给定的超时无效。 - ENOMEM
内存不足,无法完成请求。
举例使用:
int selectExample() {
struct timeval tv;
fd_set readfds;
int ret;
/*wait on stdin for input*/
FD_ZERO(&readfds);
FD_SET(STDIN_FILENO, &readfds);
/*Wait up to five seconds*/
tv.tv_sec = TIMEOUT;
tv.tv_usec = 0;
/*All right, now block!*/
ret = select(STDIN_FILENO + 1, &readfds, NULL, NULL, &tv);
if(ret == -1){
perror("select");
return 1;
}
else if(!ret){
printf("%d seconds elapsed\n", TIMEOUT);
return 0;
}
if(FD_ISSET(STDIN_FILENO, &readfds)){
char buf[BUF_LEN+1];
int len;
/*guaranteed to not block*/
len = read(STDIN_FILENO, buf, BUF_LEN);
if(len == -1){
perror("read");
return -1;
}
if(len){
buf[len] = '\0';
printf("read: %s\n", buf);
}
return 0;
}
fprintf(stderr, "This should not happen!\n");
return 1;
}
int main()
{
while(1){
selectExample();
}
return 0;
}
实验结果
用select实现可移植的延时,delay, sleep
因为select()在不同的Unix系统上比用于亚秒分辨率休眠的机制更容易实现,所以它通常被用作提供的可移植的睡眠方式。
int selectSleep(int sec, int usec) {
struct timeval tv;
tv.tv_sec = sec;
tv.tv_usec = usec;
/*select for 500 microseconds*/
select(0, NULL, NULL, NULL, &tv);
return 0;
}
int main()
{
//testO_TRUNC();
//testMode();
//testTruncate();
while(1){
printf("Hello!\n");
selectSleep(1, 0);
}
return 0;
}
delay 1s 实验
pselect()
#define _XOPEN_SOURCE 600
#include <sys/select.h>
int pselect (int n,
fd_set *readfds,
fd_set *writefds,
fd_set *exceptfds,
const struct timespec *timeout,
const sigset_t *sigmask);
/* these are the same as those used by select() */
FD_CLR(int fd, fd_set *set);
FD_ISSET(int fd, fd_set *set);
FD_SET(int fd, fd_set *set);
FD_ZERO(fd_set *set);
pselect()和select()之间有三个不同之处:
- pselect()使用timespec结构,而不是timeval结构,作为timeout参数。timespec结构使用sec和nanosec,而不sec和usec,提供理论上优越的超时分辨率。然而,在实践中,这两个都不能可靠地提供微秒分辨率。
- 调用pselect()不修改超时参数。因此,此参数不需要在后续调用上重新初始化。
- 在select()系统调用中没有sigmask参数。关于信号, 当此参数设置为NULL时,pselect()的行为类似SELECT()。
timspec结构的定义如下:
#include <sys/time.h>
struct timespec {
long tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
在Unix工具箱中添加pselect()的主要动机是添加sigmask参数,该参数试图解决等待文件描述符和signal之间的竞争。
poll()
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events to watch */
short revents; /* returned events witnessed */
};
- 每个pollfd结构指定一个要监视的文件描述符fd。
- events是对该文件描述符进行监视的事件的位掩码。用户配置
- revents字段是在文件描述符fd上见证的事件的位掩码。kernel设置并返回。
在events字段中请求的所有事件都可以在revents字段中返回。
有效事件如下:
- POLLIN
有数据可读。 - POLLRDNORM
有正常的数据可读。 - POLLRDBAND
有优先读取的数据。 - POLLPRI
有急需阅读的数据。 - POLLOUT
写入不会阻塞。 - POLLWRNORM
写入正常数据不会阻塞。 - POLLWEBAND
写入优先级数据不会阻塞。 - POLLMSG
SIGPOLL消息可用。
此外,下列事件可在revents字段中返回:
- POLLER
给定文件描述符上的错误。 - POLLHUP
挂起给定文件描述符上的事件。 - POLLNVAL
指定的文件描述符无效。
POLLIN | POLLPRI等价于select()的read事件
POLLOUT | POLLWRBAND 等价于select()的write事件
POLLIN == POLLRDNORM | POLLRDNORM
POLLOUT == POLLWRNORM
Timeout参数指定以毫秒为单位等待的时间长度,负值表示无限超时。值为0表示调用立即返回。
在成功情况下,poll()返回其结构具有非零revents字段的文件描述符的数量。
如果超时发生在任何事件发生之前,则返回0。
失败时,返回−1。
基本用法1:
int pollExample() {
struct pollfd fds[2];
int ret;
/*watch stdin for input*/
fds[0].fd = STDIN_FILENO;
fds[0].events = POLLIN;
/*watch stdout for ability to write(always true)*/
fds[1].fd = STDOUT_FILENO;
fds[1].events = POLLOUT;
/*All set, block!*/
ret = poll(fds, 2, TIMEOUT * 1000);
if(ret == -1){
perror("poll");
return 1;
}
if(!ret){
printf("%d seconds elapsed.\n", TIMEOUT);
return 0;
}
if(fds[0].revents & POLLIN){
printf("stdin is readable\n");
}
if(fds[1].revents & POLLOUT){
printf("stdout is writeable\n");
}
return 0;
}
int main()
{
pollExample();
return 0;
}
基本用法1实验结果
基本用法2:
int pollExample() {
struct pollfd fds[2];
int ret;
/*watch stdin for input*/
fds[0].fd = STDIN_FILENO;
fds[0].events = POLLIN;
/*All set, block!*/
while(1) {
ret = poll(fds, 1, TIMEOUT * 1000);
if (ret == -1) {
perror("poll");
}
if (!ret) {
printf("%d seconds elapsed.\n", TIMEOUT);
}
if (fds[0].revents & POLLIN) {
char buf[BUF_LEN + 1];
int len;
/*guaranteed to not block*/
len = read(STDIN_FILENO, buf, BUF_LEN);
if (len == -1) {
perror("read");
}
if (len) {
buf[len] = '\0';
printf("read: %s\n", buf);
}
}
}
return 0;
}
int main()
{
pollExample();
return 0;
}
基本用法2实验结果
ppoll()
Linux提供了一个ppoll轮询(),与pselect()的一脉相承。但是,与pselect()不同的是,ppoll()是一个特定于Linux的接口:
#define _GNU_SOURCE
#include <poll.h>
int ppoll (struct pollfd *fds,nfds_t nfds,
const struct timespec *timeout,
const sigset_t *sigmask);
与pselect()一样,timeout参数指定一个以秒和纳秒为单位的超时值,sigmask参数提供了一组等待的信号。
poll()和select()对比
尽管它们执行相同的基本任务,但出于以下几个原因,轮询()系统调用优于select():
- poll()不要求用户计算并将最高编号的文件描述符的值加上1。
- 对于大值文件描述符,poll()更有效。想象一下,通过select()只查看一个值为900的文件描述符内核必须检查文件描述sets中每一个位设置,直到第900位。
- select()的文件描述符集的大小为固定大小,引入了一个折衷:它们很小,限制了select()可以监视的最大文件描述符,或者它们效率低下。
- 使用select(),文件描述符集在返回时被重构,因此每次调用都必须重新初始化它们。poll()系统调用将输入(Events)字段与输出(Rven)分隔开来。允许在不更改的情况下重用数组。
- select()的超时值参数在返回时未定义。可移植代码需要重新初始化它。然而,这并不是poll()的问题。
select()调用确实也有一些优势功能:
- select()更易于移植,因为一些Unix系统不支持poll()。
- select()提供更好的超时分辨率:下降到微秒级,而poll()只提供毫秒分辨率。ppoll()和pselect()都能提供纳秒分辨率,但在实践中,这些调用都没有可靠地提供均匀的分辨率。