您的位置:首页 > 运维架构 > Linux

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实现机制分析
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: