zoukankan      html  css  js  c++  java
  • 基于mykernel 2.0编写一个操作系统内核

    1.实验要求

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

    2.实验环境

    3.实验步骤

    • 在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

    注意:第一条命令执行不成功,我直接从孟老师github上下载 

    配置成功便能输入以下命令运行内核

    qemu-system-x86_64 -kernel arch/x86/boot/bzImage

    得到运行结果

    4.简单分析

    有两条输出语句交替打印,进入mymain.c,看到

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

    可知,在循环打印中,my_start_kernel here... ...这条消息是由进程运行mymain.c时进行打印的。再打开myinterrupt.c,能够看到如下代码

    void my_timer_handler(void)
    {
        pr_notice("
    >>>>>>>>>>>>>>>>>my_timer_handler here<<<<<<<<<<<<<<<<<<
    
    ");
    }

    循环打印里的另一段就是由进程执行到此处打印的

    mykernel能够周期性的产生时钟中断,中断处理程序就会调用my_timer_handler函数,调用完成后再返回到原来的上下文中(mymain.c的循环处),就会产生交替打印的效果。

    再看mypcb.h文件

    对于一个进程所涉及的资源和控制信息,我们将其统一置于结构体PCB中。主要字段为进程id、进程状态、函数调用栈、代码入口以及ip、sp等。我们还可以把ip、sp进一步抽象为Thread结构体。每一个PCB都是链表中的一个节点,故还需要一个next字段,这样多个进程可以链接成为进程队列。在结构体PCB中可以看到进程有三种状态:unrunnable、runnable和stopped;此外每个进程都拥有自己的堆栈,并由ip、sp(对应eip寄存器和esp寄存器)进行控制。pcb块间以链表的形式串联起来。

    5.修改mymain.c的my_start_kernel函数

    #include <linux/types.h>
    #include <linux/string.h>
    #include <linux/ctype.h>
    #include <linux/tty.h>
    #include <linux/vmalloc.h>
    
    #include "mypcb.h"
    
    extern tPCB task[MAX_TASK_NUM];
    extern tPCB * my_current_task;
    extern volatile int my_need_sched;
    volatile int time_count = 0;
    
    /*
     * Called by timer interrupt.
     * it runs in the name of current running process,
     * so it use kernel stack of current running process
     */
    void my_timer_handler(void)
    {
        if(time_count%1000 == 0 && my_need_sched != 1)
        {
            printk(KERN_NOTICE ">>>my_timer_handler here<<<
    ");
            my_need_sched = 1;
        } 
        time_count ++ ;  
        return;      
    }
    
    void my_schedule(void)
    {
        tPCB * next;
        tPCB * prev;
    
        if(my_current_task == NULL 
            || my_current_task->next == NULL)
        {
            return;
        }
        printk(KERN_NOTICE ">>>my_schedule<<<
    ");
        /* schedule */
        next = my_current_task->next;
        prev = my_current_task;
        if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */
        {        
            my_current_task = next; 
            printk(KERN_NOTICE ">>>switch %d to %d<<<
    ",prev->pid,next->pid);  
            /* switch to next process */
            asm volatile(    
                "pushq %%rbp
    	"         /* save rbp of prev */
                "movq %%rsp,%0
    	"     /* save rsp of prev */
                "movq %2,%%rsp
    	"     /* restore  rsp of next */
                "movq $1f,%1
    	"       /* save rip of prev */    
                "pushq %3
    	" 
                "ret
    	"                 /* restore  rip of next */
                "1:	"                  /* next process start here */
                "popq %%rbp
    	"
                : "=m" (prev->thread.sp),"=m" (prev->thread.ip)
                : "m" (next->thread.sp),"m" (next->thread.ip)
            ); 
        }  
        return;    
    }

    新增的my_schedule函数是处理进程调度的关键。上文已经说过,pcb块以链表的形式串联起来,my_schedule函数选择进程链表中的下一个就绪进程进行切换。它也内嵌了汇编代码,实现的功能如下:

    1. 保存进程rbp寄存器内的值

    2. 保存进程rsp寄存器内的值

    3. 更新寄存器rsp为next指向的新进程内的sp变量值,此时进行了进程间栈帧的转换

    4. 保存原进程rip寄存器内的值

    5. 更新寄存器rip为新进程的ip变量值,至此进程调度完毕,切换到了新进程运行

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

    /*
     * Called by timer interrupt.
     * it runs in the name of current running process,
     * so it use kernel stack of current running process
     */
    void my_timer_handler(void)
    {
        if(time_count%1000 == 0 && my_need_sched != 1)
        {
            printk(KERN_NOTICE ">>>my_timer_handler here<<<
    ");
            my_need_sched = 1;
        } 
        time_count ++ ;  
        return;      
    }
    
    void my_schedule(void)
    {
        tPCB * next;
        tPCB * prev;
    
        if(my_current_task == NULL 
            || my_current_task->next == NULL)
        {
            return;
        }
        printk(KERN_NOTICE ">>>my_schedule<<<
    ");
        /* schedule */
        next = my_current_task->next;
        prev = my_current_task;
        if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */
        {        
            my_current_task = next; 
            printk(KERN_NOTICE ">>>switch %d to %d<<<
    ",prev->pid,next->pid);  
            /* switch to next process */
            asm volatile(    
                "pushq %%rbp
    	"         /* save rbp of prev */
                "movq %%rsp,%0
    	"     /* save rsp of prev */
                "movq %2,%%rsp
    	"     /* restore  rsp of next */
                "movq $1f,%1
    	"       /* save rip of prev */    
                "pushq %3
    	" 
                "ret
    	"                 /* restore  rip of next */
                "1:	"                  /* next process start here */
                "popq %%rbp
    	"
                : "=m" (prev->thread.sp),"=m" (prev->thread.ip)
                : "m" (next->thread.sp),"m" (next->thread.ip)
            ); 
        }  
        return;    
    }

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

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

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

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

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

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

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

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

    7.重新运行,运行结果

    make defconfig # Default configuration is based on 'x86_64_defconfig'
    make -j$(nproc)
    qemu-system-x86_64 -kernel arch/x86/boot/bzImage

    8.个人收获

    对Linux命令、C语言有一定的复习,了解Linux内核工作的基本原理、多任务的并发执行,了解了中断的产生以及中断在计算机中的重要作用,加深了对Linux系统学习的兴趣。

    参考:https://mp.weixin.qq.com/s/SzpN1BNty5aPDZhNdCO5yA 

  • 相关阅读:
    有点忙啊
    什么是协程
    HDU 1110 Equipment Box (判断一个大矩形里面能不能放小矩形)
    HDU 1155 Bungee Jumping(物理题,动能公式,弹性势能公式,重力势能公式)
    HDU 1210 Eddy's 洗牌问题(找规律,数学)
    HDU1214 圆桌会议(找规律,数学)
    HDU1215 七夕节(模拟 数学)
    HDU 1216 Assistance Required(暴力打表)
    HDU 1220 Cube(数学,找规律)
    HDU 1221 Rectangle and Circle(判断圆和矩形是不是相交)
  • 原文地址:https://www.cnblogs.com/cun-yu/p/12868147.html
Copyright © 2011-2022 走看看