Linux:进程优先级与进程切换与调度

上篇文章:Linux:进程状态(完结)

有关本章调度和Linux2.6内核的文章:

https://people.eecs.berkeley.edu/~kubitron/courses/cs194-24-S14/hand-outs/linuxKernelUnderstandingQueudet.pdf

https://developer.ibm.com/tutorials/l-completely-fair-scheduler/

https://www.linuxjournal.com/article/8041

目录

1.进程优先级概念

2.查看系统进程优先级

[2.1UID(user id)](#2.1UID(user id))

2.2PRI和NI

2.2.1修改优先级

细节

快速调整优先级:

3.进程属性:竞争,独立,并行,并发

4.进程切换

5.Linux2.6内核进程O(1)调度队列

5.1一个CPU拥有一个runqueue

5.1.2双数组机制

5.1.3active指针和expired指针

[5.1.4O(1)的奥秘:Bitmap(位图)](#5.1.4O(1)的奥秘:Bitmap(位图))

5.1.5优先级队列

5.1.6结论

6.O(1)调度器与CFS调度器对比


1.进程优先级概念

cpu资源分配的先后顺序,就是指进程的优先级

其与权限的区别在于,权限是判断有无资格,优先级是都有资格,不过要排好顺序。

意义:由于资源稀缺,所以需要通过优先级来判断先后顺序

还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能。

2.查看系统进程优先级

在linux或unix系统中,用ps -l或ps -al命令,会输出下方内容:

UID:代表执行者的身份

PID:代表这个进程的代号

PPID:代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号

PRI:代表这个进程可被执行的优先级,其值越小越早被执行

NI(nice):代表这个进程的nice值

进程优先级的计算:PRI(new) = PRI(old) + nice,本质也是进程task_struct内部的两个整数。

2.1UID(user id)

Linux系统中通过UID来显示执行者名称,不过数字形式的UID不够直观,在文件等操作中使用了昵称,要想知道自己昵称对应的UID是多少,就使用ls -ln(n表示num)来显示

昵称与UID的对应关系可以通过下述命令显示:

复制代码
cat /etc/passwd
cat /etc/shadow

2.2PRI和NI

PRI:当前进程的优先级

NI:nice,进程优先级的修正数据

PRI(new) = PRI(old) + nice

当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行

所以,调整进程优先级,在Linux下,就是调整进程nice值,nice其取值范围是-20至19,一共40个级别。

2.2.1修改优先级

使用top命令,进入后,在点击r(renice是Linux 中用于**修改正在运行进程的优先级(nice值)**的命令)

优先级范围:-20(最高)到19(最低),普通用户只能降低优先级(增加nice值),只有root可以提升优先级(减少nice值)

此时,top的pid是5338,进程pid是1850,在top处输入进程pid后,将其ni值改为10:

此时,ni值和PRI都已被修改:

在此基础上做相同操作,输入修改值为-10,结果ni和pri值变为:

结果并不是之前的初始值80,而是70,原因是因为PRI(new) = PRI(old) + nice中,PRI(old)是一个常数,常数值为80

细节

Linux操作系统,是一种分时操作系统,还有一种相对的是实时操作系统

分时操作系统在调度时,要尽可能的保证公平(相对公平)。

快速调整优先级:
复制代码
// 进程启动前设置
nice -n 10(nice值) ./myproc
// 进程启动后设置
renice -n 0(修改后的nice值) -p 26196(进程pid)

使用代码的方式修改:

复制代码
 #include <sys/time.h>
 #include <sys/resource.h>
 int getpriority(int which, int who);
 int setpriority(int which, int who, int prio);

3.进程属性:竞争,独立,并行,并发

竞争性:系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的,为了高效完成任务,更合理竞争相关资源,便具有了优先级

独立性:多进程运行,需要独享各种资源,多进程运行期间互不干扰

并行:多个进程在多个CPU下分别,同时进行运行,这称之为并行

并发:多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称

之为并发(时间片(int counter):给每个进程拥有CPU的时长)

4.进程切换

进程切换:把一个进程从cpu中剥离,选择新进程(调度),再放入新进程到cpu

知识点:

cpu会在它的寄存器中保存正在执行程序的临时数据(保存的目的是为了恢复)

寄存器!=寄存器内部的数据->当前进程的硬件上下文:包含CPU内部进程相关的所有寄存器的内容(内部的数据)

**CPU上下文切换:**其实际含义是任务切换,或者CPU寄存器切换。当多任务内核决定运行另外的任务时,它保存正在运行任务的当前状态,也就是CPU寄存器中的全部内容。这些内容被保存在任务自己的堆栈中,入栈工作完成后就把下一个将要运行的任务的当前状况从该任务的栈中重新装入CPU寄存器,并开始下一个任务的运行,这一过程就是context switch。

硬件上下文:只有当前进程自己知道,属于进程私有数据,如同每个进程都有私有的task_struct一样

Linux内核0.11代码:

5.Linux2.6内核进程O(1)调度队列

Linux2.6内核中进程队列的数据结构之间的关系:

下方小章节讲解与此图相结合:

5.1一个CPU拥有一个runqueue

这是多核处理的基础,Linux2.6为每个CPU分配了一个独立的运行队列(runqueue),如果有多个CPU就要考虑进程个数的负载均衡问题,

5.1.2双数组机制

**Active 队列:**存放当前时间片(Time Slice)还没有用完的进程。调度器只从这里取进程执行。

时间片还没有结束的所有进程都按照优先级放在该队列

nr_active:总共有多少个运行状态的进程

queue[140]:一个元素就是一个进程队列,相同优先级的进程按照FIFO规则进行排队调度,所以数组下标就是优先级!

从该结构中,选择一个最合适的进程,过程是怎么的呢?

1.从0下表开始遍历queue[140]

2.找到第一个非空队列,该队列必定为优先级最高的队列

3.拿到选中队列的第一个进程,开始运行,调度完成!

4.遍历queue[140]时间复杂度是常数!但还是太低效了!

bitmap[5]:一共140个优先级,一共140个进程队列,为了提高查找非空队列的效率,就可以用5*32个比特位表示队列是否为空,这样,便可以大大提高查找效率!

**Expired 队列:**存放时间片已经用完的进程。当Active 队列里的进程时间片耗尽,它会被重新计算优先级和时间片,然后移动到Expired队列。

过期队列和活动队列结构一模一样

过期队列上放置的进程,都是时间片耗尽的进程

当活动队列上的进程都被处理完毕之后,对过期队列的进程进行时间片重新计算

"时间片轮转"(Epoch):当active 队列为空时,说明所有进程都运行过一轮了。此时,内核只需要交换两个指针(active 和 expired 互换),原来的 Expired 变成了新的 Active。

5.1.3active指针和expired指针

active指针永远指向活动队列

expired指针永远指向过期队列

可是活动队列上的进程会越来越少,过期队列上的进程会越来越多,因为进程时间片到期时一直都存在的。

没关系,在合适的时候,只要能够交换active指针和expired指针的内容,就相当于有具有了一批

新的活动进程!

5.1.4O(1)的奥秘:Bitmap(位图)

图片左侧计算: 100/32=3,unsigned bitmap[5] ,32 × 5 = 160 。

原理解析:

系统支持140 个优先级(0-139)。

为了在 O(1)时间内找到优先级最高的进程,内核使用了一个位图 bitmap 。

一个 unsigned long 是 32 位。32 × 5 = 160 位,足够覆盖 140 个优先级。

调度查找:调度器不需要遍历链表,而是直接用汇编指令(如 bsf1 -Bit ScanForward) 查找 bitmap 中第一个被置为 1的位。这不仅快,而且时间是恒定的,与进程数量无关,所以叫O(1)

5.1.5优先级队列

图片右侧长条:queue[140] 和一个个方格。

结构:这是一个数组,包含了 140 个链表头(struct 1ist_head)。

优先级映射:

0-99(RT):实时进程(Real-Time)。图中写着"FIFO调度"、"RR"。这些优先级非常高,主要用于严格时限的任务。

100 -139(Normal):普通进程(分时进程)。图中红色笔记写着[100,139] 和nice 值相关。

映射公式:图中笔记展示了 60 ->映射到数组下标中 -> pri + 40 的字样,实际上标准内核是Static_prio 映射。 通常 Nice 值-20 对应优先级 100,Nice +19 对应 139。

图片右下角图示:展示了当一个优先级(比如 100)对应的链表里有多个进程时,它们是如何挂载的。

5.1.6结论

1.调度没有直接用某个进程的具体优先级,优先级数字,决定的是进程Task_struct入队列的问题

2.在活跃队列的调度周期内,保证按照优先级调度,过期队列的存在,保证优先级低的进程可以被调度。在宏观上,所有进程都被调度,在微观上,优先级高的先调度。

3.在系统当中查找一个最合适调度的进程的时间复杂度是一个常数,不随着进程增多而导致时间成

本增加,我们称之为进程调度0(1)算法!

复制代码
struct rq {
 spinlock_t lock;
 /*
 * nr_running and cpu_load should be in the same cacheline because
 * remote CPUs use both these fields when doing load calculation.
 */
 unsigned long nr_running;
 unsigned long raw_weighted_load;
#ifdef CONFIG_SMP
 unsigned long cpu_load[3];
#endif
 unsigned long long nr_switches;
 /*
 * This is part of a global counter where only the total sum
 * over all CPUs matters. A task can increase this counter on
 * one CPU and if it got migrated afterwards it may decrease
 * it on another CPU. Always updated under the runqueue lock:
 */
 unsigned long nr_uninterruptible;
 unsigned long expired_timestamp;
 unsigned long long timestamp_last_tick;
 struct task_struct *curr, *idle;
 struct mm_struct *prev_mm;
 struct prio_array *active, *expired, arrays[2];
 int best_expired_prio;
 atomic_t nr_iowait;
#ifdef CONFIG_SMP
 struct sched_domain *sd;
 /* For active balancing */
 int active_balance;
 int push_cpu;
 struct task_struct *migration_thread;
 struct list_head migration_queue;
#endif
#ifdef CONFIG_SCHEDSTATS
 /* latency stats */
 struct sched_info rq_sched_info;
 /* sys_sched_yield() stats */
 unsigned long yld_exp_empty;
 unsigned long yld_act_empty;
 unsigned long yld_both_empty;
 unsigned long yld_cnt;
 /* schedule() stats */
 unsigned long sched_switch;
 unsigned long sched_cnt;
 unsigned long sched_goidle;
 /* try_to_wake_up() stats */
 unsigned long ttwu_cnt;
 unsigned long ttwu_local;
#endif
 struct lock_class_key rq_lock_key;
};
/*
 * These are the runqueue data structures:
 */
struct prio_array {
 unsigned int nr_active;
 DECLARE_BITMAP(bitmap, MAX_PRIO+1); /* include 1 bit for delimiter */
 struct list_head queue[MAX_PRIO];
};

6.O(1)调度器与CFS调度器对比

特性 O(1) 调度器 (图示内容) CFS 调度器 (现代 Linux)
核心结构 active/expired 数组 + Bitmap 红黑树 (Red-Black Tree)
时间片 固定分配 (根据优先级) 动态计算 (虚拟运行时间 vruntime)
交互性判断 复杂的启发式算法 (Sleep bonus) 自然处理 (vruntime 小的先运行)
公平性 较差 (针对交互进程有时误判) 理论上完全公平

本章完。

相关推荐
never_go_away2 小时前
linux Socket限制
linux·运维·服务器
济6172 小时前
linux 系统移植(第二十四期)---- 根文件系统其他功能测试---- Ubuntu20.04根文件系统其他功能测试
linux·运维·服务器
十年编程老舅2 小时前
字节跳动 Linux C/C++ 后端 面经
linux·后端面试·八股文·字节跳动·面试八股文·服务器面试
济6172 小时前
linux 系统移植(第二十五期)---- 运用MfgTool 工具进行linux系统烧写---- Ubuntu20.04
linux·运维·服务器
EverydayJoy^v^2 小时前
RH134简单知识点——第10章——控制启动过程
linux·服务器·网络
lysine_2 小时前
实现ubuntu两个网口桥接
linux·服务器·网络·arm开发·ubuntu
翼龙云_cloud2 小时前
阿里云渠道商:弹性伸缩如何三步搭建跨可用区大模型服务?
服务器·阿里云·云计算
waves浪游2 小时前
Ext系列文件系统
linux·服务器·开发语言·c++·numpy
加油勇士2 小时前
服务器调优
运维·服务器