111qqz的小窝

老年咸鱼冲锋!

【施工中】MIT 6.828 lab 2: Memory Management

Part 1: Physical Page Management

操作系统必须时刻追踪哪些物理内存在使用,哪些物理内存没有在使用。

一个问题是,

Exercise 1. In the file kern/pmap.c, you must implement code for the following functions (probably in the order given).

boot_alloc()
mem_init() (only up to the call to check_page_free_list(1))
page_init()
page_alloc()
page_free()

check_page_free_list() and check_page_alloc() test your physical page allocator. You should boot JOS and see whether check_page_alloc() reports success. Fix your code so that it passes. You may find it helpful to add your own assert()s to verify that your assumptions are correct.

练习1要求写一个physical page allocator。我们先看第一个函数boot_alloc()

 

这个函数只有在JOS初始化虚拟内存之前会被调用一次。

通过查看 mem_init 函数可以知道,boot_alloc 是用来初始化页目录(page directory)

为什么我们需要一个单独的page allocator呢?原因是:

kernel启动时需要将物理地址映射到虚拟地址,而我们需要一个page table来记录这种映射关系。但是创建一个page table涉及到为page table所在的page分配空间…而为一个page分配空间需要在将物理地址映射到虚拟地址以后。。

解决办法是,使用一个单独的page allocator,在一个固定的位置allocate memory. 然后在这部分去做初始化的工作。

参考xv6-book:

There is a bootstrap problem: all of physical memory must be mapped in order
for the allocator to initialize the free list, but creating a page table with those mappings
involves allocating page-table pages. xv6 solves this problem by using a separate page
allocator during entry, which allocates memory just after the end of the kernel’s data
segment. This allocator does not support freeing and is limited by the 4 MB mapping
in the entrypgdir, but that is sufficient to allocate the first kernel page table.

这个函数有两个难点,第一个是,如何才能”allocate memory”? 说到”allocate memory”总是想到malloc…但是现在我们什么都没有…

然而实际上很简单(虽然我卡了好一会。。。),我们只要计算出第一个虚拟地址就好了。根据注释, magic symbol ‘end’位于没有被任何kernel code或全局变量占用的虚拟地址的起始位置。

第二个是,如何确定何时空间不够? 我们观察函数i386_detect_memory

发现这个函数的作用是得到剩余的物理内存。其中basemem就是0-640k之间的memory,extmem是1M以后的memory.

npages是剩余物理内存的页数,每页的大小是PGSIZE。因此一共能分配的空间大小为(npages*PGSIZE)

而虚拟地址的base为KERNBASE(定义在inc/memlayout.h中),因此最大能访问的虚拟地址为KERNBASE+(npages*PGSIZE)

最后的实现为:

接下来的部分就相对简单了。首先是mem_init,初始化PageInfo,由于是在page_init之前,不能使用page_alloc,因此这部分allocate也是由boot_alloc完成的。这也是唯二的由boot_alloc来分配内存的部分。代码如下:

接下来是page_init.这部分主要是判断哪些page是free的,哪些不是,参考注释,主要是[EXTPHYSMEM,…)这部分。 我们知道,对于EXTPHYSMEM之上的内存空间,首先kernel占用的空间,kernel之后是分配给kern_pgdir的空间,再然后是分配给PageInfo的空间。这之后的空间,应该都是可用的。因此代码如下:

再然后是page_alloc函数。其实就是取一个链表头的操作。

再之后的page_free. 相对应的,就是在链表头插入一个节点的操作。

到现在,练习1就算完成了。怎么知道我们的实现是对的呢,启动JOS,断言应该挂在page_insert处,并且make grade显示Physical page allocator: OK  就应该是没问题了。

x86 calling conventions

x86的调用约定主要说的是这几件事:

  • The order in which atomic (scalar) parameters, or individual parts of a complex parameter, are allocated
  • How parameters are passed (pushed on the stack, placed in registers, or a mix of both)
  • Which registers the called function must preserve for the caller (also known as: callee-saved registers or non-volatile registers)
  • How the task of preparing the stack for, and restoring after, a function call is divided between the caller and the callee

调用约定实际上并不唯一

我们比较关注gcc编译器下的cdecl(C declaration)

对于如下这段代码:

调用过程如下:

在c语言中,函数的参数被以从右向左的顺序压入栈,也就是最后一个参数最先入栈。

这里是栈指的是调用栈(Call_stack)

调用栈的结构如下(注意此图的栈是从下往上增长的,这与通常情况并不相符,不过不影响此处的说明),这是在调用的DrawSquare函数中调用DrawLine函数时的情景

