linux系统编程——文件IO——多路IO
1. 思想
若要观察5个水管的出水情况,可以让五个人分别守着,也可以用安装监控,只需要一个人观察监控。
有了多路IO,编程思路大致如下:
- 设置需要监控的IO
- 睡眠
- 唤醒
- 处理可进行的IO
- 回到步骤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 的不同
- pselect 使用 struct timespec ,提供秒 和 纳秒,理论上可以提供更好的timeout分辨率,但实际上两个select都只能提供微秒级别的分辨率
- pselect返回会改变 timeout 参数值
- 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,因为:
- select 查看单一fd的最大值为900,则内核必须为每个分组查看900个位的设定状态,所以poll的效率高
- select 的fd分组大小是固定的,导致两难权衡:若太小,则select可以查看的fd数目受限,太大,就没有效率。poll能创建一个大小刚好的数组,只需要查看一个条目。
- select 会在返回时重新构建 fd分组,所以每次调用必须重新初始化。poll将输入(events字段)输出(revents字段)分离,并允许重复使用数组而不需要变更
- 返回后select的timeout会变成未定义的
select的优点:
- select可移植性高,有些unix不支持poll
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?