15.Linux进程调度与优先级机制解析
一.孤儿进程僵尸进程是父进程在子进程退出了且子进程退出父进程什么都不做但如果父进程提前退出了会怎样呢子进程不退出父进程执行5s后退出然后编译运行后再去查看进程等父进程退出后可以看到这时我们发现子进程的PPID变成了1即如果父子关系中父进程先退出子进程要被1号进程领养这个被领养的进程子进程叫做孤儿进程top后可以看到1号进程的名字为systemd11号进程是谁现在我们就先理解它是操作系统2为什么要被领养如果不被领养的话就没有父进程那么等子进程退出变为僵尸进程后就没有父进程去回收它会造成内存泄漏问题3变成在后台运行变成孤儿进程后该进程就变成在后台运行了此时再ctrlc发现它还在运行后台运行不会阻塞命令行可以用kill杀掉二.进程优先级1.是什么是进程得到cpu资源的先后顺序2.为什么要有优先级目标资源稀缺导致要通过优先级确认谁先谁后的问题合理分配优先级可以优化系统性能尤其是在多任务环境中。3.怎么实现在一般的操作系统内优先级也是一种数字是pcbtask_struct)内的一种属性。值越小优先级越高。我们大部分操作系统都是基于时间片规定一个任务完成的时间的分时操作系统其特点会考虑公平性的问题保证优先级之间差别不会太大优先级的变化不会太大尽可能保持公平。UIDUID标识用户的唯一数字ID。每个用户账户都有一个对应的UID系统通过UID来区分不同用户的权限和资源访问。n表示以数字显示小知识系统怎么知道一个用户访问文件的时候是拥有者所属组还是other进程启动的时候会记录是谁启动的即记录下对应的UID然后进程就会以这个UID依次和文件拥有者用户的UID所属组的UID作对比哪一个先匹配就是对应的身份。如果都不相等就是other。PRI和NIPRI进程的优先级默认是80NI进程优先级的修正值默认是nice值0PRI(new)PRI(old默认就是80)niceegPRI(newPRI(old)nice801090;(都是80去运算怎么改优先级1.top命令行输入top后按下r键输入目标进程的PID进程ID随后输入新的nice值。2.nice使用nice命令启动新进程nice -n [优先级值] [命令]例如以优先级10启动一个进程nice -n 10 ./script.sh2.renicerenice -n [新nice值] -p [PID]优先级的极值问题在Linux中nice[-20,19]则优先级的范围[60,99]---只针对普通进程只有普通进程收nice值影响一共40个这对资源系统稳定性和资源分配有重要作用优先级极值限制了用户进程对系统资源的过度占用。若允许无限调整优先级低优先级进程可能因资源匮乏而完全无法运行高优先级进程可能垄断CPU导致系统不稳定。极值设定在-20最高和19最低之间确保资源分配相对公平。三.概念-竞争、独立、并行、并发竞争性:系统进程数目众多而CPU资源只有少量甚至1个所以进程之间是具有竞争属性的。为了高效完成任务更合理竞争相关资源便具有了优先级独立性:多进程运行需要独享各种资源多进程运行期间互不干扰并行:多个进程在多个CPU下分别同时进行运行这称之为并行并发:多个进程在⼀个CPU下采用进程切换的方式在⼀段时间之内让多个进程都得以推进称之为并发四.进程切换1.死循环进程怎么运行一旦一个进程占用cpu不会把其代码跑完待它的时间片结束后就会切换进程不会让cpu一直执行某个进程进入死循环2.cpu和寄存器CPU中央处理器是计算机的核心部件负责执行指令和处理数据。寄存器是CPU内部的高速存储单元用于临时存放指令、数据或地址其访问速度远高于内存。cpu执行某个进程的代码时不会一股脑全部加载到cpu而是CPU从内存读取指令到寄存器解码后执行运算结果可能写回寄存器或内存。寄存器会保存正在运行的进程的临时数据根据寄存器功能不同其里面保存的数据也不相同例如其中EIP/pc就是存储下一条待执行指令的地址这样cpu就知道要执行的下一条语句地址了。所以寄存器减少了CPU直接访问内存的次数显著提升性能。寄存器是cpu内部的临时空间即寄存器寄存器的里面内容一个是空间一个是内容空间只有一份而内容可以有多份3.怎么切换进程具体步骤保存当前进程上下文将当前进程的寄存器状态如PC、SP、通用寄存器、程序计数器和其他运行时数据保存到其进程控制块PCB中。选择下一个进程调度器从就绪队列中选择一个优先级最高的进程将其PCB加载到内存中。恢复新进程上下文从新进程的PCB中恢复寄存器状态、程序计数器等并更新内存管理单元MMU的页表或段表。更新内核数据结构修改进程状态如将原进程设为就绪或阻塞、更新调度队列并可能处理进程统计信息如运行时间。eg当进程A切换到进程B时会先通过把进程A的寄存器状态、程序计数器和其他运行时数据保存到其进程控制块PCB中的方式来保存进程A的上下文数据。然后再从调度队列中选择一个优先级最高的进程例如B将其PCB加载到内存中cpu再运行进程B这时就会从B进程的PCB中恢复寄存器状态、程序计数器等即恢复进程B的上下文数据等到B进程的时间片结束后再到A进程的时候就会一样的从A的PCB恢复寄存器状态、程序计数器等即恢复曾经进程A保存的历史上下文数据。所以进程切换本质最核心的就是保存和恢复当前进程的硬件上下文数据即寄存器的内容补充知识上下文数据保存到哪里了其实在Linux一些比较老的内核版本中是由在task_struct内部的一个结构体TSS任务状态段来保存但现在新的内核版本通常把其单独分了出来不在task_struct内部了五.Linux的真实调度算法O(1)调度算法1.几个小知识1操作系统内部会有一个指针struct task_struct *current永远指向当前进程。2操作系统一般可以分为分时操作系统按时间片公平调度实时操作系统是一种专门设计用于在严格时间限制内处理任务的系统即有新进程要想被调度时会立刻响应不会等时间片结束。主要运用在工业和制造业上。Linux优先级有140个前100个称为实时优先级而后面40个才为分时优先级这也和优先级范围[60,99]对应3调度器调度切换2.Linux2.6内核中进程队列的数据结构// 每个CPU独有一个 runqueue struct runqueue { // 锁、统计、当前运行进程、负载、时间戳...一堆全局字段 spinlock_t lock; task_struct *curr; // 当前正在跑的进程 task_struct *idle; // CPU空闲进程 // 关键两个 prio_array 实体存就绪任务 struct prio_array arrays[2]; // 两个指针分别指向上面两块数组 struct prio_array *active; struct prio_array *expired; }; // prio_array 只是runqueue里面的“就绪任务收纳盒” struct prio_array { int nr_active; unsigned long bitmap[5]; struct list_head queue[140]; };//active和expired的类型就是struct prio_array呀⼀个CPU拥有⼀个runqueue杜绝多核全局锁争抢 如果有多个CPU就要考虑进程个数的负载均衡问题其中一个 runqueue 绑定 2 个 prio_array分别是活跃队列和过期队列runqueue 管 CPU 全局调度状态prio_array 只管就绪进程的优先级排队活跃队列正在使用的队列叫活跃队列1 struct list_head queue[140] 其中每个元素类型struct list_head双链表链表上的每一个元素就是struct task_struct比如queue[10]就表示所有优先级为10的进程都挂载到queue[10]这条队列上所以数组下标就是优先级而相同优先级的进程在同一条队列中按照FIFO规则进行排队调度下面有图2nr_active:总共有多少个运行状态的进程3从该结构中选择一个最合适的进程过程是怎么的呢1、从0下表开始遍历queue[140]。2、找到第一个非空队列该队列必定为优先级最高的队列。3、拿到选中队列的第一个进程开始运行调度完成。4、遍历queue[140]时间复杂度是常数但还是太低效了。4bitmap[5]一共140个优先级一共140个进程队列为了提高查找非空队列的效率就可以用5*32160其中20个没用对应140个优先级和进程队列个比特位表示队列是否为空这样便可以大大提高查找效率比特位的位置和queue[140]的下标一一对应比特位的内容1/0表示对应queue[140]相应位置是否存在进程是否为空。所以有了bit_map调度的时候先查看nr_active大于0则再去查找bit_map确认下标再在队列中找到该下标位置从队列头部选中第一个进程把该进程的pcb给current指针最后cpu就能通过current指针找到并调度对应的进程。即调度器先挑选队列再到对应队列上挑选进程但一个队列没办法做到公平调度——进程饥饿问题只有一个活跃队列实时操作系统是没办法做到公平调度例如有优先级为60的进程不断死循环一直被调度那优先级为99的进程就不能被调度会造成进程饥饿问题。所以还得有一个结构和活跃队列相同的队列过期队列• 过期队列和活动队列结构⼀模⼀样queuebitmap这些就不再重复说了• 过期队列上放置的进程都是时间片耗尽的进程• 当活动队列上的进程都被处理完毕之后对过期队列的进程进行时间片重新计算实现双队列轮转active指针永远指向活跃队列expired指针永远指向过期队列运行方法调度器只从runqueue-active指向的 prio_array 取进程运行进程时间片耗尽 → 从 active 对应的 prio_array 摘下重算优先级后放入runqueue-expired对应的 prio_array当 active 的 prio_array 内没有就绪进程时 直接交换 runqueue 里active和expired两个指针不用移动 prio_array 里任何进程下次调度继续从新的 active原 expiredprio_array 中挑选任务。综上所述prio_array 的 bitmapO (1) 找最高优先级进程runqueue 内 active/expired 指针交换O (1) 切换调度周期整个过程时间复杂度是O(1)所以这就是Linux内核之O(1)调度算法总结一下实时和普通进程的区别都适用O1调度算法对比维度实时进程0~99普通进程100~139优先级区间0-99优先级更高100-139优先级更低调度策略SCHED_FIFO先进先出、SCHED_RR实时轮转SCHED_OTHER分时轮转优先级特性只有固定静态优先级无动态调整nice 不生效存在动态优先级内核自动奖惩nice 影响基础优先级时间片耗尽处理RR 放回 active 同优先级链表不进 expiredFIFO 无时间片一直占cpu除非自己放弃cpu移出 active放入 expired 数组等待指针交换同优先级运行规则FIFO 独占 CPU 不主动切换RR 时间片到挪队尾轮流执行时间片用完直接移出当前链表抢占权限可抢占普通进程、低优先级实时进程仅能抢占更低优先级普通进程无法抢占实时进程适用场景工业控制、音视频等低延迟实时任务桌面、浏览器、编译等通用分时程序