linux IO复用笔记_更新中
2017-05-15 15:56
471 查看
select
select函数原型如下:select(int maxfdp,fd_set* readfds, fd_set* writefds, fd_set* errorfds, struct timeval* timeout)
fd_set是一种数据结构,该数据结构中存放着文件描述符,即文件句柄。该数据结构有大小限制,受到内核参数
FD_SETSIZE的影响,一般为1024。该结构可以通过一些宏由人来操控。
fd_set set; FD_ZERO(&set); //将set清零 FD_SET(fd, &set); //将fd加入set中 FD_CLR(fd, &set); //不再监控fd FD_ISSET(fd, &set); //fd在set中是否就绪,即是否为1
timeval是一种用来表示时间值的数据结构,它有两个成员,一个是秒数,另一个是毫秒数。
struct timeval{ long tv_sec; long tv_usec; }
select函数中,
maxfdp要监控的文件描述符的最大值加1。三个
fd_set是文件描述符的集合,它们既代表输入参数,也代表输出参数。
readfds代表可读的文件描述符集合,作为输入,
select要去检查它每一个为1的位是否可读;作为输出,调用者要使用
FD_ISSET去检查自己关注的句柄是否就绪,即是否为1。其他同理。
timeout是超时时间,它可以设置为三种状态:
NULL:代表该函数是阻塞的,只有当有句柄就绪时才返回
0:代表函数是非阻塞的,调用即返回
大于0:代表超时时间,在超时时间内阻塞,一旦有就绪句柄,立马返回;否则等到超时时间到才返回。
select的返回值是集合中就绪的句柄数目。如果小于0,代表发生错误;如果等于0,代表超时。
这里有一些关于select的小问题:
为什么select有最大文件描述符个数的限制?
答:注意,这里的最大文件描述符个数指的是单进程的最大限制。主要是因为select采用了fd_set这一数据结构,从该数据结构的定义中可以看到,
fd_set其实应该是一个数据类型为
long的
fds_bits数组,该数组的大小由一个
howmany宏来控制,而观察其参数可知,一个取决于
FD_SETSIZE;另一个取决于
fd_mask,即
long的大小,该值在32位系统中是4,64位系统中是8。计算可知,如果
long大小为8,那么
fds_bits数组大小为16,
16Byte*8*8bit/Byte=1024bit,数组共有1024个
bit;若
long大小为4,那么
fds_bits数组大小为32,数组的
bit数为
32Byte*4*8bit/Byte=1024bit,还是1024。所以,系统位数对该数组的比特数没有影响。
而
fd_set被宏视为一个位图进行操作,1位可以代表一个句柄,所以总共只能放1024个句柄。
#ifndef FD_SETSIZE #define FD_SETSIZE 1024 #endif #define NBBY 8 /* number of bits in a byte */ typedef long fd_mask; #define NFDBITS (sizeof (fd_mask) * NBBY) /* bits per mask */ #define howmany(x,y) (((x)+((y)-1))/(y)) typedef struct _types_fd_set { fd_mask fds_bits[howmany(FD_SETSIZE, NFDBITS)]; } _types_fd_set; #define fd_set _types_fd_set 作者:用心阁 链接:https://www.zhihu.com/question/37219281/answer/74003967 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
如何突破该限制?
答:如上所说,该限制针对的是单进程,所以一个方法就是使用多进程;另一个方法就是修改
FD_SETSIZE这个参数,并重新编译内核;还有就是使用poll和epoll。
FD_SETSIZE限制了什么?
答:起码在linux上,既限制了监视的文件描述符数目,又限制了最大的文件描述符。因为select的第一个参数,绝对不能大于
FD_SETSIZE,否则访问fd_set数组时会越界。
其他复用方式为什么没有这种限制?
答:因为它们没有采用
FD_SET数据结构啊。
poll
poll和select类似,本质上没有太大区别,查询就绪事件时还是使用轮询,但是它没有最大文件描述符的限制。函数原型如下:
int poll(struct pollfd*fds, unsigned int nfds,int timeout)
其中,pollfd结构体如下:
struct pollfd{ int fd; //文件描述符 short events; //等待的事件 short revents; //实际发生的事件
由原型可以发现,poll可以监听多个pollfd,且没有数目的限制,events是监视该文件描述符的事件掩码,由用户设置,revents是文件描述符的操作结果事件掩码,内核在调用返回时设置这个域。events中请求的任何事件都有可能在revents中返回。合法事件如下:
POLLIN 有数据可读。 POLLRDNORM 有普通数据可读。 POLLRDBAND 有优先数据可读。 POLLPRI 有紧迫数据可读。 POLLOUT 写数据不会导致阻塞。 POLLWRNORM 写普通数据不会导致阻塞。 POLLWRBAND 写优先数据不会导致阻塞。 POLLMSGSIGPOLL 消息可用
函数返回值和select一样,成功时返回revents中不为0的事件数,超时返回0,失败返回-1。
epoll
1. int epoll_create(int size);
size是内核能够正确处理的最大句柄数目,返回值是一个epollfd。
epoll在内核中申请了一个简易的文件系统。该函数创建了一个epoll对象,并在epoll文件系统中为这个句柄对象分配资源。具体是创建了一个eventpoll结构体:
struct eventpoll{ .... /*红黑树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件*/ struct rb_root rbr; /*双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件*/ struct list_head rdlist; .... };
每个epoll对象(即fd)都有一个结构体,用于存放epoll_ctl方法向epoll对象中添加进来的事件。这些事件会挂到红黑树中。利用红黑树来维护事件。
2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
epfd就是第一个函数的返回值,op是要进行的操作(
EPOLL_CTL_ADD,EPOLL_CTL_MOD,EPOLL_CTL_DEL),fd是要进行操作的句柄,event是监听事件的集合。在event结构体中有一个epoll_data的union,一定要填写这个域来表明是哪个fd在监听事件。
struct epoll_event结构体如下:
typedef union epoll_data { void a1f5 *ptr; int fd; __uint32_t u32; __uint64_t u64; } epoll_data_t; struct epoll_event{ __uint32_t events; epoll_data_t data; };
该函数将要监控的事件放到内核cache里的红黑树上,并向中断处理程序注册一个回调函数,当设备就绪的时候,就将该事件添加到就绪列表中。
具体点说,如果有一个事件被添加到epoll中,那么内核会为它生成一个对应的epitem结构对象,epitem会被添加到eventpoll的红黑树中。
3. int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout)
epfd同上,events用来从内核获得事件的集合,maxevents告诉内核这个events有多大,timeout是超时时间。调用这个函数会直接访问eventpoll结构的rdlist就绪链表,如果不为空,就把发生的事件拷贝到用户空间(即填写events参数),并将事件数量返回给用户。用户可以访问events,来获取就绪的fd及对应的事件。
如此,一颗红黑树,一张准备就绪句柄链表,少量的内核cache,就帮我们解决了大并发下的socket处理问题。执行epoll_create时,创建了红黑树和就绪链表,执行epoll_ctl时,如果增加socket句柄,则检查在红黑树中是否存在,存在立即返回,不存在则添加到树干上,然后向内核注册回调函数,用于当中断事件来临时向准备就绪链表中插入数据。执行epoll_wait时立刻返回准备就绪链表里的数据即可。
比较
poll要比select好,因为没有文件描述符大小的限制;而且在应付大数目的文件描述符时更快,不用像select一样去对比fd_set中的每一个比特位。select中输入输出使用同一个fd_set,所以该fd_set会一直变化,在每一次调用select时都需要重新设置该值。而poll将输入输出事件分开,允许复用被监控的文件数组。
select每次返回时超时参数也是未定义的,所以每次调用都需要对其进行初始化。
select的优点就是可移植性好,超时精度较高。
epoll主要有如下优点:支持进程打开大数目的文件描述符;IO效率不随fd的增大而降低,因为它只关注活跃的fd;使用mmap加速内核和用户空间的消息传递,主要是因为epoll中内核与用户空间mmap同一块内存。
内容来自:
IO多路复用之poll总结
linux下epoll如何实现高效处理百万句柄的
epoll简介
高并发网络编程之epoll详解
epoll实现机制分析
相关文章推荐
- Linux下C编程入门笔记——文件IO操作(二)
- linux文件IO之整理笔记(一)
- linux学习笔记... ...持续更新ing
- linux C 学习中的一些小笔记,不断更新
- (转)Linux IO多路复用之epoll网络编程
- [linux笔记]我用的ubuntu8.04更新源
- Linux IO复用之select
- Linux IO模型漫谈(5)- IO复用模型之select
- 柳大的Linux游记·基础篇(5)select IO复用机制
- Linux的IO复用
- IO复用,linux poll
- 【学习笔记】Linux下磁盘IO性能评估
- Linux中select IO复用机制
- Linux下多路复用IO接口 epoll select poll 的区别
- (转)Linux IO多路复用之epoll网络编程
- Linux下C编程入门笔记——文件IO操作
- 看linux书籍做的一些重要笔记(2011.07.03更新)
- glib学习笔记三(续)——GLib核心应用支持:在Linux下使用IO通道(IO Channels)
- linux基础编程:IO模型:阻塞/非阻塞/IO复用 同步/异步 Select/Epoll/AIO
- Linux IO复用之epoll