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


    1.实验要求

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

    2.实验步骤

    (本次实验在Ubuntu 16.0.4环境下进行)

    2.1 安装和编译

    首先根据 https://github.com/mengning/mykernel 中所说,输入以下命令下载 linux-5.4.34 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
     sudo apt install qemu
     make defconfig
     make -j$(nproc)

    之后输入

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

    即可运行起内核,运行结果是一个不断循环的打印

    进入mykernel文件夹下,能看到的C文件就是这两个:mymain.c 和 myinterrupt.c,打开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的循环处),就会产生交替打印的效果。

    2.2 完善内核的进程切换部分

     在 https://github.com/mengning/mykernel 中,可以看到 mypcb.h 这一文件,即进程控制块相关。想要完善内核的进程切换,首先需要实现我们自己的进程控制块(此处直接copy的孟宁老师的代码)

    #define MAX_TASK_NUM        4
    #define KERNEL_STACK_SIZE   1024*2
    /* CPU-specific state of this task */
    struct Thread {
        unsigned long        ip;
        unsigned long        sp;
    };
    
    typedef struct PCB{
        int pid;
        volatile long state;    /* -1 unrunnable, 0 runnable, >0 stopped */
        unsigned long stack[KERNEL_STACK_SIZE];
        /* CPU-specific state of this task */
        struct Thread thread;
        unsigned long    task_entry;
        struct PCB *next;
    }tPCB;
    
    void my_schedule(void);

    可以看到进程拥有三种状态:unrunnable、runnable和stopped,每个进程都拥有自己的堆栈,并由ip、sp(对应eip寄存器和esp寄存器)进行控制。pcb块间以链表的形式串联起来

    接下来修改mymain.c

    #include <linux/types.h>
    #include <linux/string.h>
    #include <linux/ctype.h>
    #include <linux/tty.h>
    #include <linux/vmalloc.h>
    
    
    #include "mypcb.h"
    
    tPCB task[MAX_TASK_NUM];
    tPCB * my_current_task = NULL;
    volatile int my_need_sched = 0;
    
    void my_process(void);
    
    
    void __init my_start_kernel(void)
    {
        int pid = 0;
        int i;
        /* Initialize process 0*/
        task[pid].pid = pid;
        task[pid].state = 0;/* -1 unrunnable, 0 runnable, >0 stopped */
        task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process;
        task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1];
        task[pid].next = &task[pid];
        /*fork more process */
        for(i=1;i<MAX_TASK_NUM;i++)
        {
            memcpy(&task[i],&task[0],sizeof(tPCB));
            task[i].pid = i;
            task[i].thread.sp = (unsigned long)(&task[i].stack[KERNEL_STACK_SIZE-1]);
            task[i].next = task[i-1].next;
            task[i-1].next = &task[i];
        }
        /* start process 0 by task[0] */
        pid = 0;
        my_current_task = &task[pid];
        asm volatile(
            "movq %1,%%rsp
    	"     /* set task[pid].thread.sp to rsp */
            "pushq %1
    	"             /* push rbp */
            "pushq %0
    	"             /* push task[pid].thread.ip */
            "ret
    	"                 /* pop task[pid].thread.ip to rip */
            : 
            : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp)    /* input c or d mean %ecx/%edx*/
        );
    } 
    
    int i = 0;
    
    void my_process(void)
    {    
        while(1)
        {
            i++;
            if(i%10000000 == 0)
            {
                printk(KERN_NOTICE "this is process %d -
    ",my_current_task->pid);
                if(my_need_sched == 1)
                {
                    my_need_sched = 0;
                    my_schedule();
                }
                printk(KERN_NOTICE "this is process %d +
    ",my_current_task->pid);
            }     
        }
    }

    新添加的my_start_kernel函数用来执行初始化工作。它初始化了MAX_TASK_NUM个进程控制块,然后内嵌了汇编代码,这段代码会把pid为0的进程的sp和ip写入寄存器

    完善了mymain.c后,继续完善myinterrupt.c

    #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变量值,至此进程调度完毕,切换到了新进程运行

    make clean,再make一遍,再次运行时能够看到完善后的效果

     

  • 相关阅读:
    Application.Current的使用
    .NET中资源文件的使用
    PMP模拟试题与解析(七)
    PMP模拟试题与解析(四)
    RMAN命令简介
    数据库备份和恢复概述
    ORA-14402: updating partition key column would cause a partition change
    RMAN概述
    PLS-00642: local collection types not allowed in SQL statements
    SFTP(Secure File Transfer Protocol)安全的文件传输协议的使用
  • 原文地址:https://www.cnblogs.com/cccc2019fzs/p/12866417.html
Copyright © 2020-2023  润新知