linux任务调度时间片轮转,基于mykernel实现的时间片轮转调度代码.md

#基于mykernel实现的时间片轮转调度代码

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

首先,我的环境使用的实验楼的环境,所有的配置已经部署好了。

下面,我就直接开始分析代码了。

这是一个提供了时钟中断(time_handler)的周期性执行my_time_handler中断处理程序。

###进程的设置

首先,开始多进程调度执行之前,首先,这个程序开始设置环境来模拟进程。

它定义了一个task[MAX_TASK_NUM]的进程数组。本身tPCB也是一个结构体,里面数据结构有关于进程的进程号(pid),进程的状态(state),进程的栈顶指针(thread.sp),以及指向下一个进程的指针(next)。具体描述如下:

``` C

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;

unsigned longtask_entry;

struct PCB *next;

}tPCB;

```

###具体代码分析

首先,程序是从my_start_kernel开始执行的,它先创建一个0号进程,并设置相应的状态:赋予进程号,设置状态为可执行,设置代码的入口地址(即IP指向的地址),相应地也设置其他进程的这些参数,但是注意一下其它进程的状态的不可运行(-1),并相应地把这些进程组织为一个循环链表。具体为以下代码

``` C

task[pid].pid = pid;

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

{

memcpy(&task[i],&task[0],sizeof(tPCB));

task[i].pid = i;

task[i].state = -1;

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];

}

```

接下来的代码,即为马上要第一个运行的0号进程进行设置,我们来看一下这段内联汇编。

``` C

my_current_task = &task[pid];

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*/

);

}

```

首先,把esp寄存器指向0号进程的栈空间的栈顶,然后把ebp寄存器的值压入0号栈,这样就形成了一个函数运行栈的栈帧了。接着把0号进程的ip压栈,再接着ret,相当与popl %eip,这样就把eip寄存器指向了0号进程的ip值,即入口函数my_process()。(注意一下,最后一句popl %ebp没有执行。)下面,就开始0号进程的运行过程了,执行my_processs()函数。

###进程的切换

下面就会讲到本文的核心了:进程切换,由于my_process每隔10万次就会检查一下有没有进程调度(my_need_sched值是否等于1),而my_time_handler这个中断处理函数每隔1000次就会检查一下这个值,如果不为1,就把置为1,从而进行进程调度。当0号进程检查到my_need_sched等于1了,就会跳到my_schedule()函数,进行进程切换。

下面,我们来分析my_need_sched函数关于进程切换的内联汇编代码。

``` C

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

{

/* 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;

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

}

else

{

next->state = 0;

my_current_task = next;

printk(KERN_NOTICE ">>>switch %d to %d<<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)

);

}

```

####所有进程第一次执行时

首先,它设置了两个变量,当前进程为prev,下一个进程为next。然后,判断下一个进程的状态是否为可运行(即是否为0),由于除了0号进程以外,其它进程都是第一次执行,因此它们的状态都为不可运行,即为-1,走else路径。

我们看看else路径,首先,它即将执行的1号进程状态置为运行状态,然后用内联汇编进行进程切换了。首先,它保存0号进程的ebp寄存器值到0号进程的栈里,然后把esp寄存器值保存0号进程的thread.ip里。接着,把esp寄存器值指向1号进程的esp值,即1号进程栈的栈顶,接着把ebp值也只想同样的位置。我们知道,当一个函数刚开始执行时(即完成了push ebp;mov esp ,ebp),esp和ebp是指向同样的位置的。接着,执行这句,mov $1f,%1,这是保存0号进程的eip的值,让再度切换到0号进程时可以继续执行,1f是个在if 分支里面的地址,等我们分析到if分支时,我们再详细说明,这里要明白这是保存了0号进程的ip值就好。 再接着,把1号进程的入口值压栈,再ret,弹出给eip,这样1号进行就开始执行了,执行my_process函数。

类似地,当1号进程去切换到2号进程时,过程也类似,直到所有进程都执行了一遍,从最后一个进程切换到0号进程时,情况不一样了。

####所有进程非第一次执行时

当最后一个进程切换到0号进程时,由于0号进程已经可以运行了,所以它走if分支。那我们来分析一下切换的内联汇编代码。

首先,把当前进程的ebp值压栈进行保存,然后把当前进程的esp值存入到对应的trhead.sp里面。再接着,开始切换栈了,把下一个进程也就是1号进程的esp值赋給esp寄存器,也就是让esp切换到0号进程的栈空间。再接着mov 1f,%1,即保存当前进程的eip值,而1f是这段内敛汇编最后一条指令(popl %ebp)的值,这样当再次切换回来时,第一条执行的语句就是pop ebp。再接着,把下一个进程也就是0号进程的ip值压栈,再ret,弹给EIP,这样就切换到下一个进程了,也就是切换回0号进程了,然后0号进程第一句就是popl %ebp。

以后,所有的进程切换都会执行if分支了,过程跟上面描述的类似。注意,这段代码,可能有个难点就是 (movl $1f,%1)1f是最后一条指令的地址,这句就是保存eip的地址,这样让所有进程切换回来的时候,第一句就执行popl %ebp。

###最后小结

mykernel模仿进程的切换大致过程就如上述所分析的,进程和进程之间的切换,重点是上下文的保存,esp和ebp的保存,以及当进程切换回来以后能回到正确的位置执行,所以eip也要正确保存,由于所有的进程都运行一个简单的my_process程序,所以,就不要保存和还原通用寄存器了。但是,这个mykernel还是能够很好地说明进程之间切换是怎么回事。

最后附上一张图,看看进程的切换。

![进程切换](https://github.com/zbh24/LinuxCourseBlog/blob/master/123.png)

完整的mykernel源代码见本站的[mykernel](https://github.com/zbh24/mykernel)。

一键复制

编辑

Web IDE

原始数据

按行查看

历史

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值