一个简单的时间片轮转多道程序内核代码分析 (学号后三位418)
Posted xlyyz
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一个简单的时间片轮转多道程序内核代码分析 (学号后三位418)相关的知识,希望对你有一定的参考价值。
一、基于mykernel的基本Linux内核分析
1.我们按照老师在github上给出的步骤在实验楼上启动最高小内核,可以看到如下现象
在窗口中我们可以看到一个内核以及运行起来了,比较简单的内核,只时不停的输出一些字符串,>>>>>>my_time_handler here<<<<<<<和my_start_kernel here和一些计数。这时因为我们并没有加入其他的代码,再次基础上我们可以加入我们主机要实现的功能。
在myintrrupt.c以及mymain.c中的核心代码如下图,只有简单打印一些字符串以及计数的功能
在myinterrupt.c中我们可以看到一个会被时钟终端周期调用的函数my_timer_handler,在这个函数中,会周期性输出>>>>my_timer_handler here<<<的字符串。
通过这个实验我们可以知道,mykernel系统启动后会做两件事:
1)调用my_start_kernel函数
2) 周期性的调用my_timer_handler函数
观察内核启动过程可以发现,当执行myinterrupt后返回mymain时,终端输出的累计循环次数是连续的没有重置,说明CPU和内核代码实现了保存现场和恢复现场的功能,会将一些重要的寄存器,如eip、ebp、esp等寄存器的内容将会被保存到堆栈中,等待切换回来的时候继续执行。为了实现一个按时间片轮转多道程序的内核,我们需要在其基础上进行修改。
二、按时间片轮转多道程序的实现
我们需要修改mymain.c和myinterrupt.c,先来分析有关的数据结构代码:
1 /***mypcb.h***/ 2 #define MAX_TASK_NUM 4 //定义最大任务数 3 #define KERNEL_STACK_SIZE 1024*2 //堆栈大小 4 5 strcuct Thread{ 6 unsigned lone ip; //保存执行代码片段所在的实际物理内存地址 7 unsigned lone sp; //存放栈顶地址 8 }; 9 10 typedef strucut PCB{ //进程控制块 11 int pid; 12 volatile long state; 13 unsigned long stack[KERNEL_STACK_SIZE]; 14 struct Thread thread; 15 unsigned long task_entry; 16 struct PCB *next; 17 }tPCB; 18 19 void my_schedule(void);
1 /*****mymain.c*****/ 2 #include <linux/types.h> 3 #include <linux/string.h> 4 #include <linux/ctype.h> 5 #include <linux/tty.h> 6 #include <linux/vmalloc.h> 7 8 9 #include "mypcb.h" 10 11 tPCB task[MAX_TASK_NUM]; 12 tPCB * my_current_task = NULL; 13 volatile int my_need_sched = 0; 14 15 void my_process(void); 16 17 18 void __init my_start_kernel(void) 19 { 20 int pid = 0; 21 int i; 22 /* Initialize process 0*/ 23 task[pid].pid = pid; 24 task[pid].state = 0; 25 /* -1 unrunnable, 0 runnable, >0 stopped */ 26 task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process; 27 task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1]; 28 task[pid].next = &task[pid]; 29 /*fork more process */ 30 for(i=1;i<MAX_TASK_NUM;i++) 31 { 32 memcpy(&task[i],&task[0],sizeof(tPCB)); 33 task[i].pid = i; 34 task[i].thread.sp = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-1]; 35 *(task[i].thread.sp - 1) = task[i].thread.sp; 36 task[i].thread.sp -= 1; 37 task[i].next = task[i-1].next; 38 task[i-1].next = &task[i]; 39 } 40 /* start process 0 by task[0] */ 41 pid = 0; 42 my_current_task = &task[pid]; 43 asm volatile( 44 "movl %1,%%esp " 45 /* set task[pid].thread.sp to esp */ 46 "pushl %1 " 47 /* push ebp */ 48 "pushl %0 " 49 /* push task[pid].thread.ip */ 50 "ret " 51 /* pop task[pid].thread.ip to eip */ 52 "popl %%ebp " 53 : 54 : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) 55 /* input c or d mean %ecx/%edx*/ 56 ); 57 } 58 void my_process(void) 59 { 60 int i = 0; 61 while(1) 62 { 63 i++; 64 if(i%10000000 == 0) 65 { 66 printk(KERN_NOTICE "this is process %d - ",my_current_task->pid); 67 if(my_need_sched == 1) 68 { 69 my_need_sched = 0; 70 my_schedule(); 71 } 72 printk(KERN_NOTICE "this is process %d + ",my_current_task->pid); 73 } 74 } 75 }
阅读以上代码,这函数 my_start_kernel 是系统启动后,最先调用的函数,在这个函数里完成了0号进程的初始化和启动,并创建了其它的进程PCB,以方便后面的调度。在系统里,每个进程的函数代码都是一样的,即 my_process 函数,my_process 在执行的时候,会打印出当前进程的 id,从而使得我们能够看到当前哪个进程正在执行
1 /*****myinterrupt*****/ 2 #include <linux/types.h> 3 #include <linux/string.h> 4 #include <linux/ctype.h> 5 #include <linux/tty.h> 6 #include <linux/vmalloc.h> 7 8 #include "mypcb.h" 9 10 extern tPCB task[MAX_TASK_NUM]; 11 extern tPCB * my_current_task; 12 extern volatile int my_need_sched; 13 volatile int time_count = 0; 14 15 /* 16 * Called by timer interrupt. 17 * it runs in the name of current running process, 18 * so it use kernel stack of current running process 19 */ 20 void my_timer_handler(void) 21 { 22 #if 1 23 if(time_count%1000 == 0 && my_need_sched != 1) //计数一千次且需要调度标志位为1 24 { 25 printk(KERN_NOTICE ">>>my_timer_handler here<<< "); 26 my_need_sched = 1; 27 } 28 time_count ++ ; 29 #endif 30 return; 31 } 32 33 void my_schedule(void) 34 { 35 tPCB * next; 36 tPCB * prev; 37 38 if(my_current_task == NULL 39 || my_current_task->next == NULL) 40 { 41 return; 42 } 43 printk(KERN_NOTICE ">>>my_schedule<<< "); 44 /* schedule */ 45 next = my_current_task->next; 46 prev = my_current_task; 47 if(next->state == 0) 48 /* -1 unrunnable, 0 runnable, >0 stopped */ 49 { 50 my_current_task = next; 51 printk(KERN_NOTICE ">>>switch %d to %d<<< ",prev->pid,next->pid); 52 /* switch to next process */ 53 asm volatile( 54 "pushl %%ebp " /* save ebp */ 55 "movl %%esp,%0 " /* save esp */ 56 "movl %2,%%esp " /* restore esp */ 57 "movl $1f,%1 " /* save eip */ 58 "pushl %3 " 59 "ret " /* restore eip */ 60 "1: " /* next process start here */ 61 "popl %%ebp " 62 : "=m" (prev->thread.sp),"=m" (prev->thread.ip) 63 : "m" (next->thread.sp),"m" (next->thread.ip) 64 ); 65 } 66 return; 67 }
my_timer_handler函数会被内核周期性的调用,就去将全局变量my_need_sched的值修改为。通知正在执行调用程序my_schedule。在my_schedule函数中,完成进程的切换。进程的切换分为两种情况,一种情况时下一个进程没有被调度过,另一种情况时下一个进程被调度过,可以通过下一个进程的state知道其状态。进程切换依然时通过内联会变代码实现,就是保存酒进程的eip和相关堆栈,将新进程的eip和堆栈的值存入对应的寄存器中。
三、总结
通过本次实验操作,成功实现一个简单的时间片轮转的多道程序,通过一个精简的操作系统内核,完成了一个简单的功能的操作系统。操作系统的进程调度大致为以下步骤:
1)cpu通过总线根据cs:ip的值从内存的代码段读取一条指令,读取的指令进入缓冲器
2)ip=ip+所指令的长度,从而指向下一条指令;
3)转向1重复执行
以上是关于一个简单的时间片轮转多道程序内核代码分析 (学号后三位418)的主要内容,如果未能解决你的问题,请参考以下文章
一个简单的时间片轮转多道程序内核代码分析 (学号后三位418)