1、6C H A P T E RLinux IO目录q同步/异步、阻塞/非阻塞的概念qLinux IO 模型n1 同步与异步u同步:一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能算完成,这是一种可靠的任务序列。 要么成功都成功,失败都失败,两个任务的状态可以保持一致。同步/异步、阻塞/非阻塞n异步:不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务就算完成了。 至于被依赖的任务最终是否真正完成,依赖它的任务无法确定,所以它是不可靠的任务序列。n消息通知消息通知 当一个同步调用发出后,调用者要一直等待返回消息
2、(结果)通知后,才能进行后续的执行;当一个异步过程调用发出后,调用者不能立刻得到返回消息(结果)。 实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。n去银行办理业务,可能会有两种方式: 选择排队等候; 另种选择取一个小纸条上面有我的号码,等到排到我这一号时由柜台的人通知我轮到我去办理业务了;(排队等候)就是同步等待消息通知,也就是我要一直在等待银行办理业务情况;(等待别人通知)就是异步等待消息通知。 在异步消息处理中,等待消息通知者(在这个例子中就是等待办理业务的人)往往注册一个回调机制,在所等待的事件被触发时由触发机制(在这里是柜台的人)通过某种机制(在这里是写在小纸条上的
3、号码,喊号)找到等待该事件的人。2 阻塞与非阻塞阻塞与非阻塞n 阻塞和非阻塞这两个概念与程序(线程)等待消息通知(无所谓同步或者异步)时的状态有关。n 阻塞与非阻塞主要是程序(线程)等待消息通知时的状态角度来说的。n阻塞 阻塞调用是指调用结果返回之前,当前线程会被挂起,一直处于等待消息通知,不能够执行其他业务。函数只有在得到结果之后才会返回。 非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。系统的线程切换增加阻塞调用和同步调用n对于同步调用来说,很多时候当前线程可能还是激活的,只是从逻辑上当前函数没有返回而已,此时,这个线程可能也会处理其他的消息。n对
4、于阻塞调用来说,则当前线程就会被挂起等待当前函数返回;n同步阻塞、同步非阻塞:n 如果这个线程在等待当前函数返回时,仍在执行其他消息处理,那这种情况就叫做同步非阻塞;n如果这个线程在等待当前函数返回时,没有执行其他消息处理,而是处于挂起等待状态,那这种情况就叫做同步阻塞;n同步的实现方式会有两种:同步阻塞、同步非阻塞; 同理,异步也会有两种实现:异步阻塞、异步非阻塞;进程切换进程切换n保存处理机上下文,包括程序计数器和其他寄存器。n更新PCB信息。n把进程的PCB移入相应的队列,如就绪、在某事件阻塞等队列。n选择另一个进程执行,并更新其PCB。n更新内存管理的数据结构。n恢复处理机上下文。注:
5、总而言之就是很耗资源文件描述符文件描述符fdn文件描述符(File descriptor)是用于表述指向文件的引用的抽象化概念。n文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。n在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。缓存缓存 IOn缓存 IO 又被称作标准 IO,大多数文件系统的默认 IO 操作都是缓存 IO。n在 Linux 的缓存 IO 机制中,操作系统会将
6、IO 的数据缓存在文件系统的页缓存( page cache )中,也就是说,数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。Linux IO模型模型n对于一次IO访问(以read举例),数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。n当一个read操作发生时,它会经历两个阶段: 第一阶段:等待数据准备 第二阶段:将数据从内核拷贝到进程中n对socket流, 第一步:通常涉及等待网络上的数据分组到达,然后被复制到内核的某个缓冲区。 第二步:把数据从内核缓冲区复制到应用进程缓冲区。 网络应用需要处理的
7、无非就是两大类问题,网络IO,数据计算。相对于后者,网络IO的延迟,给应用带来的性能瓶颈大于后者。Linux IO的模型:n同步模型(synchronous IO)n阻塞IO(blocking IO)n非阻塞IO(non-blocking IO)n多路复用IO(multiplexing IO)n信号驱动式IO(signal-driven IO)n异步IO(asynchronous IO) signal driven IO在实际中并不常用n基本 Linux IO 模型的简单矩阵n同步阻塞同步阻塞 IO(blocking IO) 同步阻塞 IO 模型是最常用的一个模型,也是最简单的模型。在linu
8、x中,默认情况下所有的socket都是blocking。它符合人们最常见的思考逻辑。 阻塞就是进程 被 休息, CPU处理其它进程去了。n调用recv()/recvfrom()函数时,发生在内核中等待数据和复制数据的过程 2.1.3 流程描述流程描述blocking IO的特点就是在IO执行的两个阶段都被blockn用户进程调用了recv()/recvfrom()这个系统调用:n第一个阶段: kernel准备数据这个过程需要等待,也就是说数据被拷贝到操作系统内核的缓冲区中是需要一个过程的。而在用户进程这边,整个进程会被阻塞(当然,是进程自己选择的阻塞)。n第二个阶段:当kernel一直等到数据
9、准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。blocking IO的特点n优点: 能够及时返回数据,无延迟; 对内核开发者来说这是省事;n缺点: 对用户来说处于等待就要付出性能的代价;n同步非阻塞同步非阻塞 IO(nonblocking IO)n同步非阻塞就是 “每隔一会儿” 的轮询(polling)方式。在这种模型中,设备是以非阻塞的形式打开的。这意味着 IO 操作不会立即完成,read 操作可能会返回一个错误代码,说明这个命令不能立即满足(EAGAIN 或 EWOULDBLOCK)。n网络同步非阻塞同步非阻塞
10、 IO 在网络IO时候,非阻塞IO也会进行recvform系统调用,检查数据是否准备好,与阻塞IO不一样,”非阻塞将大的整片时间的阻塞分成N多的小的阻塞, 所以进程不断地有机会 被 CPU光顾”。 轮询检查内核数据,直到数据准备好,再拷贝数据到进程,进行数据处理。 轮询:非阻塞的recvform系统调用调用之后,进程并没有被阻塞,内核马上返回给进程,如果数据还没准备好,此时会返回一个error。进程在返回之后,可以干点别的事情,然后再发起recvform系统调用。重复上面的过程,循环往复的进行recvform系统调用。 n在linux下,non-blocking socket读操作流程n当用户
11、进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。n从用户进程角度讲,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。nonblocking IO的特点:用户进程需要不断的主动询问kernel数据好了没有同步非阻塞方式相比同步阻塞方式:n优点:能够在等待任务完成的时间里干其他活了(包括
12、提交其他任务,也就是 “后台” 可以有多个任务在同时执行)。n缺点:任务完成的响应延迟增大了,因为每过一段时间才去轮询一次read操作,而任务可能在两次轮询之间的任意时间完成。这会导致整体数据吞吐量的降低。nIO 多路复用(多路复用( IO multiplexing) 同步非阻塞方式需要不断主动轮询,消耗大量的CPU时间;而 “后台” 可能有多个任务在同时进行,需要循环查询多个任务的完成状态,只要有任何一个任务完成,就去处理它。 “IO 多路复用” UNIX/Linux 下的 select、poll、epoll 就是干这个的(epoll 比 poll、select 效率高,做的事情是一样的)。
13、nI/O复用模型会用到select、poll、epoll系统调用,这几个函数也会使进程阻塞,但是和阻塞I/O所不同的的,这两个函数可以同时阻塞多个I/O操作。而且可以同时对多个读操作,多个写操作的I/O函数进行检测,直到有数据可读或可写时(注意不是全部数据可读或可写),才真正调用I/O操作函数。nselect调用是内核级别的,select轮询相对非阻塞的轮询的区别在于前者可以等待多个socket,能实现同时对多个IO端口进行监听,当其中任何一个socket的数据准好了,就能返回进行可读,然后进程再进行recvform系统调用,将数据由内核拷贝到用户进程,当然这个过程是阻塞的。nselect或p
14、oll调用之后,会阻塞进程,与blocking IO阻塞不同在于,此时的select不是等到socket数据全部到达再处理, 而是有了一部分数据就会调用用户进程来处理。n如何知道有一部分数据到达了呢?监视的事情交给了内核,内核负责数据到达的处理。也可以理解为非阻塞吧多路复用既然可以处理多个IO,也就带来了新的问题,多个IO之间的顺序变得不确定了,当然也可以针对不同的编号。nIO multiplexing也称这种IO方式为event driven IO。select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。n基本原理:当用户进程调用了select /poll/e
15、poll,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。n多路复用的特点 通过一种机制一个进程能同时等待IO文件描述符,内核监视这些文件描述符(套接字描述符),其中的任意一个进入读就绪状态,select, poll,epoll函数就可以返回。 对于监视的方式,又可以分为 select, poll, epoll三种方式。nIO multiplexing和 blocking IO并没有太大的不同,事实上,还更差一些。n
16、因为IO multiplexing需要使用两个system call (select 和 recvfrom),而blocking IO只调用了一个system call (recvfrom)。nselect的优势在于它可以同时处理多个connection。n在IO multiplexing Model中,实际中,对于每一个socket,一般都设置成为non-blocking,但是,整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。n所以IO多路复用是阻塞在select,epoll这样的系统调用之上,
17、而没有阻塞在真正的I/O系统调用如recvfrom之上。n前面三种IO模式,在用户进程进行系统调用的时候,他们在等待数据到来的时候,处理的方式不一样,直接等待,轮询,select或poll轮询,两个阶段过程:n第一个阶段有的阻塞,有的不阻塞,有的可以阻塞又可以不阻塞。n第二个阶段都是阻塞的。n从整个IO过程来看,他们都是顺序执行的,因此可以归为同步模型(synchronous)。都是进程主动等待且向内核检查状态。n高并发的程序一般使用同步非阻塞方式而非多线程 + 同步阻塞方式。n高并发的情况下,为每个任务(用户请求)创建一个进程或线程的开销非常大。而同步非阻塞方式可以把多个 IO 请求丢到后台
18、去,这就可以在一个进程里服务大量的并发 IO 请求。n信号驱动式信号驱动式IO(signal-driven IO)n异步非阻塞异步非阻塞 IO(asynchronous IO) 相对于同步IO,异步IO不是顺序执行。用户进程进行aio_read系统调用之后,无论内核数据是否准备好,都会直接返回给用户进程,然后用户态进程可以去做别的事情。等到数据准备好了,内核直接复制数据给进程,然后从内核向进程发送通知。IO两个阶段,进程都是非阻塞的。很多开源的异步IO库,例如libevent、libev、libuvn用户进程发起aio_read操作之后,立刻就可以开始去做其它的事。n从kernel的角度,当它
19、受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal或执行一个基于线程的回调函数来完成这次 IO 处理过程,告诉它read操作完成了。nLinux 中, “信号”通知的方式: 1)如果这个进程正在用户态忙着做别的事(例如在计算两个矩阵的乘积),那就强行打断之,调用事先注册的信号处理函数,这个函数可以决定何时以及如何处理这个异步任务。由于信号处理函数是突然闯进来的,因此跟中断处理程序一样,有很多事情是不能做的,因此保险
20、起见,一般是把事件 “登记” 一下放进队列,然后返回该进程原来在做的事。 2)如果这个进程正在内核态忙着做别的事,例如以同步阻塞方式读写磁盘,那就只好把这个通知挂起来了,等到内核态的事情忙完了,快要回到用户态的时候,再触发信号通知。 3)如果这个进程现在被挂起了,例如无事可做 sleep 了,那就把这个进程唤醒,下次有 CPU 空闲的时候,就会调度到这个进程,触发信号通知。nLinux 的异步 IO(AIO)支持是 2.6.22 才引入的,还有很多系统调用不支持异步 IO。Linux 的异步 IO 最初是为数据库设计的,因此通过异步 IO 的读写操作不会被缓存或缓冲,这就无法利用操作系统的缓存与缓冲机制。n同步阻塞模型需要在 IO 操作开始时阻塞应用程序。这意味着不可能同时重叠进行处理和 IO 操作。n同步非阻塞模型允许处理和 IO 操作重叠进行,但是这需要应用程序根据重现的规则来检查 IO 操作的状态。n异步非阻塞 IO 了,它允许处理和 IO 操作重叠进行,包括 IO 操作完成的通知。