linux系统调用函数之select、poll、epoll

本文详细介绍了Linux系统编程中的三种I/O多路复用技术:select、poll和epoll。select函数适用于小规模的文件描述符监控,但存在最大描述符限制。poll函数在一定程度上解决了描述符数量限制,同时监听集合与返回集合分离。epoll则引入红黑树数据结构,提供高效且无上限的描述符管理,支持水平触发和边缘触发模式,避免了轮询和重复拷贝,性能更优。
摘要由CSDN通过智能技术生成

1. select函数

1.1 相关函数说明

int select(int nfds, fd_set *readfds, fd_set *writefds,
			fd_set *exceptfds, struct timeval *timeout);
  • 返回值:
  • nfds:监控的文件描述符集里的最大文件描述符+1,此参数告诉内核监测前多少个文件描述符的状态
  • readfds:传入传出参数,位图
    • 传入时:代表要监听的哪些文件描述符的可读事件
    • 传出时:代表哪些文件描述符实际发生了可读事件
  • writefds:传入传出参数,位图
    • 传入时:代表要监听哪些文件描述符的可写事件
    • 传出时:代表文件描述符哪些实际发生了可写事件
  • exceptfds:传入传出参数,位图
    • 传入时:代表要监听哪些文件描述符的异常事件
    • 传出时:代表哪些文件描述符实际发生了异常事件
  • timeout:设置阻塞时间
    • NULL:永久等下去
    • 设置timeval,等待固定时间
    • 设置timeval里时间均为0,表示检查描述字后立即返回,轮询。
  • 返回值:
    • 成功:返回所监听的所有监听集合中,满足条件的总数。
struct timeval {
	long tv_sec; /* seconds */
	long tv_usec; /* microseconds */
};
  •  设置监听集合的函数
void FD_CLR(int fd, fd_set *set); 	//将fd从文件描述符集合中清除
int FD_ISSET(int fd, fd_set *set); 	//判断fd是否在文件描述符集合中
void FD_SET(int fd, fd_set *set); 	//将fd加入文件描述符集合中
void FD_ZERO(fd_set *set); 			//把文件描述符集合里所有位清0

1.2 原理

  • 用户进程调用系统调用函数select,会进入阻塞状态,然后内核帮我们轮询我们要监听的socket接口,当有我们需要的事件发生后就会返回。
  • 返回值代表有多少个满足的事件发生了。
  • 然后我们根据传出参数找到哪些事件发生了,并对其做相应的处理。

1.3 特点(问题)

  • 单个进程可监控的fd数量被限制,即能监控的端口的大小有限。32位机器默认是1024个
  • 返回值只告诉我们有多少满足事件发生了,至于是哪个socket发生了满足事件,需要我们去挨个遍历那三个文件描述符集合才能找出。
  • select函数的三个文件描述符参数是传入传出参数,需要将整个fd_set从用户空间拷贝到内核空间,select结束时还要再次拷贝回用户空间。

2. poll函数

2.1 相关函数说明

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
  • fds:结构体pollfd数组的首地址
  • nfds:fds数组里面的元素个数
  • timeout:设置阻塞时间
    • -1:阻塞等待
    • 0:立刻返回,不阻塞进程
    • >0:等待指定毫秒数
struct pollfd {
	int fd; /* 文件描述符 */
	short events; /* 对该文件要监控的事件 */
	short revents; /* 该文件的监控事件中满足条件返回的事件 */
};
  •  fd:文件描述符
  • envents:对该文件要监控的事件,常见的可选项:
    • POLLIN:表示监控读事件
    • POLLOUT:表示监听写事件
    • POLLERR:表示监听异常事件
  • revents:该文件所监听事件中满足条件返回的事件

2.2 原理

  • 本质与select区别不大。它将用户传入的pollfd数组拷贝到内核空间,然后内核轮询每个要监听的fd对应的设备状态。返回时还需要将pollfd数组再拷贝回用户空间。
  • 返回的也是有多少个设备准备就绪,然后用户进程遍历pollfd数组,找到准备就绪的socket进行处理
  • poll返回后,需要对pollfd数组中的每个元素检查其revents值,来确定是否有满足事件发生。

