Lab 1中Exercise 9的解答报告

Exercise 1.9:

  判断一下操作系统内核是从哪条指令开始初始化它的堆栈空间的,以及这个堆栈坐落在内存的哪个地方?内核是如何给它的堆栈保留一块内存空间的?堆栈指针又是指向这块被保留的区域的哪一端的呢?

答:

  1. 首先需要判断操作系统内核是从哪条指令开始初始化它的堆栈空间的。

  前面已经分析过boot.S和main.c文件的运行过程,这个文件中的代码是PC启动后,BIOS运行完成后,首先执行的两部分代码。但是它们并不属于操作系统的内核。当main.c文件中的bootmain函数运行到最后时,它执行的最后一条指令就是跳转到entry.S文件中的entry地址处。此时控制权已经被转交给了entry.S。

  在跳转到entry之前,并没有对%esp,%ebp寄存器的内容进行修改,可见在bootmain中并没有初始化堆栈空间的语句。

  下面进入entry.S,在entry.S中我们可以看到它最后一条指令是要调用i386_init()子程序。这个子程序位于init.c文件之中。在这个程序中已经开始对操作系统进行一些初始化工作,并且自重进入mointor函数。可见到i386_init子程序时,内核的堆栈应该已经设置好了。所以设置内核堆栈的指令就应该是entry.S中位于 call i386_init 指令之前的两条语句:

  movl    $0x0,%ebp            # nuke frame pointer
  movl $(bootstacktop),%esp

  这两条指令修改了%ebp,%esp两个寄存器的值,而这两个寄存器的值是和堆栈息息相关的。

  

  2. 这个堆栈坐落在内存的什么地方?

  解答这个问题就需要我们好好分析一下entry.S文件了。最好的方法自然是阅读其源码,并且也要配合反汇编文件。

  首先我们配置好一个qemu,gdb调试环境,现在我们首先在gdb中设置一个断点(指令: b *0x7d63),就设置到马上进入entry之前,然后一步步进行调试。

  

  当我们准备运行entry.S中第一条指令 movx $0x1234, 0x472 时,指令地址是0x10000C,如上图所示。这个比较好理解,因为在bootmain里面,我们已经把操作系统的内核文件全部加载到物理内存0x100000处了。所以0x10000C是系统内核的第一条指令所在的物理地址处。

  而当我们继续运行运行到jmp *%eax之后,后面的指令地址就都变化了,变换为:

  

  图中的地址是0xf010002f,很明显这是一个虚拟地址,它的真实地址应该是0x0010002f,因为所有的内核代码都实际存放在这个内存区域中。之所以现在要把指令地址设置为0xf010002f,即把操作系统的代码的虚拟地址设置为从0xf0100000开始。目的就是能够让程序员在编程时,能够利用虚拟地址空间的低地址空间。如果它编写的程序调用了操作系统的代码,则操作系统代码的虚拟地址一定位于高地址空间0xf0100000处。这样非常有利于程序员写程序。

  所以必须有一种机制能够实现,即便程序员在程序中指定的操作系统的代码的虚拟地址在0xf0100000高地址空间,但是我们这个机制也能够把这个高地址转换为这个代码真实的在内存中的位置。比如上图中,我们想访问0xf010002f处的指令,这是个虚拟地址,当实际运行时,会有一种机制把这个地址转换为真实地址0x0010002f。

  在entry.S文件中,下面的这些代码是来实现这个机制的:

   movl    $(RELOC(entry_pgdir)), %eax
  movl %eax, %cr3
  movl %cr0, %eax
  orl $(CR0_PE|CR0_PG|CR0_WP), %eax
  movl %eax, %cr0

  这个机制的实现方式是通过写了一个c语言的页表,entry_pgdir,这个手写的页表可以自动的把[0xf0000000-0xf0400000]这4MB的虚拟地址空间映射为[0x00000000-0x00400000]的物理地址空间。可见这个页表的映射能力还是比较有限的,只能映射一个区域。对于当前执行的这些指令,这个映射空间就已经足够了。因为当前运行的是内核程序,他们的虚拟空间地址范围在[0xf0000000-0xf0400000]之内。但是当操作系统真正正常的运行起来的时候,这个映射就不够用了。必须采用更全面的,也就是在lab 2中要介绍的页表机制。所以当操作系统真正正常运行起来时,entry_pgdir这个页表将不会再使用。

  现在依次分析上述语句,首先看第1句,它的功能是把entry_pgdir这个页表的起始物理地址送给%eax,这里RELOC宏的功能是计算输入参数的物理地址。

  第2句,把entry_pgdir这个页表的起始地址传送给寄存器%cr3。

  控制寄存器cr2和cr3都是和分页机制相关的寄存器。其中cr3寄存器存放页表的物理起始地址。

  第3~5句,修改cr0寄存器的值,把cr0的PE位,PG位, WP位都置位1。其中PE位是启用保护标识位,如果被置1代表将会运行在保护模式下。PG位是分页标识位,如果这一位被置1,则代表开启了分页机制。WP位是写保护标识,如果被置位为1,则处理器会禁止超级用户程序向用户级只读页面执行写操作。

  这条指令过后,就开始工作在具有分页机制的模式之下了。接下来的指令就可以指定[0xf0000000-0xf0400000]范围的指令了。

 

  然后下面两条指令就把当前运行程序的地址空间提高到[0xf0000000-0xf0400000]范围内。

     mov    $relocated, %eax
