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

    实验要求

    1、按照 https://github.com/mengning/mykernel 的说明配置mykernel 2.0,熟悉Linux内核的编译;

    2、基于mykernel 2.0编写一个操作系统内核,参照 https://github.com/mengning/mykernel 提供的范例代码;

    3、简要分析操作系统内核核心功能及运行工作机制。


    实验环境及配置

    VMware® Workstation 15 Pro

    Ubuntu 16.04.3 LTS

    一、配置 mykernel 2.0

    1、配置mykernel 2.0的开发环境

    在Linux终端中依次输入以下命令:

    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 gcc-multilib  libncurses5-dev bison flex libssl-dev libelf-dev
    sudo apt install qemu 

    命令、工具解释分析如下:

    (1)wget    

        非交互式网络文件下载工具,用于下载软件或从远程服务器恢复备份到本地服务器。

        此处从指定github网站上下载 mykernel-2.0_for_linux-5.4.34.patch到本地。

    (2)axel

        多线程下载工具,可以进行批量下载。

        第2条命令是安装axel工具,第3条命令是打开20个连接下载linux-5.4.34.tar.xz到本地。

    (3)xz

        xz命令用于解压“.xz”文件。

        此处观察到linux-5.4.34.tar.xz,因此使用xz命令将其解压到当前文件夹,得到linux-5.4.34.tar。

    (4)tar

        tar命令用于将打包后的文件解包。

        对linux-5.4.31.tar解包后,得到linux-5.4.34文件夹。

        进入到该文件夹中,方便进入后续操作。

    (5)patch

        patch命令用于修补文件,让用户利用修补文件的方式进行原始文件的修改、更新等。

        因此第7条命令是对第一步下载好的mykernel-2.0_for_linux-5.4.34.patch文件进行更新。

    (6)build-essential

        build-essential是一个包,里面开发包含很多开发必要的软件包。

        第8条命令是安装保证可以对mykernel 2.0的运行和操作所必要的包。

    (7)qemu

        qemu是一个本身是一个强大的虚拟机,相当于是一个沙盒环境,可以运行自己编译的内核。

        第9条命令为安装该qemu工具。

    2、对内核进行编译

    在Linux终端中依次输入以下命令:

    make defconfig 
    make -j$(nproc)

    命令解释分析如下:

    (1)make defconfig

        生成内核编译,按照默认的i386_defconfig生成.config。

    (2)make -j$(nproc)

        make命令后的-j参数可以加快编译速度。

    编译完成后,结果如下:

    3、启动qemu,在窗口中观察执行结果

    在linux终端输入如下命令:

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

    执行结果如下:

    qemu窗口输出的内容是由 linux-5.3.34内核源代码根目录下mykernel目录中的mymain.c和myinterrupt.c决定的。

     代码分析如下:

    (1)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);
                
        }
    }

         当我们提供了一个虚拟的CPU执行C代码的上下文环境时,mymain.c中的代码就在不停的执行,即是不停地输出“my_start_kernel here i”。

    (2)myinterrupt.c

        主要代码如下:

    /*
     * Called by timer interrupt.
     */
    void my_timer_handler(void)
    {
        pr_notice("
    >>>>>>>>>>>>>>>>>my_timer_handler here<<<<<<<<<<<<<<<<<<
    
    ");
    }

        同时我们也提供了一个中断处理程序的上下文环境,周期性地产生时钟中断信号,触发了myinterrupt.c中代码的执行,即不停的输出“my_time_handler here"。

    二、基于 mykernel 2.0 编写一个操作系统内核——进程切换的实现

    代码来自:https://github.com/mengning/mykernel

    1、定义进程控制块(mypcb.h)

        在linux-5.4.34/mykernel目录中新增一个名为 mypcb.h 头文件,用于定义进程控制块(Processing Control Block,PCB)。

        进程控制块主要表示进程状态,操作系统根据PCB来对并发执行的进程进行控制和管理,PCB包含的信息主要包括:

    • 进程id:用于区别进程
    • 进程的状态:就绪、运行、挂起、停止
    • 进程切换时需要保存和恢复的一些CPU寄存器
    • 描述虚拟地址空间的信息:主要是描述了虚拟地址和物理地址的对应关系
    • 当前工作目录
    • umask掩码:保护文件创建和修改的权限
    • ……

        代码如下:

    #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);

    代码分析如下:

        结构体PCB定义了进程控制块中的各个信息:

    变量名 含义 解释
    pid 进程id 用于区分进程
    state 进程状态

    此处定义了三种状态,分别用-1、0、>0来表示

    初始化值为-1,当被调度运行时,变为0,当被阻塞时,变为>0。

    stack[] 进程使用的堆栈  
    thread 当前正在执行的线程信息  
    task_entry 进程入口函数  
    next 指向下一个进程PCB的指针 所有进程的PCB以链表的形式存储

        结构体Thread用于存储函数入口指针ip和栈顶指针sp。

        函数my_schedule(void)是一个进程调度函数。

    2、修改mymain.c

        mymain.c是mykernel内核代码的入口,负责初始化内核的各个组成部分。

        代码如下:

    #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);
            }     
        }
    }

        代码分析如下:

        __init my_start_kernel(void):初始化多个进程并且启动0号进程

        myprocess(void):模拟进程执行过程,此处采用的是时间片轮转方法进行进程的调度,即进程运行完一个时间片后主动让出CPU。

        

    3、修改myingerrupt.c

        mykernel提供了时钟中断机制,即通过周期性地执行my_time_handler函数来中断处理程序,因此我们就对该函数进行修改来记录时间片,当时间片消耗完时就进行进程的切换。

        代码如下:

    #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(void)函数:主要进行进程的切换。

    4、执行结果

        执行以下代码重新编译linux内核,并启动qemu来展示修改后代码的执行结果:

    make defconfig 
    make -j$(nproc)
    qemu-system-x86_64 -kernel arch/x86/boot/bzImage

        执行结果如下所示:

        process0 - process3 分别进行切换。

    三、操作系统内核核心功能及工作机制

    1、操作系统内核核心功能

        内核是操作系统的核心部分,通常运行进程,并提供进程间的通信,主要管理系统的进程、内存、设备驱动程序、文件和网络系统。

    2、内核工作机制

        中断和系统调用

    四、实验心得体会

        通过本次实验,我了解了计算机系统的基本工作原理,对操作系统的内核有了更加深入的认识,对进程上下文的切换原理和实现代码也有了一定的了解,为之后的Linux操作系统的学习也打下了一定的基础。

        

  • 相关阅读:
    CSUFT 1002 Robot Navigation
    CSUFT 1003 All Your Base
    Uva 1599 最佳路径
    Uva 10129 单词
    欧拉回路
    Uva 10305 给任务排序
    uva 816 Abbott的复仇
    Uva 1103 古代象形文字
    Uva 10118 免费糖果
    Uva 725 除法
  • 原文地址:https://www.cnblogs.com/liujianing0421/p/12878727.html
Copyright © 2011-2022 走看看