A-A+

Linux操作系统内核的进程调度简析

2016年01月04日 站长资讯 暂无评论

学习的过程其实就是不断的模仿,重复老师演示的内容,不断地练习,直到成为自己所能独立表述的知识。自己实在很笨了,作业勉强完成,好在也算努力,花时间多些,毕竟是自己的辛苦学习的过程体现。所以摆出来给方家一笑,好歹也是自己学习的收获。

一、 实验用的是实验楼环境,虚拟机环境如下:Linux d0c756f6c18a 3.13.0-30-generic #55-Ubuntu SMP Fri Jul 4 21:40:53 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux。实验开始使用简单代码,可以看见中断调度演示。

cd LinuxKernel/linux-3.9.4

qemu -kernel arch/x86/boot/bzImage

二、将老师的代码mypch.b,mymain.c,myinterrupt.c复制到mykernel目录中。回到kernel目录下:

make all

qemu -kernel arch/x86/boot/bzImage

就可以看到进程调度的过程在虚拟机中体现出来。以下截图:

三、下面来分析一下代码的执行过程,描述一下现代操作系统的工作机制。

1.在linux核心中为了实现高效执行,大量使用了内联汇编,所以在此先介绍一下内联汇编的相关知识。(1)虽然现代编译器优化代码,但仍比不过手写的汇编代码;(2)有些平台相关的指令必须手写,在C语言中没有等价的语法,例如x86是端口I/O。

gcc提供了一种扩展语法可以在C代码中使用内联汇编。最简单的格式是__asm__("assembly code");,例如__asm__("nop");就只是执行一条空指令。执行多条汇编指令,则应该用\n\t将各条指令分隔开。

内联汇编要和C的变量建立关联,使用完整的内联汇编格式:

  1. __asm__(assembler template   
  2.  : output operands                  /* optional */  
  3.  : input operands                   /* optional */  
  4.  : list of clobbered registers      /* optional */  
  5.  );  

这种格式由四部分组成,第一部分是汇编指令,第二部分和第三部分是约束条件,第二部分指示汇编指令的运算结果 要输出到哪些C操作数中,C操作数应该是左值表达式,第三部分指示汇编指令需要从哪些C操作数获得输入,第四部分是在汇编指令中被修改过的寄存器列表,指示编译器哪些寄存器的值在执行这条__asm__语句时会改变。后三个部分都是可选的,如果有就填写,没有就空着只写个:号。

2.mypcb.h代码如下:

  1. /*  
  2. * linux/mykernel/mypcb.h  
  3. *  
  4. * Kernel internal PCB types  
  5. *  
  6. * Copyright (C) 2013 Mengning  
  7. *  
  8. */  
  9. #define MAX_TASK_NUM 4               //定义系统执行的最大进程数。  
  10. #define KERNEL_STACK_SIZE 1024*8     //内核堆栈大小  
  11. /* CPU-specific state of this task */  
  12. struct Thread {                     //定义结构体Thread  
  13.     unsigned long    ip;                //存储指令指针和堆栈指针  
  14.     unsigned long    sp;  
  15. };  
  16. typedef struct PCB{               //结构体类型进程控制块PCB  
  17.     int pid;                          //进程id   
  18.     volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */ //进程状态  
  19.     char stack[KERNEL_STACK_SIZE];        //进程堆栈  
  20.     /* CPU-specific state of this task */  
  21.     struct Thread thread;  
  22.     unsigned long    task_entry;    //入口  
  23.     struct PCB *next;               //形成链表,下一个进程  
  24. }tPCB;  
  25. void my_schedule(void);     //调度函数  

