8进程的切换和系统的一般执行过程

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

1,进程切换关键代码switch to分析

1,

wps4D4C.tmp

wps4D5D.tmp wps4D5E.tmp

因为有这些不同的进程,所以就需要不同的进程调度策略:

wps4D5F.tmp

wps4D60.tmp

以下为系统调用来配置系统调用的优先级:

wps4D70.tmp

wps4D81.tmp

wps4D82.tmp

wps4D83.tmp

wps4D84.tmp

schedule函数负责实现调度,他是个内核函数且无法直接调用,只能间接调用。

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

wps4D85.tmp

wps4D86.tmp

wps4D97.tmp

2,

wps4DA8.tmp

wps4DBF.tmp

首先看schedule函数,然后进入_schedule:

wps4DC8.tmp

wps4DC9.tmp

wps4DCA.tmp

pick next task就使用了某种进程调度策略;

之后完成进程上下文切换,通过context switch:

wps4DCB.tmp

wps4DCC.tmp

wps4DCD.tmp

swtich to切换堆栈和寄存器状态

wps4DD9.tmp

wps4DDF.tmp

outter中因为在内核态,所以thread.sp为内核堆栈,thread.ip为当前进程eip;

input为下一个内核进程的堆栈和起点

整体为压栈当前进程堆栈,把esp保存在prev sp(这里用的字符串标记参数),之后把next sp放入esp,这两步完成了内核堆栈切换。

wps4DE0.tmp

把1f放入prev ip,保存当前进程eip从这里恢复,然后push next ip,把next进程起点压入next堆栈,即栈底此时就是起点。

wps4DF5.tmp

这里用的jmp  __switch to,通过寄存器a prev,d next传递参数。当函数结束return时,因为这里没有call来调用函数,所以会pop出$1f位置,从下图开始就认为是next开始执行:

从eip角度来看,从jmp就开始执行,从堆栈角度看save esp,restore esp之后便开始执行了,所以这里比较模糊:

为什么next进程由pop ebp呢?

wps4DF6.tmp

因为next层级是prev进程

wps4DF7.tmp

剩余代码:

1. 31#define switch_to(prev, next, last)                    \

2. 32do {                                 \

3. 33  /*                              \

4. 34   * Context-switching clobbers all registers, so we clobber  \

5. 35   * them explicitly, via unused output variables.     \

6. 36   * (EAX and EBP is not listed because EBP is saved/restored  \

7. 37   * explicitly for wchan access and EAX is the return value of   \

8. 38   * __switch_to())                     \

9. 39   */                                \

10. 40  unsigned long ebx, ecx, edx, esi, edi;                \

11. 41                                  \

12. 42  asm volatile("pushfl\n\t"      /* save    flags */   \

13. 43           "pushl %%ebp\n\t"        /* save    EBP   */ \

14. 44           "movl %%esp,%[prev_sp]\n\t"  /* save    ESP   */ \

15. 45           "movl %[next_sp],%%esp\n\t"  /* restore ESP   */ \

16. 46           "movl $1f,%[prev_ip]\n\t"    /* save    EIP   */ \

17. 47           "pushl %[next_ip]\n\t"   /* restore EIP   */    \

18. 48           __switch_canary                   \

19. 49           "jmp __switch_to\n"  /* regparm call  */ \

20. 50           "1:\t"                        \

21. 51           "popl %%ebp\n\t"     /* restore EBP   */    \

22. 52           "popfl\n"         /* restore flags */  \

23. 53                                  \

24. 54           /* output parameters */                \

25. 55           : [prev_sp] "=m" (prev->thread.sp),     \

26. 56             [prev_ip] "=m" (prev->thread.ip),        \

27. 57             "=a" (last),                 \

28. 58                                  \

29. 59             /* clobbered output registers: */     \

30. 60             "=b" (ebx), "=c" (ecx), "=d" (edx),      \

31. 61             "=S" (esi), "=D" (edi)             \

32. 62                                       \

33. 63             __switch_canary_oparam                \

34. 64                                  \

35. 65             /* input parameters: */                \

36. 66           : [next_sp]  "m" (next->thread.sp),        \

37. 67             [next_ip]  "m" (next->thread.ip),       \

38. 68                                       \

39. 69             /* regparm parameters for __switch_to(): */  \

40. 70             [prev]     "a" (prev),              \

41. 71             [next]     "d" (next)               \

42. 72                                  \

43. 73             __switch_canary_iparam                \

44. 74                                  \

45. 75           : /* reloaded segment registers */           \

46. 76          "memory");                  \

47. 77} while (0)

2,linux系统的一般执行过程

wps4E13.tmp

2,先压入用户进程X的内核堆栈,把当前进程内核堆栈相关信息ss esp 中断对应的服务历程起点加载到eip。

5,从schedule选出的进程Y(next必须曾经做过prev)

6,从Y 恢复现场

7,pop出Y发生中断时保存的eip esp flags

wps4E14.tmp

1,逻辑相同,只是内核线程无需切换状态

2,也不需要iret返回

3,fork时候,如果next是新创建的子进程,那么返回的执行起点是ret from fork,这里做switch to就比较复杂一点了,就不是从标号1的地方开始执行了

wps4E27.tmp

4,在execve内部修改了进程上下文

wps4E35.tmp

每个进程地址空间4G,3G以上只有内核态可以访问。进程发生切换它的内核态,如果每个进程都有自己空间如何切换呢?其实内核进程都是共享的,只是其他的进程描述符和上下文切换。只有返回到用户态才会有不同。

wps4E36.tmp

那个进程招手都可以陷入内核态,走一程之后返回到用户态。

3,linux操作系统架构概览

wps4E37.tmp

wps4E38.tmpwps4E49.tmp

敲击键盘Io中断,把当前进程中断,在控制台输出也是中断;

COW写时复制技术;

wps4E4A.tmp

0到3G的部分,进程地址空间;

如果main函数有个gets(),gets是个系统调用就会陷入内核,从用户态堆栈进入内核。压栈等等。

然后进入进程管理,等待键盘输入过程,cpu会调度到其他进程执行,由进程管理调度。在执行其他进程过程中也在等待着键盘的输入,输入键盘就发生IO中断,调度回来。

敲击之后发生IO中断给CPU,cpu就执行中断处理程序,过程中知道键盘的输入,且是X进程在等待输入,从阻塞态变为就绪态。

最后从系统调用返回。

wps4E4B.tmp

4,实验:

远程调试并设置断点

wps4E4C.tmp

wps4E4D.tmp

调试运行

wps4E4E.tmpwps4E4F.tmpwps4E50.tmp

5,总结

进程调度程序是内核重要的组成部分,因为运行着的进程首先在使用计算机(至少在我们大多数人看来)。然而,满足进程调度的各种需要绝不是轻而易举的,很难找到“一刀切”的算棒,既适合众多的可运行进程,又具有可伸缩性,还能在调度周期和吞吐量之间求得平衡,同时还满足各种负载的需求。不过, Linux 内核的新CFS 调度程序尽量满足了各个方面的需求,并以较完善的可伸缩性和新颖的方挫提供了最佳的解决方案。前面的章节覆盖了进程管理的相关内容,本章则考察了进程调度所遵循的基本原理、具体实现、调度算能以及目前Linux 内核所使用的接口。

posted on 2017-04-16 13:06  wk2016just  阅读(293)  评论(0编辑  收藏  举报