1、基于Event-Driven(事件驱动)实现模拟进程调度,包括
最短工作优先(SJF); 最短剩余时间优先(SRTF); 最高响应比优先(HRRF); 优先级调度(Priority); 轮转调度(RR)。 其中,SJF、SRTF为非抢占式调度,其余为抢占式调度。
进程状态
最简单的概括(三态模型)是:进程的状态分为——等待态、就绪态、运行态。五态模型比三态模型多了新建态和终止态。
以同时炒多道菜为例。进程就相当于炒菜的过程。
等待态——某道菜可能需要煮、蒸、焖一会儿,这时就需要等待它完成; 就绪态——某道菜煮、蒸、焖完了,需要你去处理,然而你现在正在做另一道菜,还没来得及切换过来; 运行态——炒当前的某道菜; 新建态——正准备做这道菜; 终止态——这道菜做完了,要做一些收尾工作。
进程特征
进程的特征有四点:动态性、并发性、独立性、异步性。
仍以同时炒多道菜为例。
动态性——白天炒菜的整个过程,进程是“活”的,因为“你”正在工作。而到了晚上,厨房空无一人,进程是“死”的,厨房内的设施都回到了原位,如同没有人在这里炒过菜一样。进程在一天一夜之间有状态的切换,因此是动态的; 并发性——你可以同时炒多道菜; 独立性——你准备的调料是以某道菜的需求而准备的,而不是里面的某块肉。菜谱中的步骤必须按部就班来,不能少不能乱。一种菜谱完成一道菜,不同菜谱完成的菜不同。你炒这道菜的过程不会影响炒那道菜的过程; 异步性——每道菜被烹饪的过程是间断的(由于是炒多道菜),被中断的时间是未知的。每次炒多道菜的总体时间并不相同,这也体现了炒多道菜的不可预知性。 以上是我的譬喻,而文字描述是这样的:
动态性——进程的实质是程序在多道程序系统中的一次执行过程,既然是过程就要有始有终,所以进程会产生、会消亡。进程执行完毕后,一般不会留下关于它运行的一丝痕迹; 并发性——任何进程都可以同其他进程一起并发执行,并发是任意时刻只有一个程序在运行(要与并行相区别); 独立性——进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位; 异步性——由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进。
并发与并行
并发的含义 当有多个进程在运行时,如果系统是单核的CPU,那它根本不可能真正地同时运行一个以上的进程。系统只能把CPU运行时间划分成若干个时间段(在每个时刻段的起始时刻使用调度算法分配任务),再将每个时间段分配给每个进程执行。在一个时间段内,某进程在运行时,其它进程处于挂起状态(就绪状态)。这种方式我们称之为并发(Concurrent)。
首先,上述讲到的进程调度就涉及到了进程并发。并发的精髓就是分配时间片,微观上是间断的,宏观上是连续的。假如系统一共有26个进程,在一个时间段内,只运行进程A,接着在下一个时间段内运行进程B,直到运行完进程Z,那么所有进程都被运行过了。进程被“重视”的标志就是它的CPU时间,时间多了,运行的就久,进度就越快。当前A-Z的顺序只是一个例子,在真实情况中很少出现这种情况。
上面的定义中,提到了单核的概念。
单核是和多核相区分的,可以这么理解,某时刻,单核CPU只能做一个任务,而多核CPU可以做多个任务。任务做的越多,任务的进度就越快速,系统完成任务的效率就高了。
还有一个概念是超线程(Hyperthreading Technology)。
超线程技术就是通过采用特殊的硬件指令,可以把两个逻辑内核模拟成两个物理超线程芯片,在单处理器中实现线程级的并行计算,同时在相应的软硬件的支持下大幅度提高运行效能,从而实现在单处理器上模拟双处理器的效能。其实,从实质上说,超线程是一种可以将CPU内部暂时闲置处理资源充分“调动”起来的技术。
虽然采用超线程技术能够同时执行两个线程(如果每个进程同一时刻只能运行它的一个子线程的话,那就等同个能够同时执行两个进程),但它并不像两个真正的CPU那样,每个CPU都具有独立的资源。当两个线程都同时需要某一个资源时,其中一个要暂时停止,并让出资源,直到这些资源闲置后才能继续。因此超线程的性能并不等于两颗CPU的性能。
超线程与多核的区别主要取决于资源的独立性。当运行的这两个线程属于同一进程,那么对于超线程技术,就会遇到两个线程的资源发生冲突的情况;对于多核,就不会发生这种情况,因此两个线程在两个不同的CPU之中运行,纵使它们属于同一进程。
并行的含义 当系统有一个以上CPU(即多核)时,则线程的操作有可能不是并发的。当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。
并行是多核CPU的概念。并发是微观上间断,宏观上连续,而并行在微观上离“连续”更近了一步。
实现逻辑
进程控制块PCB(Process Control Block)是OS用于记录和刻画进程状态及环境信息的数据结构。
借助PCB,OS可以全面管理进程的物理实体,刻画进程的执行现状,控制进程的执
标识信息:用于存放唯一标识该进程的信息
系统分配的标识号、系统分配的进程组标识号、用户定义的进程名、用户定义的进程组名
现场信息:用于存放该进程运行时的处理器现场信息,:
进程组成信息:代码/数据地址、外存映像地址
进程队列指引元:进程队列指针、父子兄弟进程指针
进程通信相关信息:消息队列、信号量、锁
进程处理器使用信息:占用的处理器、时间片、处理器使用时间/已执行总时间、记账信息
进程特权信息:如内存访问权限、处理器特权
进程资源清单信息:如正占有的资源、已使用的资源
进程上下文包括:
(1)用户级上下文:用户程序块/用户数据区/用户堆栈/用户共享内存组成的用户空间信息
(2)寄存器上下文:即进程的现场信息,包括PSW/栈指针/通用寄存器。
(3)系统级上下文:由进程控制块(进程的状态)、内存管理信息(进程页表或段表)和系统核心栈(进程内核态运行时的工作区)等操作系统管理进程需要的信息
用户级上下文地址空间和系统级上下文地址空间一起构成了一个进程的整个存储器映像。
关键的进程管理软件包括:
(1)系统调用/中断/异常处理程序
(2)队列管理模块
(3)进程控制程序
(4)进程调度程序(独立进程居多)
(5)进程通信程序(多个程序包)
(6)终端登录与作业控制程序、性能监控程序、审计程序等外围程序
把处于同一状态的所有进程的PCB链接在一起的数据结构称为进程队列,有两种常用的队列组织方式:
(1)索引方式:系统建立若干索引表,各索引表在内存中的起始地址放在内核专用指针单元。
(2)链接方式:队列中的进程可以通过PCB中的队列指引元采用单向链接或双向链接,系统为每个队列设置队列标志以标志和识别队列。
进程的控制与管理:
进程创建:进程列表加一项,申请PCB并初始化,分配唯一进程标识符,建立映像,分配资源,移入就绪队列 进程撤销:从队列中移除,归还资源,撤销标识符,回收PCB,移除进程表项(先要撤销子进程) 进程阻塞:保存现场信息,修改PCB,移入等待队列,转向进程调度程序调度其他进程执行 进程唤醒:等待队列中移出,修改PCB,移入就绪队列(该进程优先级高于运行进程,则重新设置调度标志) 进程挂起:修改状态并出入相关队列,收回内存等资源送至对换区 进程激活:分配内存,修改状态并出入相关队列 其他:如修改进程特权
Linux是以链表方式管理用户空间中的区域area,内核不需要记录那些不存在的页面。通过task结构、task结构指向的mm结构、mm结构指向的其它结构来记录进程标识信息、进程现场信息、进程控制信息。
整体涉及的数据结构图如下:
task_struct:为进程任务基础数据结构,存储着进程相关信息 sched_entity:存储着进程调度相关的信息,其中run_node为可执行红黑树的节点 ofs_rq: 存储着rb_root,红黑树的根节点task_timeline slab: linux内核对于对象内存的一种高效的管理机制, 可以有效的降低内存碎片。task_struct这类数据结构由slab分配并管理
过程简述
进程切换过程为
(中断/异常等触发)正向模式切换并压入PSW/PC
保存被中断进程的现场信息
处理具体中断/异常
把被中断进程的系统堆栈指针SP值保存到PCB
调整被中断进程的PCB信息,如进程状态
把被中断进程的PCB加入相关队列
选择下一个占用CPU运行的进程
修改被选中进程的PCB信息,如进程状态
设置被选中进程的地址空间,恢复存储管理信息
恢复被选中进程的SP值到处理器寄存器SP
恢复被选中进程的现场信息进入处理器
中断返回指令触发逆向模式转换并弹出PSW/P
一些中断/异常不会引起进程状态转换,不会引起进程切换,只是在处理完成后把控制权交回给被中断进程,处理流程为
中断/异常触发正向模式切换压入PSW/PC
保存被中断进程的现场信息
处理中断/异常
恢复被中断进程的现场信息
中断返回指令触发逆向模式转换弹出PSW/PC
调度器完成以下任务:
时钟中断(或类似的定时器)时间内刷新进程的时间片,设置进程调度标志 系统调用返回或中断完成时检查调度标志
进程优先级
早期的调度算法根据进程优先级计算进程运行的时间片、选择被调度的进程。对于普通进程(normal process)而言,nice表示其优先级,nice的取值范围为-20~19,nice值越大优先级越低;对于实时进程(real time process)而言,优先级的取值范围为0~99,同样值越大优先级越低。
内核代码中nice取值范围对应100~139,MAX_RT_PRIO宏定义了实时进程优先级与nice取值的分界点,实时进程的优先级比普通进程优先级高。
调度算法
CFS调度算法中,交互型的进程将获得更大的权重、更多被执行的机会。假如现在有一个vruntime为1ms的交互型进程,另有一个vruntime为10ms的cpu消耗型进程,调度器连续调度交互型进程10次,再调度vruntime为10ms的cpu消耗型进程。Android中使用的调度方法亦为CFS。
migration内核线程完成平衡多核工作负载的工作,migration以以下方式拉起:
时钟中断处理函数timer_interrupt调用scheduler_tick函数 在scheduler_tick中,trigger_load_balance函数被调用 若满足调整cpu负载条件,trigger_load_balance通过raise_softirq调用产生一个软中断 后续软中断得到处理, migration线程被拉起,完成负载平衡工作
一个好的调度算法(时间片轮转调度算法、优先权调度算法、多级反馈队列调度、实时调度)应当考虑以下几个方面。 (1)公平:保证每个进程得到合理的 CPU 时间。 (2)高效:使 CPU 保持忙碌状态,即总是有进程在 CPU 上运行。 (3)响应时间:使交互用户的响应时间尽可能短。 (4)周转时间:使批处理用户等待输出的时间尽可能短。 (5)吞吐量:使单位时间内处理的进程数量尽可能多。
在每个进程的 task_struct 结构中有如下 5 项:need_resched、nice、counter、policy 及rt_priority这五项在前面进程概念中介绍过,对于普通进程,选择进程的主要依据为counter 和nice 。对于实时进程,Linux采用了两种调度策略,即FIFO(先来先服务调度)和RR(时间片轮转调度)
时间片
1、Jiffies 为Linux核心变数,是一个unsigned long类型的变量,它被用来记录系统自开机以来,已经过了多少tick。每发生一次timer interrupt,Jiffies变数会被加1。 2、CPU利用率分为用户态、系统态、空闲态 3、cat /proc/进程id/stat 查看时间片