第八章
进程的切换和系统的一般执行过程
进程的调度实际与进程的切换
- ntel定义的中断类型
- 硬中断:就是CPU的两根引脚(可屏蔽中断和不可屏蔽中断)
- 软中断/异常:包括除零错误、系统调用、调试断点等在CPU执行命令过程中发生的各种特殊情况统称为异常。异常会导致程序无法继续执行,而跳转到CPU预设的处理函数。
- 异常分为3种:
- 故障
- 退出
- 陷阱
- 调用schedule函数的时候就是进程调度的时机
- CPU在任何时候都处于以下三种情况:
- 运行于用户空间,执行用户进程上下文
- 运行于内核空间,处于进程(一般是内核线程)上下文
- 运行于内核空间,处于中断(中断处理程序ISR,包括系统调用处理过程)上下文
- 内核线程以进程上下文的形式运行在内核空间中,本质上还是进程,但它有调用内核代码的权限。
- 进程调度实际如下:
- 用户进程通过特定的系统调用主动让出CPU
- 中断处理程序在内核返回用户态势进行调度
- 内核线程主动调用schedule函数让出CPU
- 中断处理程序主动调用schedule函数让出CPU,涵盖以上第一种和第二种情况
- linux系统在用户态实现的线程库pthread是通过在内核中多个进程共享一个地址空间实现的
- 进程的分类:
- I/O-bound:频繁的进行I/O,通常会花费很多的时间等待I/O操作的完成
- CPU-bound:计算密集型 ,需要大量的CPU时间进行运算第二种分类
- 第二种分类:
- 批处理进程
- 实时进程
- 交互式进程
- Linux的进程根据优先级排队:根据特定的算法计算出进程的优先级,用一个值表示,这个值表示把进程如何适当的分配给CPU。
- Linux中进程的优先级是动态的:调度优先级会根据进程的行为周期性的调整进程的优先级:较长时间未分配到CPU的进程,通常上升;已经在CPU上运行了较长时间的进程,通常下降。挂起正在CPU上执行的进程,与中断时保存现场是不同的,中断前后是在同一个进程上下文中,只是由用户态转向内核态执行。
- 进程上下文包含了进程执行所需要的所有信息。
- 用户地址空间:包括程序代码,数据,用户堆栈等
- 控制信息:进程描述符,内核堆栈等
- 硬件上下文(注意中断也要保存硬件上下文只是保存的方法不同)
- schedule()函数选择一个新的进程来运行,并调用context_switch进行上下文的切换,这个宏调用switch_to来进行关键上下文切换
- next = pick_next_task(rq, prev); //进程调度算法都封装这个函数内部
- context_switch(rq, prev, next); //进程上下文切换
- switch_to利用了prev和next两个参数:prev指向当前进程,next指向被调度的进程
31#define switch_to(prev, next, last)
32do {
33 /*
34 * Context-switching clobbers all registers, so we clobber
35 * them explicitly, via unused output variables.
36 * (EAX and EBP is not listed because EBP is saved/restored
37 * explicitly for wchan access and EAX is the return value of
38 * __switch_to())
39 */
40 unsigned long ebx, ecx, edx, esi, edi;
41
42 asm volatile("pushfl
" /* save flags */
43 "pushl %%ebp
" /* save EBP */
44 "movl %%esp,%[prev_sp]
" /* save ESP */
45 "movl %[next_sp],%%esp
" /* restore ESP */
46 "movl $1f,%[prev_ip]
" /* save EIP */
47 "pushl %[next_ip]
" /* restore EIP */
48 __switch_canary
49 "jmp __switch_to
" /* regparm call */
50 "1: "
51 "popl %%ebp
" /* restore EBP */
52 "popfl
" /* restore flags */
53
54 /* output parameters */
55 : [prev_sp] "=m" (prev->thread.sp),
56 [prev_ip] "=m" (prev->thread.ip),
57 "=a" (last),
58
59 /* clobbered output registers: */
60 "=b" (ebx), "=c" (ecx), "=d" (edx),
61 "=S" (esi), "=D" (edi)
62
63 __switch_canary_oparam
64
65 /* input parameters: */
66 : [next_sp] "m" (next->thread.sp),
67 [next_ip] "m" (next->thread.ip),
68
69 /* regparm parameters for __switch_to(): */
70 [prev] "a" (prev),
71 [next] "d" (next)
72
73 __switch_canary_iparam
74
75 : /* reloaded segment registers */
76 "memory");
77} while (0)
Linux系统的一般执行过程
- Linux系统的一般执行过程分析:这时候我们可以有了一个条件来理解linux系统的一般运行状态,其中有一个用户态进程X需要切换到用户进程Y。从正在运行的用户态进程X切换到正在运行的用户态进程Y的过程
- 1.正在运行的用户态进程X
- 2.发生中断——save cs:eip/esp/eflags(current)to kernel stack,then load cs:eip(entry of a specific ISR)and ss:esp(point to kernel stack)
- 3.SAVE_ALL //保存现场
- 4.中断处理过程中或中断返回前调用了schedule(),其中的switch_to做了关键的进程上下文切换
- 5.标号1之后开始运行用户态进程Y(这里Y曾经通过以上步骤被切换出去过因此可以从标号1继续执行)
- 6.restore-all //恢复现场
- 7.iret -pop cs:eip/ss:eip/eflags from kernel stack
- 8.继续运行用户态进程Y
- 通过中断处理过程中的调度时机,用户态进程与内核线程之间互相切换和内核线程之间互相切换,与最一般的情况非常类似,只是内核线程运行过程中发生中断没有进程用户态和内核态的转换;
- 内核线程主动调用schdule(),只有进程上下文的切换,没有中断上下文的切换,与最一般的情况略简略;
- 创建子进程的系统调用在子进程中的执行起点及返回用户态,如fork;
- 加载一个新的可执行程序后返回到用户态的情况,如execve;
视频学习笔记
- 内核(进程管理,进程调度,进程间通讯机制,内存管理,中断异常处理,文件系统,I/O系统,网络部分)
- 其它程序(例如函数库,shell程序、系统程序等等)
操作系统的目的 - 与硬件交互,管理所有的硬件资源
- 为用户程序(应用程序)提供一个良好的执行环境
那么我们可以看一张图:最底层有磁盘管理,物理内存的管理,内存控制器还有控制台,这个地方它有一个Kernel Intelface to the hardware,也就是对硬件资源的管理,在上一层就是内核的实现,内核的实现也就是CPU调度,内存管理,按需调度,虚拟内存等等,还有其他的驱动,磁盘管理,文件系统等等,这些操作系统的内核,我们这门课分析的内核只是计算机操作系统里面最最关键的一些代码,实际上整个操作系统非常复杂,很多其他的部分都没有涉及。那么呢,这个地方有一个系统调用接口,这里涉及到不同的部分,基础软件也就是share共享库lib ,动态加载器这些等等。
- 最简单也是最复杂的操作---执行ls命令
实验
- 按照上一周的步骤,重新clone一个menu,并用test_exec.c覆盖test.c。
- 启动gdb进行调试,在schedule处设置断点
- 单步运行,直至__schedule()
- 在context_switch处设立断点,执行
总结:
进程调度程序是内核重要的组成部分,因为运行着的进程首先在使用计算机(至少在我们大多数人看来)。然而,满足进程调度的各种需要绝不是轻而易举的,很难找到“一刀切”的算棒,既适合众多的可运行进程,又具有可伸缩性,还能在调度周期和吞吐量之间求得平衡,同时还满足各种负载的需求。不过, Linux 内核的新CFS 调度程序尽量满足了各个方面的需求,并以较完善的可伸缩性和新颖的方挫提供了最佳的解决方案。前面的章节覆盖了进程管理的相关内容,本章则考察了进程调度所遵循的基本原理、具体实现、调度算能以及目前Linux 内核所使用的接口。