目录
前言
Part1. 孤儿进程
Part2. 进程优先级
Part2.1. 概念
Part2.2. 修改进程优先级
Part2.3. 系统API修改优先级
Part2.4. UID
Part3. 时间片、进程切换
Part3.1. 并行与并发
Part3.2. 进程切换机制
Part3.3. 进程上下文
Part4. Linux O(1)调度器原理
Part4.1. 调度队列结构
Part4.2. O(1)找到最高优先级进程
Part4.3. 调度流程
Part5. 结语
前言
这篇文章我们来认识一下孤儿进程与进程优先级切换调度,接下来来跟随小编的视角来学习一下吧
let's go!!!!!!!!
Part1. 孤儿进程
父进程先于子进程死亡,子进程就变成孤儿进程。
1. 孤儿进程会被pid=1的init/systemd进程领养,成为init的子进程。
2. 为什么需要领养?
如果不领养,当子进程退出之后,没有父进程回收,孤儿进程就会变成僵尸进程,持续占用PCB资源。
3. 领养之后,init进程作为父进程,会自动回收孤儿进程退出后的资源。
4. 孤儿进程会自动变为后台进程:ctrl + c无法杀死,只能使用kill -9 pid杀掉。
Part2. 进程优先级
Part2.1. 概念
进程优先级决定进程获取CPU资源的先后顺序。
CPU资源是有限的,优先级高的进程,优先拿到CPU时间片。
Linux中优先级由两个数值表示:
• PRI:进程真实优先级,默认80,数值越小,优先级越高。
• NI(nice值):优先级修正数据。
真实优先级公式:PRI = 80 + nice
nice取值范围:‑20 ~ 19
对应真实PRI优先级范围:60 ~ 99
注意:nice值越小,代表修正越高,最终PRI越小,进程优先级越高。
Part2.2. 修改进程优先级
方式1:top命令
1. 输入top进入工具
2. 按下r
3. 输入目标进程pid
4. 输入要设置的nice值
方式2:命令行 nice / renice
# 启动进程时直接设置nice,x为nice值,y为程序名
nice -n x y
# 修改已经运行进程的nice,x为nice值,y为pid
renice x -P y
Part2.3. 系统API修改优先级
// 获取优先级
int getpriority(int which, id_t who);
/*
which:
PRIO_PROCESS:按进程pid;
PRIO_PGRP:按进程组;
PRIO_USER:按用户uid;
who:对应id,填0代表当前自身
*/
// 设置优先级,prio就是nice值
int setpriority(int which, int who, int prio);
用码道免费领 1 个月 Token
cpp
运行
普通用户只能降低优先级(nice变大);root用户可以随意修改到‑20最高优先级。
p.s. 优先级不能差距拉得太大,操作系统兼顾公平性,高优先级只是获取更多时间片,不会完全霸占CPU。
Part2.4. UID
操作系统看用户,依靠UID(ls -ln可以查看)。
子进程继承父进程的UID;UID代表进程属于哪个用户。
Linux内核依靠UID做权限校验,控制进程访问资源。
Part3. 时间片、进程切换
Part3.1. 并行与并发
• 并发:多个进程快速切换,单个CPU,交替执行多个进程。每个进程拿到一小段CPU时间片。
• 并行:多个CPU核心,多个进程同时真正运行。
p.s. 多核CPU才存在并行;单核CPU只有并发。
Part3.2. 进程切换机制
CPU不会把一个进程执行完毕才切换。操作系统分配时间片,给每一个进程一小段CPU执行时间。
时间片耗尽,触发时钟中断,保存当前进程上下文,切换到另外一个进程。
Part3.3. 进程上下文
CPU内部寄存器集合,保存当前进程运行现场:程序计数器、通用寄存器等。
进程被切出去的时候,把寄存器现场保存到TSS任务状态段;当进程再次被调度回来,把现场恢复到寄存器,从上一次中断位置继续运行。
上下文就是进程运行的全部硬件现场,没有上下文,进程就无法继续执行。
Part4. Linux O(1)调度器原理
Linux经典O(1)调度器,每一个优先级对应一条调度队列。
Part4.1. 调度队列结构
1. 一共140个优先级,0~139,对应queue[140]数组。数组每一个元素,是一条双向链表,存放对应优先级的进程(task_struct)。
2. 维护两个位图bitmap[140/8],一共140bit。每一个bit代表对应优先级链表是否存在就绪进程。bit置1代表该优先级队列里面有就绪进程。
3. 两套调度结构体:active(活跃队列,还没消耗完时间片)、expired(过期队列,时间片耗尽)。
◦ active:存放还拥有剩余时间片,可以继续调度运行的进程。
◦ expired:进程时间片用完,放入expired队列,等待所有active进程全部调度完毕,再交换两个队列。
4. nr_active:记录active队列里面总就绪进程数量。
Part4.2. O(1)找到最高优先级进程
1. 读取bitmap位图,找最低的置1的bit位,这个bit下标就是当前最高就绪优先级。
2. 通过下标,直接访问queue[优先级]链表,取出链表头部进程,送入CPU运行。
p.s. 找最高优先级只需要一次位运算,时间复杂度O(1),所以叫O(1)调度器。
Part4.3. 调度流程
1. 通过bitmap找到最高优先级;
2. 将该优先级链表头部进程摘出来,交给CPU执行;
3. 进程时间片耗尽,把进程放入expired过期队列;
4. 不断重复调度,直到nr_active == 0,active队列全部进程时间片耗尽;
5. 交换active与expired结构体指针,expired直接变成新的active队列,继续调度。
why: 为什么修改nice值,不去直接修改PRI
运行中的进程,修改NI(nice),不修改当前正在使用的PRI。不会立刻把进程挪动队列。
等到该进程时间片耗尽,进入expired队列的时候,再重新计算新PRI,放入对应优先级链表。避免调度过程中打乱队列。
p.s. 补充负载均衡
多CPU环境,每个CPU都拥有自己一套独立的active/expired调度结构体。
调度器会统计各个CPU的负载load,在多个CPU之间迁移进程,平衡各个CPU的任务数量。
Part5. 结语
这篇文章我们认识并知道了孤儿进程与进程优先级切换调度,接下来,小编还会带来更多Linux相关的知识,敬请期待~
最后,祝大家可以:春风得意马蹄疾,一日看尽长安花!
最后的最后,要是觉得本文还可以的话,可以点点赞,关注小编一波,谢谢大家!~