基于mykernel的Linux内核时间调度的分析

学号:274

原创作品转载请注明出处 + https://github.com/mengning/linuxkernel/ 

 

一、环境

Ubuntu14.04LTS

Linux内核3.9.4

 

 

 

 

 

 

二、实验结果

(进程在不停地切换)

三、代码分析

mypcb.h文件内主要是对进程的PCB进行数据结构的定义,一些常量的定义,以及my_schedule函数的声明。这些定义会包含在下面的两个代码文件中。mypcb.h具体代码如下:

#define MAX_TASK_NUM        4
#define KERNEL_STACK_SIZE   1024*2

struct Thread {
    unsigned long       ip;    //eip
    unsigned long       sp;    //esp
};

typedef struct PCB{
    int pid;
    volatile long state;        //-1 means unrunnable, 0 means runnable, >0 means stopped
    char stack[KERNEL_STACK_SIZE];
    /* CPU-specific state of this task */
    struct Thread thread;
    unsigned long task_entry;
    struct PCB *next;
}tPCB;

void my_schedule(void);

 

宏定义:

MAX_TASK_NUM 代表最大进程数为4 
KERNEL_STACK_SIZE 代表每个进程堆栈的大小

PCB: 
pid 进程的唯一标识,用于区分进程 
state 进程当前的状态,之所以声明为volatile是希望编译器不要对其进行优化,保证每次都能从内存中获取此值。 
stack[KERNEL_STACK_SIZE] 进程的堆栈 
thread 进程的ip(程序指针)和sp(栈顶指针) 
task_entry 进程的入口 
next 指向下一个PCB

mymain.c


#include <linux/types.h>

#include <linux/string.h>

#include <linux/ctype.h>

#include <linux/tty.h>

#include <linux/vmalloc.h>





#include "mypcb.h"



tPCB task[MAX_TASK_NUM];

tPCB * my_current_task = NULL;

volatile int my_need_sched = 0;



void my_process(void);





void __init my_start_kernel(void)

{

	int pid = 0;

	int i;

	/* Initialize process 0*/

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

	}

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

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

		    );

}   

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

		}     

	}

}

函数开头定义了一个PCB的数组task,一个指向当前运行进程的my_current_task指针,以及保存进程是否需要调度的my_need_sched,当其值为1时表示进程需要调度。

接着__init my_start_kernel进行所有PCB的初始化。

主要流程是对每个进程的PCB各个属性进行赋值,并将所有PCB通过next指针链接。初始化完成后,使用asm(内嵌汇编代码)将当前进程,0号进程装入内存中。每个进程的入口地址均是下面定义的my_process函数。

my_process是一个死循环,即每循环一千万次输出信息,查看是否有调度需求,若有,进行调度,调度后输出调度后的进程的信息。若无,输出本进程信息。调度函数为my_schedule

 

myinterrupt.c


#include <linux/types.h>

#include <linux/string.h>

#include <linux/ctype.h>

#include <linux/tty.h>

#include <linux/vmalloc.h>



#include "mypcb.h"



extern tPCB task[MAX_TASK_NUM];

extern tPCB * my_current_task;

extern volatile int my_need_sched;

volatile int time_count = 0;



/*

 * Called by timer interrupt.

 * it runs in the name of current running process,

 * so it use kernel stack of current running process

 */

void my_timer_handler(void)

{

#if 1

	if(time_count%1000 == 0 && my_need_sched != 1)

	{

		printk(KERN_NOTICE ">>>my_timer_handler here<<<\n");

		my_need_sched = 1;

	} 

	time_count ++ ;  

#endif

	return;  	

}



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

	{

		/* 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<<<\n",prev->pid,next->pid);   	

	}

	else

	{

		next->state = 0;

		my_current_task = next;

		printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->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;	

}

my_timer_handler()循环1000次,且调度位为0时,将my_need_sched置1,使得my_main.c中的my_process可以进行主动调度

my_schedule()实现的是进程的上下文切换,分为两种情况:(1)该进程已经执行过(即state为0),此时先后保存当前进程的ebp和esp,然后从新进程断点恢复esp,保存当前进程的eip,读入新进程的eip。(2)进程尚未执行过(即state不为0),此时与上一种情况不同的是对新进程的ebp进行了赋值,将新进程的sp赋给颗esp和ebp。

四、总结

从上面的简单的内核进程切换代码,可以看出在系统运行的各个进程间如何通过ebp和esp的改变和保存,来进行上下文切换。另外系统通过时间中断来使得各个进程获得了切换的机会,这是个时分复用的内核机制。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值