burningcodes 原创作品转载请注明出处《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

从迷你型Linux内核理解进程调度的原理  一文中已经介绍过简化版的linux内核进程进程调度,这篇文章将深入linux源码进一步分析linux进程调度的原理。

1.进程调度的时机

进程调度主要有以下几个时机:

  • 中断处理过程中:中断处理包括时钟中断、I/O中断、系统调用和异常),直接调用schedule(),或者返回用户态时根据need_resched标记调用schedule()
  • 内核线程中:内核线程可以直接调用schedule()进行进程切换,也可以在中断处理过程中进行调度,也就是说内核线程作为一类的特殊的进程可以主动调度,也可以被动调度;
  • 用户态进程中:用户态进程无法实现主动调度,仅能通过陷入内核态后的某个时机点进行调度,即在中断处理过程中进行调度。

2.schedule详细分析

在entry_32.S(/linux-3.18.6/arch/x86/kernel/entry_32.S)的汇编代码中经常能见到一句话:

这个其实就是在中断处理过程中进行的主动调度,在退出前主动让出调度权,同时也能找到返回用户态时设置need_resched标记来申请被动调度的代码。接下来就分析下schedule的具体调度过程:

schedule函数如下(/linux-3.18.6/kernel/sched/core.c#2865):

在进行调度前首先得通过调度算法选取下一个要被调度的进程,linux内核中是通过优先级来进行调度(/linux-3.18.6/kernel/sched/core.c#__schedule):

选取完要调度的进程后就要进行进程上下文切换,调用switch_to函数进行寄存器状态切换和栈的切换(linux-3.18.6/kernel/sched/core.c#context_switch):

switch_to通过内联汇编进行进程切换,具体切换过程和 从迷你型Linux内核理解进程调度的原理 中基本类似(/linux-3.18.6/arch/x86/include/asm/switch_to.h#31):

可以看到,最大的差别是在标号1之前不是用ret来恢复eip而是通过:

__switch_to代码主要如下:

注意一点:在函数返回时必定有一个pop eip的操作,而在调用jmp __switch_to之前先把下一个进程的eip  push进了栈(即pushl %[next_ip]),所以说当调用完__switch_to函数之后就会将当前eip赋值为next_ip,也就完成了进程的切换。

3.gdb跟踪schedule

gdb调试并且在关键函数上下断点:

继续运行后逐步跟踪来到__switch_to,通过上面的分析可以知道在完成这个函数后才真正的完成进程切换:

switch

__switch_to的两个参数分别是prev进程的task_struct,和next进程的task_struct,通过这个结构体中的pid可以知道这个的进程的pid,从上图可以看出,当前的进程正在从0号进程(idle进程)切换值2号进程(kthreadd进程)。

接下来看看哪些地方调用了schedule。info b查看断点,通过delete 2 3删除后面两个断点,继续执行:

bt

通过bt查看函数调用堆栈可以找到在哪调用了schedule,从上图可以看出是kthreadd调用了schedule,继续跟踪:

bt2

在timer中也调用了schedule,类似的调用点有很多,可以直接从内核源码中搜索schedule来查看,这里也就不再继续跟踪了。

4.总结

至此,线上的课程基本上圆满结束,通过这两个多月的学习,对linux内核有了个初步的认识, 通过线上和线下的学习感觉自己收获了很多,从《操作系统》中的理论知识到内核源码还是有很长的一段路要走的。这门课给我的最大感触是线上的教学方式对传统教学模式带来了很大的冲击,另外我也很喜欢写blog的这种学习方式(记录 + 理解 + 总结)。最后还得感谢孟老师,希望这种教学模式可以在软院进一步推广。

 

观看更多有关 的文章?

*

+
跳转到评论