linux IO复用笔记_更新中

来源:互联网 发布:zip暴力解压软件 编辑:程序博客网 时间:2024/06/05 19:11

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);    //不再监控fdFD_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其实应该是一个数据类型为longfds_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        *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实现机制分析

0 0
原创粉丝点击