ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Linux中的高级IO

Linux中的高级IO 目录一、五种IO基本模型二、select三、poll四、epoll一、五种IO基本模型IO的本质等待数据就绪 将数据从内核拷贝到用户空间1. 阻塞IO在内核数据准备好之前系统调用一直处于等待状态所有套接字默认都是阻塞方式2. 非阻塞IO当内核数据没有准备好的时候系统调用仍然会返回并且返回EWOULDBOLCK[是EAGAIN的别名]非阻塞IO需要不断访问文件描述符这个过程成为轮询对于CPU资源的消耗较大只有特定场景下使用3. 信号驱动式IO当内核数据准备好的时候使用SIGIO信号通知应用程序进行IO操作4. IO多路转接与阻塞IO方式类似但最大的区别在IO多路转接可以同时监听多个文件描述符5. 异步IO当内核将数据拷贝完成后通知应用程序IO中的小点:1.同步IO vs 异步IO同步IO当发起一个调用的时候在没有得到返回结果之前不会得到返回值只有当得到返回值的时候才会得到返回结果调用者主动等待结果异步IO与同步IO相反当发起一个调用的时候会立刻得到返回值但是不会得到返回结果而是被调用着通过状态或者回调函数来通知调用者2.阻塞IO vs 非阻塞IO阻塞或IO在没有拿到返回结果之前该线程会被挂起非组设IO不能立刻拿到返回结果的时候该线程不会被挂起3. 如何通用设置非阻塞IO二、selectselect是系统提供实现多路复用输入/输出的接口select系统调用会同时监听多个文件描述符的状态变化在监听的时候默认处于等待状态只有当一个或多个文件描述符就绪的时候才会返回1.接口int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds , struct timeval *timeout);2.参数参数nfds是需要监视的最⼤的⽂件描述符值1rdset,wrset,exset分别对应于需要检测的可读⽂件描述符的集合可写⽂件描述符的集 合及异常⽂件描述符的集合;参数timeout为结构timeval⽤来设置select()的等待时间NULL: 则表示select ()没有timeout, select将一直被阻塞 直到某个文件描述符上发生了事件0:仅检测描述符集合的状态然后立即返回 并不等待外部事件的发生。特定的时间值如果在指定的时间段里没有事件发生 select将超时返回。3.返回值执⾏成功则返回⽂件描述词状态已改变的个数如果返回0代表在描述词状态改变前已超过timeout时间没有返回当有错误发⽣时则返回-1错误原因存于errno此时参数readfdswritefds, exceptfds和 timeout的值变成不可预测。4.select特点fd_set中的每一个比特位对应一个文件描述符除此之外还需要一个数组来存储监控的文件描述符因为每次返回之后都会把之前加入但是为响应的文件描述符清空同时这个数组还需要用来判断文件描述符的就绪转态5.select缺点用户每次使用的时候都需要手动设置fd,使用并不方便每次调用接口的时候都需要将fd从用户态拷贝到内核态同时在内核中也需要遍历fd_set数组当fd数量较大的时候开销很大除此之外select支持的文件描述符数量太少三、poll1.接口int poll(struct pollfd *fds, nfds_t nfds, int timeout);2.参数fds是一个poll函数监听的结构列表.每一个元素中,包含了三部分内容:文件描述符,监听的事件集合,返回的事件集合.nfds表示fds数组的长度.timeout表示poll函数的超时时间,单位是毫秒(ms).3.返回值返回值小于0,表示出错;返回值等于0,表示poll函数等待超时;返回值大于0,表示poll由于监听的文件描述符就绪而返回.4.特点pollfd结构包含了要监视的event和发生的event不再使用select“参数-值”传递的方式.接口使用比select更方便.poll没有最大数量的限制5.缺点和select函一样poll返回后需要循环遍历pollfd数组每次调用poll都需要把大量的pollfd结构从用户态拷贝到内核中.同时连接的大量客户端在一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降.四、epoll1.接口//创建epoll模型 int epoll_create(int size); //添加/修改/删除监听的文件描述符 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); //监听fd int epoll_wait(int epfd, struct epoll_event *events,int maxevents , int timeout);2.参数epoll_create:自从linux2.6.8之后size参数是被忽略的,用完之后,必须调用close()关闭.epoll_ctl:epfd是epoll_create()的返回值(epoll的句柄).op表示动作用三个宏来表示.[EPOLL_CTL_ADD,EPOLL_CTL_MOD,EPOLL_CTL_DEL]fd是需要监听的fd.event是告诉内核需要监听什么事.epoll_wait:events是分配好的epoll_event结构体数组,epoll将会把发生的事件赋值到events数组中(events不可以是空指针内核只负责把数据复制到这个events数组中不会去帮助我们在用户态中分配内存).maxevents告诉内核,events的最大容量参数timeout是超时时间(毫秒0会立即返回-1是永久阻塞)3.返回值如果函数调用成功返回对应I/O上已准备好的文件描述符数目返回0表示已超时,返回小于0表示函数失败.4.工作原理当我们使用epoll_create()时候内核会创建一个evenloop结构体对象其中包含一个红黑树当我们调用epoll_ctl()的时候内核会将该文件描述符对应的节点注册到红黑树上并将节点与网卡驱动程序做绑定此外还有一个双向链表用来记录已经就绪的文件描述符对应的节点当有事件到来的时候网卡驱动程序触发中断将就绪文件描述符对应的节点加入到双向链表中5.优点1.epoll能够监听的文件描述符是没有上限的2.epoll提供三个接口函数给用户并没有使操作更加复杂通过epoll_ctl()我们只有在注册事件的时候才会放生从用户态到内核态的数据拷贝增加了资源利用的效率3.同过双向链表我们可以实现O(1)的获取已经就绪的文件描述符极大提高了效率6.工作模式1.LT模式(level trigger)当epoll检测到socket上事件就绪的时候我们可以不进行处理或处理一部分当第二次epoll_wati()的时候仍然会立刻返回并通知socekt事件就绪2.ET模式(edge trigger)当epoll检测到socket上事件就绪的时候我们必须一次性将数据处理完因为当第二次epoll_wait()的时候如果没有新数据的到来并不会返回上次没有处理完数据对应的文件描述符7.ET与非阻塞ET模式使用的时候对应的应该将对应的文件描述符设置为非阻塞并不是接口上的要求而是工程实践上的需求假如存在一种场景客户端只有接受到服务端响应的时候才会发送第二次请求服务端这边只有在读到10k数据的时候才会向客户端发送响应这时候我们如果使用阻塞模式读的话如果只读到1k数据那么后续epoll_wait()并不再回返回这次通信的文件描述符就会导致通信错误8.惊群现象与解决方法惊群现象多个 worker 进程线程各自持有独立 epoll同时注册同一个 listen 监听 fd。当有新 TCP 连接到达listen socket 变为就绪内核唤醒多个阻塞于 epoll_wait 的 worker随后多个 worker 调用 accept但只能有一个 accept 成功其余 accept 返回 EAGAIN这些进程白白被唤醒发生不必要的上下文切换造成 CPU 损耗解决方法1.多线程单独一个线程负责 accept分发连接给工作线程2.多进程2.1.lighttpd 放任惊群多个 worker 被唤醒accept 遇到 EAGAIN 之后退回 epoll_wait但是仍然存在进程调度开销2.2.nginx accept‑mutex使用共享内存上的进程间互斥锁非阻塞 trylock 抢锁抢到锁的 worker 才监听 listen_fd同时依靠 ngx_accept_disabled 做负载控制同一时刻只有一个 worker 监听 listen消除惊群2.3.Linux4.5 之后注册 listen fd 时带上EPOLLEXCLUSIVE标记内核保证就绪时仅唤醒一个 worker在内核层面解决惊群。
返回列表