文章来源:https://www.zhihu.com/question/19732473
基础知识
- 用户空间和内核空间
- 进程切换
- 系统调用
- 中断
- 进程的阻塞
用户空间和内核空间
操作系统为了支持多个应用同时运行,需要保证不同进程之间相对独立(一个进程的崩溃不会影响其他的进程 , 恶意进程不能直接读取和修改其他进程运行时的代码和数据)。 因此操作系统内核需要拥有高于普通进程的权限, 以此来调度和管理用户的应用程序。于是内存空间被划分为两部分,一部分为内核空间,一部分为用户空间,内核空间存储的代码和数据具有更高级别的权限。内存访问的相关硬件在程序执行期间会进行访问控制( Access Control),使得用户空间的程序不能直接读写内核空间的内存
进程切换
上图展示了进程切换中几个最重要的步骤:
- 当一个程序正在执行的过程中, 中断(interrupt) 或 系统调用(system call) 发生可以使得 CPU 的控制权会从当前进程转移到操作系统内核
- 操作系统内核负责保存进程 i 在 CPU 中的上下文(程序计数器, 寄存器)到 PCBi (操作系统分配给进程的一个内存块)中
- 从 PCBj 取出进程 j 的CPU 上下文, 将 CPU 控制权转移给进程 j , 开始执行进程 j 的指令
几个底层概念的通俗(不严谨)解释:
- 中断(interrupt):CPU 微处理器有一个中断信号位, 在每个CPU时钟周期的末尾, CPU会去检测那个中断信号位是否有中断信号到达, 如果有, 则会根据中断优先级决定是否要暂停当前执行的指令, 转而去执行处理中断的指令。 (其实就是 CPU 层级的 while 轮询)
- 时钟中断( Clock Interrupt ):一个硬件时钟会每隔一段(很短)的时间就产生一个中断信号发送给 CPU,CPU 在响应这个中断时, 就会去执行操作系统内核的指令, 继而将 CPU 的控制权转移给了操作系统内核, 可以由操作系统内核决定下一个要被执行的指令
- 系统调用(system call)system call 是操作系统提供给应用程序的接口。 用户通过调用 systemcall 来完成那些需要操作系统内核进行的操作, 例如硬盘, 网络接口设备的读写等
从上述描述中, 可以看出来, 操作系统在进行进切换时,需要进行一系列的内存读写操作, 这带来了一定的开销:对于一个运行着 UNIX 系统的现代 PC 来说, 进程切换通常至少需要花费 300 us 的
进程阻塞
上图展示了一个进程的不同状态:
- New. 进程正在被创建.
- Running. 进程的指令正在被执行
- Waiting. 进程正在等待一些事件的发生(例如 I/O 的完成或者收到某个信号)
- Ready. 进程在等待被操作系统调度
- Terminated. 进程执行完毕(可能是被强行终止的)
我们所说的 “阻塞”是指进程在发起了一个系统调用(System Call) 后, 由于该系统调用的操作不能立即完成,需要等待一段时间,于是内核将进程挂起为等待 (waiting)状态, 以确保它不会被调度执行, 占用 CPU 资源
友情提示:**在任意时刻, 一个 CPU 核心上(processor)只可能运行一个进程 **
IO System Call的阻塞/非阻塞,同步/异步
这里再重新审视 阻塞/非阻塞 IO 这个概念, 其实阻塞和非阻塞描述的是进程的一个操作是否会使得进程转变为“等待”的状态, 但是为什么我们总是把它和 IO 连在一起讨论呢?
原因是, 阻塞这个词是与系统调用 System Call 紧紧联系在一起的, 因为要让一个进程进入 等待(waiting) 的状态, 要么是它主动调用 wait() 或 sleep() 等挂起自己的操作, 另一种就是它调用 System Call, 而 System Call 因为涉及到了 I/O 操作, 不能立即完成, 于是内核就会先将该进程置为等待状态, 调度其他进程的运行, 等到 它所请求的 I/O 操作完成了以后, 再将其状态更改回 ready 。
操作系统内核在执行 System Call 时, CPU 需要与 IO 设备完成一系列物理通信上的交互, 其实再一次会涉及到阻塞和非阻塞的问题, 例如, 操作系统发起了一个读硬盘的请求后, 其实是向硬盘设备通过总线发出了一个请求,它即可以阻塞式地等待IO 设备的返回结果,也可以非阻塞式的继续其他的操作。 在现代计算机中,这些物理通信操作基本都是异步完成的, 即发出请求后, 等待 I/O 设备的中断信号后, 再来读取相应的设备缓冲区。 但是,大部分操作系统默认为用户级应用程序提供的都是阻塞式的系统调用 (blocking systemcall)接口, 因为阻塞式的调用,使得应用级代码的编写更容易(代码的执行顺序和编写顺序是一致的)。
但同样, 现在的大部分操作系统也会提供非阻塞I/O 系统调用接口(Nonblocking I/O system call)。 一个非阻塞调用不会挂起调用程序, 而是会立即返回一个值, 表示有多少bytes 的数据被成功读取(或写入)。
非阻塞I/O 系统调用( nonblocking system call )的另一个替代品是 异步I/O系统调用 (asychronous system call)。 与非阻塞 I/O 系统调用类似,asychronous system call 也是会立即返回, 不会等待 I/O 操作的完成, 应用程序可以继续执行其他的操作, 等到 I/O 操作完成了以后,操作系统会通知调用进程(设置一个用户空间特殊的变量值 或者 触发一个 signal 或者 产生一个软中断 或者 调用应用程序的回调函数)。
此处,非阻塞I/O 系统调用( nonblocking system call ) 和**异步I/O系统调用 (asychronous system call)**的区别是:
- 一个非阻塞I/O 系统调用 read() 操作立即返回的是任何可以立即拿到的数据, 可以是完整的结果, 也可以是不完整的结果, 还可以是一个空值
- 而异步I/O系统调用 read()结果必须是完整的, 但是这个操作完成的通知可以延迟到将来的一个时间点
下图展示了同步I/O 与 异步 I/O 的区别 (非阻塞 IO 在下图中没有绘出)
注意, 上面提到的 非阻塞I/O 系统调用( nonblocking system call ) 和 异步I/O系统调用 都是非阻塞式的行为(non-blocking behavior)。 他们的差异仅仅是返回结果的方式和内容不同。注意, 上面提到的 非阻塞I/O 系统调用( nonblocking system call ) 和 异步I/O系统调用 都是非阻塞式的行为(non-blocking behavior)。 他们的差异仅仅是返回结果的方式和内容不同
IO概念区分:
- 同步(Synchronous)
- 异步( Asynchronous)
- 阻塞( Blocking )
- 非阻塞( Nonblocking)
相关学习博客:https://segmentfault.com/a/1190000003063859
IO多路复用解析:https://www.zhihu.com/question/32163005/answer/55772739