本文主要介绍操作系统中进程管理的逻辑,主要包含了典型的进程调度算法,进程和线程的关系,进程之间互斥和同步算法几块。
基础知识进程调度和管理是操作系统知识中非常重要的一环。
每个进程都有如下三个状态:
就绪
运行
阻塞
一般来说,一个进程一开始处于就绪状态,等待CPU选择他运行了之后,就进入了运行状态,当进程出现IO操作之后,就进入阻塞状态。也有可能是时间片用光了,从运行状态进入就绪状态等待CPU调度。
普通调度算法 FCFSFirst Come First Service。FIFO方式的调度策略,先来后到的服务方式。
这种方式的优势是实现简单,也是最容易想到的调度方案。但是有两个重大问题:
对短进程的运行不利
短进程必须等到前面长进程执行完毕了之后才能运行,可能会等待较长时间。
对IO密集型运行不利
IO密集型比短进程还惨。还不容易排队等到他运行了,结果没运行一会儿就因为IO阻塞去了,等IO操作完毕了之后,还得重新排队。
所以这个算法对IO密集型的进程运行效率是极其低下的。
RRRound Robin。轮询调度算法为每个进程分配固定的时间片,时间片用完了就必须重新到队尾去排队。
这样的设计解决了FCFS的第一个问题,相对而言也部分解决了第2个问题。
但是对IO密集型进程依然解决得不太好,有一个优化的方案就是设计两个队列,将因为IO阻塞的进程单独放一个队列,在选择下一个运行进行的时候对这个队列的进程提权。
FCFS还有另外一个比较复杂的问题就是如何选择时间片。时间片过长就退化成FCFS算法了,过短又会造成切换开销太大。
Prediction基于预测的算法。这类预测算法都是假设我们知道每个进程总共所需要的时间,以及IO占比信息。
假设我们能收集到这些信息,可以有如下几种调度算法:
最短运行时间优先
最短剩余时间优先
综合已经运行时间和剩余时间来排序
但是在真实世界中,一般这个信息是无法预测的。即使是同一个进程,之前运行的情况对当前运行的进程也不一定有参考价值。比如cat程序,不同参数对运行时间影响很大。
Feedback这个是基于Prediction来优化的。如果说Prediction是需要预测将来进程还需要多少资源的话,Feedback算法就是基于已经消耗了的资源来判断优先级。
一般来说,也就是运行时间越长,优先级越低的调度算法。
Unix调度算法 老版调度算法这是2.6版本之前的调度算法,该算法采用优化的RR算法,每个进程的优先级算法如下:
p(i) = base(i)+nice(i)+cpu(i)其中base和nice值都是静态固定的,可以由用户指定的。
cpu是随着进程占用CPU时间越长权重就越低的调整因子,该因子调整逻辑如下:
cpu(i) = cpu(i-1)/2也就是每次进程被选中调度一遍之后,下次对应的cpu因子的值都会被除以2,降低下次运行的权重。
新版调度算法内核2.6版本之后重写了调度算法。也叫O(1)算法。
该算法针对普通进程,设置了100~139共40个优先级,进程属于哪个优先级的计算跟老版调度算法类似。系统再存储了一个位图,每个位图代表一个优先级是否有等待的进程。然后每次都选择优先级最高的且有进程的那个队列选取第一个进程来运行。
SMP的调度对于多处理器的处理,跟上面的调度算法类似,只是在选择出进程之后,需要再判断一下给哪个CPU合适。
一般来说,考虑到CPU的本地cache,所以尽量将进程调度到之前运行的CPU上运行。当然,考虑到CPU本身的均衡性,所以肯定还是会有迁移的工作。
线程相关 用户线程&内核线程线程从一开始诞生就有两个分类:用户级线程 和 内核级线程。
我们在Linux上常见的是内核级线程, 即线程调度相关操作都在内核里实现, 不太常见的是用户级线程。
用户级线程的优势是:
线程切换成本低,不用内核操作
用户可以自定义线程调度策略
跟操作系统无关,可以很快移植到另外一台机器上
但是用户线程也有如下问题:
一个线程的阻塞会影响其他线程,因为操作系统看不到别的线程
不能很好的利用多核能力,因为操作系统会把一个内核进程放到一个CPU上
目前Linux上只使用内核级线程, Solaris上面两者都提供。
线程切换一个进程的上下文主要有如下几个部分的信息构成:
程序计数器
寄存器信息
栈信息
一个进程切换的过程包含:
保存当前进程的上下文
将当前进程加入操作系统对应队列
通过调度算法选择另外一个进程
调整虚存映射
加载新进程的上下文