linux系统编程——文件IO——多路IO

1. 思想

若要观察5个水管的出水情况,可以让五个人分别守着,也可以用安装监控,只需要一个人观察监控。

有了多路IO,编程思路大致如下:

  1. 设置需要监控的IO
  2. 睡眠
  3. 唤醒
  4. 处理可进行的IO
  5. 回到步骤1

linux 为 多路IO提供了3中机制:select, poll, epoll

2. select

       int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);

       void FD_CLR(int fd, fd_set *set);
       int  FD_ISSET(int fd, fd_set *set);
       void FD_SET(int fd, fd_set *set);
       void FD_ZERO(fd_set *set);

       #include <sys/select.h>

       int pselect(int nfds, fd_set *readfds, fd_set *writefds,
                   fd_set *exceptfds, const struct timespec *timeout,
                   const sigset_t *sigmask);
struct timeval {
	long tv_sec;
	long tv_usec;
};

示例

#include <stdio.h>
#include <unistd.h>
#include <sys/select.h>

#define TIMEOUT  4
#define BUF_LEN  256

int main()
{
	int maxfd, ret, len;
	fd_set readfds;
	struct timeval tv;
	char buf[BUF_LEN + 1];

	FD_ZERO(&readfds);
	FD_SET(STDIN_FILENO, &readfds);

	tv.tv_sec = TIMEOUT;
	tv.tv_usec = 0;

	maxfd = 0;
	if (STDIN_FILENO > maxfd)
		maxfd = STDIN_FILENO;

	ret = select(maxfd + 1, &readfds, NULL, NULL, &tv);
	if (ret < 0) {
		perror("select");
		return -1;
	}
	else if (ret == 0) {
		printf("%d seconds elapsed\n", TIMEOUT);
		return 0;
	}

	if (FD_ISSET(STDIN_FILENO, &readfds)) {
		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;
	}
	printf("This should not happen\n");

	return -1;
}

2.1 休眠机制

select 提供亚秒级别的睡眠

struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 500;
select (0, NULL, NULL, NULL, &tv);

注意,select返回后,会改变 tv的值,值为剩余的睡眠时间

2.2 pselect

       int pselect(int nfds, fd_set *readfds, fd_set *writefds,
                   fd_set *exceptfds, const struct timespec *timeout,
                   const sigset_t *sigmask);

struct timespec {
	long tv_sec;
	long tv_nsec;
};

pselect和 select 的不同

  1. pselect 使用 struct timespec ,提供秒 和 纳秒,理论上可以提供更好的timeout分辨率,但实际上两个select都只能提供微秒级别的分辨率
  2. pselect返回会改变 timeout 参数值
  3. select 没有sigmask

pselect的必要性——解决竞争条件

比如

// select一直阻塞,直到信号中断,信号处理函数修改 g_flag
select(0, NULL, NULL, NULL, NULL);
if (g_flag == 1) { // 检查 g_flag
	// 处理
}

如果 信号发生在 select 调用前,则导致 select 一直阻塞。

而pselect可以在select期间零时修改信号屏蔽字,所以可以解决竞争

3. poll

poll对select进行改进,但出于可移植性原因,select仍然常被使用

       int poll(struct pollfd *fds, nfds_t nfds, int timeout);

           struct pollfd {
               int   fd;         /* file descriptor */
               short events;     /* 需要查看的事件 */
               short revents;    /* 发生的事件 */
           };

不同于select使用效率低下的三个基于位掩码的文件描述符分组,poll使用的是 nfds和fds描述的 数组。

每个 pollfd 结构用于指定一个要查看的文件描述符,events字段是该文件描述符要查看的事件,revents是文件描述符发生的事件,由内核设置。events中所要求的事件可能会从revents字段返回。

有效事件如下:

POLLIN : 有数据可读
POLLRDNORM : 有一般数据可读
POLLRDBAND : 有优先级数据可读
POLLPRI : 有紧急数据可读
POLLOUT : 写入操作将不受阻挡
POLLWRNORM : 写入一般数据将不受阻挡
POLLWRBAND : 写入优先级数据将不受阻挡
POLLMSG : 有SIGPOLL消息可用

此外revents可能返回下列事件,这些事件在events字段无意义,因为只要适用,它们总是被返回:

POLLER : 所指定的文件描述发生错误
POLLHUP : 所指定的文件描述符发生挂起事件
POLLNVAL : 所指定的文件描述符无效

POLLIN | POLLPRI 等效于 select 的 读事件

POLLOUT | POLLWRBAND 等效于 select 的写事件

POLLIN 等效于 POLLRDNORM | POLLRDBAND

POLLOUT 等效于 POLLWRNORM

timeout 若 为负值,表示一直等待,为0,表示立即返回,并列出已经就绪可进行IO的任何fd,这种情况就是探询一次便立即返回。

3.1 示例

#include <stdio.h>
#include <unistd.h>
#include <poll.h>

#define TIMEOUT    5

int main()
{
	struct pollfd fds[2];
	int ret;

	fds[0].fd = STDIN_FILENO;
	fds[0].events = POLLIN;

	fds[1].fd = STDOUT_FILENO;
	fds[1].events = POLLOUT;

	ret = poll(fds, sizeof(fds)/sizeof(*fds), TIMEOUT * 1000);
	if (ret == -1) {
		perror("poll");
		return -1;
	}

	if (!ret) {
		printf("%d seconds timeout\n", TIMEOUT);
		return -1;
	}

	if (fds[0].revents & POLLIN)
		printf("stdin is readable\n");
	if (fds[1].revents & POLLOUT)
		printf("stdout is writable\n");

	return 0;
}

3.2 poll 和 select的比较

poll优于select,因为:

  1. select 查看单一fd的最大值为900,则内核必须为每个分组查看900个位的设定状态,所以poll的效率高
  2. select 的fd分组大小是固定的,导致两难权衡:若太小,则select可以查看的fd数目受限,太大,就没有效率。poll能创建一个大小刚好的数组,只需要查看一个条目。
  3. select 会在返回时重新构建 fd分组,所以每次调用必须重新初始化。poll将输入(events字段)输出(revents字段)分离,并允许重复使用数组而不需要变更
  4. 返回后select的timeout会变成未定义的

select的优点:

  1. select可移植性高,有些unix不支持poll

posted on 2021-08-18 09:18  开心种树  阅读(65)  评论(0编辑  收藏  举报