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

    实验环境:

    Ubuntu 16.0.4

    1. 搭建基于mykernel虚拟⼀个x86-64的CPU硬件平台

        包括下载mykernel补丁、linux5.4.34内核代码、依赖库文件和qemu模拟器,给linux内核打上mykernel的补丁等等,ppt上已经有详细的命令,逐条运行即可,这里不再赘述。值得一提的是,从github上clone速度还是不太稳定,因此我将这一项目搬运到了码云上,可以:

    git clone https://gitee.com/dexttter/mykernel.git

    获取整个仓库。总而言之,经过以上的步骤后,我们可以使用qemu加载我们的mykernel的启动镜像:

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

          运行结果:

       

          从qemu的窗口我们可以看到:my_start_kernel在持续打印,同时my_timer_handler也在周期性打印。

      

    2. 分析源码

        接着上面,我们来关注下my_timer_handler和my_timer_handler这两条打印信息的源头。

        进入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); 
    
        }  
    
    }

         这是我们mykernel的系统入口,一个死循环,每当计数器等于整100000时打印一条信息。

        myinterrupt.c

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

        更为简洁,只要它被调用就打印my_timer_handler的函数。获取时钟中断、进入时钟中断linux的内核已经帮我们完成,相当于屏蔽了这些细节。因此我们只需要在这里完成我们在中断时想处理的工作即可——也就是我们要完成的进程切换。

    3. 完成进程切换代码

        参照老师的范本,我们完成了以下工作:

        3.1   完成进程控制块这一数据结构的定义和基本操作——mypcb.h

    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];
    
        struct Thread thread;
    
        unsigned long  task_entry;
    
        struct PCB *next;
    
    }tPCB;
    void my_schedule(void);

       ip和sp就是eip寄存器和esp寄存器,我们要完成进程的切换,也即进程栈帧的切换,实际上就是把rsp指向即将运行进程栈帧的栈顶.rbp指向栈底。

       此外,在pcb这一结构体中,我们还有pid——进程id,state——进程的三状态、task_entry——入口以及next——指向进程链表中的下一进程的指针。

        3.2   完成mymain.c

    my_start_kermel函数:
    
    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*/
    
              );

        它完成了:初始化0号进程:包括初始化pid,状态,入口,ip和sp等等。然后,我们fork了更多的进程,并加入进程管理链表,这是实现进程切换的前提。然后我们运行0号进程,它主要是由这段嵌入式的汇编代码实现的,我们重点关注下:

    首先明确:%0指代task[pid].thread.ip,%1指代task[pid].thread.sp。

    首先将sp的值赋给rsp,然后将rbp压栈(这里0号进程的栈帧为空,rsp和rbp相等),然后将0号程的ip压栈,return操作完成对eip的pop操作,使得rip = ip=myprocess,开始执行myprocess。

        my_process函数:

     当计数器my_need_sched=1时,我们运行my_schedule函数(在myinterrupt.c中,下面会提到)完成进程的切换

        3.3   完成myinterrupt.c

    my_timer_handler:
    
    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;   
    
    }

        当tim_count这个变量为1000,且my_need_sched!=1时我们把my_need_sched置为1,然后会调用my_schedule,这是进程切换的核心,它也是由一段汇编代码实现的:

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

    它完成的工作可以用上面这张图来解释:

    1. 保存push rbp,将prev进程的rbp的值压栈
    2. 把rsp的值保存到它自己的sp变量中
    3. 之后将next进程的sp变量中的值赋给rsp寄存器(完成栈帧的转换)
    4. 并保存prev进程的rip
    5. 随后压栈next进程的ip,也即$1(这里已经是在next的栈帧中操作了)
    6. 这之后return, 也就是pop next进程的rip,执行next进程也就是$1后面的语句
    7. pop rbp,将rbp指向next进程的栈底

    至此,进程切换彻底完成,rbp和rsp都分别指向了next进程的栈底和栈顶

    4. make后重新运行mykernel

      完成了进程的切换

  • 相关阅读:
    命名之法 —— 男名、女名、家族(古诗词与古典名著)
    findContours函数参数详解
    推理集 —— 特殊的时间
    推理集 —— 特殊的时间
    分蛋糕问题 —— 9 个烧饼分给 10 个人
    分蛋糕问题 —— 9 个烧饼分给 10 个人
    辩论之术
    辩论之术
    findContours 轮廓查找
    坚持是一种品行
  • 原文地址:https://www.cnblogs.com/hhssqq9999/p/12860497.html
Copyright © 2011-2022 走看看