中科大软院linux内核分析--进程调度实验

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

下面分析一个非常简单的linux内核调度,首先说明一下,程序的核心是mymain.c文件,链接:https://github.com/mengning/mykernel/blob/master/mymain.c

mypcb.h链接:https://github.com/mengning/mykernel/blob/master/mypcb.h

myinterrupt.c链接:https://github.com/mengning/mykernel/blob/master/myinterrupt.c

实验过程是到实验楼上面老师提供的环境上面切换到linux3.9.4内核下面的mykernel文件夹下面,然后将里面的mymain.c和myinterrupt.c文件中的内容替换然后新建mypcb.h并将上面链接文件中的内容复制到新建的文件中,切换到linux3.9.4目录下执行make指令,重新编译代码,然后执行

qemu -kernel arch/x86/boot/bzImage命令就可以观察实验结果了,为了便于观察,可以将mymain.c中64行处 if(i%10000000 == 0)的模数后面加上一个0,这样的话输出就没有那么杂乱了,写博客前将截图用qq截了,但是没有及时 保存,所以博客上面就不放图了,各位手下留情。

 

下面分析进程切换的过程:

其实在这个试验中,所谓的进程切换实际上就是同一个函数在执行的过程中通过切换使用的数据结构来实现进程调度的模拟(这个说法有点强调函数的作用,实际上进程控制块的数据结构才是关键,函数只是他的一个成员变量)。而这里切换的结构就是进程控制块(在操作系统课上应该接触过)。在这里这个进程控制块的定义在mypcb.h中,这个进程控制块相对于现代操作系统的超级大的进程控制块来说简直是小的不能再小了。

本实验中进程的定义如下:

 

typedef struct PCB{
 int pid;
 volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
 char stack[KERNEL_STACK_SIZE]; //内核堆栈
 /* CPU-specific state of this task */
 struct Thread thread; //存储进程的 cs:ip
 unsigned long task_entry; //进程的入口函数
 struct PCB *next; //下一个进程
 }tPCB;

 

相关字段的含义已经在上面的代码中进行了注释。

 

 

本实验程序执行的概述为:首先进行内核初始化函数,此函数会进行进程的初始化,进程的初始化就是创建n个进程,然后将进程的各个字段进行赋值,包括进程的入口函数,这样进程进行切换的时候就是在进程的入口楼函数之间进行切换了。 

 

下面先看一下进程初始化函数(在mymain.c中):

首先将当前执行的进程标记为0号进程,然后将0号进程的pcb块中的各个字段进行赋值,这里不再赘述。

下面是创建MAX_TASK_NUM-1个进程,这里创建这些进程主要是为了演示进程调度,进程并没有什么特定的用途,这里创建进程的时候偷懒,首先将0号进程的pcb块复制过来然后将里面要改变的字段改一下。

接着将始终指向当前执行的进程的pcb块指向0号进程,然后下面是一段汇编代码,这段汇编代码很关键,如下:

 

asm volatile(
 "movl %1,%%esp\n\t" /* set task[pid].thread.sp to esp */
 "pushl %1\n\t" /* push ebp */
 "pushl %0\n\t" /* push task[pid].thread.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*/
 );

 

这段代码的作用是将cs:ip设置为当前要执行的pcb块中的cs:ip,由于此处将当前要执行的进程设置为0(mymain.c的46行),所以这段代码没用,如果要是将当前进程设置为其他进程,那么这段代码之后就将cpu的控制权指向了对应的进程。

 

由于我们将所有的进程的入口函数都指向void my_process(void)这个函数,所以在初始化完成之后就是这个程序的天下了。

函数如下:

 

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);
 }
 }
 }

执行这个函数之后,程序进入死循环,每隔固定的时间(可以更改,此处的设置有点小,试验的时候输出太多)进行一次打印,,然后判断是否需要进程调度,如果需要的话就执行调度函数。然后将需要调度的参数设置为不需要调度,这个后面在调度函数中进行更改。

下面分析my_schedule();

这个函数就是对进程进行调度的函数了,首先看一下系统是否需要调度,如果需要调度的话就将pcb块链上的下一个pcb设置为运行的进程。详细代码在myinterrupt.c文件中。

 

 

总结:这个实验的代码虽然简短,但是对于理解计算机的线程调度来说是超级好的。 通过此实验可以认识到计算机的执行核心是:冯诺依曼的程序存储执行思想,最低层就是cpu始终指向eip指向的指令,虽然eip有时候乱跳,但是这个乱跳要到上层代码处才看明白。  上层代码的核心是逻辑,首先设计好逻辑,然后通过相关的结构体定义和函数实现来实现程序要实现的功能,这上层和下层之间的对应就要靠编译技术来实现了。

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值