一、实验环境配置
系统环境:阿里云 ubuntu_16_04_x64
参照https://github.com/mengning/mykernel配置
配置完成后,可以看到进程在不断执行
二、精简实现mykernel操作系统
1、进程基本结构定义
先在mykernel目录下增加一个mypcb.h 头文件,来定义进程控制块(Process Control Block),也就是进程结构体的定义,在Linux内核中是struct tast_struct结构体。
1 //定义最大进程数和进程栈空间大小 2 #define MAX_TASK_NUM 4 3 #define KERNEL_STACK_SIZE 1024*8 4 5 struct Thread { 6 unsigned long ip; 7 unsigned long sp; 8 }; 9 10 typedef struct PCB{ 11 //进程号 12 int pid; 13 14 // -1:就绪态, 0:运行态, >0:阻塞态 15 volatile long state; 16 //当前进程的栈 17 char stack[KERNEL_STACK_SIZE]; 18 //当前线程 19 struct Thread thread; 20 //进程入口地址 21 unsigned long task_entry; 22 //指向下一个进程的指针 23 struct PCB *next; 24 }tPCB; 25 26 void my_schedule(void);
2、模拟进程
对mymain.c进行修改,这是mykernel内核代码的入口,负责初始化内核的各个组成部分。在mymain.c中添加了my_process函数,来作为进程的代码模拟一个个进程。
1 void __init my_start_kernel(void){ 2 int pid = 0; 3 int i; 4 5 //初始化0号进程 6 task[pid].pid = pid; 7 task[pid].state = 0; 8 task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process; 9 task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1]; 10 task[pid].next = &task[pid]; 11 12 //创建后续进程,最大数为4 13 for(i=1;i){ 14 memcpy(&task[i],&task[0],sizeof(tPCB)); 15 task[i].pid = i; 16 task[i].state = 0; 17 task[i].thread.sp = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-1]; 18 task[i].next = task[i-1].next; 19 task[i-1].next = &task[i]; 20 } 21 22 //启动进程 23 pid = 0; 24 my_current_task = &task[pid]; 25 26 asm volatile( 27 "movq %1,%%rsp\n\t" /* set task[pid].thread.sp to rsp */ 28 "pushq %1\n\t" /* push rbp */ 29 "pushq %0\n\t" /* push task[pid].thread.ip */ 30 /* pop task[pid].thread.ip to rip 31 这一步没有直接将thread.ip放入rip中,而是通过ret操作实现,是因为rip寄存器 32 不能直接访问 33 */ 34 "ret\n\t" 35 : 36 : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) /* input c or d mean %ecx/%edx*/ 37 ); 38 } 39 40 void my_process(void){ 41 int i = 0; 42 while(1){ 43 i++; 44 //减少输出,每1千万次输出一个 45 if(i%10000000 == 0){ 46 printk(KERN_NOTICE "this is process %d -\n",my_current_task->pid); 47 if(my_need_sched == 1){ 48 my_need_sched = 0; 49 my_schedule(); 50 } 51 printk(KERN_NOTICE "this is process %d +\n",my_current_task->pid); 52 } 53 } 54 }
3、进程调度和切换
在myinterrupt.c中修改my_timer_handler用来记录时间片,同时在myinterrupt.c中增加进程切换代码。
1 void my_timer_handler(void) { 2 if(time_count%1000 == 0 && my_need_sched != 1){ 3 printk(KERN_NOTICE ">>>my_timer_handler here<<<\n"); 4 my_need_sched = 1; 5 } 6 time_count ++ ; 7 return; 8 } 9 10 void my_schedule(void){ 11 tPCB *next; 12 tPCB *prev; 13 14 if(my_current_task==NULL||,my_current_task->next==NULL){ 15 return; 16 } 17 18 printk(KERN_NOTICE ">>>my_schedule<<<\n"); 19 /* schedule */ 20 next = my_current_task->next; 21 prev = my_current_task; 22 if(next->state == 0){ 23 my_current_task = next; 24 printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);/* 切换进程 */ 25 asm volatile( 26 "pushq %%rbp\n\t" 27 "movq %%rsp,%0\n\t" 28 "movq %2,%%rsp\n\t" 29 "movq $1f,%1\n\t" 30 "pushq %3\n\t" 31 "ret\n\t" 32 "1:\t" 33 "popq %%rbp\n\t" 34 : "=m" (prev->thread.sp),"=m" (prev->thread.ip) 35 : "m" (next->thread.sp),"m" (next->thread.ip) 36 ); 37 } 38 return; 39 }
重新编译后,再次运行可以发现进程正在不断切换进行
4、关键代码分析
重点在于进程的切换,整个进程切换过程,主要体现在rbp,rsp的存储和切换
1)"pushq %%rbp\n\t"
将rbp中的值压入堆栈中,这步完成来rbp的存储(当前进程栈基地址)
2)"movq %%rsp,%0\n\t"
将rsp中的值存入thread.sp中,完成rsp的存储
3)"movq %2,%%rsp\n\t"
将next.thread.sp的值传入rsp中,也就是rsp表示的栈顶指针指向下一个进程的内存区
4)"movq $1f,%1\n\t"
将$1f存入thread.ip中
5)"pushq %3\n\t"
将thread.ip的值($1f)压入栈中,因为要完成rip的存储切换,但rip不能随意更改,只能通过堆栈操作进行更改,所以使用这样的方法来保存
6)"ret\n\t"
pop next.thread的rip,执行next.thread也就是$1后面的语句
7)"popq %%rbp\n\t"
pop出的值存入rbp中,也就是rbp指向当前进程的栈底