阻塞、非阻塞,同步、异步的理解
总结
参考:https://leetcode-cn.com/circle/discuss/uHGOZo/
1.用户空间和内存空间
用户空间和内核空间操作系统为了支持多个应用同时运行,保证进程相对独立。操作系统内核需要拥有高于普通进程的权限。
内核空间可以用来控制用户空间的进程的调度和切换,用户空间用来实现进程的隔离?
2.进程切换的过程

- 当一个程序正在执行的过程中,中断(interrupt) 或 系统调用(system call) 发生可以使得 CPU 的控制权会从当前进程转移到操作系统内核。
- 操作系统内核负责保存进程 i 在 CPU 中的上下文(程序计数器,寄存器)到 PCBi (操作系统分配给进程的一个内存块)中。
- 从 PCBj 取出进程 j 的CPU 上下文,将 CPU 控制权转移给进程 j ,开始执行进程 j 的指令。
- 附:系统中存放进程的管理和控制信息的数据结构称为进程控制块(PCB Process Control Block)
- 当OS要调度某进程执行时,要从该进程的PCB中查处其现行状态及优先级;在调度到某进程后,要根据其PCB中所保存的处理机状态信息,设置该进程恢复运行的现场,并根据其PCB中的程序和数据的内存始址,找到其程序和数据;进程在执行过程中,当需要和与之合作的进程实现同步,通信或者访问文件时,也都需要访问PCB;当进程由于某种原因而暂停执行时,又须将器断点的处理机环境保存在PCB中。
- 在进程的整个生命期中,系统总是通过PCB对进程进行控制的,即系统是根据进程的PCB而不是任何别的什么而感知到该进程的存在的。所以说,PCB是进程存在的唯一标志。
几个底层概念的通俗(不严谨)解释:
中断(interrupt)
CPU 微处理器有一个中断信号位,在每个CPU时钟周期的末尾, CPU会去检测那个中断信号位是否有中断信号到达,如果有,则会根据中断优先级决定是否要暂停当前执行的指令,转而去执行处理中断的指令。(其实就是 CPU 层级的 while 轮询)
时钟中断(Clock Interrupt)
一个硬件时钟会每隔一段(很短)的时间就产生一个中断信号发送给 CPU,CPU 在响应这个中断时,就会去执行操作系统内核的指令,继而将 CPU 的控制权转移给了操作系统内核,可以由操作系统内核决定下一个要被执行的指令。
系统调用(system call)
system call 是操作系统提供给应用程序的接口。用户通过调用 systemcall 来完成那些需要操作系统内核进行的操作,例如硬盘,网络接口设备的读写等。
3.进程阻塞的理解
- New. 进程正在被创建.
- Running. 进程的指令正在被执行
- Waiting. 进程正在等待一些事件的发生(例如 I/O 的完成或者收到某个信号)
- Ready. 进程在等待被操作系统调度
- Terminated. 进程执行完毕(可能是被强行终止的)

“阻塞”是指进程在发起了一个系统调用(System Call) 后,由于该系统调用的操作不能立即完成,需要等待一段时间,于是内核将进程挂起为等待 (waiting)状态,以确保它不会被调度执行,占用 CPU 资源。
- 在任意时刻,一个 CPU 核心上(processor)只可能运行一个进程。
4.非阻塞 & 异步
阻塞和非阻塞描述的是进程的一个操作是否会使得进程转变为“等待”的状态
- 计算机物理通信:基本都是异步完成的,即发出请求后,等待 I/O 设备的中断信号后,再来读取相应的设备缓冲区
- 操作系统为用户级提供的调用接口,有以下两类
- 阻塞式系统调用:阻塞式的调用,使得应用级代码的编写更容易(代码的执行顺序和编写顺序是一致的)
- 非阻塞式系统调用:非阻塞调用不会挂起调用程序,而是会立即返回一个值,表示有多少bytes 的数据被成功读取(或写入)
异步处理:
- asychronous system call 也是会立即返回,不会等待 I/O 操作的完成,应用程序可以继续执行其他的操作,等到 I/O 操作完成了以后,操作系统会通知调用进程(设置一个用户空间特殊的变量值 或者 触发一个 signal 或者 产生一个软中断 或者 调用应用程序的回调函数)。
此处,非阻塞I/O 系统调用( nonblocking system call ) 和 异步I/O系统调用 (asychronous system call)的区别是:
1) 一个非阻塞I/O 系统调用 read() 操作立即返回的是任何可以立即拿到的数据,可以是完整的结果,也可以是不完整的结果,还可以是一个空值。而异步I/O系统调用 read()结果必须是完整的,但是这个操作完成的通知可以延迟到将来的一个时间点。
2)操作系统的IO过程分为两步:
-
NIO中需要一个selector线程来建通所有的socket,第一步等待数据过程不阻塞,第二部数据转移过程还是会阻塞( “阻塞”是指进程在发起了一个系统调用(System Call) 后,由于该系统调用的操作不能立即完成,需要等待一段时间,于是内核将进程挂起为等待 (waiting)状态,以确保它不会被调度执行,占用 CPU 资源。)
-
AIO中不需要监听的线程,第一步第二步均不阻塞,两步完成后通过回调机制通知对应的进程(用户空间)
-
第一个阶段是非阻塞的
-
第二个阶段是阻塞的
-
三者的区别
-
-
select:
-
可以跨任何操作系统
-
timeout参数精度ns级,另外两个都是ms级
-
事件类型比较少:readset、writeset、exceptset,分别对应读、写、异常条件的描述符集合
-
select 的描述符类型使用数组实现,FD_SETSIZE 大小默认为 1024,因此默认只能监听 1024 个描述符
-
poll
-
事件类型比较多【但也有数量限制】
-
事件描述符使用链表连接起来,没有数量的限制
-
select 和 poll 速度都比较慢(轮询方式找到IO完成的时间描述符)
-
select 和 poll 每次调用都需要将全部描述符从应用进程缓冲区复制到内核缓冲区。
-
select 和 poll 的返回结果中没有声明哪些描述符已经准备好,所以如果返回值大于 0 时,应用进程都需要使用轮询的方式来找到 I/O 完成的描述符。
-
epoll【向内核注册新描述符+回调函数+链表存储已准备好的IO描述符】
-
epoll_ctl() 用于向内核注册新的描述符或者是改变某个文件描述符的状态。已注册的描述符在内核中会被维护在一棵红黑树上,通过回调函数内核会将 I/O 准备好的描述符加入到一个链表中管理,进程调用 epoll_wait() 便可以得到事件完成的描述符。
-
从上面的描述可以看出,epoll 只需要将描述符从进程缓冲区向内核缓冲区拷贝一次,并且进程不需要通过轮询来获得事件完成的描述符。
-
selector:单个线程去监听多个channel(只是在准备好的时候调用,阻塞一下线程)

-
AIO的实现方式:
-

浙公网安备 33010602011771号