stack frame通常按照入栈顺序(写在前面的先入栈)由三部分组成(可能某部分为空):

  • 函数的参数值(以从右向左的顺序入栈)
  • caller的地址值,为的是调用函数之后能继续执行caller其余的代码。
  • 函数的局部变量

 

接下来我们看一下调用过程对寄存器的影响。这里暂且不提eax寄存器通常用来保存结果之类,主要想谈谈调用过程对sp和bp两个寄存器的影响。

sp是stack pointer,保存的是当前栈顶地址

bp是base pointer(就是stack frame中的frame pointer), 值为函数刚刚被调用时的栈顶位置。

bp这个寄存器的作用主要是比较方便,因为如果只有stack pointer,那么在函数里面,stack pointer也是可能变的,显然不如使用base pointer方便。

具体来说,在使用base pointer的情况下,函数的返回地址永远为ebp + 4,第一个参数的地址为ebp+8,第一个局部变量的地址为ebp-4

而且使用bp的情况下,回溯调用栈会变得非常方便。

At ebp is a pointer to ebp for the previous frame (this is why push ebp; mov ebp, esp is such a common way to start a function).  This effectively creates a linked list of base pointers.  This linked list makes it very easy to trace backwards up the stack.  For example if foo() calls bar() and bar() calls baz() and you’re debugging baz() you can easily find the parameters and local variables for foo() and bar().

为什么ebp指向的内容是上一个 stack frame中的ebp?我们看push ebp; mov ebp esp这两条指令。push ebp相当于先esp-=4,然后将ebp放到esp所指向的位置。接着mov ebp esp,相当于把当前的esp,也就是上一个ebp所在的位置,赋值给新的ebp.  所以。。这其实是个链表啊

 

 

参考资料:

x86 calling conventions

Stack_register

Call_stack#STACK-FRAME

What is exactly the base pointer and stack pointer? To what do they point?

All About EBP

 

 

【施工完成】MIT 6.828 lab 1: C, Assembly, Tools and Bootstrapping

花费了30+小时,终于搞定了orz

 

Part 1: PC Bootstrap

The PC’s Physical Address Space

8086/8088时代

由于8086/8088只有20跟地址线,因此物理内存空间就是2^20=1MB.地址空间从0x00000到0xFFFFF.其中从0x00000开始的640k空间被称为”low memory”,是PC真正能使用的RAM。从 0xA0000 到 0xFFFFF 的384k的non-volatile memory被硬件保留,用作video display buffers和BIOS等。

READ MORE →

【施工中】MIT 6.828 Operating System Engineering 学习笔记

课程主页

这课稍微有点硬核…感觉基础稍微有些不扎实就做不下去orz.

网上似乎是有博客写了6.828的学习笔记,不过我更希望自己能够独立完成,二手的知识,谁知道是对的错的呢…况且课程本身给的参考资料应该还是足够多的。

环境的话,手头没有ubuntu系统,恰好半年前剁了阿里云的轻应用服务器,就在上面做吧。

为了这门课,我读了/计划读以下书籍(随时更新)。大概也是为了检查一遍自己的知识体系。

每个lab用到的网页形式的参考资料,会在每个lab的博客中分别给出。

最后,放一段《游褒禅山记》中的文字,与君共勉!

夫夷以近,则游者众;险以远,则至者少。而世之奇伟、瑰怪,非常之观,常在于险远,而人之所罕至焉,故非有志者不能至也。有志矣,不随以止也,然力不足者,亦不能至也。有志与力,而又不随以怠,至于幽暗昏惑而无物以相之,亦不能至也。然力足以至焉,于人为可讥,而在己为有悔;尽吾志也而不能至者,可以无悔矣,其孰能讥之乎?

 

geekos project 1 (ELF文件相关)

一、目的
熟悉ELF文件格式,了解GeekOS系统如何将ELF格式的可执行程序装入到内存,建立内核进程并运行的实现技术。
二、流程
1、修改/geekos/elf.c文件:在函数Parse_ELF_Executable( )中添加代码,分析ELF格式的可执行文件(包括分析得出ELF文件头、程序头,获取可执行文件长度,代码段、数据段等信息),并填充Exe_Format数据结构中的域值。
2、在Linux环境下编译系统得到GeekOS镜像文件。
3、编写一个相应的bochs配置文件。
4、在bochs中运行GeekOS系统显示结果。

 

编译以及启动bochs同project0…
project0遇到的那些错误还是都会遇到一遍233.

然后在project1/src/geekos/ 目录下的elf.c中添加函数:int Parse_ELF_Executable(char *exeFileData, ulong_t exeFileLength,
struct Exe_Format *exeFormat)

原理部分不过多阐释,具体可见我参考的博客。

最后实现为:

 

然后由于编译之后比project0多生成了一个diskc.img文件

