每日一句
You cannot swim for new horizons until you have courage to lose sight of the shore. 除非有勇气离开岸边,否则你永远游不到彼岸。
概念
IO 是主存和外部设备(硬盘、终端和网络等)拷贝数据的过程。IO是操作系统的底层功能实现,底层通过I/O指令进行完成。
以下是5种类Unix下可用的I/O模型
-
阻塞式I/O:Blocking IO
-
非阻塞式I/O:nonblocking IO
-
I/O 复用(Select,poll epoll):IO multiplexing
-
信号驱动式I/O(SIGIO):signal driven IO
-
异步 I/O(posix 的 aio 系列函数):asynchromous IO
Blocking IO
在 Linux 中,默认情况下所有的 socket 都是 Blocking,一个典型的读操作流程大概是这样:
-
通常涉及等待数据从网络到达。当所有等待数据到达时,它被复制到内核中的某个缓冲区
-
把数据从内核缓冲区复制到应用程序缓冲区
当用户进程调用了 recvfrom 这个系统调用, kernel 就开始了 IO 的第一个阶段:准备数据。对于 network IO 来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的 UDP 包)。这个时候 kernel 就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。当 kernel 一直等到数据准备好了,它就会将数据从kernel 中拷贝到用户内存,然后kernel返回结果,用户进程才解除 block 的状态,重新运行起来。 所以,Blocking IO 的特点就是在IO执行的两个阶段都被 block了
非阻塞式I/O
Linux 下,可以用过设置 socket 使其变为 non-blocking。当对一个 non-blocking socket 执行操作时,流程如下: 从图中可以看出,当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。 从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次 发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。
所以,用户进程第一个阶段不是阻塞的,需要不断的主动询问kernel数据好了没有;第二个阶段依然总是阻塞的。
IO 多路复用(NIO)
select、epoll 的好处就在于单个 process 就可以同时处理多个网络链接的 IO。
IO 复用和同步阻塞本质一样,不过利用了新的 Select 系统调用,由内核来负责本来是请求进程该做的轮训操作,看似不非阻塞IO还多了哥系统调用开销,不过因为支持多路IO才算提高了效率 也就是一个可以监听多个。 它的基本原理就是 select、epoll 这个 function会不断的轮询所负责的所有 socket,当某个 socket 有数据到达了,就通知用户进程。它的流程如下图:
当用户线程调用select,那么整个进程会被阻塞,而同时,kernel会监视所有select负责的 socket =,当任何一个 socket 中的数据准备好了,select 就会返回,这个时候用户进程会调用 read 操作,将数据 kernel 拷贝到用户进程。 首先开启套接字的信号驱动式IO功能,并且通过sigaction(信号处理程序) 系统调用安装一个信号处理函数 ,该函数调用将立即返回,当前进程没有被阻塞 ,继续工作;当数据报准备好的时候,内核为该进程产生SIGIO 的信号,随后既可以在信号处理函数中调用recvfrom 读取数据报,并且通知主循环数据已经准备好等待处理;也可以直接通知主循环让它读取数据报;(其实就是一个待读取的通知和待处理的通知),基本不会用到。
异步IO(AIO)
多线程和多进程的模型虽然解决了并发的问题,但是系统不能无限的增加线程,由于系统的切换线程的开销恒大,所以,一旦线程数量过多,CPU的时间就花在线程的切换上,正真运行代码的时间就会减少,结果导致性能严重下降
由于我们要解决的问题是CPU高速执行能力和IO设备的龟速严重不匹配,多线程和多进程只是解决这一个问题的一种方法。 另一种解决IO问题的方法是异步IO,当代码需要执行一个耗时的IO操作时,他只发出IO指令,并不等待IO结果然后就去执行其他代码,一段时间后,当IO返回结果是,在通知CPU进行处理我们调用aio_read函数,给内核传递描述符,缓冲区指针,缓冲区大小,和文件偏移量,并且告诉内核当整个操作完成时如何通知我们,该函数调用后,立即返回,不会被阻塞
另一方面:从kernel的角度,当他收到一个aio_read之后,首先它立即返回,所以不会对用户进程产生block,然后kernel会等待数据准备完成,然后将数据拷贝到用户内存(copy由内核完成),当着一切完成后,kernel会给用户进程发送一个singal或者执行下一个基于线程回调函数来完成此次IO处理过程,告诉他read操作完成
美文佳句
岁月应是静好。近来,心神总是不宁,想必是被尘世间的诱惑所困。
我想,要是远方的祥夫先生来到我这“梅知堂”,两人清茶一杯,盘腿而坐,或看他画梅,纸上的梅花仿佛兀自开在飞雪里,如是,那该是一幅怎样的墨梅图?
昨晚,梦里看到那个戴着小黑圆眼镜的祥夫背着黄色的大包正往江南赶路。是又到了梅花开的日子了吗?