2.3 特点:

  • 突破监听文件描述符最大上线为1024
  • 监听集合与返回集合分离。
  • 搜索返回结果范围变小(只需要遍历所指定要监听的文件描述符)

3. epoll

3.1 相关函数说明

int epoll_create(int size);
  • 函数说明:创建一个epoll句柄,
  • size:指明内核监听的文件描述符的个数,是个建议值
  • 返回值:
    • 成功:epoll句柄,指向一颗红黑树的树根。
    • 失败:-1,设置errno
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
  • 函数说明:控制某个epoll监听的文件描述符的事件:注册、修改、删除
  • epfd:epoll句柄
  • op:操作:
    • EPOLL_CTL_ADD:注册新的fd到epfd
    • EPOLL_CTL_MOD:修改已经注册的fd的监听事件
    • EPOLL_CTL_DEL:从epfd中删除一个fd
  • fd:文件描述符
  • event:结构体,要监听的事件。(结构体的地址)
struct epoll_event {
	__uint32_t events; /* Epoll events */
	epoll_data_t data; /* User data variable */
};

typedef union epoll_data {
	void *ptr;
	int fd;
	uint32_t u32;
	uint64_t u64;
} epoll_data_t;

  • events常用可选项
    • EPOLLIN:表示对应的文件描述符可以读
    • EPOLLOUT:表示对应的文件描述符可以写
    • EPOLLERR:表示对应的文件描述符发生错误
  • ptr:泛型指针。表示可以传任意类型的数据,包括结构体。
    • 设置时,通过传入一个结构体,在结构体内部定义函数。可以实现指定监听事件的指定回调。
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
  • 函数说明:等待所监控的文件描述符上有事件产生,类似select()调用
  • epfd:epoll句柄
  • events:传出参数,要监听的事件集合(数组)
  • maxevents:告之内核这个events这个集合大小。maxevents的值不能大于epoll_create()时的size
  • timeout:超时时间
    • -1:阻塞
    • 0:立即返回,非阻塞
    • >0:指定毫秒
  • 返回值
    • 成功:返回有多少个文件描述符就绪
    • 时间到时:返回0
    • 出错:返回-1

3.2 原理

  • 先执行epoll_create,创建了一颗红黑树,将句柄返回。该红黑树上的每一个节点都代表要监听的事件。
  • 通过执行epoll_ctl函数,将要监听的事件注册到红黑树上去。
    • 每个监听事件都有一个回调函数,当监听的事件触发后,会自动将这个时间添加到内核的就绪列表中
  • 最后调用epoll_wait函数,进行实际的监听。
    • 会将内核的就绪列表拷贝到用户空间

3.3 特点

  • 返回的结构体数组中代表出现了监听事件的文件描述符。所以不用轮询所有的监控文件描述符。
  • 基于epoll实例中的红黑树保存要监听的FD,理论无上限,而且增删改查效率都非常高
  • 每个FD只需要执行一次epoll_ctl添加红黑树,以后每次epoll_wait无需传递任何参数,无需重复拷贝FD到内核空间
  • 利用epoll_callback机制来监听FD状态,无需遍历所有FD,因此性能不会随着监听的FD数量增多而下降

3.4 epoll的两种触发模式

  • 水平触发(LT)
    • 当被监听的文件描述符上有可读写事件发生时,epoll_wait会通知处理程序去读写。如果这次没有把数据一次性全部读写完(比如内核缓冲区来了1000字节,应用程序只读了500字节)。那么下次调用epoll_wait时,它还会通知你在上次没有读完的文件描述符上继续读写。
    • select、poll都是只支持水平触发模式。epoll默认水平触发,也支持边缘触发
  • 边缘触发(ET)
    • 当当被监听的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完,那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现了第二次可读写事件才会通知你。
    • 如何设置边缘触发:event.events=EPOLLIN|EPOLLET;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值