一、按照 https://github.com/mengning/mykernel 的说明配置 mykernel 2.0,熟悉 Linux 内核的编译
配置环境
1 wget https://raw.github.com/mengning/mykernel/master/mykernel-2.0_for_linux-5.4.34.patch 2 sudo apt install axel 3 axel -n 20 https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/linux-5.4.34.tar.xz 4 xz -d linux-5.4.34.tar.xz 5 tar -xvf linux-5.4.34.tar 6 cd linux-5.4.34 7 patch -p1 < ../mykernel-2.0_for_linux-5.4.34.patch 8 sudo apt install build-essential gcc-multilib 9 sudo apt install qemu # install QEMU 10 sudo apt install libncurses5-dev bison flex libssl-dev libelf-dev 11 make defconfig # Default configuration is based on 'x86_64_defconfig' 12 make -j$(nproc) 13 qemu-system-x86_64 -kernel arch/x86/boot/bzImage
进入 mykernel 目录可以查看 mymain.c 和 myinterrupt.c 代码。
mymain.c 中主要是一个 while 循环,模拟 CPU 执行 C 代码的上下文环境,而周期性产⽣的时钟中断信号能够触发 myinterrupt.c 中的代码。这样就通过 Linux 内核代码模拟
了⼀个具有时钟中断和 C 代码执⾏环境的硬件平台 。
二、基于 mykernel 2.0 编写一个操作系统内核,参照 https://github.com/mengning/mykernel 提供的范例代码
1、mypcb.h
添加 mypcb.h 文件,定义 pcb 结构
1 //最大任务数 2 #define MAX_TASK_NUM 4 3 #define KERNEL_STACK_SIZE 1024*2 4 5 //存储ip和sp 6 struct Thread { 7 unsigned long ip; 8 unsigned long sp; 9 }; 10 11 //PCB结构 12 typedef struct PCB{ 13 int pid; //进程id 14 volatile long state; 15 unsigned long stack[KERNEL_STACK_SIZE]; //进程堆栈 16 struct Thread thread; //线程 17 unsigned long task_entry; //进程入口地址 18 struct PCB *next; //下一个进程控制块地址 19 }tPCB; 20 21 void my_schedule(void); //调度函数
2、myinterrupt.c
myinterrupt.c 文件中定义了进程如何切换
1 #include "mypcb.h" 2 3 tPCB task[MAX_TASK_NUM]; 4 tPCB * my_current_task = NULL; 5 volatile int my_need_sched = 0; 6 7 void my_process(void); 8 9 10 void __init my_start_kernel(void) 11 { 12 int pid = 0; 13 int i; 14 /* Initialize process 0*/ 15 task[pid].pid = pid; 16 task[pid].state = 0;/* -1 unrunnable, 0 runnable, >0 stopped */ 17 task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process; 18 task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1]; 19 task[pid].next = &task[pid]; 20 /*fork more process */ 21 for(i=1;i) 22 { 23 memcpy(&task[i],&task[0],sizeof(tPCB)); 24 task[i].pid = i; 25 task[i].thread.sp = (unsigned long)(&task[i].stack[KERNEL_STACK_SIZE-1]); 26 task[i].next = task[i-1].next; 27 task[i-1].next = &task[i]; 28 } 29 /* start process 0 by task[0] */ 30 pid = 0; 31 my_current_task = &task[pid]; 32 asm volatile( 33 "movq %1,%%rsp\n\t" /* set task[pid].thread.sp to rsp */ 34 "pushq %1\n\t" /* push rbp */ 35 "pushq %0\n\t" /* push task[pid].thread.ip */ 36 "ret\n\t" /* pop task[pid].thread.ip to rip */ 37 : 38 : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) /* input c or d mean %ecx/%edx*/ 39 ); 40 } 41 42 int i = 0; 43 44 void my_process(void) 45 { 46 while(1) 47 { 48 i++; 49 if(i%10000000 == 0) 50 { 51 printk(KERN_NOTICE "this is process %d -\n",my_current_task->pid); 52 if(my_need_sched == 1) 53 { 54 my_need_sched = 0; 55 my_schedule(); 56 } 57 printk(KERN_NOTICE "this is process %d +\n",my_current_task->pid); 58 } 59 } 60 }
3、mymain.c
mymain.c 是内核运行的程序,有一个中断处理程序的上下文环境,周期性地产生中断信号来触发 myinterrupt.c
对代码进行修改,在原来的基础上增加了进程管理的代码
1 #include "mypcb.h" 2 3 extern tPCB task[MAX_TASK_NUM]; 4 extern tPCB * my_current_task; 5 extern volatile int my_need_sched; 6 volatile int time_count = 0; 7 8 /* 9 * Called by timer interrupt. 10 * it runs in the name of current running process, 11 * so it use kernel stack of current running process 12 */ 13 void my_timer_handler(void) 14 { 15 if(time_count%1000 == 0 && my_need_sched != 1) 16 { 17 printk(KERN_NOTICE ">>>my_timer_handler here<<<\n"); 18 my_need_sched = 1; 19 } 20 time_count ++ ; 21 return; 22 } 23 24 void my_schedule(void) 25 { 26 tPCB * next; 27 tPCB * prev; 28 29 if(my_current_task == NULL 30 || my_current_task->next == NULL) 31 { 32 return; 33 } 34 printk(KERN_NOTICE ">>>my_schedule<<<\n"); 35 /* schedule */ 36 next = my_current_task->next; 37 prev = my_current_task; 38 if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */ 39 { 40 my_current_task = next; 41 printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid); 42 /* switch to next process */ 43 asm volatile( 44 "pushq %%rbp\n\t" /* save rbp of prev */ 45 "movq %%rsp,%0\n\t" /* save rsp of prev */ 46 "movq %2,%%rsp\n\t" /* restore rsp of next */ 47 "movq $1f,%1\n\t" /* save rip of prev */ 48 "pushq %3\n\t" 49 "ret\n\t" /* restore rip of next */ 50 "1:\t" /* next process start here */ 51 "popq %%rbp\n\t" 52 : "=m" (prev->thread.sp),"=m" (prev->thread.ip) 53 : "m" (next->thread.sp),"m" (next->thread.ip) 54 ); 55 } 56 return; 57 }
重新编译运行
三、简要分析操作系统内核核心功能及运行工作机制
进程调度算法
基于时间片,每次执行完一个时间片后会触发一个时钟中断进行进程调度。初始化将第一个进程的 id 指向函数 my_process 的地址,进程以循环链表的方式进行存储。指令寄存器 ip 初始化为第一个进程的 id 值,执行 my_process 函数。
进程切换
1、当前栈基址寄存器 rbp 入栈,保存当前进程的栈底地址
2、栈寄存器 rsp 的值保存到当前线程的 sp 中,保存当前进程的栈顶地址
3、将 rsp 指向下一个进程的 sp,完成当前进程和下一个进程的堆栈切换
4、保存当前进程的 ip 字段放入 rip 寄存器中
5、将即将执行的下一个进程的 ip 放入指令寄存器进行执行
6、将下一个进程栈基址从堆栈中恢复到 rbp 寄存器中