jmp *%eax

  其中在实际调试时,这两条指令如下:

  

  可见relocated的值为0xf010002f。此时分页系统会把这个虚拟地址,转换为真实的物理地址。

  

  接下来就到了最关键的两句指令:

  

     movl    $0x0,%ebp            # nuke frame pointer
movl $(bootstacktop),%esp
call i386_init

  这两个指令分别设置了%ebp,%esp两个寄存器的值。其中%ebp被修改为0。%esp则被修改为bootstacktop的值。这个值为0xf0110000。另外在entry.S的末尾还定义了一个值,bootstack。注意,在数据段中定义栈顶bootstacktop之前,首先分配了KSTKSIZE这么多的存储空间,专门用于堆栈,这个KSTKSIZE = 8 * PGSIZE  = 8 * 4096 = 32KB。所以用于堆栈的地址空间为 0xf0108000-0xf0110000,其中栈顶指针指向0xf0110000. 那么这个堆栈实际坐落在内存的 0x00108000-0x00110000物理地址空间中。

  3. 内核是如何给它的堆栈保留一块内存空间的?

   其实就是通过刚刚分析的,在entry.S中的数据段里面声明一块大小为32Kb的空间作为堆栈使用。从而为内核保留了一块空间。

 

  4. 堆栈指针又是指向这块被保留的区域的哪一端的呢?

  堆栈由于是向下生长的,所以堆栈指针自然要指向最高地址了。最高地址就是我们之前看到的bootstacktop的值。所以将会把这个值赋给堆栈指针寄存器。

  

  以上就是对Exercise 1.9的分析,欢迎大家提出问题或建议~

    zzqwf12345@163.com

  

  

