第八章 进程的切换和系统的一般执行过程
1.进程调度的时机
-
硬中断和软中断
- 中断:在本质上都是软件或者硬件发生了某种情形而通知处理器的行为,处理器进而停止正在运行的指令流(当前进程),对这些通知做出相应的反应,即转去执行预定义的中断处理程序(内核代码)。
- ntel定义的中断类型主要有:
- 硬中断:就是CPU的两根引脚(可屏蔽中断和不可屏蔽中断)。
- 软中断/异常:包括除零错误、系统调用、调试断点等在CPU执行命令过程中发生的各种特殊情况统称为异常。异常会导致程序无法继续执行,而跳转到CPU预设的处理函数。异常分为以下三种:
- 故障:出现问题,可以恢复到当前指令。
- 退出:不可恢复的严重故障,导致程序无法继续运行,只能退出。
- 陷阱:程序主动产生的异常。
-
进程调度时机
- schedule()函数:Linux内核通过schedule函数实现进程调度。调用schedule函数一次就是调度一次,调用schedule函数的时候就是进程调度的时机。调度schedule()函数的两种方法:
- 进程主动调用schedule():如果进程调用阻塞的系统调用等待外设或主动睡眠等,最终都会在内核中调用到schedule函数。
- 松散调用:内核代码中可以随时调用schedule()函数使当前内核路径让出CPU;也会根据need_resched标记做进程调度,内核检测到need_resched决定是否调用schedule函数。
- 上下文:一般来说,CPU在任何时刻都处于以下三种情况之一
- 运行于用户空间,执行用户进程上下文;
- 运行于内核空间,处于进程(一般是内核线程)上下文;
- 运行于内核空间,处于中断(中断处理程序ISR,包括系统调用处理过程)上下文。
- 进程调度时机就是内核调用schedule()函数的时机。 进程调度时机情况总结如下:
- 用户进程通过特定的系统调用主动让出CPU;
- 中断处理程序在内核返回用户态时进行调度;
- 内核线程主动调用schedule函数让出CPU;
- 中断处理程序主动调用schedule函数让出CPU(包括以上两点)。
- schedule()函数:Linux内核通过schedule函数实现进程调度。调用schedule函数一次就是调度一次,调用schedule函数的时候就是进程调度的时机。调度schedule()函数的两种方法:
2.调度策略和算法
- 进程的分类:
- 分类1:
- I/O消耗型进程
- 处理器消耗型进程
- 分类2:
- 交互式进程
- 批处理进程
- 实时进程
- 根据进程的不同分类,Linux采用不同的调度策略:
- 对于实时进程,Linux采用FIFO或者RR的调度策略;
- 对于其他进程,Linux采用CFS调度器,核心思想是“完全公平”。
- 分类1:
- 调度策略
- 实时进程的调度策略(优先级0~99,静态设定)
- SCHED_FIFO 先进先出
- SCHED_RR 轮转策略(时间片)
- 普通进程的调度策略
- SCHED_NORMAL,使用CFS调度管理程序,普通进程只有nice值,映射到优先级为100~139。按优先级占比计算占用CPU的时间。
- 实时进程的调度策略(优先级0~99,静态设定)
- CFS调度算法(完全公平调度算法)
- 基本原理:基于权重的动态优先级调度算法。
- 调度周期:进程越多,周期越长;上限默认8ms;一个时间周期内队列的所有进程都会至少被调度一次。
__sched_period = nr_running(进程数)*sysctl_sched_min_granularity(默认值为0.75ms)
- 理论运行时间:每次可获取CPU后最长可占用时间为ideal_runtime.
ideal_runtime = __sched_period * 进程权重/队列运行总权重
- 虚拟运行时间:每个进程拥有一个vruntime,每次需要调度时就运行队列中拥有最小的vruntime的进程来运行,最长运行时间为ideal_runtime.
if se->load.weight != NICE_0_LOAD vruntime+= delta_exec; else vruntime+= delta_exec *NICE_0_LOAD/se.load->weight
3.进程上下文切换
- 为了控制进程的执行,内核必须有能力挂起正在CPU中运行的进程,并恢复执行以前挂起的进程,这个行为称为进程切换。
- 进程上下文包含了进程执行的所有信息:
- 用户地址空间:进程代码、数据和用户堆栈等;
- 控制信息:进程描述符、内核堆栈;
- 硬件上下文:存储相关寄存器的值。
- 实际代码中进程切换由两个步骤组成:
- 切换页全局目录(RC3)以安装一个新的地址空间,这样不同的虚拟地址经过不同的页表转为不同的物理地址;
- 切换内核态堆栈和硬件上下文。
4.Linux系统的运行过程
- Linux系统的一般执行过程:正在运行的用户态进程X切换到用户态进程Y的过程:
- 1.正在运行的用户态进程X;
- 2.发生中断(包括异常,系统调用等),硬件完成以下动作:
- save cs:eip/esp/eflags:当前CPU上下文压入用户态进程X的内核堆栈;
- load cs:eip(entry of a specific ISR) and ss:esp(point to kernel stack)
- 3.SAVE_ALL,保存现场
- 4.中断处理过程中或中断返回前调用了schedule(),其中的switch_to做了关键的进程上下文切换;
- 5.标号1,之后开始运行用户态进程Y(这里Y曾经通过以上步骤被切换出去过因此可以从标号1继续执行);
- 6.restore_all,恢复现场;
- 7.iret-pop cs:eip/ss:esp/eflags,从Y进程的内核堆栈中弹出(2)中硬件完成的压栈内容;
- 8.继续运行用户态进程Y。
5.Linux系统构架与执行过程概览
- 操作系统的基本概念
-
Linux操作系统的整体构架示意图
-
ls命令执行过程示意图
6.进程调度相关源代码跟踪和分析
配置运行MenuOS系统
另外打开一个shell命令窗口进行gdb远程调试,设置gdb远程调试和设置断点如下:
使用c继续运行:
使用list查看断点处的代码。可以看到MenuOS运行到schedule函数停下来,schedule函数是进程调度的主体函数,schedule()函数断点截图如下:
pick_next_task函数是schedule函数中的重要函数,负责根据调度策略和调度算法选择下一个进程,pick_next_task函数断点截图如下所示:
context_switch函数是schedule函数中实现进程切换的函数,context_switch函数断点截图如下所示:
switch_to是context_switch函数中进行进程关键上下文切换的函数。switch_to内部是内嵌汇编代码,无法跟踪调试。