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

Posted 刹那很好

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于mykernel 2.0编写一个操作系统内核相关的知识,希望对你有一定的参考价值。

实验基于https://github.com/mengning/mykernel完成

一.配置虚拟机QEMU

安装过程不再阐述,参考上方链接即可

为了使得qemu能够正常进行debug,需要设置相关内核选项

# 打开debug相关选项

# 关闭KASLR,否则会导致打断点失败 Processor type and features ---->

 

之后终端输入make,编译。

接下来配置内存根文件系统,需要用到busybox。

首先,取消busybox的动态链接,然后编译,将编译后_install下的文件,以及dev目录下的文件打包制作根文件系统。

由于默认的内核命令行上有 init=/linuxrc, 因此,在文件系统被挂载后,运行的第一个程序是根目录下的 linuxrc。 这是一个指向/bin/busybox 的链接,也就是说,系统起来后运行的

第一个程序也就是 busybox 本身。

启动qemu,

二,代码具体分析

首先定义进程控制块PCB

PCB主要包含下面几部分的内容:

1. 进程的描述信息,比如进程的名称,pid,

2. 处理机的状态信息,当程序中断是保留此时的信息,以便CPU返回时能从断点执行

3. 进程调度信息,比如进程状态,优先级等等

4. 进程控制和资源占用,同步通信机制,链接指针(指向队列中下一个进程的PCB地址)

 

 

 

 1 typedef struct PCB{
 2     int pid;//进程id
 3     //进程状态
 4     volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
 5     char stack[KERNEL_STACK_SIZE];//每个进程都有自己独立的栈空间
 6     /* CPU-specific state of this task */
 7     struct Thread thread;//线程
 8     unsigned long   task_entry;//函数入口地址
 9     struct PCB *next;//下一个进程控制块
10 }tPCB;

接下来定义线程

struct Thread {
    unsigned long       ip;//指向的是函数地址
    unsigned long       sp;//指向栈底
};

由线程的定义可见,线程自己不拥有自己的地址空间,它使用的是进程的栈,也就是线程和进程共享数据。

 

接下来初始化所有的pcb,每个进程的pcb的入口地址,以及线程的ip,其值都是my_process函数的地址。

int pid = 0;//0号进程
    int i;
    /* Initialize process 0*/
    task[pid].pid = pid;
    task[pid].state = 0;/* -1 unrunnable, 0 runnable, >0 stopped */
    //任务入口,即my_process函数的地址
    task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process;//把my_process函数的地址赋给了ip
    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].state = -1;
        task[i].thread.sp = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-1];//sp指向栈底
        task[i].next = task[i-1].next;//当前的next指向上一个,上一个的next指向当前。最后的pcb的next指向第0个pcb,形成环形
        task[i-1].next = &task[i];
    }

 

接下来,准备进程切换/调用

栈帧调整: 
1.将调用者的ebp压栈处理,保存指向栈底的ebp的地址(方便函数返回之后的现场恢复),此时esp指向新的栈顶位置; push ebp
2.将当前栈帧切换到新栈帧(将ebp值装入esp,更新栈帧底部), 这时ebp指向栈顶,而此时栈顶就是old esp ,mov esp, ebp
3.之后将my_process的地址入栈,ret执行后rip保存my_process的地址,之后就会进入这个函数
asm volatile(
        "movq %1,%%rsp\\n\\t"  /* set task[pid].thread.sp to rsp */
        "pushq %1\\n\\t"          /* push rbp */
        "pushq %0\\n\\t"          /* push task[pid].thread.ip */
        "ret\\n\\t"              /* pop task[pid].thread.ip to rip */
        :
        : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp)   /* input c or d mean %ecx/%edx*/
    );

 

 

 

进程切换的过程与上面的过程类似,都是先保存当前栈底rbp,这是为了之后的返回,

接着调整把当前线程thread的ip,和sp保存到pcb中

然后下一个pcb的ip,即函数地址,压栈,因为rip无法直接操作

然后rsp指向了新的堆栈的栈顶

asm volatile(    
            "pushq %%rbp\\n\\t"         /* save rbp of prev */
            "movq %%rsp,%0\\n\\t"     /* save rsp of prev */
            "movq %2,%%rsp\\n\\t"     /* restore  rsp of next */
            "movq $1f,%1\\n\\t"       /* save rip of prev */    
            "pushq %3\\n\\t" 
            "ret\\n\\t"                 /* restore  rip of next */
            "1:\\t"                  /* next process start here */
            "popq %%rbp\\n\\t"
            : "=m" (prev->thread.sp),"=m" (prev->thread.ip)
            : "m" (next->thread.sp),"m" (next->thread.ip)
        ); 

为了清楚知道rsp,rbp的变化情况,根据vscode的调试,方便起见,把stack的size调整到8*8

一开始,4个pcb的stack全都是空,那么在运行上述代码的时候,当rsp指向next的stack栈顶时,stack全空,如图

 

那么当执行popq %%rbp的时候, rsp-8的位置,也就是本该存放rbp的位置,全0,那么在弹栈的时候,rbp的值应该也为全0

为了验证rbp的值到底是多少,修改popq %2,也就是弹栈到next->thread.sp

 

不知道什么原因,therad.sp的值在前后并未发生改变。。。

本来以为popq可能没有执行,但是将popq %%rbp删除后,第一次循环正常运行,当再次循环到0号pcb的时候就发生错误了

 但是可以看到新堆栈的值已经改变

 

后来查询,在vscode调试控制台输入-exec info registers可以直接查询寄存器的值

在进入进程切换前

 

rbp=0xffffffff82b57b00,rsp=0xffffffff82b5bb3f

切成切换后:

 

 

 rsp的值确确实实被改变了,但是,rbp的值并没有变化(疑惑

以上是关于基于mykernel 2.0编写一个操作系统内核的主要内容,如果未能解决你的问题,请参考以下文章

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

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

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

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

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

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