所以还需要相应得修改配置文件.bochsrc

最后内容如下:

project0遇到的启动bochs的那些问题project1也会遇到一遍233

 

然后。。。启动。。。

报错如下:

选区_123

 

查了好多。。。一无所获。。。

最后看到一遍博客: 参考博客1

那篇博客里虽然和我遇到的问题不一致。。。

但是死马当做活马医。。。

objdump -d a.exe 查看了反汇编代码。。。。a.exe的路径是project1/build/user/

 

可以看到在_Entry入口处的汇编,首先

sub    $0x1c,%esp

然而程序却抢先在

add    $0x1c,%esp

之前使用leave和lret长跳转返回了,这样程序当然出错了。

我这里是将
  __asm__ __volatile__ (“leave”);
__asm__ __volatile__ (“lret”);
改成了
  __asm__ __volatile__ (“add $0x1c, %esp”);    (注意,原文作者这里把%esp写成$esp,坑死小白啊。。还写错两次)
  __asm__ __volatile__ (“lret”);

就能正常工作了。

 

选区_125

geek OS project 0 (下)

现在我们环境已经搭好了,参考 geekos实验环境的搭建

在main.c中新加个函数,命名为projecto,函数的代码如下:

 

 

再修改Main函数,将TODO(“…..这一行替换为以下代码:

struct Kernel_Thread *thread;
thread = Start_Kernel_Thread(&project0,0,PRIORITY_NORMAL,false);

替换的意思是,要把TODO那一行注释掉。。。

TODO语句的定义在src/project0/include/geekos/kassert.h中

可以看到,这是一个宏打印错误提示后,就直接进入一个死循环中,也就是执行到TODO之后程序就不会继续往下运行了,所以要继续进行调试project0就必须删除或者注释掉那条TODO。

 

保存代码,按上一篇文章中的方法编译,并在bochs中引导系统。
运行效果如下图所示:

选区_121

 

 

参考博客:参考博客1
参考博客2

geekok project0(上)(实验环境的搭建)

此处下载的bochs应该是比较新的…如果之后遇到

failed assertion in init_idt :g_handlersizenoterr == g_handlersizeerr

这个错误,建议安装比较老的nasm版本,比如2.08.02链接

 

下载geekos-0.3软件包,地址为:
geekOS下载地址

然后解压到~/work目录。

然后进入到 /work/geekos-0.3.0/src/project0/build 目录下

之后的操作都是在这个目录下进行的。

 

 

然后执行 make

报错,原因是编译检查过于严格。。我们修改makefile文件,取消把warning当成错误看待。

makefile 的路径就是当前路径,也就是:

rkz2013@111qqz-ThinkPad-X200 ~/work/geekos-0.3.0/src/project0/build $ vim Makefile

把149行的 -Werror 去掉。

然后再次make

解决办法是把makefile文件中第148行添加编译选项

-fno-stack-protector

然后把makefile文件中的100行至109行修改为如下内容
(修改了100行,106行,109行,条件编译什么的。。可能遇到依赖的库不全的情况。。。安装就好)

然后要把以前失败的清理干净。。重新编译。。。

编译成功。。。
检查一下:

然后启动bochs

报错:

因为配置文件.bochsrc 太古老了。。。路径什么的都是错的。。。

最终修改为如下:

然后继续启动。。报错:

这是由apt-get install bochs-x 得到的 libbx_x.so不完善造成的
解决办法: 换个显示方案。

sudo apt-get install bochs-sdl

然后在.bochsrc文件中添加

display_library: sdl

再次运行bochs  。。终于可以了。。。感动

选区_120

OS课设之geek os 非最终版

参考了这篇博客

流程部分不再具体描述,可以参考上面的博客。

只详细给出我遇到的问题。

我的pc环境是:Linux 111qqz-ThinkPad-X200 3.16.0-38-generic #52~14.04.1-Ubuntu SMP Fri May 8 09:43:57 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux

linux mint 17.2 cinnamon

http://sourceforge.net/projects/geekos/files/ 下载geekos软件包并且解压

报错。。

解法办法:修改/home/rkz2013/geekos-0.3.0/src/project0/build

目录下的Makefile文件。

 

make后再次出现错误:

解决办法:

 

然后又报错

 

解决办法:

修改/home/rkz2013/geekos-0.3.0/src/project0/build目录下的Makefile的100行至109行如下。
(改动了100行,106行,109行。。。交叉编译什么的,因为做OS大作业的时候搞过这个。。。如果之前没有交叉编译过可能出现库依赖不全的情况。。。? 缺什么安什么就好了。)

新建一个.bochsrc的配置文件

放入一下内容

保存在主目录下。

然后再启动bochs
再次报错

解决办法: