进程的切换和系统的一般执行过程
进程调度的时机
Linux内核通过schedule函数来实现进程调度。每次调用schedule函数,其都会在进程队列中查找一个进程,将CPU分配给它。
调用schedule函数的方法
- 进程主动调用schedule函数,如进程调用阻塞的系统调用等待外设或主动休眠,最终会在内核中调用到schedule函数。
- 松散调用,内核代码中可以随时调用schedule函数使当前内核路径让出CPU;也会根据need_resched标记做进程调度,内核会检测need_resched决定是否调用schedule函数。
调用schedule函数的时机
内核调用schedule函数的时机可以简单归结为如下过程:
- 用户进程通过特定的系统调用主动让出CPU;
- 中断处理程序在内核返回用户态时进行调度;
- 内核线程主动调用schedule函数让出CPU;
- 中断处理程序主动调用schedule函数让出CPU(包括以上两点)。
进程调度策略
考虑算法的整体目标,选择可以满足这个目标所的方法或机制作为对策,即为调度策略。
Linux系统中常用的调度策略有以下三种:
- SCHED_FIFO 先进先出的实时进程,如果没有其它更高优先级的可运行实时进程,就可以一直使用cpu运行。对于这种进程,时间片长度是没有意义的。
- SCHED_RR 时间片轮转的实时进程,所具有相同优先级(且都是当前情况下优先级最高)的SCHED_RR以时间片轮转的方式公平使用cpu。
- SCHED_NORMAL 时间片轮转的普通进程,时间片用完之后变成过期进程,所有进程都成为过期进程之后,再统一把过期进程转变为活动进程。
其中前两种为实时进程,优先级取值为099;最后一种为普通进程,只有映射到优先级100139的nice值。
进程调度算法
Linux系统的进程调度算法采用CFS即完全公平调度算法,其原理是基于权重的动态优先级调度算法。即:
- 每个进程使用CPU的顺序由进程已使用的CPU虚拟时间(vrumtime)决定,已使用的虚拟时间越少,进程排序越靠前,进程被在此调度执行的概率越高;
- 每个进程每次占用CPU后可执行的运行时间(ideal_runtime)由进程的权重决定;
- 保证每个时间周期(__sched_period)内运行队列中的所有进程都至少被调度一次。
其中:
__sched_period = nr_running(进程数)* sysctl_sched_min_granularity(默认值为0.75ms);
ideal_runtime = __sched_period * 进程权重 / 运行队列总权重 ;
vrumtime += delta_exec(本次CPU占用时间)* NICE_0_lOAD (nice值为0的进程的权重) / se->load.weight (自身权重);
进程上下文
进程上下文包含了进程执行需要的所有信息:
- 用户地址空间:包括程序代码,数据,用户堆栈等;
- 控制信息:进程描述符,内核堆栈等;
- 硬件上下文:相关寄存器的值。
进程切换就是变更进程上下文。
代码跟踪分析
配置断点:
schedule()函数上下文:
pick_next_task函数上下文:
context_switch函数上下文:
问题分析
对于书中有关vruntime
的部分:
在进行计算时,书中的代码存在一些问题。
- 第一行的判断条件应当是
if se->load.weight == NICE_0_LOAD
因为 if 部分计算的是nice值为0的进程的 vruntime值,因此判断条件应为==
,而非!=
。 - 最后一行else部分的代码应当是
vrumtime+= delta_exec *NICE_0_lOAD/se->load.weight
。
另外在之后的论述中:
- 虚拟时间等于实际执行的物理时间的进程不应当是
0优先级
的进程,而是nice值为0
的进程。 - 检查是否需要设置的调度标志是
need_resched
而非need_schedule
。