一种理解同步/异步,阻塞/非阻塞,Linux IO 模型,select /poll /epoll 的方法

前言

强迫症不能忍受这种极其绕的概念而不给个说法。同步(synchronous)/异步(asynchronous),阻塞(blocking)/非阻塞(non-blocking),阻塞IO/非阻塞IO/同步IO/异步IO/IO复用(IO Multiplexing),select/poll/epoll这些概念困扰我许久,下面给出这一阶段我个人的理解。

同步/异步与阻塞/非阻塞的理解

线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。用线程执行程序流的过程去理解同步异步,阻塞非阻塞。同步异步关注的是流执行过程需不需要等待外部调用的结果,而阻塞非阻塞关注的是外部调用对流本身产生的影响。

同步与异步

线程的执行过程中,产生一个外部调用,如果需要等待该调用返回才能继续线程流则叫做同步,不需要等待结果返回线程流可以继续往下执行的情况叫做异步。 区分:线程流向下执行需不需要等待系统调用的结果。

阻塞与非阻塞

线程执行过程中,产生一个外部调用后,会不会把该线程流“堵”住,会“堵”对应的是阻塞,反之为非阻塞。

Linux的五种IO模型

上一节中对同步/异步,阻塞/非阻塞的描述只能说能够恰好区分它们,如果不是在计算机领域而是生活中,道理也类似。然而计算机中的某些专业术语又需要放在专门的情景中去看,例如下面将要提到的Linux IO模型,建议理解模型本身,而不是抠同步/异步与阻塞非阻塞的字眼,因为会发现就算是非阻塞模型也有阻塞的部分,同步IO与异步IO的区别是IO操作的时候会不会让process阻塞。 下面对模型描述的最终来源为: Richard Stevens的“UNIX:registered: Network Programming Volume 1, Third Edition: The Sockets Networking ”,6.2节“I/O Models ” A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes; An asynchronous I/O operation does not cause the requesting process to be blocked;

同步IO模型

用IO操作中有阻塞来判断,5种IO模型中4种属于同步IO,分别是阻塞IO模型,非阻塞IO模型,IO复用模型,信号驱动IO模型。

blocking IO

阻塞IO是socket的默认设置,其模型如下图所示,程序调用recvfrom产生一个系统调用,kernel收到该调用请求后有两个步骤,第一是等待数据准备好,第二是将数据从内核空间拷贝到用户空间然后返回OK,用户空间收到系统调用返回后才会继续程序流的执行。

non-blocking IO

socket使用非阻塞IO模型需要对socket进行另行设置,非阻塞IO模型如下所示。内核收到系统调用后,若数据未准备好立即返回error,用户进程收到error会继续产生系统调用,直到数据准备好了并被拷贝到用户空间。

IO multiplexing

select/poll/epoll对应的是IO复用模型,优势是能够监听多个socket,模型图如下所示。用户进程调用select产生系统调用,kernel会监听所有select负责的socket,一旦有一个socket数据准备好了,kernel即返回,用户再去recvfrom产生系统调用将数据从内核空间读到用户空间。

signal-driven IO

用户程序注册一个信号handler,然后继续做后续的事情,当内核数据准备好了会发送一个信号,程序调用recvfrom进行系统调用将数据从内核空间拷贝到用户空间。

异步I/O模型

用IO操作中无阻塞来判断,5种IO模型中只有异步IO。

asynchronous IO

异步IO模型如下,aio_read产生系统调用,kernel在数据准备好后将数据从内核空间拷贝到用户空间后返回一个信号告知read数据成功,整个过程程序调用aio_read后就继续执行其他部分直到收到信号,调用handler处理。

模型的对比

Kernel有两个过程,等待数据准备好和拷贝数据到用户空间,用户程序的阻塞一般有两种情况,select的阻塞和socket IO的阻塞,5中IO模型的对比如下。

select/poll/epoll