3.以下mymain.c主程序代码

  1. /*  
  2. * linux/mykernel/mymain.c  
  3. *  
  4. * Kernel internal my_start_kernel  
  5. *  
  6. * Copyright (C) 2013 Mengning  
  7. *  
  8. */  
  9. #include <linux/types.h>  
  10. #include <linux/string.h>  
  11. #include <linux/ctype.h>  
  12. #include <linux/tty.h>  
  13. #include <linux/vmalloc.h>  
  14.    
  15. #include "mypcb.h"  
  16.    
  17. tPCB task[MAX_TASK_NUM];      //定义进程数组  
  18. tPCB * my_current_task = NULL;  //当前进程指针,从0号进程开始  
  19. volatile int my_need_sched = 0; //0号进程不需要调度  
  20. void my_process(void);  
  21.    
  22. void __init my_start_kernel(void)    //内核创建进程,从0号进程开始初始化  
  23. {  
  24.     int pid = 0;  
  25.     int i;  
  26.     /* Initialize process 0*/  
  27.     task[pid].pid = pid;          
  28.     task[pid].state = 0;/* -1 unrunnable, 0 runnable, >0 stopped */  
  29.   
  30.   
  31.     //指令指针指向自己  
  32.     task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process;   
  33.     //堆栈指向定义的内核Stack  
  34.     task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1];  
  35.     task[pid].next = &task[pid];  
  36.     /*fork more process */  
  37.     for(i=1;i<MAX_TASK_NUM;i++)    //通过fork函数启动更多的进程,本例0,1,2,3  
  38.     {  
  39.         //我们是简单演示,此处直接复制0号进程的状况作为新的进程  
  40.         memcpy(&task[i],&task[0],sizeof(tPCB));  
  41.         task[i].pid = i;  
  42.         task[i].state = -1;  
  43.         task[i].thread.sp = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-1];   
  44.         task[i].next = task[i-1].next;    //进程之间形成链表  
  45.         task[i-1].next = &task[i];  
  46.     }  
  47.     /* start process 0 by task[0] */ //启动0号进程  
  48.     pid = 0;  
  49.     my_current_task = &task[pid];  
  50.     /*  
  51.         内联汇编,%0,%1代表输入输出部分的变量"c"代表ECX,"d"代表EDX,"=m"表示内存  
  52.         %%reg表示寄存器。\n\t表示结束。  
  53.         以下汇编代码不难理解,就是为了效率。构建起CPU的运行环境,启动了0号进程。  
  54.     */  
  55.     asm volatile(  
  56.         "movl %1,%%esp\n\t" /* set task[pid].thread.sp to esp */  
  57.         "pushl %1\n\t" /* push ebp */   
  58.         "pushl %0\n\t" /* push task[pid].thread.ip */  
  59.         "ret\n\t" /* pop task[pid].thread.ip to eip */  
  60.         "popl %%ebp\n\t"  
  61.         :  
  62.         : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) /* input c or d mean %ecx/%edx*/  
  63.     );  
  64.     }  
  65.     /*以下是我们的简单进程所执行的代码,用来让人类知道CPU执行了哪个进程。实际上很多操作系统进程  
  66.     只是在后台执行,并不需要进行人机交互,但我们不要忽略了它们。  
  67.     */  
  68.     void my_process(void)  
  69.     {  
  70.         int i = 0;  
  71.         while(1)  
  72.         {   
  73.             i++;  
  74.         if(i%10000000 == 0)    //循环一千万次,输出一次进程id,主动调度,避免消息机制。  
  75.         {  
  76.             printk(KERN_NOTICE "this is process %d -\n",my_current_task->pid);  
  77.             if(my_need_sched == 1)  
  78.             {  
  79.                 my_need_sched = 0;  
  80.                 my_schedule();  
  81.             }  
  82.             printk(KERN_NOTICE "this is process %d +\n",my_current_task->pid);   
  83.         }  
  84.     }  
  85. }  

