前面几节,我们一起学习了操作系统中的核心概念——进程,讨论了 linux 内核是如何记录这些进程信息(task_struct结构体)的,以及 linux 内核如何查找这些信息(thread_info 结构体)。还知道了 linux 中的进程是如何被创建的(clone),以及进程死亡后,它的遗产是如何被处理的。从本节开始,我们一起来学习 linux 是如何管理进程的。
单核 cpu 运行多进程的能力
稍微有些计算机常识的人都知道,即使是单核 cpu 的计算机,无论是 windows,还是 linux 系统,内存中运行的常常也都不止一个程序。打开 windows 的任务管理器,就可以看到有几十个进程在内存中运行。
在 linux 中输入 ps -A 命令,也可以看到几十个进程在内存中运行。
按理说,单核的 cpu 同时只能执行一个进程,那为何这些进程可以同时运行呢?
想想,即使是单核 cpu 的电脑,你也可以一边听歌,一边打游戏,甚至,还能一边看电影。
单核 cpu 当然同时只能执行一个进程,但是因为操作系统能够管理进程,让诸多进程有条不紊的交替执行,这一交替过程非常快,所以给我们的感觉就是多个进程“同时运行着”。事实上,不仅是单核 cpu,只要系统中可运行的进程数目比 cpu 的数目多,就注定某一个时刻有进程不能运行。而操作系统就负责合理安排 cpu 的使用,让这些可运行的进程有机会运行。
抢占式和非抢占式多任务系统
能够合理安排多个进程共享cpu的操作系统,常常被称作“多任务操作系统”,这类系统一般会划分为两类:一种是抢占式多任务系统,一种是非抢占式多任务系统。我们更关心的 linux 提供了抢占式多任务模式,因此 linux 内核有能力强制挂起某个正在运行的进程,并改让其他进程运行,被强制挂起的进程被迫让出cpu的使用权,看起来就好像自己的财产被“抢占”了一样。
大多数抢占式多任务操作系统,一般会为进程预先设定好可执行时间,这一“可执行时间”常常被称作时间片,系统通过管理各个进程的时间片,即可从全局角度作出诸多进程的 cpu 使用安排,这样做还有一点好处,就是可以避免某一进程独享 cpu,导致系统崩溃。不过,linux 与一般的操作系统不太一样,它的管理方式有些“独一无二”,在之后的介绍中,我们将一起看到这点。
对于非抢占式多任务操作系统,它们无法强制剥夺进程对 cpu 的占用,只能依靠进程主动让出 cpu。然而,这种模式下,操作系统无法对诸多进程的运行时间做出协调,可能会导致进程A每运行10秒钟,进程B才能运行1ms。而且,一旦某个进程未能成功让出 cpu,可能整个系统就崩溃了。
绝大部分的现代操作系统都能提供抢占式多任务模式,linux 从设计时,就是抢占式的多任务系统。
IO 密集型和计算密集型任务
操作系统中的任务可以简要的分为IO密集型和计算密集型两种。IO密集型任务大多数时候都在等待或者提交 IO 请求,因此这种类型的任务实际上需要占用 cpu 很少的运算能力,它更多的时间是处于被阻塞状态。
IO是指任何可能造成阻塞的资源。例如从磁盘读取数据,等待网络数据,或者等待键盘鼠标输入等,这些操作消耗的时间一般都远大于进程的其他操作所消耗的时间。
计算密集型任务则是占用 cpu 运算能力的大头。因为没有阻塞,所以这类任务一旦获取 cpu 的使用权,就会毫不停歇地疯狂计算。例如图像处理算法,以及科学研究者常用的 matlab 等一些需要执行大量数学计算的任务,就是典型的计算密集型任务。
操作系统在为进程分配 cpu 使用权的时候,就要充分考虑这两种情况。一方面,用户使用键盘或者鼠标输入的时候,系统要尽可能快的做出响应,另一方面,系统还要尽可能的为计算密集型任务分配更多的 cpu 使用权,这样其实是矛盾的。
linux 系统的 cpu 使用比
多数操作系统,是通过分配给 IO 密集型任务更高的优先级和更多时间片来调和上述矛盾的。但是,由于 IO 密集型任务常常处于阻塞状态,往往会消耗大量时间片,这就会造成 cpu 的使用效率不高。因此,linux 采用了另外一种设计——分配 cpu 的使用比。
在 linux 系统中,假设分配给键盘处理程序(IO密集型)和视频数据处理程序(计算密集型)均 50% 的使用比,在用户敲击一次键盘之后,linux 立刻处理了这一输入,接着又立刻进入休眠,把 cpu 使用权让给视频处理程序,视频处理程序疯狂的使用 cpu 进行计算。这时,用户又敲击了一次键盘,因为键盘处理程序还没有消耗完 linux 分配给它的 50% 的 cpu 使用比,所以 linux 内核立刻调度键盘处理程序处理这次输入,而让视频处理程序在键盘处理程序再次阻塞时运行。这样,系统就既能快速响应 IO 密集型任务,又能尽可能的把 IO 密集型任务阻塞时的 cpu 让给计算密集型任务运行。