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

1.构建并运行初始Linux系统内核

在Ubuntu的终端依次执行,如下命令。

wget https://raw.github.com/mengning/mykernel/master/mykernel-2.0_for_linux-5.4.34.patch
sudo apt install axel
axel -n 20 https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/linux-5.4.34.tar.xz
xz -d linux-5.4.34.tar.xz
tar -xvf linux-5.4.34.tar
cd linux-5.4.34
patch -p1 < ../mykernel-2.0_for_linux-5.4.34.patch
sudo apt install build-essential libncurses-dev bison flex libssl-dev libelf-dev
make defconfig # Default configuration is based on 'x86_64_defconfig'
make -j$(nproc)
sudo apt install qemu # install QEMU
qemu-system-x86_64 -kernel arch/x86/boot/bzImage

make编译成功后,可以看到如下输出:

启动成功后,可以在qemu窗口看到,如下输出打印信息。

 2.初始输出状态分析

此时的输出来自于,mymain.c和myinterrupt.c。

可以清楚地看到,my_start_kernel函数,会循环打印输出"my_start_kernel here %d"信息。

 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 }

my_timer_handler也会不断被时钟中断触发,而输出>>>>>>>>>>>>>>>>>my_timer_handler here<<<<<<<<<<<<<<<<<<

1 //myinterrupt.c
2 void my_timer_handler(void)
3 {
4     pr_notice("\n>>>>>>>>>>>>>>>>>my_timer_handler here<<<<<<<<<<<<<<<<<<\n\n");
5 }

3.进程调度实现

1)添加pcb结构体

在mykernel路径下,创建一个mypcb.h文件。

具体代码以及各字段的含义如下,

 1 #define MAX_TASK_NUM        4
 2 #define KERNEL_STACK_SIZE   1024*2
 3 /* CPU-specific state of this task */
 4 struct Thread {
 5     unsigned long        ip;  //指令指针
 6     unsigned long        sp;  //堆顶指针
 7 };
 8 
 9 typedef struct PCB{
10     int pid; //进程id
11     volatile long state;    /*进程状态 -1 unrunnable, 0 runnable, >0 stopped */
12     unsigned long stack[KERNEL_STACK_SIZE];//进程关联的栈
13     /* CPU-specific state of this task */
14     struct Thread thread; //进程持有的线程
15     unsigned long    task_entry;  //进程执行任务入口
16     struct PCB *next;   //指向下一个PCB的指针,所有PCB利用该指针形成一个循环单链表
17 }tPCB;
18 
19 void my_schedule(void);

2)修改mymain.c的my_start_kernel函数

具体代码如下, 

 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].thread.sp = (unsigned long)(&task[i].stack[KERNEL_STACK_SIZE-1]);
33         task[i].next = task[i-1].next;
34         task[i-1].next = &task[i];
35     }
36     /* start process 0 by task[0] */
37     pid = 0;
38     my_current_task = &task[pid];
39     asm volatile(
40         "movq %1,%%rsp\n\t"     /* set task[pid].thread.sp to rsp */
41         "pushq %1\n\t"             /* push rbp */
42         "pushq %0\n\t"             /* push task[pid].thread.ip */
43         "ret\n\t"                 /* pop task[pid].thread.ip to rip */
44         : 
45         : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp)    /* input c or d mean %ecx/%edx*/
46     );
47 } 
48 
49 int i = 0;
50 
51 void my_process(void)
52 {    
53     while(1)
54     {
55         i++;
56         if(i%10000000 == 0)
57         {
58             printk(KERN_NOTICE "this is process %d -\n",my_current_task->pid);
59             if(my_need_sched == 1)
60             {
61                 my_need_sched = 0;
62                 my_schedule();
63             }
64             printk(KERN_NOTICE "this is process %d +\n",my_current_task->pid);
65         }     
66     }
67 }

此时my_start_kernel执行的功能是,创建0号进程以及其他进程,并将CPU运行控制权交付给0号进程。

其中创建进程代码较为直接,因此不予分析。只看一下交付CPU运行控制权的相关代码,即39-46L的汇编。

第40行:将0号进程持有线程的初始栈顶地址存入RSP寄存器中,此后所有的push和pop操作都是针对进程0的栈进行操作。

第41行:将进程0的当前栈顶地址压入栈中,因为此时栈为空栈,所以相当于压入栈底地址。

第42-43行:将进程0的初始指令地址压入栈中,并利用ret指令,将栈顶的初始指定地址出栈并赋给rip寄存器。

经过这四条指令,就可以从进程0的初始指令地址开始执行。

3)修改myinterrupt.c的my_timer_handler函数并实现具体的my_schedule调度函数

具体代码如下,

 1 /*
 2  * Called by timer interrupt.
 3  * it runs in the name of current running process,
 4  * so it use kernel stack of current running process
 5  */
 6 void my_timer_handler(void)
 7 {
 8     if(time_count%1000 == 0 && my_need_sched != 1)
 9     {
10         printk(KERN_NOTICE ">>>my_timer_handler here<<<\n");
11         my_need_sched = 1;
12     } 
13     time_count ++ ;  
14     return;      
15 }
16 
17 void my_schedule(void)
18 {
19     tPCB * next;
20     tPCB * prev;
21 
22     if(my_current_task == NULL 
23         || my_current_task->next == NULL)
24     {
25         return;
26     }
27     printk(KERN_NOTICE ">>>my_schedule<<<\n");
28     /* schedule */
29     next = my_current_task->next;
30     prev = my_current_task;
31     if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */
32     {        
33         my_current_task = next; 
34         printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);  
35         /* switch to next process */
36         asm volatile(    
37             "pushq %%rbp\n\t"         /* save rbp of prev */
38             "movq %%rsp,%0\n\t"     /* save rsp of prev */
39             "movq %2,%%rsp\n\t"     /* restore  rsp of next */
40             "movq $1f,%1\n\t"       /* save rip of prev */    
41             "pushq %3\n\t" 
42             "ret\n\t"                 /* restore  rip of next */
43             "1:\t"                  /* next process start here */
44             "popq %%rbp\n\t"
45             : "=m" (prev->thread.sp),"=m" (prev->thread.ip)
46             : "m" (next->thread.sp),"m" (next->thread.ip)
47         ); 
48     }  
49     return;    
50 }

其中my_timer_handler函数的功能为周期性将my_need_sched置为1,标志进程需要进程调度。实际的调度代码为my_schedule函数。

该函数执行的具体任务为保存当前进程(prev)的上下文,并调出下一个进程(next)的上下文。核心代码为36L开始的汇编。

37L:将当前进程的栈底指针压入栈中,保存其状态。

38L:将rsp寄存器的值保存到prev->thread.sp中

39L:将rsp寄存器的值更新为下一进程的栈顶地址,实现进程操作栈的切换。

40L:将43行指令地址保存到当前进程持有线程的指令地址中。指定该进程重新被调入时,开始执行指令的位置。

41-42L:将下一进程的ip地址入栈并ret,从而来更新rip寄存器的值。如果下一进程之前运行过,此时rip寄存器的值便是之前保存的43L地址。

43L:将下一进程之前被调出时在37L保存的栈底地址出栈,赋值给rbp寄存器。 

 4)运行结果

posted @ 2020-05-11 01:40  fiveFish  阅读(185)  评论(0)    收藏  举报