zoukankan      html  css  js  c++  java
  • 基于mykernel 2.0编写一个操作系统内核--课程实验1

    一、配置mykernel 2.0  

    按照 https://github.com/mengning/mykernel 的说明配置mykernel 2.0,熟悉Linux内核的编译;
    环境:VMware Workstation 15 pro ,Ubuntu 18.04.4 LTS;

    1. 配置前先拍快照,保存当前状态备用。

    2. 按照 https://github.com/mengning/mykernel 的说明配置mykernel 2.0,执行以下命令。

    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

           执行完上述代码后,可以看到QEMU窗口中my_start_kernel进程周期性的调用my_handler_here方法,如下图所示。

            在Linux-5.3.34 内核源代码根目录下进入mykernel目录,可以看到QEMU窗口输出的内容的代码 mymain.c 和 myinterrupt.c ,当前有一个虚拟的CPU执行C代码的上下文环境,可以看到 mymain.c 中的代码在不停地执行。同时有一个中断处理程序的上下文环境,周期性地产生的时钟中断信号,能够触发myinterrupt.c中的代码。这样就通过Linux内核代码模拟了一个具有时钟中断和C代码执行环境的硬件平台。

    void __init my_start_kernel(void)
    {
        int i = 0;
        while(1)
        {
            i++;
            if(i%100000 == 0)
                printk(KERN_NOTICE "my_start_kernel here  %d 
    ",i);
        }
    }

            观察上述代码可知:每当 i 能够被 100000 整除 就输出 “my_start_kernel here” ,即相当于一个时钟信号。下面的代码就是进行处理时钟中断的。

    void my_timer_handler(void)
    {
        printk(KERN_NOTICE "
    >>>>>>>>>>>>>>>>>my_timer_handler here<<<<<<<<<<<<<<<<<<
    
    ");
    }

    二、编写一个操作系统内核并简要分析

      参照 https://github.com/mengning/mykernel 提供的范例代码,基于mykernel 2.0编写一个操作系统内核。
      在 https://github.com/mengning/mykernel 中 下载 mypcb.h,myinterrupt.c和mymain.c文件,并拷贝到本机的 mykernel 目录下,并新增头文件mypcb.h,修改好文件后重新配置编译内核,并使用QEMU加载,结果如下图:

    make allnoconfig
    make 
    qemu -kernel arch/x86/boot/bzImage

      观察可知上图中正在进行进程的切换:进程2 切换到 进程3,进程切换的关键代码是一段嵌入式汇编,最有技巧性的地方是通过pushq %rip和ret指令来间接修改%rip的值,从而更改代码执行流,再配合%rsp和%rbp的修改切换进程的工作栈,从而达到切换进程的目的。

      简单分析:

      首先在mykernel目录下增加一个mypcb.h 头文件,用来定义进程控制块PCB(Process Control Block),也就是进程结构体的定义,在Linux内核中是struct tast_struct结构体。

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

      其中:pid表示进程号;state表示进程状态,在模拟系统中,所有进程控制块信息都会被创建出来,其初始化值就是-1,如果被调度运行起来,其值就会变成0;stack是进程使用的堆栈,栈大小为1024*8;task_entry为进程入口函数;*next:指向下一个PCB,此模拟系统中的PCB是以链表的形式组织起来的;函数的声明 my_schedule,它的实现在my_interrupt.c中,在mymain.c中的各个进程函数会根据一个全局变量的状态来决定是否调用它,从而实现主动调度。

      对mymain.c进行修改,初始化各个进程并启动0号进程,这里是mykernel内核代码的入口,负责初始化内核的各个组成部分。在Linux内核源代码中,实际的内核入口是init/main.c中的start_kernel(void)函数。

     1 /*
     2  *  linux/mykernel/mymain.c
     3  */
     4  
     5 #include "mypcb.h"
     6 
     7 tPCB task[MAX_TASK_NUM];
     8 tPCB * my_current_task = NULL;
     9 volatile int my_need_sched = 0;
    10 
    11 void my_process(void);
    12 
    13 void __init my_start_kernel(void)
    14 {
    15     int pid = 0;
    16     int i;
    17     /* Initialize process 0*/
    18     task[pid].pid = pid;
    19     task[pid].state = 0; /* -1 unrunnable, 0 runnable, >0 stopped */
    20     task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process;
    21     task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1];
    22     task[pid].next = &task[pid];
    23     /*fork more process */
    24     for(i=1;i<MAX_TASK_NUM;i++)
    25     {
    26         memcpy(&task[i],&task[0],sizeof(tPCB));
    27         task[i].pid = i;
    28         task[i].state = -1;
    29         task[i].thread.sp = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-1];
    30         task[i].next = task[i-1].next;
    31         task[i-1].next = &task[i];
    32     }
    33     /* start process 0 by task[0] */
    34     pid = 0;
    35     my_current_task = &task[pid];
    36     asm volatile(
    37         "movq %1,%%rsp
    	"  /* set task[pid].thread.sp to rsp */
    38         "pushq %1
    	"          /* push rbp */
    39         "pushq %0
    	"          /* push task[pid].thread.ip */
    40         "ret
    	"              /* pop task[pid].thread.ip to rip */
    41         :
    42         : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp)   /* input c or d mean %ecx/%edx*/
    43     );
    44 }

      进程调度为一个环形队列,每次创建新的进程时,都将该进程插入到队尾,然后将该进程指向第一个进程。

      在mymain.c中添加了my_process函数,用来作为进程的代码模拟一个个进程,只是我们这里采用的是进程运行完一个时间片后主动让出CPU的方式(简单的时间片轮转方式的进程切换),没有采用中断的时机完成进程切换,因为中断机制实现起来较为复杂,等后续部分再逐渐深入。

     1 void my_process(void)
     2 {
     3     int i = 0;
     4     while(1)
     5     {
     6         i++;
     7         if(i%10000000 == 0)
     8         {
     9             printk(KERN_NOTICE "this is process %d -
    ",my_current_task->pid);
    10             if(my_need_sched == 1)
    11             {
    12                 my_need_sched = 0;
    13                 my_schedule();
    14             }
    15             printk(KERN_NOTICE "this is process %d +
    ",my_current_task->pid);
    16         }
    17     }
    18 }

      进程运行过程中是怎么知道时间片消耗完了呢?这就需要时钟中断处理过程中记录时间片。对myinterrupt.c中修改my_timer_handler用来记录时间片。

     1 /*
     2  *  linux/mykernel/myinterrupt.c
     3  */
     4 #include "mypcb.h"
     5 
     6 extern tPCB task[MAX_TASK_NUM];
     7 extern tPCB * my_current_task;
     8 extern volatile int my_need_sched;
     9 volatile int time_count = 0;
    10 
    11 /*
    12  * Called by timer interrupt.
    13  */
    14 void my_timer_handler(void)
    15 {
    16     if(time_count%1000 == 0 && my_need_sched != 1)
    17     {
    18         printk(KERN_NOTICE ">>>my_timer_handler here<<<
    ");
    19         my_need_sched = 1;
    20     }
    21     time_count ++ ;
    22     return;
    23 }

      对myinterrupt.c进行修改,主要是增加了进程切换的代码my_schedule(void)函数,在Linux内核源代码中对应的是schedule(void)函数。

     1 void my_schedule(void)
     2 {
     3     tPCB * next;
     4     tPCB * prev;
     5 
     6 
     7     if(my_current_task == NULL
     8         || my_current_task->next == NULL)
     9     {
    10       return;
    11     }
    12     printk(KERN_NOTICE ">>>my_schedule<<<
    ");
    13     /* schedule */
    14     next = my_current_task->next;
    15     prev = my_current_task;
    16     if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */
    17     {
    18       my_current_task = next;
    19       printk(KERN_NOTICE ">>>switch %d to %d<<<
    ",prev->pid,next->pid);
    20       /* switch to next process */
    21       asm volatile(
    22          "pushq %%rbp
    	"       /* save rbp of prev */
    23          "movq %%rsp,%0
    	"     /* save rsp of prev */
    24          "movq %2,%%rsp
    	"     /* restore  rsp of next */
    25          "movq $1f,%1
    	"       /* save rip of prev */
    26          "pushq %3
    	"
    27          "ret
    	"               /* restore  rip of next */
    28          "1:	"                  /* next process start here */
    29          "popq %%rbp
    	"
    30         : "=m" (prev->thread.sp),"=m" (prev->thread.ip)
    31         : "m" (next->thread.sp),"m" (next->thread.ip)
    32       );
    33     }
    34     return;
    35 }

      my_time_handler中断处理程序,该函数每隔1000 判断 my_need_sched 是否不等于1,如果是则将其置为1,使 myprocess 执行 my_schedule() 。my_schedule 函数在进程队列中选择下一个要执行的进程;对于处于不同状态的进程,调度方式也不同,如果即将上CPU的进程之前已经运行过(即state为0),我们需要保存当前进程的上下文信息,然后把下一个进程的信息写入到寄存器中,执行 ret 使下一个进程开始执行。之前没有在运行态的(state不为0),我们先将其设置为运行态,我们这里需要初始化其ebp,因为该进程的堆栈是空栈 。

    参考资料:

    1. https://github.com/mengning/mykernel/blob/master/README.md

    2. 计算机系统的基本工作原理

    3. 自己动手写一个操作系统内核

  • 相关阅读:
    第36课 经典问题解析三
    第35课 函数对象分析
    67. Add Binary
    66. Plus One
    58. Length of Last Word
    53. Maximum Subarray
    38. Count and Say
    35. Search Insert Position
    28. Implement strStr()
    27. Remove Element
  • 原文地址:https://www.cnblogs.com/LiScott/p/12871507.html
Copyright © 2011-2022 走看看