操作系统课设实验一(Nachos上下文切换)

一、准备

1.32位系统的Linux

这里我使用了32位的Ubuntu 16.04镜像。

64位的我尝试挣扎了一下不过还是跪了,而且我也没有看到成功在64位Linux下跑成功过的博主。。

如果你是64位Linux,也可以考虑使用虚拟机VMware workstation,是的,这个Linux也是有相应版本的,然后你就可以看到Linux里面跑Linux的场景了。。

画中画
画中画

2.Nachos源码

百度应该能找到,不过下载前记得自己检查符不符合自己的要求,我们老师要求的是3.4的C++版本,本文也是基于此的,如果你是用Java的可以不用往下看了。。

3.Makefile可能遇见的坑

从其他博主那里看到的坑似乎还不少,不过我只遇见了编译时的问题:

  • 将code目录下的Makefile中的gmake改成make
  • 删除Makefile.common中的-fwritable-strings

然后在code目录下进行make即可,如果没有报错就说明make成功。

make成功

 二、问题重述

1. trace the execution of Nachos and observe the executions of
(a) context switch function SWITCH()
(b) function ThreadRoot()
using gdb and
2. answer the following questions:
(a) What are the addresses of the following functions in your Nachos:
i.InterruptEnable()
ii.SimpleThread()
iii.ThreadFinish()
iv.ThreadRoot()
and describe how did you find them.
(b) What are the addresses of the thread objects for
i. the main thread of the Nachos
ii. the forked thread created by the main thread
and describe how did you find them.
(c) When the main thread executes SWITCH() function for the first time, to what address
the CPU returns when it executes the last instruction ret of SWITCH()? What
location in the program that address is referred to?
(d) When the forked thread executes SWITCH() function for the first time, to what
address the CPU returns when it executes the last instruction ret of SWITCH()?
What location in the program that address is referred to?

原题是英文的,不过基本上比较简单,后面解决部分会用中文来进行描述,此外还要注意,本文题目调试的是在threads文件夹下编译好的的nachos,因为问题都和线程的上下文切换有关。

三、问题解答

1.追踪SWITCH和ThreadRoot函数

想查看某个函数的执行情况,只需要为该函数打上断点然后运行程序即可,如:break SWITCH,然后run即可,但是由于SWITCH函数和ThreadRoot函数内部都是汇编语句,因此这里我们可以显示汇编代码来辅助我们进行分析,使用layout asm命令即可:

显示SWTICH内部的汇编语句情况

2.解答问题

首先这里运行的是threads目录下的nachos,不要进错了目录。。

cd threads
gdb nachos

a.找到几个函数的内存地址

gdb自带命令可以查看某个函数的地址空间,指令为

  p 函数名

借此可以查看题目给出的四个函数在内存中的地址空间,如图:

各函数的内存地址空间

b.找到主线程和子线程的地址空间

要想找到两个线程的地址空间,我们首先应该对源码进行分析,然后确定好断点要打的位置。

i.通过分析,我们发现在位于system.cc中的Initialize函数有这样的代码:

Initialize函数部分代码

而根据Initialize函数在main函数中十分靠前的位置,我们可以确定,这里就是主线程的初始处。

main函数中的Initialize函数

因此,我们只需要给Initialize函数打断点,进入后执行到currentThread设置状态的部分,然后查看其内存地址即可,效果如下:

主线程的位置

如图所示,其内存地址为0x8053a88。

ii.通过分析源码,我们知道主线程Fork子线程的地方在threadtest.cc的ThreadTest1函数中:

ThreadTest1函数中进行Fork

因此我们只需要在ThreadTest1函数打上断点,并执行到给t指针申请空间的部分,再查看t的地址即可,如图:

子线程的位置

通过调试,我们可以知道子线程的内存地址为0x8053ae8。

事实上,这一问也可以通过打印currentThread里面的值来获取到两个线程的地址,但是这样不太好哪个是主线程哪个是子线程

c.找到主线程进入SWITCH最后一句话执行时CPU的返回值

这一问相对较复杂,首先我们分析swtich.s文件

switch.s

通过注释可以得知,在ret指令的倒数第三行提到了返回地址被存入了寄存器eax中 ,接下来我们调试分析汇编部分的代码。

gdb中SWITCH函数的情况

而通过对汇编部分的查看,可以看到其对应的是SWITCH指令偏移77的位置,所以应该在SWITCH+80处打断点然后分析eax寄存器的数值。

这里也是通过查阅资料知道原来gdb可以将断点打在汇编语句上面,并且还能查看寄存器的值,真的涨姿势。。

内部打断点和查看寄存器的值的指令如下:

(gdb) b *SWITCH +84 #在SWTICH偏移84的位置打断点
(gdb) i r eax #查看eax寄存器的值

效果如图:

eax寄存器情况

如图可见,此时eax寄存器内部的值为0x804b1c2,我们可以进一步为这个地址打上断点然后执行:

0x804b1c2指向的地址空间

可见,此时返回值为ThreadRoot函数的地址空间。 

d.找到子线程进入SWITCH最后一句话执行时CPU的返回值

这一问与上一问略有相似,但是不同之处在于我们需要分析的是在线程fork之后执行SWITCH函数最后一句后的CPU返回值,因此要完成这一问,我们首先需要定位到线程fork之后执行SWITCH的位置,通过分析源代码我们知道每一次SWITCH都会完成一次进程切换,因此,我们先进行一次SWITCH,然后查看当前的currentThread的值:

第一次到达SWITCH

该值为子线程的地址空间,可见,此时已经进入了子进程的线程空间,我们按下n直至再次执行到SWITCH函数。我们按上一问的方式继续查看eax寄存器的值,其值变成了0x80491cf。

再次执行到SWITCH函数

我们再次为该值打上断点然后执行到那里: 

0x80491cf指向的地址空间

可以看到,此时的值指向的是scheduler.cc文件中的116行,该行代码为:

scheduler.cc第116行

程序完整运行效果:

程序完整运行效果

四、小结

由实验结果可以看出,程序运行与老师课上所讲基本一致,比如其中的一些关键点:

  1. 主线程初始化的方式和子线程不一致,前者在Initialize函数中完成,而后者使用Fork实现。
  2. 主线程进入SWITCH最后一句话执行时CPU的返回值指向ThreadRoot函数,子线程进入SWITCH最后一句话执行时CPU的返回值指向Run函数中的SWITCH函数语句。

而gdb功能的强大也真的是令我佩服。

此外这篇报告已经修改四次了,感谢小伙伴提出的修改意见emmm。。

猜你喜欢

转载自blog.csdn.net/zekdot/article/details/82991002