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

实验环境

  • 虚拟机版本:VMware 12
  • 操作系统:Ubuntu 16.04 LTS

  

实验内容

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

  1. 按照https://github.com/mengning/mykernel 的说明配置mykernel 2.0,熟悉Linux内核的编译;
  2. 基于mykernel 2.0编写一个操作系统内核,参照https://github.com/mengning/mykernel 提供的范例代码
  3. 简要分析操作系统内核核心功能及运行工作机制

实验过程

  按照老师的说明配置mykernel2.0

1 wget https://raw.github.com/mengning/mykernel/master/mykernel-2.0_for_linux-5.4.34.patch

  多次尝试下载patch失败,于是直接宿主机下好了再复制到虚拟机里面...

  下载原始内核文件,解压

1 sudo apt install axel
2 axel -n 20 https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/linux-5.4.34.tar.xz
3 xz -d linux-5.4.34.tar.xz
4 tar -xvf linux-5.4.34.tar
5 cd linux-5.4.34

  用 mykernel-2.0_for_linux-5.4.34.patch 对 内核文件进行更新

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

 

 

   安装编译工具,配置编译文件,并进行编译

1 sudo apt install build-essential libncurses-dev bison flex libssl-dev libelf-dev
2 make defconfig 
3 make -j$(nproc) 

  小霸王学习机编译耗时40min

  安装qemu虚拟机,运行 arch/x86/boot 目录下的 bzImage,这是刚才编译好的内核镜像

1 sudo apt install qemu 
2 qemu-system-x86_64 -kernel arch/x86/boot/bzImage

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

 

 

   从qemu窗口中可以看到my_start_kernel在重复执行,中间穿插着 my_timer_handler 时钟中断处理程序。

  查看源码

 1 // mymain.c
 2 void __init my_start_kernel(void)
 3 {
 4     int i = 0;
 5     while(1)
 6     {
 7         i++;
 8         if(i%100000 == 0)
 9             pr_notice("my_start_kernel here  %d \n",i);
10             
11     }
12 }
1 // myinterrupt.c
2 void my_timer_handler(void)
3 {
4     pr_notice("\n>>>>>>>>>>>>>>>>>my_timer_handler here<<<<<<<<<<<<<<<<<<\n\n");
5 }

  由代码可分析得,my_start_kernel 每循环十万次就会产生一次中断,转入中断处理函数,处理完毕又回到 my_start_kernel  继续循环。

  基于以上原理,对内核加入更多的功能:在mymain.c基础上继续写进程描述PCB和进程链表管理等代码,在myinterrupt.c的基础上完成进程切换代码。

  以下参考老师给出的示例代码:

  首先为了标识各个进程,需要定义一个进程控制块PCB结构体文件 mypcb.h

 1 #define MAX_TASK_NUM        4
 2 #define KERNEL_STACK_SIZE   1024*8
 3 
 4 
 5 /* CPU-specific state of this task */
 6 struct Thread {
 7     unsigned long       ip;
 8     unsigned long       sp;
 9 };
10 
11 
12 typedef struct PCB{
13     int pid;
14     volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
15     char stack[KERNEL_STACK_SIZE];
16     /* CPU-specific state of this task */
17     struct Thread thread;
18     unsigned long   task_entry;
19     struct PCB *next;
20 }tPCB;
21 
22 
23 void my_schedule(void);

  其中 pid 标识进程;state 表示进程当前是处于何种状态,状态关系到进程能否调度CPU执行;stack[KERNEL_STACK_SIZE] 堆栈;thread 对应的线程;task_entry 入口地址;PCB *next 进程链表中的下一个进程。 对于每一个thread有一个ip指针存储指令地址和sp指针存储堆栈地址。

  重写main.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 }

  可见此时支持的最大并发进程数为4。 将0号进程初始化为可运行状态,其他3个初始化为不可运行状态,sp指针均指向各自堆栈栈底。

  嵌入式汇编代码将0号进程的sp和ip更新到cpu的rsp和rip寄存器,即开始执行该进程。

  在my_process中,每一千万次循环产生一次中断,显示当前进程pid, 然后进行一次调度,显示调度后进程pid。

  进程切换 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 }

  在中断处理程序中time_count每增加1000,且调度标识位my_need_sched 为0时将标志位改为1。

  my_schedule用于完成进程切换,无进程运行或只有一个进程时无需切换,否则需进行切换。将下一个进程的状态改为可运行,并将其更新为当前运行进程,同时在汇编代码中,将之前进程的运行环境即rbp,rsp,rip的值压入堆栈,然后将rsp,rip的值更新为切换后进程的sp和ip, 并从堆栈中弹出bp,由此完成了进程的切换。

  用图能更好的理解这个过程:

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

  重新编译并运行内核:

1 make clean
2 make defconfig 
3 make -j$(nproc) 
4 qemu-system-x86_64 -kernel arch/x86/boot/bzImage

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

 

 实验总结

  在本次的实验中,通过对源码特别是嵌入式汇编代码的理解,让我从更底层的层面学习了进程的调度和切换过程,操作系统不再只是一个抽象的概念,加深了理解。

 

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