首页 > 代码库 > 《linux 内核分析》 第二周 实验

《linux 内核分析》 第二周 实验

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

 

本次课的核心是通过中断机制完成进程的调度 ,在本次课程中__init my_start_kernel作为入口函数,定义0号进程的tPCB结构体,通过复制来制造其他进程的tPCB数据结构,中断时间函数被 my_timer_handler周期性的调用来修改my_need_sched 的值,而0号进程一直在检测my_need_sched 的状态,当状态改变的时候调用my_schedule()函数来进行进程的切换,在进程切换的过程中,采用的是汇编代码。下面分析一下代码:

1、mypcb.h

struct Thread

{

Unsigned long ip; //

Unsigned long sp; //在当前进程中,esp指向的地址

}

typedef struct PCB

{

int pid;

volatile long state; /-1 unrunnable ,0 runnable,>0 stopped*/

char stack[KERNEL_STACK_SIZE];

struct Thread thread;

unsigned long task_entry;

struct PCB*next;

}tPCB;

void my_schedule(void);// 调度器

在这个头文件中定义了一个 Thread结构和tPCB结构体,Thread结构体的作用是存储相应的进程的esp和eip的地址。它的作用是保存当前进程的esp和eip,当当前进程运行的时候将加载到相应的esp和eip中,当切换出去的时候,就会把esp和eip中的值存储到这个结构体中。tPCB的作用是存储相应进程的信息。

2、mymain.c

void __init my_start_kernel(void)  

{ int pid = 0;

    int i;

    /* Initialize process 0*/

    task[pid].pid = pid; //此处初始化进程0

    task[pid].state = 0;/* -1 unrunnable, 0 runnable, >0 stopped */

    task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process; //入口处理函数

    task[pid].thread.sp=(unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1]; //堆栈的顶

 

    task[pid].next = &task[pid]; //环形链表

    /*fork more process */

    for(i=1;i<MAX_TASK_NUM;i++)

    { 

        memcpy(&task[i],&task[0],sizeof(tPCB)); // 通过复制来制作进程

        task[i].pid = i;

        task[i].thread.sp = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-1];

        task[i].next = task[i-1].next; //加入进程列表

        task[i-1].next = &task[i];

    }

    /* start process 0 by task[0] */

    pid = 0;

    my_current_task = &task[pid];

asm volatile(

                "movl %1,%%esp\n\t"      /* set task[pid].thread.sp to esp ,载入进程的sp*/

                "pushl %1\n\t"            /* push ebp  */

                "pushl %0\n\t"            /* push task[pid].thread.ip ,通过这两个语句来载入ip,指向函数入口*/

                "ret\n\t"                    /* pop task[pid].thread.ip to eip */

                "popl %%ebp\n\t"

                :

                : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp)     /* input c or d mean %ecx/%edx*/

             ); //启动0号进程

void my_process(void)

{

    int i = 0;

    while(1)

    {

        i++;

        if(i%10000000 == 0)

        {

            printk(KERN_NOTICE "this is process %d -\n",my_current_task->pid);

            if(my_need_sched == 1)

            {

                my_need_sched = 0;

my_schedule();

        }

 printk(KERN_NOTICE "this is process %d +n",my_current_task->pid);

    }

上面的代码是初始化进程并且来启动0好进程的函数,在这些代码中下面的汇编部分是通过讲tPCB结构中的sp载入到esp,ebp和ip载入到eip中来启动线程,由于eip不能直接赋值,通过push和ret结构来加载eip。下面的my_process函数是所有进程的处理函数,来检验my_need_sched状态来调用进程切换函数。

3、进程切换

 

void my_schedule(void)

{

    tPCB * next;

    tPCB * prev;

 

    if(my_current_task == NULL

        || my_current_task->next == NULL)

    {

         return;

    }

    printk(KERN_NOTICE ">>>my_schedule<<<\n");

    /* schedule */

    next = my_current_task->next;

    prev = my_current_task;

    if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */

    {       

         my_current_task = next;

         printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid); 

         /* switch to next process */

         asm volatile(

            "pushl %%ebp\n\t"        /* save ebp */

            "movl %%esp,%0\n\t"      /* save esp */

            "movl %2,%%esp\n\t"     /* restore  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)

         );

 my_current_task = next;//switch to the next task 

 printk(KERN_NOTICE "   switch from %d process to %d process\n   >>>process %d running!!!<<<\n\n",prev->pid,next->pid,next->pid);

 

 

  }

    else

    {

        next->state = 0;

        my_current_task = next;

    printk(KERN_NOTICE "                switch from %d process to %d process\n                >>>process %d running!!!<<<\n\n\n",prev->pid,next->pid,next->pid);

 

 

     /* switch to new process */

     asm volatile(   

         "pushl %%ebp\n\t" /* save ebp */

         "movl %%esp,%0\n\t" /* save esp */

         "movl %2,%%esp\n\t" /* restore esp */

         "movl %2,%%ebp\n\t" /* restore ebp */

         "movl $1f,%1\n\t" /* save eip */

         "pushl %3\n\t"

         "ret\n\t" /* restore eip */

         : "=m" (prev->thread.sp),"=m" (prev->thread.ip)

         : "m" (next->thread.sp),"m" (next->thread.ip)

     );

    }

    return; 

}//end of my_schedule

 

在进程切换函数中,分两种情况通过要切换到的进程的状态来判断,根据进程是否已经运行过,如果运行过,直接保存上一个进程的ebp,esp和eip,加载下一个进程的eip和esp。如果没有运行过,还要将esp和ebp加载sp的地址。

 

4、计算机的三个法宝

存储程序计算机

函数调用堆栈

中断

5、操作系统两把宝剑

中断处理

进程切换

 下图是运行的结果:

技术分享

《linux 内核分析》 第二周 实验