Select/poll/epoll能够同时监听多个文件描述符fd,当有fd的读写操作完成时会返回这些fd,可以对应于IO复用模型中的系统调用查询fd是否准备好数据的那一部分。

select

int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);

typedef struct {
    unsigned long *in, *out, *ex;
    unsigned long *res_in, *res_out, *res_ex;
} fd_set_bits;

select在用户层使用某个结构标识被监听的fd以及监听的状态,每一个fd用1bit表示,为1表示这个文件是被监听的,0表示不监听。in, out, ex指向的bit数组表示对应的读,写,异常文件的描述符。res_in, res_out,res_ex指向的bit数组表示对应的读,写,异常文件的描述符的检测结果。

  1. 这个结构被拷贝到内核层,
  2. 对所有的fd注册回调函数__pollwait
  3. 调用fd的poll方法遍历整个FD_SESIZET所有的fd,检查是不是自己需要监听的,如果监听的fd发生了感兴趣的事(文件读写操作完成或者异常,参考用户态预先的设置),则poll方法返回一个描述读写操作是否就绪的mask掩码,根据mask掩码给fd_set赋值。poll->poll_wait->__pollwait会把当前进程挂到对应文件的inode中的fd的等待队列。
  4. 如果一轮遍历无果则挂起,直到超时或者有设备驱动发生自身资源可读写后将其从等待队列唤醒,则执行新一轮的遍历。
  5. 把fd_set从内核空间拷贝到用户空间并将进程从各个等待队列中删除。

poll

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

struct pollfd {
    int fd;
    short events;
    short revents;
};

poll的实现机制与select类似,不一样的是poll的使用中用户态直接提供了需要监听的fd的信息,pollfd结构记录被监听的fd和它的状态。

struct poll_list {
struct poll_list *next;
    int len;
    struct pollfd entries[0];
};

另外poll使用poll_list结构来记录监听的fd,每一个poll_list节点都包含一个pollfd数组,参数被拷贝到内核后,poll_list被遍历,换言之pollfd数组被遍历,与select一样,所有的fd都被遍历。

epoll

int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

epoll将过程细化为一组系统调用,包括1个epoll_create,多个epoll_ctrl,1个epoll_wait。内核针对epoll操作添加了一个文件系统”eventpollfs”,每一个或者多个要监视的fd都有一个对应的eventpollfs文件系统的inode节点,主要信息保存在eventpoll结构体中,而被监视的文件的重要信息则保存在epitem结构体中。在执行epoll_create和epoll_ctrl时把用户态的信息保存到内核态了,之后即使反复地调用epoll_wait,也不会像select/poll那样重复地拷贝参数,扫描fd,反复地把当前进程放入/放出等待队列。

epoll_create

epoll_create()的功能是创建一个eventpollfs文件系统的inode节点,主要信息保存在eventpoll结构体中,eventpoll记录了eventpollfs文件系统的inde节点的重要信息,其中成员rbr保存该epoll文件节点监视的所有描述符,组织的方式是一棵红黑树,被监视的文件的重要信息则保存在epitem结构体中。

epoll_ctl

Epoll_ctl实现一系列操作,它调用ep_find()从eventpoll中的红黑树获得epitem结构体,根据op参数的不同而选择不同的操作。当op为EPOLL_CTL_ADD,一般情况下epitem无法在eventpoll的红黑树中找到,所以调用ep_insert创建一个epitem结构体并插入到对应的红黑树中,

init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);
…
revents = tfile->f_op->poll(tfile, &epq.pt);
…

然后ep_insert调用init_poll_funcptr注册一个回调函数ep_ptable_queue_proc,该函数会在调用f_op->poll时被执行。Ep_ptable_queue_proc函数分配一个epoll等待队列结点epoll_entry,一方面把它挂到文件操作的等待队列中,另一方面把它挂到epitem的队列中。此外它还注册了一个等待队列的回调函数ep_poll_callback,ep_poll_callback在完成操作完成,唤醒当前等待进程之前被调用,会把epitem放到eventpoll的完成队列,然后唤醒等待进程。

