实验八:理解进程调度时机跟踪分析进程调度与进程切换的过程
实验要求:
- 理解 Linux 系统中进程调度的时机,可以在内核代码中搜索 schedule()函数,看都是哪里调用了 schedule(),判断我们课程内容中的总结是否准确;
- 使用 gdb 跟踪分析一个 schedule()函数 ,验证您对 Linux 系统进程调度与进程切换过程的理解;推荐在实验楼 Linux 虚拟机环境下完成实验。
- 特别关注并仔细分析 switch_to 中的汇编代码,理解进程上下文的切换机制,以及与中断上下文切换的关系
基础知识:
一、进程调度的时机:
(1)schedule函数:
- Linux内核通过schedule函数实现进程调度,schedule函数在运行队列中找到一个进程,把CPU分配给它。
- 调用schedule函数一次就是调度一次,调用schedule函数的时候就是进程调度的时机。
- 当内核即将返回用户空间时,内核会检查need_resched标志是否设置,若设置,则调用schedule函数,此时是从中断处理程序返回用户空间的时间点作为一个固定的调度时机点。
- 内核线程是一个特殊进程,只有内核态没有用户态,内核线程和中断处理程序中任何需要暂时中止当前执行路径的位置都可以直接调用schedule()
- 内核线程作为一类的特殊的进程可以主动调用schedule函数让出CPU,也可以被动调度.
(2)上下文:
- CPU在任何时候都处于以下三种情况:
- 运行于用户空间,执行用户进程上下文。
- 运行于内核空间,处于进程上下文。
- 运行于内核空间,处理中断上下文。
二、进程切换的过程(正在运行的用户态进程X切换到用户态进程Y的过程)
- 正在运行的用户态进程X
- 发生中断(包括异常、系统调用等),硬件完成以下动作
- save cs:eip/ss:esp/eflags:当前CPU上下文压入用户态进程X的内核堆栈
- load cs:eip(entry of a specific ISR) and ss:esp(point to kernel stack):加载当前进程内核堆栈相关信息,跳转到中断处理程序,即中断执行路径的起点
- SAVE_ALL,保存现场,此时完成了中断上下文切换,即从进程X的用户态到进程X的内核态
- 中断处理过程中或中断返回前调用了schedule函数,其中switch_to做了关键的进程上下文切换。将当前用户进程X的内核堆栈切换到选出的next进程(此处为进程Y)的内核堆栈,并完成了进程上下文所需的EIP等寄存器状态切换
- 标号1(即代码1:\t处)之后开始运行用户态进程Y
- restore_all,恢复现场,与保存现场对应
- iret - pop cs:eip/ss:esp/eflags,从Y进程的内核堆栈中弹出硬件完成的压栈内容。至此,完成了中断上下文切换,即从进程Y的内核态返回到进程Y的用户态
- 继续运行用户态进程Y
实验过程:
在虚拟机环境中打开一个shell命令窗口,按照之前的方式对MenuOS进行调试,在gdb中设置以下断点:
- schedule:进程调度的主体函数
- context_switch:实现进程切换的函数
- pick_next_task:负责根据调度策略和调度算法选择下一个进程
- switch_to:其中switch_to为宏定义,不能设置断点,需到context_switch函数中单步执行查看调用。
总结:
- Linux内核调用schedule()函数进行进程调度,并调用context_switch进行上下文的切换,调用switch_to来进行进程关键上下文切换。
- 在linux中,进程主动调度的时机可以在中断处理过程中、内核线程中,但用户态进程无法实现主动调度,仅能通过陷入内核态的新时机点进行调度,即在中断处理过程中进行调度。