一、原理分析
1.调度时机 背景
不同类型的进程有不同的调度需求
第一种分类
I/O-bond:频繁的进行I/O;通常会花费很多时间等待I/O操作的完成
CPU-bound:计算密集型;需要大量的CPU时间进行运算
第二种分
批处理进程(batch process):不必与用户交互,通常在后台运行;不必很快响应。典型的批处理程序:编译程序、科学计算
实时进程(real-time process):有实时需求,不应被低优先级的进程阻塞;响应时间要短、要稳定。典型的实时进程:视频/音频、机械控制等
交互式进程(interactive process):需要经常与用户交互,因此要花很多时间等待用户输入操作;响应时间要快,平均延迟要低于50~150ms。典型的交互式程序:shell、文本编辑程序、图形应用程序等
2.Linux中的进程调度
Linux既支持普通的分时进程,也支持实时进,Linux中的调度是多种调度策略和调度算法的混合。
调度策略是一组规则,它们决定什么时候以怎样的方式选择一个新进程运行
Linux的调度基于分时和优先级Linux的进程根据优先级排队,根据特定的算法计算出进程的优先级,用一个值表示,这个值表示把进程如何适当的分配给CPU。
Linux中进程的优先级是动态的,调度程序会根据进程的行为周期性的调整进程的优先
较长时间未分配到CPU的进程,通常↑
已经在CPU上运行了较长时间的进程,通常↓
3.scheudle 函数◦schedule函数实现调度
目的:在运行队列中找到一个进程,把CPU分配给它
调用方法:
直接调用,如sleep_on
松散调用,根据need_resched标记
4.进程调度的时机
中断处理过程(包括时钟中断、I/O中断、系统调用和异常)中,直接调用schedule(),或者返回用户态时根据 need_resched 标记调用schedule()。
内核线程可以直接调用schedule()进行进程切换,也可以在中断处理过程中进行调度,也就是说内核线程作为一类的特殊的进程可以主动调度,也可以被动调度。
用户态进程无法实现主动调度,仅能通过陷入内核态后的某个时机点进行调度,即在中断处理过程中进行调度。
用户态被动调度
内核线程只有内核态没有用户态的特殊进程,无需系统调用。
5.进程的切换
为了控制进程的执行,内核必须有能力挂起正在CPU上执行的进程,并恢复以前挂起的某个进程的执行,这叫做进程切换、任务切换、上下文切换;
挂起正在CPU上执行的进程,与中断时保存现场是不同的,中断前后是在同一个进程上下文中,只是由用户态转向内核态执行。
进程上下文包含了进程执行需要的所有信息:用户地址空间: 包括程序代码,数据,用户堆栈等;控制信息 :进程描述符,内核堆栈等;硬件上下文(注意中断也要保存硬件上下文只是保存的方法不同)
schedule()函数选择一个新的进程来运行,并调用context_switch进行上下文的切换,这个宏调用switch_to来进行关键上下文切换
next = pick_next_task(rq, prev);//进程调度算法都封装这个函数内部
context_switch(rq, prev, next);//进程上下文切换
switch_to利用了prev和next两个参数:prev指向当前进程,next指向被调度的进程
6.进程代码切换代码分析
esp 先切,eip 再切换利用push eip +call 起到了类似的call 作用,但是灵活修改了eip 。
7.Linux系统的一般执行过程
最一般的情况:正在运行的用户态进程X切换到运行用户态进程Y的过程
在运行的用户态进程X 发生中断,硬件完成以下:
save cs:eip/esp/eflags(current) to kernel stack
load cs:eip(entry of a specific ISR) and ss:esp(point to kernel stack).
SAVE_ALL //保存现场
中断处理过程中或中断返回前调用了schedule(),其中的switch_to做了关键的进程上下文切换
标号1之后开始运行用户态进程Y(这里Y曾经通过以上步骤被切换出去过因此可以从标号1继续执行
已经变成Y进程上下文,真是 庄周做梦
restore_all //恢复现场
iret - pop cs:eip/ss:esp/eflags from kernel stack
继续运行用户态进程Y
二、实验内容
在shell中依次运行以下命令,获取本次实验的代码,并编译运行
cd LinuxKernel
rm menu -rf
git clone https://github.com/mengning/menu.git
cd menu
mv test_exec.c test.c
make rootfs
效果如下:
关闭QEMU窗口,在shell窗口中,cd LinuxKernel回退到LinuxKernel目录,使用下面的命令启动内核并在CPU运行代码前停下以便调试:
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S
接下来,我们就可以水平分割一个新的shell窗口出来,依次使用下面的命令启动gdb调试
gdb
(gdb) file linux-3.18.6/vmlinux
(gdb) target remote:1234
并在内核函数schedule的入口处设置断点,接下来输入c继续执行,则系统即可停在该函数处,接下来我们就可以使用命令n或者s逐步跟踪,可以详细浏览pick_next_task,switch_to等函数的执行过程
三、总结
经过这一阶段的学习,我对Linux内核有了一些初步的认识。和Windows相比,Linux使用命令较多,这是让我感觉比较困难的一点。而且由于之前未学过操作系统,因此对于一些基础概念的理解也不够,经常听着听着就晕了,得结合网上的资料再反复看课程才能勉强理解一二。
但是,我觉得自学是大学的特色(虽然我高中就开始自学),因此,我在此期间还学习了另一本书——《Linux内核设计的艺术》,这本书的特点是图比较多,不是完全靠文字和代码讲解,对于我这种喜欢看漫画的人非常合适。而且讲解得深入浅出,适合没有基础从头学起。我想,既然学不会跑,那我就从走学起。这本讲解Linux内核的书链接为http://pan.baidu.com/s/1kTFXkTh,祝大家都能学懂Linux内核设计的精妙。
刘帅
原创作品转载请注明出《Linux内核分析》
MOOC课程http://mooc.study.163.com/course/USTC-1000029000