网易云课堂 Linux内核分析(八)

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

理解进程调度时机跟踪分析进程调度与进程切换的过程

进程调度的基本概念

操作系统中进程的调度是非常常见的一种状态,无论采用何种调度册策略,它都是从进程队列中选择了一个新进程而已。因此,掌握进程调度的时机与进程上下文的切换非常重要。

进程调度时机

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

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

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

进程切换

为了控制进程的执行,内核必须有能力挂起正在 CPU 上执行的进程,并恢复以前挂起的某个进程的执行的过程,叫做进程切换、任务切换、上下文切换。挂起正在 CPU 上执行的进程,与中断时保存现场是有区别的,中断前后是在同一个进程上下文中,只是由用户态转向内核态执行。

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

  • 用户地址空间:包括程序代码,数据,用户堆栈等
  • 控制信息:进程描述符,内核堆栈等
  • 硬件上下文(注意中断也要保存硬件上下文只是保存的方法不同)
  • schedule 函数选择一个新的进程来运行,并调用 context_switch 宏进行上下文的切换,这个宏又调用 switch_to 宏来进行关键上下文切换
  • switch_to 宏中定义了 prev 和 next 两个参数:prev 指向当前进程,next 指向被调度的进程

进程切换函数

schedule函数为调度函数,用以选择切换进程,其主要调用的函数有 pick_next_task,context_switch,在context_switch中调用switch_to。
重点分析下switch_to中的汇编代码

asm volatile("pushfl\n\t"  /*  保存当前进程的flags */ 
       "pushl %%ebp\n\t"  /* 把当前进程的ebp压入当前进程的栈  */ 
       "movl %%esp,%[prev_sp]\n\t" /*保存当前的esp到prev->thread.sp指向的内存中   */ 
       "movl %[next_sp],%%esp\n\t" /* 重置esp,把下个进程的next->thread.sp赋予esp   */ 
       "movl $1f,%[prev_ip]\n\t" /* 把1:的代码在内存中存储的地址保存到prev->thread.ip中  */ 
       "pushl %[next_ip]\n\t" /* 重置eip    */ 
       "jmp __switch_to\n" /* regparm call  */ 
       "1:\t"      
       "popl %%ebp\n\t"  /* 重置ebp    */ 
       "popfl\n"   /*重置flags*/ 

       /* output parameters */                       
       : [prev_sp] "=m" (prev->thread.sp),  
       /* =m 表示把变量放入内存,即把 [prev_sp] 存储的变量放入内存,最后再写入prev->thread.sp */
         [prev_ip] "=m" (prev->thread.ip),  
         "=a" (last),                                           
         /*=a 表示把变量 last 放入 ax, eax = last */         

         /* clobbered output registers: */  
         "=b" (ebx), "=c" (ecx), "=d" (edx),  
         /* b 表示放入ebx, c 表示放入 ecx,d 表示放入 edx, S表示放入 si, D 表示放入 edi */
         "=S" (esi), "=D" (edi)    

         /* input parameters: */    
       : [next_sp]  "m" (next->thread.sp),  
       /* next->thread.sp 放入内存中的 [next_sp] */
         [next_ip]  "m" (next->thread.ip),  

         /* regparm parameters for __switch_to (): */ 
         [prev]     "a" (prev),    
         /*eax = prev  edx = next*/
         [next]     "d" (next)    

       : /* reloaded segment registers */   
       "memory");

其实,在前四行汇编代码执行完,已经切换到了next进程的内核堆栈,但应该还算是在prev进程执行,到了标号1开始,才开始执行next进程。为什么next进程有popl ebp和popf1呢?因为next进程曾经是prev,曾经被push过,即前两句汇编代码,此处可以参考前边对mykernel的理解!

总结

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

特殊情况

  1. 通过中断处理过程中的调度,用户态进程与内核进程之间互相切换,与一般情形类似;
  2. 内核进程程主动调用 schedule 函数,只有进程上下文的切换,没有中断上下文切换;
  3. 创建子进程的系统调用在子进程中的执行起点及返回用户态,如:fork;
  4. 加载一个新的可执行程序后返回到用户态的情况,如:execve;

实验截图

参考实验报告

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值