进程调度与进程切换的过程分析

一、实验过程

1.打开终端,与前几次实验相同,输入命令cd Linux/menu进入到menu目录下,然后输入命令qemu -kernel ../linux-3.18.6/arch/x86/boot/bzImage -initrd ../rootfs.img -s -S启动MENUOS ,然后打开gdb,输入命令file ../linux-3.18.6/vmlinux加载符号表,然后如何命令target remote :1234连接MENUOS,并在schedulecontext_switchswitch_to三个函数处设置断点。实验结果如图1所示。
进程调度与进程切换的过程分析_第1张图片
图 1

2. 然后利用gdb的命令c、n、s、l等进行调试,我们可以看到,首先执行了函数schedult(),实验结果如下图所示。

进程调度与进程切换的过程分析_第2张图片
图 2


输入命令c进行继续调试,我们可以看到进程在context_switch()函数处停止,实验结果如图3所示。

进程调度与进程切换的过程分析_第3张图片
图 3
继续进行调试,我们从图4可以看到进程在schedule()处暂停,实验结果如图4所示。

进程调度与进程切换的过程分析_第4张图片
图 4

继续调试。

进程调度与进程切换的过程分析_第5张图片
图 5
利用命令s进行单步调试。

进程调度与进程切换的过程分析_第6张图片
图 6

二、进程的调度时机与进程的切换分析

1. 进程调度的时机有如下可能

1. 中断处理过程(包括时钟中断、I/O中断、系统调用和异常)中,直接调用schedule(),或者返回用户态时根据need_resched标记调用schedule();

2. 内核线程可以直接调用schedule()进行切换,也可以在中断处理过程中调用,也就是说内核线程作为一类特殊的线程可以主动调度,也可以被动调度。

3. 用户态进程无法实现主动调度,仅能通过陷入内核态后的某个时机点进行调度,即在中断处理过程中进行调度。

2. 进程的切换

1. 为了控制进程的执行,内核必须有能力挂起正在CPU上执行的进程,并恢复以前挂起的某个进程的执行,这叫做进程切换、任务切换、上下文切换。


2.  挂起正在CPU上执行的进程,与中断时保存现场是不同的,中断前后是在同一个进程上下文中,只是由用户态转向内核态执行。


3. 进程上下文包含了进程执行需要的所有信息

  • 用户地址空间: 包括程序代码,数据,用户堆栈等

  • 控制信息 :进程描述符,内核堆栈等

  • 硬件上下文


4. schedule()函数选择一个新的进程来运行,并调用context_switch进行上下文的切换,这个宏调用switch_to来进行关键上下文切换

  • next=pick_next_task(rq,prev)  //进程调度算法都封装这个函数内部

  • context_switch(rq,prev,next) //进程上下文

  • switch_to利用了prev和next两个参数:prev指向当前进程,next指向被调度的进程。


3. switch_to()函数中的汇编代码如下

#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\n\t"		/* save    flags */	\
43		     "pushl %%ebp\n\t"		/* save    EBP   */	\
44		     "movl %%esp,%[prev_sp]\n\t"	/* save    ESP   */ \
45		     "movl %[next_sp],%%esp\n\t"	/* restore ESP   */ \
46		     "movl $1f,%[prev_ip]\n\t"	/* save    EIP   */	\
47		     "pushl %[next_ip]\n\t"	/* restore EIP   */	\
48		     __switch_canary					\
49		     "jmp __switch_to\n"	/* regparm call  */	\
50		     "1:\t"						\
51		     "popl %%ebp\n\t"		/* restore EBP   */	\
52		     "popfl\n"			/* 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)


5. Linux系统的一般执行过程


最一般的情况:正在运行的用户态进程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:esp/eflags from kernel stack。


8. 继续运行用户态进程Y。


6. 几种特殊情况


1.  通过中断处理过程中的调度时机,用户态进程与内核线程之间互相切换和内核线程之间互相切换,与最一般的情况非常类似,只是内核线程运行过程中发生中断没有进程用户态和内核态的转换。


2.  内核线程主动调用schedule(),只有进程上下文的切换,没有发生中断上下文的切换,与最一般的情况略简略 。


3.  创建子进程的系统调用在子进程中的执行起点及返回用户态,如fork。


4.  加载一个新的可执行程序后返回到用户态的情况,如execve。


7.典型的Linux操作系统的结构如图7所示。

进程调度与进程切换的过程分析_第7张图片

图 7

8. 一个shell命令执行的过程如图8所示。

进程调度与进程切换的过程分析_第8张图片

图 8


9. CPU执行指令的过程如图9所示。

进程调度与进程切换的过程分析_第9张图片

图 9

10. 站在内存的角度来看如图10所示。


进程调度与进程切换的过程分析_第10张图片

图 10


三、总结

1.  从schedule()开始,几种不同类型的进程之间的调度选择;在相同类型的进程之间的调度选择算法。

2.  从CPU的IP值的变化上,说明在switch_to宏执行后,执行分析。

3. 堆栈发生切换位置,在切换堆栈前后,current_thread_info变化。

4. 地址空间发生切换,解释地址空间的切换不会影响后续切换代码的执行。

5. current宏所代表的进程发生变化的源码位置。

6. 任务状态段中关于内核堆栈的信息发生变化源码位置。

你可能感兴趣的:(进程调度与进程切换的过程分析)