基于mykernel的时间片轮转多道程序内核代码分析
学号:390。原创作品转载请注明出处:https://github.com/mengning/linuxkernel/
一、mykernel简介
mykernel是由孟宁老师在github上发布的,用于开发自己操作系统内核的一个平台。它是基于linux3.9.4 内核代码。源代码可以从https://github.com/mengning/linuxkernel/上获取。
本次实验是使用http://www.shiyanlou.com/courses/195提供的虚拟机来完成的。
二、实验过程
在实验楼提供的虚拟机上打开终端,输入以下指令:
1 cd LinuxKernel/linux-3.9.4 2 rm -rf mykernel 3 patch -p1 < …/mykernel_for_linux3.9.4sc.patch 4 make allnoconfig 5 make 6 qemu -kernel arch/x86/boot/bzImage
当编译完成之后,显示如下,可以看到这时 Kernel:arch/x86/boot/bzImage is ready
接下来执行如下命令,这时会弹出qemu界面,并不断输出 >>>>>my_timer_handler here <<<<< 和 my_start_kernel here
qemu -kernel arch/x86/boot/bzImage
关闭qemu窗口,在mykernel中打开mymain.c 和myinterrupt.c
这段代码中 my_start_kernel函数中不断循环,输出my_start_kernel here,周期性的产生时钟中断信号,这个信号会触发myinterrupt.c。
这段代码中 my_time_handler 就是不断被时钟中断的函数。
这两个函数就可以组成一个最简单的操作系统,从而完成进程的轮转调度过程。
三、时间片轮转多道程序
此程序代码由孟宁老师提供,代码地址:https://github.com/mengning/mykernel
将孟老师提供的源代码中的mypcb.h、mymain.c和myinterrupt.c复制到mykernel目录下,替换原先的文件,然后重新编译,运行内核程序。
这里需要注意将mypcb.h中的源码#defin KERNEL_STACK_SIZE 1024*2 # unsigned long中的# unsigned long删除,不然编译会出错。
源码分析:
这里主要修改个这三个文件,基本功能如下
mypcb.h : 进程控制块PCB结构体定义。
mymain.c: 初始化各个进程并启动0号进程。
myinterrupt.c:时钟中断处理和进程调度算法。
1.mypcb.h
/* * linux/mykernel/mypcb.h * * Kernel internal PCB types * * Copyright (C) 2013 Mengning * */ #define MAX_TASK_NUM 4 #define KERNEL_STACK_SIZE (unsigned long)1024*2 /* CPU-specific state of this task */ struct Thread { unsigned long ip; unsigned long sp; }; typedef struct PCB{ // 进程号 int pid; // -1 unrunnable, 0 runnable, >0 stopped volatile long state; // 进程堆栈 unsigned long stack[KERNEL_STACK_SIZE]; // CPU-specific state of this task struct Thread thread; // 入口事件 unsigned long task_entry; // 下一个pcb块的位置 struct PCB *next; }tPCB; void my_schedule(void);
mypcb.h 定义了结构体PCB,也就是进程控制块,结构体中定义的变量含义如下:
- pid :进程编号,用于唯一标志一个进程。
- state:表示进程当前的状态,1 -- 运行,0 -- 就绪,-1 -- 阻塞。
- stack:进程的堆栈。
- thread:一个结构体,保存着ip和sp的指针
- task_entry: 进程的入口,保存的在内存的存储地址。
- *next : 一个指针,指向下一个PCB结构体。
2.mymain.c
1 #include <linux/types.h> 2 #include <linux/string.h> 3 #include <linux/ctype.h> 4 #include <linux/tty.h> 5 #include <linux/vmalloc.h> 6 7 8 #include "mypcb.h" 9 10 tPCB task[MAX_TASK_NUM]; 11 tPCB * my_current_task = NULL; 12 volatile int my_need_sched = 0; 13 14 void my_process(void); 15 16 17 void __init my_start_kernel(void) 18 { 19 int pid = 0; 20 int i; 21 /* Initialize process 0*/ 22 task[pid].pid = pid; 23 task[pid].state = 0;/* -1 unrunnable, 0 runnable, >0 stopped */ 24 task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process; 25 task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1]; 26 task[pid].next = &task[pid]; 27 /*fork more process */ 28 for(i=1;i<MAX_TASK_NUM;i++) 29 { 30 memcpy(&task[i],&task[0],sizeof(tPCB)); 31 task[i].pid = i; 32 //*(&task[i].stack[KERNEL_STACK_SIZE-1] - 1) = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-1]; 33 task[i].thread.sp = (unsigned long)(&task[i].stack[KERNEL_STACK_SIZE-1]); 34 task[i].next = task[i-1].next; 35 task[i-1].next = &task[i]; 36 } 37 /* start process 0 by task[0] */ 38 pid = 0; 39 my_current_task = &task[pid]; 40 asm volatile( 41 "movl %1,%%esp\n\t" /* set task[pid].thread.sp to esp */ 42 "pushl %1\n\t" /* push ebp */ 43 "pushl %0\n\t" /* push task[pid].thread.ip */ 44 "ret\n\t" /* pop task[pid].thread.ip to eip */ 45 : 46 : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) /* input c or d mean %ecx/%edx*/ 47 ); 48 } 49 50 int i = 0; 51 52 void my_process(void) 53 { 54 while(1) 55 { 56 i++; 57 if(i%10000000 == 0) 58 { 59 printk(KERN_NOTICE "this is process %d -\n",my_current_task->pid); 60 if(my_need_sched == 1) 61 { 62 my_need_sched = 0; 63 my_schedule(); 64 } 65 printk(KERN_NOTICE "this is process %d +\n",my_current_task->pid); 66 } 67 } 68 }
系统启动后最先调用函数 my_start_kernel ,完成了0号进程的初始化和启动,并创建了其它的进程PCB便于调度。my_process 函数在执行的时候,会打印出当前进程的 id,从而使得我们能够看到当前哪个进程正在执行。
另外,在 my_process 也会检查一个全局标志变量 my_need_sched,一旦发现其值为 1 ,就调用 my_schedule 完成进程的调度。
3.myinterrupt.c
1 #include <linux/types.h> 2 #include <linux/string.h> 3 #include <linux/ctype.h> 4 #include <linux/tty.h> 5 #include <linux/vmalloc.h> 6 7 #include "mypcb.h" 8 9 extern tPCB task[MAX_TASK_NUM]; 10 extern tPCB * my_current_task; 11 extern volatile int my_need_sched; 12 volatile int time_count = 0; 13 14 /* 15 * Called by timer interrupt. 16 * it runs in the name of current running process, 17 * so it use kernel stack of current running process 18 */ 19 void my_timer_handler(void) 20 { 21 #if 1 22 if(time_count%1000 == 0 && my_need_sched != 1) 23 { 24 printk(KERN_NOTICE ">>>my_timer_handler here<<<\n"); 25 my_need_sched = 1; 26 } 27 time_count ++ ; 28 #endif 29 return; 30 } 31 32 void my_schedule(void) 33 { 34 tPCB * next; 35 tPCB * prev; 36 37 if(my_current_task == NULL 38 || my_current_task->next == NULL) 39 { 40 return; 41 } 42 printk(KERN_NOTICE ">>>my_schedule<<<\n"); 43 /* schedule */ 44 next = my_current_task->next; 45 prev = my_current_task; 46 if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */ 47 { 48 my_current_task = next; 49 printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid); 50 /* switch to next process */ 51 asm volatile( 52 "pushl %%ebp\n\t" /* save ebp */ 53 "movl %%esp,%0\n\t" /* save esp */ 54 "movl %2,%%esp\n\t" /* restore esp */ 55 "movl $1f,%1\n\t" /* save eip */ 56 "pushl %3\n\t" 57 "ret\n\t" /* restore eip */ 58 "1:\t" /* next process start here */ 59 "popl %%ebp\n\t" 60 : "=m" (prev->thread.sp),"=m" (prev->thread.ip) 61 : "m" (next->thread.sp),"m" (next->thread.ip) 62 ); 63 } 64 return; 65 }
myinterrupt.c主要实现两个函数:my_time_handler和my_schedule
- my_time_handler(), 每1000毫秒产生一个时钟中断,同时设置my_need_sched=1,mymain.c中的函数就会调用my_schedule()以执行进程切换的操作。
- my_schedule(),用于进程的切换操作。
- 声明next,pre指针执行下一个需要调度的进程和上一个进程
- 判断下一个需要调度的进程的状态值,为0则切换
- 通过汇编实现进程的切换。保存现场,将当前线程相关内容入栈,上一线程相关内容出栈,然后切换线程ip;
四、实验总结
操作系统是指控制和管理整个计算机系统的硬件和软件资源,并合理地组织调度计算机的工作和资源的分配,以提供给用户和其他软件方便的接口和环境的程序集合。为了提高操作系统的性能和对资源的利用率,使用进程的调度与中断机制是必须的。
操作系统想要实现多进程,程序的并发执行,就必须实行进程的调度来合理分配CPU的使用权。不同的调度算法实现的效果各有差异,而时间片轮转调度算法是一个比较常见的调度算法。系统将所有就绪进程按时间次序排成一个队列,进程调度总是选择就绪队列中的第一个进程执行,但仅能运行一个时间片。在使用完一个时间片后,如果进程还未完成,就会释放CPU,并移动到就绪队列末尾等待下一个时间片来临。
系统实现调度与中断机制,需要利用堆栈空间,通过esp堆栈指针寄存器、ebp基址指针寄存器和cs:eip寄存器等关键寄存器来保存和恢复现场,进行进程上下文的切换。