4.以下是myinterrupt.c的代码及简单说明:

  1. /*  
  2. * linux/mykernel/myinterrupt.c  
  3. *  
  4. * Kernel internal my_timer_handler  
  5. *  
  6. * Copyright (C) 2013 Mengning  
  7. *  
  8. */  
  9. #include <linux/types.h>  
  10. #include <linux/string.h>  
  11. #include <linux/ctype.h>  
  12. #include <linux/tty.h>  
  13. #include <linux/vmalloc.h>  
  14.    
  15. #include "mypcb.h"  
  16. extern tPCB task[MAX_TASK_NUM];  
  17. extern tPCB * my_current_task;  
  18. extern volatile int my_need_sched;  
  19. volatile int time_count = 0;    //时间计数已实现主动执行,我们的简单代码不接受输入  
  20. /*  
  21. * Called by timer interrupt.  
  22. * it runs in the name of current running process,  
  23. * so it use kernel stack of current running process  
  24. */  
  25. void my_timer_handler(void)  
  26. {  
  27.     #if 1  
  28.     //计数1000次并且没有切换进程就输出一行提醒  
  29.     if(time_count%1000 == 0 && my_need_sched != 1)  
  30.   
  31.   
  32.     {  
  33.     printk(KERN_NOTICE ">>>my_timer_handler here<<<\n");  
  34.     my_need_sched = 1;  
  35.     }  
  36.     time_count ++ ;  
  37.     #endif  
  38.     return;  
  39. }  
  40. void my_schedule(void)  
  41. {  
  42.     tPCB * next;  
  43.     tPCB * prev;  
  44.     if(my_current_task == NULL  
  45.     || my_current_task->next == NULL)  
  46.     {  
  47.         return;                //出错处理  
  48.     }  
  49.     printk(KERN_NOTICE ">>>my_schedule<<<\n");  
  50.     /* schedule */  
  51.     next = my_current_task->next;  
  52.     prev = my_current_task;  
  53.     if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */   
  54.     {  
  55.     /* switch to next process */  
  56.     //进程切换的关键代码,主要工作和分析函数调用时基本相同,保存当前上下文  
  57.     asm volatile(  
  58.     "pushl %%ebp\n\t" /* save ebp */  
  59.     "movl %%esp,%0\n\t" /* save esp */  
  60.     "movl %2,%%esp\n\t" /* restore esp */  
  61.     "movl $1f,%1\n\t" /* save eip */      
  62.     "pushl %3\n\t"  
  63.     "ret\n\t" /* restore eip */  
  64.     "1:\t" /* next process start here */  
  65.     "popl %%ebp\n\t"  
  66.     : "=m" (prev->thread.sp),"=m" (prev->thread.ip)  
  67.     : "m" (next->thread.sp),"m" (next->thread.ip)  
  68.     );  
  69.     my_current_task = next;   
  70.     printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);  
  71.     }  
  72.     else  
  73.     {  
  74.     next->state = 0;  
  75.     my_current_task = next;  
  76.     printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);  
  77.     /* switch to new process */  
  78.     //建立新的运行环境,开始从新的代码行开始执行新的进程。  
  79.     asm volatile(  
  80.     "pushl %%ebp\n\t" /* save ebp */  
  81.     "movl %%esp,%0\n\t" /* save esp */  
  82.     "movl %2,%%esp\n\t" /* restore esp */  
  83.     "movl %2,%%ebp\n\t" /* restore ebp */  
  84.     "movl $1f,%1\n\t" /* save eip */      
  85.     "pushl %3\n\t"   
  86.     "ret\n\t" /* restore eip */  
  87.     : "=m" (prev->thread.sp),"=m" (prev->thread.ip)  
  88.     : "m" (next->thread.sp),"m" (next->thread.ip)  
  89.     );  
  90.     }  
  91.     return;  
  92. }  

四、实验总结,老师简化的代码还不难理解,但要自己编写还没有这个本事,所以直接抄下来自己理解一下,执行的过程没有出现报错。虽然是简化代码,但对于理解操作系统的工作机制还是很有帮助的。首先是内核的自举,毕竟所有的程序都不过是内存中的代码,内核不过是认为指定了特权,0号进程,开始运行,自己建立自己所需要的环境。其次,操作系统毕竟是为实际的程序服务的,接下来就要负责创建其他进程执行环境、资源分配,采用链表机制切换到新进程,并且执行。最后,内核要负责管理进程的状态,利用中断机制实现进程切换,控制程序的执行。总之,操作系统所作的就是中断上下文的处理和进程切换上下文的处理。

标签:

给我留言