JOS学习笔记(二)

接上篇,文件跳转到了entry.S里面,这是kernel的入口。首先面临这么一个问题,kernel被加载到了什么地方?

回想上篇elf文件的加载机制,以及objdump里打印出的kernel信息,可以看到,kernel的代码段(text段)被加载到了0x100000的位置,也就是1m的位置,所以内存布局如下:

+------------------+  <- 0xFFFFFFFF (4GB)
|      32-bit      |
|  memory mapped   |
|     devices      |
|                  |
/\/\/\/\/\/\/\/\/\/\

/\/\/\/\/\/\/\/\/\/\
|                  |
|      Unused      |
|                  |
+------------------+  <- depends on amount of RAM
|                  |
|                  |
| Extended Memory  |
|------------------|
|       kernnel    |
+------------------+  <- 0x00100000 (1MB)
|     BIOS ROM     |
+------------------+  <- 0x000F0000 (960KB)
|  16-bit devices, |
|  expansion ROMs  |
+------------------+  <- 0x000C0000 (768KB)
|   VGA Display    |
+------------------+  <- 0x000A0000 (640KB)
|                  |
|    Low Memory    |
|------------------|  <-0x00010000 (elf herader here!)
|------------------|  <-0x00007c00   (boot loader here!)
|  bootmain stack  |
+------------------+  <- 0x00000000

 

值得注意的是kernel的VMA地址为0xf0100000,也就是内核“认为”自己是在一个高位内存里执行的,因此其中的符号,包括函数名、汇编里定义的符号,都会指向一个高位的地址(大于0xf0000000)的地址,所以到目前为止在entry.S里,只要调用任何和“符号”相关的操作,比如jmp指令等,均会出错,因为高位地址连是否存在都不知道,更不用说里面是否有内容了(qemu默认是256m内存吧?)。

因此,为了使代码正常工作,需要对内存地址进行一定的映射。

上篇文章也分析了,JOS开启的8086分段机制实际上只是一个幌子,根本没有对空间进行任何的映射,所以进行映射的工作一定要由分页机制来完成。但是为了使开启分页之前的代码能正常工作,在entry.S里面定义了宏reloc。这个宏就是将一个地址减去一个kernelbase,kernelbase定义在memlayout里面,可以看到是0xf0000000,就目前来说,任何一个高位地址减去此值,就能得到实际在物理内存中的地址了。

在定义了此宏之后,entry.S首先对entry符号和pgdir符号(也就是页表目录)地址做了重定向, 接着通过更改cr3寄存器里的某些位,开启分页内存转换。

我们跟一下pgdir里的内容,也就是entrypgdir.c里的内容,发现对于内存做了两块映射,首先是将虚拟内存的0--4M映射到物理内存的0--4M,其次是将从0xf000000之后的4M映射到物理内存的0--4M。前者的映射是为了保证低于4M的物理地址还能正确的访问(开启分页后所有的内存地址均会做变换,即使你不想让它变换),后面的映射是为了kernel能正常的工作。

关于页目录、页表不再详细说明,毕竟这是LAB2的主要内容。

之后从低地址跳转到高地址relocate处,然后初始化内核调用堆栈(调用函数都需要堆栈,所以在进入kernel的c语言代码前需要先给它初始化好堆栈,突然觉得在做用户态编程的时候不要考虑这些内存分布、栈啥的实在是太幸福了),之后就跳转到i386_init,转入c语言代码了。

为什么要从低地址跳转到relocate处?个人认为只是想验证之前开启的分页机制、加载的页表是否有错误而已,如果不跳转,直接执行貌似也可,反正call i386_init得时候也就跳转到高地址了。

另外不要被memlayout.h里面的那个内存分布所迷惑。目前进入操作系统后的调用栈位于代码的data段(就在entry.S文件的下面定义),而从objdump取得的信息来看,这个data段是加载到了物理内存的0x0010800,大概在内核代码段上面一点,虚拟内存的位置为0xf010800,也在内核代码段上面的位置,memlayout.h给的貌似是lab2以及以后的虚拟内存分布方式。

之后就跳转到c代码执行,一切都变的简单起来了。

你可能感兴趣的:(JOS学习笔记(二))