39
社区成员




1. schedule函数介绍
schedule() 是 linux 调度器中最重要的一个函数,就像 fork 函数一样优雅,它没有参数,没有返回值,却实现了内核中最重要的功能,当需要执行实际的调度时,直接调用 shedule(),进程就这样神奇地停止了,而另一个新的进程占据了 CPU。
schedule主要完成的工作内容如下:
(1)sched_submit_work用于检测当前进程是否有plugged io需要处理,由于当前进程执行schedule后,有可能会进入休眠,所以在休眠之前需要把plugged io处理掉放置死锁。
(2)执行__schedule()这个函数是调度的核心处理函数,当前CPU会选择到下一个合适的进程去执行了。
(3)need_resched()执行到这里时说明当前进程已经被调度器再次执行了,此时要判断是否需要再次执行调度。
2. 使用 gdb 跟踪分析一个 schedule()函数
(1)在内核代码中搜索schedule函数
通过查找可以发现,schedule函数定义在在内核的kernel/sched/core.c文件中
(2)将menu目录删除,利用git命令克隆一个新的menu目录,并运行MenuOS。
(3)配置gdb
cd ..
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S
gdb # 打开另一个终端,使用gdb调试
file linux-3.18.6/vmlinux
target remote:1234
(4)在schedule、context_switch、switch_to、pick_next_task设置断点
b schedule
b context_switch
b switch_to
b pick_next_task
(5)开始运行
可以看到,程序停在schedule函数断点处,发现调用了schedule(),即发生了进程调度。
继续执行到pick_next_task断点处:发现在这里使用了某种调度策略选择下一个进程来切换
(6)switch_to汇编代码部分
#define switch_to(prev, next, last)
do {
/*
* Context-switching clobbers all registers, so we clobber
* them explicitly, via unused output variables.
* (EAX and EBP is not listed because EBP is saved/restored
* explicitly for wchan access and EAX is the return value of
* __switch_to())
*/
unsigned long ebx, ecx, edx, esi, edi;
asm volatile("pushfl\n\t" /* 保存当前进程flags */
"pushl %%ebp\n\t" /* 当前进程堆栈基址压栈*/
"movl %%esp,%[prev_sp]\n\t" /*保存ESP,将当前堆栈栈顶保存起来*/
"movl %[next_sp],%%esp\n\t" /*更新ESP,将下一栈顶保存到ESP中*/
//完成内核堆栈的切换
"movl $1f,%[prev_ip]\n\t" /*保存当前进程EIP*/
"pushl %[next_ip]\n\t" /*将next进程起点压入堆栈,即next进程的栈顶为起点*/
//完成EIP的切换
__switch_canary
//next_ip一般是$1f,对于新创建的子进程时ret_from_fork
"jmp __switch_to\n" /*prev进程中,设置next进程堆栈*/
//jmp不同于call是通过寄存器传递参数
"1:\t" //next进程开始执行
"popl %%ebp\n\t"
"popfl\n"
/*输出变量定义*/
: [prev_sp] "=m" (prev->thread.sp), //[prev_sp]定义内核堆栈栈顶
[prev_ip] "=m" (prev->thread.ip), //[prev_ip]当前进程EIP
"=a" (last),
/* 要破坏的寄存器: */
"=b" (ebx), "=c" (ecx), "=d" (edx),
"=S" (esi), "=D" (edi)
__switch_canary_oparam
/* 输入变量: */
: [next_sp] "m" (next->thread.sp), //[next_sp]下一个内核堆栈栈顶
[next_ip] "m" (next->thread.ip),
//[next_ip]下一个进程执行起点,,一般是$1f,对于新创建的子进程是ret_from_fork
/* regparm parameters for __switch_to(): */
[prev] "a" (prev),
[next] "d" (next)
__switch_canary_iparam
: /* 重新加载段寄存器 */
"memory");
} while (0)
Linux调度的核心函数为schedule,schedule函数封装了内核调度的框架。细节实现上调用具体的调度类中的函数实现。schedule函数主要流程为:将当前进程从相应的运行队列中删除,计算和更新调度实体和进程的相关调度信息;将当前进重新插入到调度运行队列中,根据具体的运行时间进行插入而对于实时调度插入到对应优先级队列的队尾,从运行队列中选择运行的下一个进程,进程调度信息和上下文切换。当进程上下文切换后,调度就基本上完成了,当前运行的进程就是切换过来的进程了。
(7)进程上下文的切换机制,以及与中断上下文切换的关系
进程上下文切换是操作系统在多任务环境下管理进程的重要机制。当操作系统决定暂停当前运行的进程并开始执行另一个进程时,它必须保存当前进程的状态(称为上下文),包括程序计数器、寄存器的值、内存映像以及其他关键信息,然后加载下一个即将执行的进程的状态,以便让它继续执行。这个过程被称为进程上下文切换。而中断上下文切换是发生在处理器响应硬件中断时的过程。当发生硬件中断时,处理器会暂停当前执行的任务,保存当前任务的上下文,并转而执行中断处理程序。这个中断处理程序在处理完中断请求后,会将之前被中断的任务的上下文恢复,让其继续执行。因此,中断上下文切换也涉及到保存和恢复任务的状态。
两者的关系在于:进程上下文切换是由操作系统控制的,它涉及到在不同进程之间切换,而中断上下文切换是在处理器响应硬件中断时发生的,涉及到从用户态切换到内核态执行中断处理程序,然后再切换回之前的任务执行状态。两者都需要保存和恢复任务或进程的上下文信息,但触发的原因和所涉及的上下文不同。