Linux小白实验之基于mykernel完成多进程的简单内核

#学号后三位:072
原创作品转载请注明出处 + https://github.com/mengning/linuxkernel/

一:实验要求

(1) 完成一个简单的时间片轮转多道程序内核代码
(2) 分析进程的启动和进程的切换机制
(3) 理解操作系统如何工作

二:实验环境

本次实验主要使用实验楼linux内核分析课程线上虚拟机的linux环境来完成,具有环境免配置,使用方便,不消耗主机资源等优点。
实验楼链接:https://www.shiyanlou.com/courses/195

三:实验过程

1. 实验前环境测试

由于本次实验是在实验楼的虚拟机上运行的,MyKernel的实验环境已经部署好了。如果想要在自己的虚拟机上进行实验的,请按照孟老师的部署方案一步步执行即可完成署。
在开始进行正式实验之前,我们先来测试一下。

第一步:打开虚拟机终端,输入以下命令
Linux小白实验之基于mykernel完成多进程的简单内核
成功运行后,会弹出一个QEMU窗口,不断输出字符串:>>>>>my_timer_handler here <<<<< 和 my_start_kernel here
截图如下所示:
Linux小白实验之基于mykernel完成多进程的简单内核
第二步:查看mymain.c 和myinterrupt.c代码
Linux小白实验之基于mykernel完成多进程的简单内核
通过mymain.c文件代码可以看出my_start_kernel函数是一个无限循环函数,不停的输出 my_start_kernel here。
Linux小白实验之基于mykernel完成多进程的简单内核
通过myinterrupt.c文件代码可以看出my_timer_handler函数是一个被时钟中断周期调用的函数,输出>>>>>my_timer_handler here <<<<< 字符串。
第三步:总结
mykernel在启动之后,系统调用my_start_kernel函数,并周期性调用my_timer_handler函数。

2. 进行实验

第一步:获取实验代码
代码主要来自于孟宁老师的GIthub,非常感谢孟老师
代码连接:https://github.com/mengning/mykernel
一共需要拷贝了三个文件,将其放置在实验环境中的LinuxKernel/linux-3.9.4/mykernel中。这三个文件分别是mypcb.h、mymain.c、 myinterrupt.c。
注意:拷贝时采用实验楼虚拟机里自带的剪贴板进行复制粘贴,直接从Window电脑进行复制粘贴拷贝有可能不会成功。
第二步:采用make命令编译运行
运行成功后,截图如下:Linux小白实验之基于mykernel完成多进程的简单内核
从上述图片可以清楚的看到进程2和进程3进行了切换,实验完成。

3. 代码分析

  1. mypcb.h
    Linux小白实验之基于mykernel完成多进程的简单内核
    mypcb.h主要定义了以下内容:
    (1) 最大进程数 MAX_TASK_NUM 即有MAX_TASK_NUM个进程参与内核的时间片轮转调度。
    (2) 内核进程栈的大小 KERNEL_STACK_SIZE 即每一个进程可以使用的堆栈大小。
    (3) 线程Thread 包括指令指针ip和栈顶指针sp。
    (4) 进程控制块PCB 包括进程编号、进程状态state、进程堆栈、线程、任务实体、下一个进程指针。
    (5) 调度函数my_schedule 用于模拟进程执行一段时间后切换到其他进程继续执行的过程。
  2. mymain.c
    Linux小白实验之基于mykernel完成多进程的简单内核
    在mymain.c中,主要负责进程的初始化并启动进程,做了以下三件事
    (1) 初始化一个进程 为其分配进程编号、进程状态state、进程堆栈、线程、任务实体等,并将其next指针指向自己。
    (2) 初始化更多的进程 根据第一个进程的部分资源,包括内存拷贝函数的运用,将0号进程的信息进行了复制,修改pid等信息。
    (3) 设置当前进程 因为是初始化,所以当前进程就决定给0号进程了,通过执行嵌入式汇编代码,开始执行mykernel内核。
    重点分析一下嵌入式汇编代码:
    Linux小白实验之基于mykernel完成多进程的简单内核
    汇编代码是学习Linux内核编程的重点和难点,一定得花时间弄明白代码的含义。在本代码中,"c"就是ecx,内容为task[pid].thread.ip,"d"就是edx,内容为task[pid].thread.sp。从冒号开始,以逗号分隔开,从0开始,每一个寄存器有一个标号,即ecx的标号为%0,edx的标号为%1。
    从第二行开始,第二行将esp置为标号%1,即将esp指向当前进程的栈顶;第三行将标号%1压栈,因为上一句就是当前进程的esp被压栈,此时栈为空,当前ebp被压栈,esp下移一位;第四行将标号%0压栈,即当前进程的指令指针eip压栈,第五行将eip指向当前进程指针,开始执行当前进程。
    最后实现了my_process函数,这是一个死循环,每个进程都是执行此函数。每10000000次,打印当前进程的pid,全局变量my_need_sched,通过对my_need_sched进行判断,若为1,则通知正在执行的进程执行调度程序,然后打印调度后的进程pid。
  3. myinterrupt.c
    Linux小白实验之基于mykernel完成多进程的简单内核
    这部分实现比较简单,主要实现定时器中断,每1000下进行my_need_sched的检查,如果不为1,则置其为1使其进程调度。my_schedule函数具体实现了进程的切换。声明了两个指针,prev和next,分别指向当前进程和下一个进程。进程切换时分两种情况,当next_state ==0 时,即下一个进程正在执行。
    Linux小白实验之基于mykernel完成多进程的简单内核
    重点分析一下上面的嵌入式汇编代码:在my_schedule函数中,完成进程的切换。进程的切换分两种情况,一种情况是下一个进程没有被调度过,另外一种情况是下一个进程被调度过,可以通过下一个进程的state知道其状态。进程切换依然是通过内联汇编代码实现,无非是保存旧进程的eip和堆栈,将新进程的eip和堆栈的值存入对应的寄存器中。
    上边代码实现了进程的切换,第三行将当前的ebp保存压栈,然后将当前的esp存入内存中(prev->thread.sp);第四行将ebp指向要切换进程的esp;第五行保存当前的eip到pre->thread.ip;第六行将下一个进程的eip压栈;第七行ret实现将栈顶,即刚刚压栈的eip弹出,赋给eip,程序开始从此处执行(即要切换的进程),完成了进程切换。
    当下一个进程状态不为0时,即表示还未执行,此时esp等与ebp,其余部分和第一种情况相同。

四:实验总结

通过本次实验,我不仅了解了inux操作系统的工作方式和计算机工作的三大法宝,而且了解了堆栈的工作方式和如何在C语言中嵌套汇编语言的方法。我意识到操作系统的核心功能就是:进程调度和中断机制,通过与硬件的配合实现多任务处理,再加上上层应用软件的支持,最终变成可以使用户可以很容易操作的计算机系统。
纸上得来终觉浅,觉知此事要躬行。身为一个Linux小白,我深知自己还有太多需要学习的地方,今后我会多动手,多练习,更好的学好这门课。