学习目标:重点关注进程切换的过程,进程调度的时机,操作系统的基本构成以及一般的执行过程。
因为进程的调度只发生在内核中,进程调度函数schedule()只能在内核中被调用,用户进程无法调用。 因此,进程切换需要用到实现用户态到内核态的切换。
除了主动让出CPU外,进程的调度都需要在进程外进行。中断可以实现切出进程指令流的作用,中断处理程序是与进程无关的内核指令流。
Intel定义的中断有以下几种:
linux内核通过schedule函数实现进程调度,schedule函数在运行队列中找到一个进程。把CPU分配给他。每调用一次schedule函数就实现一次进程调度,调用schedule函数就是进程调度的时机。
调用schedule函数的两种方式:
need_resched标记做进程调度,内核检测到need_resched决定是否调用schedule函数。CPU在任何时刻都处于以下3种情况之一:
中断上下文中的get_current可以获取一个指向当前进程的指针,指向被中断进程或即将运行的进程。硬件上下文切换信息也存储于该进程的内核堆栈中。
进程调度的时机就是内核调用schedule函数的时机。当内核即将返回用户空间时,内核会检查need_resched标志是否设置。如果设置,则调用schedule函数,将从中断处理程序返回用户空间的时间点作为一个固定的调度时机点。
某个时间周期内队列的所有进程都会至少被调度一次
__sched_period = nr_running(进程数)*sysctl_sched_min_granularity(默认值)理论运行时间
ideal_runtime = __sched_period * 进程权重/队列运行总权重
每次可获取CPU后最长可占用时间为ideal_runtime虚拟运行时间
每个进程拥有一个vruntime,每次需要调度时就运行队列中拥有最小的vruntime的进程来运行,最长课运行时间为ideal_runtime
vruntime = 实际运行时间 * NICE_0_LOAD / 进程权重
= 实际运行时间 * 1024 / 进程权重
NICE_0_LOAD = 1024, 表示nice值为0的进程权重
可以看到, 进程权重越大, 运行同样的实际时间, vruntime增长的越慢
vruntime = 进程在一个调度周期内的实际运行时间 * 1024 / 进程权重
= (调度周期 * 进程权重 / 所有进程总权重) * 1024 / 进程权重
= 调度周期 * 1024 / 所有进程总权重
内核需要有能力挂起正在CPU中运行的进程,并恢复执行以前挂起的进程,这个行为称为进程切换。进程切换中,挂起的CPU上执行的进程与中断时保存现场是不同的,中断前后在同一个进程上下文中只是用户态转向内核态。
进程上下文包含了:
实际代码中进程切换由两个步骤组成:
进程切换的函数schedule()来选择一个新的进程来运行,并调用context_switch进行上下文的切换。在context_switch中最重要的是宏switch_to进行硬件上下文的切换。
以下是context_switch的关键代码


switch_mm表示将下一进程的页表地址装入RC3,得到物理地址struct_mm是内存描述符,其中存储了进程地址空间中的所有信息以下是汇编的switch_to代码

pushfl
pushl %ebp
注意,因为现在esp还在A的堆栈中,所以这两个东西被保存到A进程的内核堆栈中。
pushl %%ebp 当前堆栈的栈基址保存
movl %%esp, %[prev_sp] 保存当前堆栈的栈顶
movl %[next_sp],%%esp 将esp指向next进程的栈顶
从这个时候开始,CPU当前执行的进程已经是next进程了,因为esp已经指向next的内核堆栈。
上面三行汇编代码,实现的功能是完成内核堆栈的切换
movl $1f,%[prev_ip]
pushl %[next_ip]
jmp __switch_to
这三行代码使用到了next进程的内核堆栈,但实际上还是在prev内核堆栈上执行。
1:
popl %%ebp
popf1
这三行代码才是next进程开始执行的真正位置。
如果之前B也被switch_to出去过,那么[next_ip]里存的就是下面这个1f的标号,但如果进程B刚刚被创建,之前没有被switch_to出去过,那么[next_ip]里存的将是ret_ftom_fork。
这部分代码是内核代码,它们跟用户代码不在同一个代码段,所有进程在内核态共用这一段内核代码。这里涉及到的所有堆栈都是内核堆栈,而不涉及用户堆栈。
详细参考博客switch_to执行详解
首先还是冻结MenuOS,随后打开调试,分别设置断点。
gdb
file linux-3.18.6/vmlinux
target remote:1234
b schedule
b context_switch
b switch_to
b pick_next_task
开始执行后,可以看到,代码停在了schedule处

之后可以看到context_switch和pick_next_task


linux操作系统的整体架构如图所示:

从下向上每一层分别是 底层硬件 --> 内核实现 --> 系统调用接口 -->基础软件(shell 共享库等) --> 用户级的应用程序
内核向上为用户系统调用提供接口,向下调用硬件服务接口。
2018-2019-1 20189206 《Linux内核原理与分析》第九周作业
原文:https://www.cnblogs.com/zz-1226/p/10090921.html