前言:本文为我学习孟宁老师的《庖丁解牛linux 内核》课程的简单总结,同时作为课间作业。 

       唐建,《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

 1、概述

      本文通过一个简单的自定义内核程序来简单描述内核中程序的相互切换过程,给大家展示内存多进程随着时间片轮转而来回切换

      的过程以及原理。

2、代码结构

      我们的例子代码结构:主要有两个文件mypcb.c myinterrupt.c

 mypcb.c文件内容:      

myinteruppt.c文件内容:

  my_start_kernel() ,系统的起点,完成系统环境的准备

 my_process()这问进程的主体,我们将起多个进程实例。

  my_schedule() 系统进程调度器,我们将在这个函数看到进程是怎么切换的。

  my_time_handler()  时间中断函数,这个函数里面产生时间片,时间片到了后就需要进行进程切换了。

3、my_start_kernel() 系统环境的准备。

    我们先看下重要的结构体 ,进程的结构体,相当于系统的task_struct

 

struct Thread {
unsigned long ip;
unsigned long sp;
};

typedef struct PCB{
int pid;                                  ——pid
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */  ——当前进程调状态
unsigned long stack[KERNEL_STACK_SIZE];                            ——进程的堆栈
/* CPU-specific state of this task */                                              
struct Thread thread;                                                                 ——线程,不用理会线程。主要看其内容ip=eip,sp=esp。就是进程当前执行到哪里了。
unsigned long task_entry;                                       ——进程的入口
struct PCB *next;               ——下一个进程
}tPCB;

   我们的linux 系统进程调度大体上就是操作task_struct,所以我们这模拟的系统就是围绕这tpcb进行操作

  回到正题,my_start_kernel如下图这个函数分三部分

     (1)、0号进程的初始化

                 进程的入口为my_process,所以我们一会主要看怎么跳转到进程的入口的。

                  这里task_entry 赋予了进程入口my_process()。同时sp=esp = 进程的栈底,因为是刚开始嘛,栈还是空的。

       (2)、fork多个进程,这里模拟了fork的过程,

             (3)、我们这着重讲这个,就是my_start_kernel怎么切换到0号进程的。 

  asm volatile(
      "movl %1,%%esp\n\t" /* set task[pid].thread.sp to esp */ ——将esp 赋值为0号进程的sp,好了,这样栈就切换到0号进程的栈了
      "pushl %1\n\t" /* push ebp */                                         ——将将因为0号进程还没开始跑呢,所以ebp=esp啊,所以这里实际上是将ebp压栈
      "pushl %0\n\t" /* push task[pid].thread.ip */                  ——将ip=eip 将0号进程的eip入栈。
      "ret\n\t" /* pop task[pid].thread.ip to eip */                    ——ret= pop eip。准备切换到0号进程了,将0号进程的ip放入eip寄存器中。
      "popl %%ebp\n\t"                                                         ——sp=ebp,准备栈底。到这里,ebp、eip、esp都准备好了,注意eip=my_process,

                                                                                                                    也就是已经切换到0号进程了。
      :
      : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) /* input c or d mean %ecx/%edx*/
  );

  4、时间片

  

       如上图,实际上就是没1000个时钟就赋值my_need_sched=1,这个是调度标志位,表示要开始调度到下一个进程了。

   5、my_process

        前面我们讲到0号进程已经开始跑起来了,也就是跑到了my_process这里。

    

   如上图,可以看到这里是死循环,当发现调度标志被设置为1时,表示要开始调度了,这个进程不能再执行了,轮到别人了,于是调用my_schedule去切换到其他进程。

6、my_schedule,进程切换。

     现在来到我们的核心了,实际上就保存上一个进程的现场,然后将需要调度的进程的现场恢复到寄存器中,让寄存器按照这个进程的现场运行。

   (1)、这个就是取出马上需要进程的结构体

    (2)、这个就是难点了 

           

  /* switch to next process */
  asm volatile(
    "pushl %%ebp\n\t" /* save ebp */       ——将本进程的ebp入栈
    "movl %%esp,%0\n\t" /* save esp */   ——将esp保存到本进程中,用于下次执行
    "movl %2,%%esp\n\t" /* restore esp */  ——将新进程的sp写入esp

    "movl $1f,%1\n\t" /* save eip */          
    "pushl %3\n\t"
    "ret\n\t" /* restore eip */
    "1:\t" /* next process start here */
    "popl %%ebp\n\t"
    : "=m" (prev->thread.sp),"=m" (prev->thread.ip)
    : "m" (next->thread.sp),"m" (next->thread.ip)
  );

     这个实际上就是保存本进程的信息,然后将下一个进程的东西装入寄存器,并开始执行。

 7、执行结果

     可以推测,我们的进程是按照序号顺序执行的