王康 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
1,进程切换关键代码switch to分析
1,
因为有这些不同的进程,所以就需要不同的进程调度策略:
以下为系统调用来配置系统调用的优先级:
schedule函数负责实现调度,他是个内核函数且无法直接调用,只能间接调用。
时机就是中断处理过程(包括时钟中断、I/O中断、系统调用和异常)中,直接调用schedule(),或者返回用户态时根据need_resched标记调用schedule();
2,
首先看schedule函数,然后进入_schedule:
pick next task就使用了某种进程调度策略;
之后完成进程上下文切换,通过context switch:
swtich to切换堆栈和寄存器状态
outter中因为在内核态,所以thread.sp为内核堆栈,thread.ip为当前进程eip;
input为下一个内核进程的堆栈和起点
整体为压栈当前进程堆栈,把esp保存在prev sp(这里用的字符串标记参数),之后把next sp放入esp,这两步完成了内核堆栈切换。
把1f放入prev ip,保存当前进程eip从这里恢复,然后push next ip,把next进程起点压入next堆栈,即栈底此时就是起点。
这里用的jmp __switch to,通过寄存器a prev,d next传递参数。当函数结束return时,因为这里没有call来调用函数,所以会pop出$1f位置,从下图开始就认为是next开始执行:
从eip角度来看,从jmp就开始执行,从堆栈角度看save esp,restore esp之后便开始执行了,所以这里比较模糊:
为什么next进程由pop ebp呢?
因为next层级是prev进程
剩余代码:
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系统的一般执行过程
2,先压入用户进程X的内核堆栈,把当前进程内核堆栈相关信息ss esp 中断对应的服务历程起点加载到eip。
5,从schedule选出的进程Y(next必须曾经做过prev)
6,从Y 恢复现场
7,pop出Y发生中断时保存的eip esp flags
1,逻辑相同,只是内核线程无需切换状态
2,也不需要iret返回
3,fork时候,如果next是新创建的子进程,那么返回的执行起点是ret from fork,这里做switch to就比较复杂一点了,就不是从标号1的地方开始执行了
4,在execve内部修改了进程上下文
每个进程地址空间4G,3G以上只有内核态可以访问。进程发生切换它的内核态,如果每个进程都有自己空间如何切换呢?其实内核进程都是共享的,只是其他的进程描述符和上下文切换。只有返回到用户态才会有不同。
那个进程招手都可以陷入内核态,走一程之后返回到用户态。
3,linux操作系统架构概览
敲击键盘Io中断,把当前进程中断,在控制台输出也是中断;
COW写时复制技术;
0到3G的部分,进程地址空间;
如果main函数有个gets(),gets是个系统调用就会陷入内核,从用户态堆栈进入内核。压栈等等。
然后进入进程管理,等待键盘输入过程,cpu会调度到其他进程执行,由进程管理调度。在执行其他进程过程中也在等待着键盘的输入,输入键盘就发生IO中断,调度回来。
敲击之后发生IO中断给CPU,cpu就执行中断处理程序,过程中知道键盘的输入,且是X进程在等待输入,从阻塞态变为就绪态。
最后从系统调用返回。
4,实验:
远程调试并设置断点
调试运行
5,总结
进程调度程序是内核重要的组成部分,因为运行着的进程首先在使用计算机(至少在我们大多数人看来)。然而,满足进程调度的各种需要绝不是轻而易举的,很难找到“一刀切”的算棒,既适合众多的可运行进程,又具有可伸缩性,还能在调度周期和吞吐量之间求得平衡,同时还满足各种负载的需求。不过, Linux 内核的新CFS 调度程序尽量满足了各个方面的需求,并以较完善的可伸缩性和新颖的方挫提供了最佳的解决方案。前面的章节覆盖了进程管理的相关内容,本章则考察了进程调度所遵循的基本原理、具体实现、调度算能以及目前Linux 内核所使用的接口。