MIT 6.828 JOS学习笔记12 Exercise 1.9的更多相关文章

  1. MIT 6.828 JOS学习笔记11 Exercise 1.8

    Exercise 1.8       我们丢弃了一小部分代码---即当我们在printf中指定输出"%o"格式的字符串,即八进制格式的代码.尝试去完成这部分程序. 解答: 在这个练 ...

  2. MIT 6.828 JOS学习笔记5. Exercise 1.3

    Lab 1 Exercise 3 设置一个断点在地址0x7c00处,这是boot sector被加载的位置.然后让程序继续运行直到这个断点.跟踪/boot/boot.S文件的每一条指令,同时使用boo ...

  3. MIT 6.828 JOS学习笔记13 Exercise 1.10

    Lab 1 Exercise 10 为了能够更好的了解在x86上的C程序调用过程的细节,我们首先找到在obj/kern/kern.asm中test_backtrace子程序的地址, 设置断点,并且探讨 ...

  4. MIT 6.828 JOS学习笔记8. Exercise 1.4

    Lab 1 Exercise 4 阅读关于C语言的指针部分的知识.最好的参考书自然是"The C Programming Language". 阅读5.1到5.5节.然后下载poi ...

  5. MIT 6.828 JOS学习笔记9. Exercise 1.5

    Lab 1 Exercise 5 再一次追踪一下boot loader的一开始的几句指令,找到第一条满足如下条件的指令处: 当我修改了boot loader的链接地址,这个指令就会出现错误. 找到这样 ...

  6. MIT 6.828 JOS学习笔记3. Exercise 1.2

    这篇博文是对Lab 1中的Exercise 2的解答~ Lab 1 Exercise 2: 使用GDB的'si'命令,去追踪ROM BIOS几条指令,并且试图去猜测,它是在做什么.但是不需要把每个细节 ...

  7. MIT 6.828 JOS学习笔记2. Lab 1 Part 1.2: PC bootstrap

    Lab 1 Part 1: PC bootstrap 我们继续~ PC机的物理地址空间 这一节我们将深入的探究到底PC是如何启动的.首先我们看一下通常一个PC的物理地址空间是如何布局的:        ...

  8. MIT 6.828 JOS学习笔记0. 写在前面的话

    0. 简介 操作系统是计算机科学中十分重要的一门基础学科,是一名计算机专业毕业生必须要具备的基础知识.但是在学习这门课时,如果仅仅把目光停留在课本上一些关于操作系统概念上的叙述,并不能对操作系统有着深 ...

  9. MIT 6.828 JOS学习笔记10. Lab 1 Part 3: The kernel

    Lab 1 Part 3: The kernel 现在我们将开始具体讨论一下JOS内核了.就像boot loader一样,内核开始的时候也是一些汇编语句,用于设置一些东西,来保证C语言的程序能够正确的 ...

随机推荐

  1. 语言模型kenlm的训练及使用

    一.背景 近期研究了一下语言模型,同事推荐了一个比较好用的工具包kenlm,记录下使用过程. 二.使用kenlm训练 n-gram 1.工具介绍:http://kheafield.com/code/k ...

  2. angularjs-$http.post请求传递参数,后台Controller接受不到原因

    现象回显 js文件 app.controller('indexCtrl', function($scope, $state, $http) { $scope.login = function() { ...

  3. 第3月第21天 nsclassfromstring返回null SVN报错:clean the working copy and then retry the operation

    1. xcodeproj工程损坏时,.m文件没有加入编译. 2. SVN报错:clean the working copy and then retry the operation http://bl ...

  4. -webkit-box-flex被内容撑开了

    <!doctype html> <html> <head> <meta charset="utf-8"> <title> ...

  5. Python 动态创建函数【转】

    知乎上也有相似的问题 偶然碰到一个问题,初想是通过动态创建Python函数的方式来解决,于是调研了动态创建Python函数的方法. 定义lambda函数 在Python中定义lambda函数的写法很简 ...

  6. 耿丹CS16-2班第五次作业汇总

    Deadline: 2016-10-26 23:59 作业内容 实验4-1 求1到20的阶乘的和,其中求阶乘用函数完成. 实验4-2 写一个判素数的函数,在主函数输入一个整数,输出其是否是素数的信息. ...

  7. Object类.

    equals方法. 比较的是内存地址.比较的是是否指向同一对象. toString:将对象转换成字符串. System.out.println()等价于  System.out.println(obj ...

  8. Mongodb 基础(Z)

    Mongodb的客户端支持 作为一款非常成熟NoSQL数据库,Mongdb对各种编程语言的支持已经非常完善了,目前已经支持各大主流编程语言包括:1,mongo shell 2,Python 3,Jav ...

  9. java基础高级2 MySQL 高级

    1.数据库简介 DDL(数据定义语言) DML(数据操作语言) 2. 准备工作 解压缩文件目录下找到my.ini文件,文件中写入[mysql] default-character set= utf-8 ...

  10. jquery追加内容

    <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8&quo ...