epoll_waite

epoll_wait的工作是等待文件操作完成并返回。它的主体是ep_poll(),该函数检查eventpoll中有没有已经完成的事件,有的话就把结果返回。没有的话调用schedule_timeout()进入休眠,直到进程被再度唤醒或者超时。

对比

select/poll的弱点在于需要轮询遍历fd,当监听fd多时开销大;而epoll依赖于回调函数,当活跃fd过多时开销就大。

监听fd的个数

消息传递的方式

是否遍历所有fd

适用场景

select

最大为FD_SETSIZE,修改需要重新编译内核

内核与用户空间的拷贝

连结数少且活跃

poll

无限制,基于链表存储

内核与用户空间的拷贝

连接数少且活跃

epoll

很大

共享内存

活跃socket少

总结与展望

其实我并不是很相信自己写的某些部分,例如poll的细节,毕竟是整合的别人的资料,但是目前为止确实能解释我的某些疑惑,以后有空有机会再去深挖求证。

参考资料

[1]http://blog.csdn.net/hguisu/article/details/7453390 [2]http://blog.csdn.net/historyasamirror/article/details/5778378

原创声明,本文系作者授权云+社区-专栏发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

4 条评论
登录 后参与评论

相关文章

来自专栏Golang语言社区

让事件飞 ——Linux eventfd 原理与实践

目前越来越多的应用程序采用事件驱动的方式实现功能,如何高效地利用系统资源实现通知的管理和送达就愈发变得重要起来。在Linux系统中,eventfd是一个用来通知...

1520
来自专栏项勇

笔记66 | eclipse/android studio/ADB查看Android应用内存使用情况

1554
来自专栏邹立巍的专栏

Linux 的进程间通信:管道

管道,英文为 pipe 。这是一个我们在学习 Linux 命令行的时候就会引入的一个很重要的概念。管道是UNIX 环境中历史最悠久的进程间通信方式。本文主要说明...

3341
来自专栏java达人

Spring声明式事务的一个注意点及原理简析

以前我们说过,Spring通过ThreadLocal机制解除了事务管理模块与数据访问层的紧密耦合,提高了模块的可重用性,也保证了多线程环境下的对connecti...

2026
来自专栏积累沉淀

Java批处理

批处理 JDBC对批处理的操作,首先简单说一下JDBC操作sql语句的简单机制。 JDBC执行数据库操作语句,首先需要将sql语句打包成为网络字...

1785
来自专栏皮振伟的专栏

[linux][memory]mlock技术分析和使用以及问题

前言: 使用了mlock,会把内存lock在内存中,不会被交换,在一定场景下,可以提高性能。 虚拟化场景下,qemu也可以选择lock住一部分内存,来提高Gue...

36011
来自专栏desperate633

深入分析Redis特点及应用场景Redis的特点:Redis的优势:Redis与其他key-value存储有什么不同?Redis应用场景2. 删除与过滤3. 排行榜相关

在Web应用中,“列出最新的回复”之类的查询非常普遍,这通常会带来可扩展性问题。这令人沮丧,因为项目本来就是按这个顺序被创建的,但要输出这个顺序却不得不进行排序...

962
来自专栏Java技术栈

Java 中的伪共享详解及解决方案

893
来自专栏皮振伟的专栏

[linux][network]互联网后台模型

前言: 虽然话题的范围有点大,但是在这里作者不打算谈框架,侃架构。 这里也只是作者最熟悉的一种后台架构模型。 并发模型: 多进程模型: 来一个请求,设定环境...

2638
来自专栏PingCAP的专栏

TiDB 源码阅读系列文章(十九)tikv-client(下)

上篇文章 中,我们介绍了数据读写过程中 tikv-client 需要解决的几个具体问题,本文将继续介绍 tikv-client 里的两个主要的模块——负责处理分...

490

扫码关注云+社区