• 基于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 

  • 相关阅读:
    怎樣在不同DB環境生成其它DB的存儲過程
    XML之sql:column用法对性能影响
    XML之sql:variable性能比较
    环回链接服务器
    动态列名数据视图实现
    一起学习win8应用1构建我们的第一个应用
    linux 限制root SSH登陆和限制su
    nginx 直接在配置文章中设置日志分割
    linux建立ssh信任关系
    linux系统meminfo详解(待补充)
  • 原文地址:https://www.cnblogs.com/cun-yu/p/12868147.html
Copyright © 2020-2023  润新知