一、Linux的进程调度时机
进程切换分为自愿(voluntary)和强制(involuntary)两种。通常自愿切换是指任务由于等待某种资源,将state改为非RUNNING状态后,调用schedule()主动让出CPU;而强制切换(即抢占)则是任务状态仍为RUNNING却失去CPU使用权,情况有任务时间片用完、有更高优先级的任务、任务中调用cond_resched()或yield让出CPU。
1、进程状态转换的时刻:进程终止、进程睡眠;
2、当前进程的时间片用完时(current->counter=0);
3、设备驱动程序
4、进程从中断、异常及系统调用返回到用户态时;
时机1,进程要调用sleep()或exit()等函数进行状态转换,这些函数会主动调用调度程序进行进程调度;
时机2,由于进程的时间片是由时钟中断来更新的,因此,这种情况和时机4是一样的。
时机3,当设备驱动程序执行长而重复的任务时,直接调用调度程序。在每次反复循环中,驱动程序都检查need_resched的值,如果必要,则调用调度程序schedule()主动放弃CPU。
时机4,如前所述,不管是从中断、异常还是系统调用返回,最终都调用ret_from_sys_call(),由这个函数进行调度标志的检测,如果必要,则调用调用调度程序。那么,为什么从系统调用返回时要调用调度程序呢?这当然是从效率考虑。从系统调用返回意味着要离开内核态而返回到用户态,而状态的转换要花费一定的时间,因此,在返回到用户态前,系统把在内核态该处理的事全部做完。
switch_to为宏定义,不能设置断点,查看其内联汇编代码:
#define switch_to(prev, next, last)
do {
// 定义寄存器变量
unsigned long ebx, ecx, edx, esi, edi;
// 使用汇编嵌入代码实现进程切换
asm volatile(
// 保存当前进程的标志寄存器 (EFLAGS) 到堆栈
"pushfl\n\t"
// 保存当前进程的基址寄存器 (EBP) 到堆栈
"pushl %%ebp\n\t"
// 将当前进程的栈指针 (ESP) 存储到 [prev_sp] 变量
"movl %%esp,%[prev_sp]\n\t"
// 将下一个进程的栈指针 (next_sp) 加载到 ESP 寄存器中,实现切换
"movl %[next_sp],%%esp\n\t"
// 将标号 '1' 存储到 [prev_ip] 变量,以便返回当前进程的位置
"movl $1f,%[prev_ip]\n\t"
// 将下一个进程的入口地址 (next_ip) 压入堆栈,以便下一次进程切换时返回该地址
"pushl %[next_ip]\n\t"
// 执行进程切换的汇编代码(__switch_to 函数)
__switch_canary
// 跳转到 __switch_to 标号执行下一个进程的切换
"jmp __switch_to\n"
// 标号 '1' 用于返回当前进程的位置
"1:\t"
// 弹出下一个进程的基址寄存器 (EBP)
"popl %%ebp\n\t"
// 恢复标志寄存器 (EFLAGS) 的值
"popfl\n"
: [prev_sp] "=m" (prev->thread.sp), // 输出约束,将 [prev_sp] 绑定到 prev 进程的栈指针
[prev_ip] "=m" (prev->thread.ip), // 输出约束,将 [prev_ip] 绑定到 prev 进程的入口地址
"=a" (last), // 输出约束,将 'last' 绑定到寄存器 EAX
"=b" (ebx), "=c" (ecx), "=d" (edx), // 输出约束,将寄存器 EBX、ECX、EDX 绑定到相应变量
"=S" (esi), "=D" (edi) // 输出约束,将寄存器 ESI、EDI 绑定到相应变量
__switch_canary_oparam // 输出约束,可能包括一些其他参数
: [next_sp] "m" (next->thread.sp), // 输入约束,将 [next_sp] 绑定到 next 进程的栈指针
[next_ip] "m" (next->thread.ip), // 输入约束,将 [next_ip] 绑定到 next 进程的入口地址
[prev] "a" (prev), // 输入约束,将当前进程的指针 'prev' 绑定到寄存器 EAX
[next] "d" (next) // 输入约束,将下一个进程的指针 'next' 绑定到寄存器 EDX
__switch_canary_iparam // 输入约束,可能包括一些其他参数
"memory"); // 内存约束,告诉编译器需要刷新内存缓存
} while (0)