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

来源:互联网 发布:程序员必备软件 编辑:程序博客网 时间:2024/04/30 05:06

寇亚飞 + 原创作品转载请注明出处 + 《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