首先,这篇总结针对于linux操作系统
进程和线程的基本概念
进程:进程是一个程序运行起来的状态,是资源分配和管理的基本单位
线程:线程是进程中不同的执行路径,是执行调度的基本单位
区别:
不同的进程会拥有自己独立的内存空间,而线程共享进程的内存空间,没有自己独立的内存空间。
在linux操作系统中,一个进程只包含一个线程。
进程的状态切换
线程创建:linux操作系统中6通过fork /vfork 由父线程开启一个子线程。fork采用了写时拷贝(copy-on-write),新创建的子进程不复制整个进程的地址空间,而是让父进程和子进程共享同一个拷贝。
linux通过clone()系统调用实现的fork();
内核通过一个唯一的进程标识值或PID来标识每个进程。
孤儿线程 :如果父进程在子进程之前退出,必须有机制来保证子进程能找到一个新的父亲,否则这些称谓孤儿的进程就会在退出时永远处于僵死状态,白白消耗内存。
解决方案:给子进程在当前进程组内找一个线程作为父亲,如果不行就让init作为他的父亲。
僵尸进程:父进程产生子进程后,会维护一个PCB结构,子进程退出后,由父进程释放,如果父进程没有释放,子进程将成为一个僵尸进程。
调度器和时间片
在操作系统内核中有一个调度程序(调度器)负责决定那个进程投入运行
何时运行以及运行多长时间。
多任务系统可以分为两类:抢占式多任务和非抢占式多任务。而linux操作系统采用的是抢占式多任务的模式。在这种模式下,由调度程序来决定什么时候停止一个进程的运行,以便其他进程能够得到执行机会。这个强制的挂起动作就叫做抢占。
而进程在被抢占之前能够运行的时间是预先设置好的,这个叫进程的时间片。
调度策略
在linux中将进程分为了普通进程和实时进程。
实时进程的优先级永远高于普通进程的优先级。
普通进程的调度策略:
SCHED_NORMAL(SCHED_OTHER),这种调度策略采用的是CFS算法
对于实时进程采用的调度策略:
SCHED_FIFO: 采用先入先出的调度算法,处于可运行状态的
SCHED_FIFO级的进程会比任何SCHED_NORMAL级的进程都先得到调度
SCHED_RR:轮询算法,进程在耗尽事先分配给他的时间后就不能
再继续运行了。他是带有时间片的先入先出算法。
调度算法
进程优先级
基于优先级调度是最基本的一类调度算法
在linux系统中采用了两种不同的优先级范围:
普通进程(nice = -20 ~ +19): 越大的nice值意味着更低的优先级
实时进程(nice = 0 ~ 99):越大的nice值,意味着更高的优先级
CFS
完全公平调度,是针对于普通进程的调度算法。
由于进程可以被分为I/O消耗性和CPU消耗型。
I/O消耗型更多的是在等待用户的输入,更注重响应时间,他希望每次调用CPU时,能够在尽量短的时间内做出响应。
CPU消耗型则大部分时间用来计算,很少的时间用于IO消耗,所以这类进程更注重吞吐量。
为了平衡这二者之间的差异,采用了CFS.
CFS不再使用传统的跟进优先级分配固定的时间片的方式,而是分配一个给定的处理器使用比。
例如:文本编辑器和视频解码器。文本编辑器更多的在等待IO输入,
使用CPU的时间很少,但是一旦使用,就希望CPU能及时响应。而视频
解码器对于响应时间并没有太多要求,我们不会关心是立即开始解码
还是半秒钟之后才开始,但是他需要占用更多的CPU去执行。
假设每个人都分配了50%的处理器使用比。由于视频解码器占用CPU时间长,文字编辑器实际根本用不了这么长时间,于是,每当用户输入时,cpu会判断文字编辑器远远没有用到分配给他的CPU使用比,就会立刻抢占视频解码器让文本编辑器运行,以兑现承诺给文本编辑器的50%使用比。
linux调度算法CFS的实现
在多任务操作系统中,同时运行多个任务时一个理想的状态,但是
事实上无法在一个处理器上真的同时运行多个进程。而且每个任务
运行无限小的时间周期也是不高效的,进程抢占会带来巨大的消耗。
CFS的做法是允许每个进程运行一段时间、循环轮转、选择运行最少
的进程作为下一个运行进程,而不再采用分配每个进程时间片的做法了
CFS根据进程总数的基础上计算一个进程应该运行多久,
而不是依靠nice值来计算时间片。
CFS根据nice值的优先级来获取处理器运行比的权重:
优先级越高,权重越高,相反 ,优先级越低,权重越低。
CFS具体实现步骤:
-
时间记账
CFS不再有时间片的概念,但是它也必须维护每个进程运行的时间记账,因为他需要确保每个进程只在公平分配给他的处理器时间内运行。
CFS使用调度器结构追踪进程的运行记账,其中 vruntime虚拟实时记录了一个程序到底运行了多长时间以及他还应该再运行多久。在linux系统中系统优先级的所有进程的虚拟运行时间都是相同的。
-
进程选择
在CFS选择下一个进程时,它会挑选一个具有最小的vruntime虚拟实时的进程。(在linux系统中采用红黑树来组织可运行的进程队列) -
调度器入口
调度器入口函数schedule(), 他需要根据优先级找到一个最高优先级的调度类,然后调度类会有自己的可运行队列,由调度器类来决定谁才是下一个该运行的线程,每个调度类都实现了pick_next_tasks()方法,执行该方法,可以选择下一个要执行的进程。
-
睡眠和唤醒
这点也很重要,拥有睡眠和唤醒,就可以不去执行那些本不愿意执行的进程。
好了,以上就是总结的linux操作系统中的进程调度。