383 + 原创作品转载请注明出处 + 中科大孟宁老师的linux操作系统分析:https://github.com/mengning/linuxkernel。
本章内容分为三个部分:1和2、实验操作(以及实验中遇见的问题)3、源码分析。4、总结
一、实验操作(part1)
该项实验听着好像挺难的,但实际操作起来还是比较简单的,因为孟宁老师已经提供了相应的源码。
第一部分的实验操作主要是讲述如何部署kernel内核。
1)准备虚拟机和ubuntu操作系统(本人的是ubuntu16.04)。
2)为了便于理解和管理,首先自己先创建一个文件夹:
mkdir MyKernel
3)做好事前准备,kernel内核linux-3.9.4:
wget https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.9.4.tar.xz
以及孟宁老师的kernel补丁包mykernel_for_linux3.9.4sc.patch:
wget https://raw.github.com/mengning/mykernel/master/mykernel_for_linux3.9.4sc.patch
![76947f938921987c6239d35c0730000e.png](https://img-blog.csdnimg.cn/img_convert/76947f938921987c6239d35c0730000e.png)
(PS:先不管5.0版本,本文中不涉及该版本。)
4)将内核解压并打上孟宁老师的补丁包(PS:上图可知,我已经进行了初步的解压,源文件为linux-3.9.4.tar.xz。解压指令为:xz -d linux-3.9.4.tar.xz):
tar xvf linux-3.9.4.tar
![e3670c931426880ba1ddbbf18f980321.png](https://img-blog.csdnimg.cn/img_convert/e3670c931426880ba1ddbbf18f980321.png)
(PS:如果没在同一个文件夹下,需要移动到同一个文件夹下面,如下图所示)
![bed261c5992fd968090c97ad647c5eb8.png](https://img-blog.csdnimg.cn/img_convert/bed261c5992fd968090c97ad647c5eb8.png)
进入linux3.9.4文件夹下,开始打补丁包:
patch -p1 > mykernel_for_linux3.9.4sc.patch
![a2f46dfa3b8c79ed598e9db8f3ab1441.png](https://img-blog.csdnimg.cn/img_convert/a2f46dfa3b8c79ed598e9db8f3ab1441.png)
(PS:补丁就不详细说明了,patch文件中看源码可以看明白)
5)编译:
(复位)make allnoconfig
![06d5da2ffa2a5554b0e447802c326713.png](https://img-blog.csdnimg.cn/img_convert/06d5da2ffa2a5554b0e447802c326713.png)
(PS:此处warning和note的提示先不管他)
{
此处可能会出现的问题:如果没有安装过bison(语法分析器)和flex(词法分析器)可能会报错。
解决方案:sudo apt-get install flex bison
}
(编译)make
{
![b365ac822e9749dde9e70a5fd5271cf7.png](https://img-blog.csdnimg.cn/img_convert/b365ac822e9749dde9e70a5fd5271cf7.png)
此处的问题:因为linux-3.9.4是老版本的内核,没有compiler-gcc5.h。
解决方案:将complier-gcc4.h拷贝给complier-gcc5.h。
cd include/linux/
cp compiler-gcc4.h compiler-gcc5.h
然后再回到linux-3.9.4路径下进行编译
}
![0a74e4448fcf8754e634f4d66cd4b3fb.png](https://img-blog.csdnimg.cn/img_convert/0a74e4448fcf8754e634f4d66cd4b3fb.png)
好了编译已经完成。
6)安装qemu
sudo apt-get install qemu
安装完成qemu后可以测试之前的内核是否能够正常运行了。
qemu -kernel arch/x86/boot/bzImage
{
此处可能出现的问题:No command ‘qemu’ found, did you mean...
解决方案:将qemu-system添加链接到usr/bin目录下。
sudo ln –s /usr/bin/qemu-system-i386 /usr/bin/qemu
}
![aa75058ba970e6d60588a5ed8dddfce3.png](https://img-blog.csdnimg.cn/img_convert/aa75058ba970e6d60588a5ed8dddfce3.png)
弹窗显示上图,说明linux-3.9.4布置成功。
补充、patch中到底做了些什么?
从之前的图片中可以看出patch补丁主要修改了原kernel中的几个文件:time.c、timer.h、start_kernel.h、main.c、Makefile。
以及添加了mykernel文件夹和几个文件:mymain.c、myinterrupt.c、Makefile。
patch中主要修改的内容(均在linux-3.9.4文件夹下):
1)arch/x86/kernel/time.c
![075eef5abfea570d3db63c2433f4e74d.png](https://img-blog.csdnimg.cn/img_convert/075eef5abfea570d3db63c2433f4e74d.png)
主要是在时钟终端函数中添加了自己的函数my_timer_handler()。(irq:Interrupt Request中断请求)
2)申明外部函数:my_start_kernel(void)、my_timer_handler(void)
![b41c884bfd2c409106b1931f696febff.png](https://img-blog.csdnimg.cn/img_convert/b41c884bfd2c409106b1931f696febff.png)
3)init/main.c
![eb3880d1064a8cf98b33244e75a20d91.png](https://img-blog.csdnimg.cn/img_convert/eb3880d1064a8cf98b33244e75a20d91.png)
在原kernel内核初始化的时候同时添加自己的内核启动函数。
4)原kernel中的Makefile
![e43753af18083038d3745bf059a2ac93.png](https://img-blog.csdnimg.cn/img_convert/e43753af18083038d3745bf059a2ac93.png)
主要添加了mykernel文件夹,将自己的内核文件与原内核绑定在一起。
5)mykernel文件夹中的内容
![ed3320177734b92a77a4072e7d6989df.png](https://img-blog.csdnimg.cn/img_convert/ed3320177734b92a77a4072e7d6989df.png)
mykernel中的Makefile添加mymain.o和myinterrupt.o两个目标文件。
![f4bb2aae0f93d411260bdd80161015ae.png](https://img-blog.csdnimg.cn/img_convert/f4bb2aae0f93d411260bdd80161015ae.png)
![0e784dcb91467213e45bad794cf1a4bd.png](https://img-blog.csdnimg.cn/img_convert/0e784dcb91467213e45bad794cf1a4bd.png)
在myinterrupt.c中实现my_timer_handler函数。
![bd8f609033254115d658adf5462cd49f.png](https://img-blog.csdnimg.cn/img_convert/bd8f609033254115d658adf5462cd49f.png)
![4e8ec63a58e856038928ce11000fccbf.png](https://img-blog.csdnimg.cn/img_convert/4e8ec63a58e856038928ce11000fccbf.png)
在mymain.c中实现my_start_kernel函数。
总结:这部分的补丁的目的就是在原kernel内核总添加自己的mykernel内核程序,以及自己的中断处理程序。
二、实验操作(part2)
第二部分的实验操作主要为时间转轮片的实现。
此处因为孟宁老师已经提供了源码,直接拷贝替换到linux-3.9.4/mykernel/内编译运行就好了,此处不多做赘述(将会在第三部分进行具体的源码分析),下面展示运行结果。
{
此处会出现一个错误:
![e0062b9b4680f13718a31f6e6892c870.png](https://img-blog.csdnimg.cn/img_convert/e0062b9b4680f13718a31f6e6892c870.png)
主要原因就是孟宁老师的代码mypcb.h中的一个问题,编译会报错,稍微改下代码就行了。
解决方案:
![20bc5db0b2c64e75878ee3e5204ac9f2.png](https://img-blog.csdnimg.cn/img_convert/20bc5db0b2c64e75878ee3e5204ac9f2.png)
将上图的代码改成下图模式
![40380990b500effb7ee2bc40cfdcf12a.png](https://img-blog.csdnimg.cn/img_convert/40380990b500effb7ee2bc40cfdcf12a.png)
}
![8273e7dffce53192faeb4c0eb0b59e87.png](https://img-blog.csdnimg.cn/img_convert/8273e7dffce53192faeb4c0eb0b59e87.png)
![0cdf926a13f6de99cf7eef6440c12611.png](https://img-blog.csdnimg.cn/img_convert/0cdf926a13f6de99cf7eef6440c12611.png)
总共有4个状态,process0-3,系统开始在process 0中运行,在出现my_timer_handler后跳转到process 1,之后在process 1中运行,在出现my_timer_handler后跳转到process 2,之后在process 2中运行,在出现my_timer_handler后跳转到process 3,之后在process 3中运行,在出现my_timer_handler后跳转到process 0,依次循环。
三、源码分析
此部分主要对第二部分的源码进行分析,为了更好地理解操作系统是如何工作的。(PS:直接上代码。)
1)mypcb.h
![9969b06ee89205105724a3b268f3b47b.png](https://img-blog.csdnimg.cn/img_convert/9969b06ee89205105724a3b268f3b47b.png)
mypcb.h中主要实现了进程的主体类(pcb:进程管理块)。
2)mymain.c
![e85f1bb9d62711acd931d7a6ba2fd45a.png](https://img-blog.csdnimg.cn/img_convert/e85f1bb9d62711acd931d7a6ba2fd45a.png)
此处定义了进程数量MAX_TASK_NUM=4的进程组以及当前进程my_current_task。
![ae5abef6f0a22133c214a3bb5700faa9.png](https://img-blog.csdnimg.cn/img_convert/ae5abef6f0a22133c214a3bb5700faa9.png)
(PS:这张图有点糊,将就下)
首先定义初始化了第一个任务进程,这里可以看到thread.ip为进程函数,thread.sp为进程分配的栈顶指针,栈大小为2*1024。
之后再初始化剩下三个任务进程,并将所有任务进程按照链表的方式进行链接(1->next=2,2->next=3...)。
主要操作为最后一段的汇编代码(PS:C语言看的懂吧?我把内联汇编翻译成C语言来表示):
{
首先明确一些基本的内联汇编常识:
内敛汇编中用%0,%1来标识变量,要在内敛汇编中使用寄存器%eax就必须再加一个%,即%%eax。
}
另外,在解释前先明确一点,每个task都具有一个进程的栈,此处标记第i个进程为stki,stki初始时栈顶指针为task[i].thread.sp,为了方便说明,此处从stki[0]开始,即栈顶指针指向stki[0]开始。
{
解释:
"movl %1 %%espnt" ----> %esp=task[i].thread.sp(%esp = &stki[0])
"pushl %1nt" ----> stki[1] = task[i].thread.sp(%esp = &stki[1])
"pushl %0nt" ----> stki[2] = my_process(%esp = &stki[2])
"retnt" ----> eip = stki[2] = my_process(%esp = &stki[1],补充说明:eip寄存器中存放的下一个CPU指令存放的内存地址,当CPU执行完当前的指令后,从EIP寄存器中读取下一条指令的内存地址,然后继续执行)
总结:汇编实现的目的就是实现进程的操作。
补充:此处汇编好像少了一条代码"popl %%ebp"---->%ebp=task[i].thread.sp(%ebp=&stki[0],%esp=&stki[0])进行堆栈的复位。虽然不进行复位也不会出什么问题。
}
![199772127593525e9d4895e243acd55d.png](https://img-blog.csdnimg.cn/img_convert/199772127593525e9d4895e243acd55d.png)
这个是进程函数,即该函数地址赋值给了进程的thread.ip,可以通过调用thread.ip执行此函数操作。该函数的操作为,在没有中断的情况下打印“XXX... process i -”,在出现中断的时候执行中断处理my_schedule()并打印“XXX... process i +”。
3)my_interrupt.c
![54dac8b616cd85fb766335e43c7287a0.png](https://img-blog.csdnimg.cn/img_convert/54dac8b616cd85fb766335e43c7287a0.png)
申明外部变量task数组和my_current_task,时钟中断处理标志位my_need_sched,每当发生时钟中断的时候就把my_need_sched值置为1,并使当前的task执行my_schedule()。
![889fd5badb4abf7f2e31a42eec2e9836.png](https://img-blog.csdnimg.cn/img_convert/889fd5badb4abf7f2e31a42eec2e9836.png)
my_schedule()函数操作,主要实现了时间片轮转。非汇编部分主要是初始化定义了next和prev两个进程。汇编部分内容主要是实现prev进程和next进程的调度工作。
主要内容为汇编代码:
"pushl %%ebpnt" ----> 将当前的prev的%ebp保存,保存现场操作
"movl %%esp, %0nt" ----> prev->thread.sp=%esp,将prev的%esp保存到prev的栈顶中,保存现场操作。
"movl %2, %%espnt" ----> %esp=next->thread.sp,使%esp指向next栈的栈顶,切换进程。
"movl $1f, %1nt" ----> 向前找标签1的地址,就是prev进程的开始执行的地址,并将其赋值给prev->thread.ip,即重置当前的prev的指令地址。
"pushl %3nt" ----> 将next->thread.ip入栈,即将下一个进程的函数地址入栈,切换进程。
"retnt" ----> eip = next->thread.ip,即接下来执行next进程。
"1:t" ----> 此句的目的是为next进程的指令地址打上标签,为之后切换进程做准备。
"popl %%ebpnt" ----> %ebp=task[i].thread.sp,即进行栈复位,准备开始next进程。
四、总结
对这次实验进行一次总结,我本身稍微懂一点汇编,但是因为对内联汇编的不熟悉所以刚开始看代码的时候经常理解错。本门课程对我来说还是有很多学习的意义的。本次实验的重点就是理解时间转轮片中断和my_schedule()函数中进程的调度机制,即如何利用时间转轮片的中断机制以及寄存器和堆栈的使用进行保存现场和恢复,最终实现操作系统进程的切换。