Lab1:基于 mykernel 2.0 编写一个操作系统内核

一、按照 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

Lab1:基于 mykernel 2.0 编写一个操作系统内核_第1张图片

进入 mykernel 目录可以查看 mymain.c 和 myinterrupt.c 代码。

mymain.c 中主要是一个 while 循环,模拟 CPU 执行 C 代码的上下文环境,而周期性产⽣的时钟中断信号能够触发 myinterrupt.c 中的代码。这样就通过 Linux 内核代码模拟
了⼀个具有时钟中断和 代码执⾏环境的硬件平台 。

Lab1:基于 mykernel 2.0 编写一个操作系统内核_第2张图片

Lab1:基于 mykernel 2.0 编写一个操作系统内核_第3张图片

 

 

二、基于 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 }

 

重新编译运行

Lab1:基于 mykernel 2.0 编写一个操作系统内核_第4张图片

 

三、简要分析操作系统内核核心功能及运行工作机制

进程调度算法

  基于时间片,每次执行完一个时间片后会触发一个时钟中断进行进程调度。初始化将第一个进程的 id 指向函数 my_process 的地址,进程以循环链表的方式进行存储。指令寄存器 ip 初始化为第一个进程的 id 值,执行 my_process 函数。

进程切换

  1、当前栈基址寄存器 rbp 入栈,保存当前进程的栈底地址

  2、栈寄存器 rsp 的值保存到当前线程的 sp 中,保存当前进程的栈顶地址

  3、将 rsp 指向下一个进程的 sp,完成当前进程和下一个进程的堆栈切换

  4、保存当前进程的 ip 字段放入 rip 寄存器中

  5、将即将执行的下一个进程的 ip 放入指令寄存器进行执行

  6、将下一个进程栈基址从堆栈中恢复到 rbp 寄存器中

 

你可能感兴趣的:(Lab1:基于 mykernel 2.0 编写一个操作系统内核)