linux实验:基于mykernel的一个简单的时间片轮转多道程序内核代码分析

linux实验:基于mykernel的一个简单的时间片轮转多道程序内核代码分析

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

实验准备

对实验开始进行的准备,主要为安装qemu 与Kernel
个人环境为 vm+ubantu 16.04虚拟机

  1. 安装 qemu
sudo apt-get install qemu
sudo ln -s /usr/bin/qemu-system-i386 /usr/bin/qemu #连接目录
  1. 安装Kernel
wget https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.9.4.tar.xz  # 下载Linux Kernel 3.9.4
wget https://raw.github.com/mengning/mykernel/master/mykernel_for_linux3.9.4sc.patch # 下载孟老师提供的补丁
  1. 解压并且打补丁
xz -d linux-3.9.4.tar.xz
tar -xvf linux-3.9.4.tar
cd linux-3.9.4
patch -p1 < ../mykernel_for_linux3.9.4sc.patch
  1. 编译内核代码
make allnoconfig
make

在这里可能会出现错误(emmmm,我出现了o(╥﹏╥)o)
错误
这里主要是内核版本与编译器 版本的不匹配,修改方法就是将已有的gcc文件拷贝进去,如下
解决
5. 使用 qemu观看运行

qemu -kernel arch/x86/boot/bzImage

运行结果如下
在这里插入图片描述
观看myKernel中的mymain.c文件
在这里插入图片描述
与myinterrupt.c文件
在这里插入图片描述
可以看出这个QEMU显示的内容主要是由 myinterrupt.c与mymain.c循环执行显示,即不断执行my_start_kernel与间接的调用my_timer_handler函数。

时间片轮转多道程序实现

  1. 修改mykernel中的文件
    这一步主要感谢孟宁老师,已经提供了mymain.c、myinterrupt.c、mypcb.h三个文件来供我们使用。
    去https://github.com/mengning/mykernel来进行下载替换原文件夹中的内容。
  2. 重新进行内核编译
make clean #清除原来编译内容
make allnoconfig
make
  1. 使用qemu观看运行情况
    新的运行情况

代码分析

  1. mypcb.h
#define MAX_TASK_NUM        4
#define KERNEL_STACK_SIZE   1024*2 # unsigned long
/* CPU-specific state of this task */
struct Thread {
    unsigned long		ip;
    unsigned long		sp;
};

typedef struct PCB{
    int pid;
    volatile long state;	/* -1 unrunnable, 0 runnable, >0 stopped */
    unsigned long 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);

增加了一个mypcb.h头文件,这个头文件是用来定义进程控制块
state :进程状态,初始值为-1,运行时为0,停止为大于0。
PCB *next 表示指向下一个PCB
2. mymain.c

tPCB task[MAX_TASK_NUM];
tPCB * my_current_task = NULL;
volatile int my_need_sched = 0;

这里面 my_current_task 表明当前进程的指针
my_need_sched是一个进程状态转换的信号,当其为1时,表明进程状态转换。

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

这一段主要是对pid=0的进程0初始化,紧接着后面的一段代码

    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];
    }
    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 */
    	: 
    	: "c" (task[pid].thread.ip),"d" (task[pid].thread.sp)	/* input c or d mean %ecx/%edx*/
	);

是按照初始化0号进程的方式陆续初始化更多进程。
这里使用了内联汇编代码,表明进程0的堆栈与相关寄存器的变化过程,1.将原进程堆栈栈顶的地址存入esp寄存器, 2.然后将当前ebp寄存器值入栈, 3.将当前进程的eip入栈,4.ret命令让入栈进程eip保存到eip寄存器中

void my_process(void)
{    
    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);
        }     
    }
}

这个函数的作用是每过10000000次进行一次检查,如果my_need_sched 为1 执行myinterrupt中的 my_schedule()函数,并且将my_need_sched回复1,不然的话就一直输出当前进程的pid(即进程号)
3. myinterrupt.c

extern tPCB task[MAX_TASK_NUM];
extern tPCB * my_current_task;
extern volatile int my_need_sched;
volatile int time_count = 0;

使用了mymain.c中定义的当前工作进程指针,工作进程大小,状态转换指针以及自己执行的计数指针,

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

这里面是一个进程中断函数,使用了开头的time_count计数器,每次加1,当到1000是,将全局变量my_need_sched改为1,其中 mymain.c中的my_process函数执行就会检测到,从而执行myinterrupt.c中的my_schedule函数

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

my_schedule()主要是实现了时间片的轮转,定义了:next 当前进程的下一个进程,prev,当前进程
内联汇编代码主要是写两个进程的调度工作

1.保存当前EBP到堆栈中,
2.保存当前esp到当前进程PCB中,
3.将next进程中的堆栈栈顶的值存入esp寄存器中,切换进程
4.保存当前的EIP值,下次回复进程后将在标号1开始执行,
5.将next进程继续执行的代码位置压栈,即将下一个进程的函数地址入栈,切换进程,
6.出栈标号1到EIP寄存器,说明接下来执行next进程
7.标号1,为next进程开始执行的位置打上标签,为之后切换进程做准备 8.回复EBP寄存器的值,然后准备开始next进程。

总结

对这次实验的总结,这次试验对我来说是一次很好的认识系统内部进程的调度的机会,汇编代码接触的很少,确实需要不断的看书来学习才能弄懂,这次的实验重点是理解时间轮转终端与进程调度,即进程在执行的过程中,当时间片用完,进行进程切换时,需要保存当前进程执行环境,然后在进程下一次调用时恢复,最终实现操作系统进程的切换与多道程序的并发处理。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值