111qqz的小窝

老年咸鱼冲锋!

【施工中】MIT 6.828 lab 3: User Environments

JOS的environments基本可以理解成”process”进程的同义词,但是由于”process”是一个unix术语,因此使用environment这个词.

Part A: User Environments and Exception Handling

查看 kern/env.c文件,看到三个全局变量:

envs会在JOS启动后会指向一个Env structures的数组,表示JOS中的全部environments. 理论上,JOS kernel最多能支持NENV个同时运行的environments. 但是实际上不会远不会达到这个数量.

env_free_list是一个链表结构,用来存放当前没有在运行的Env structure.. 和page_free_list 类似.

curenv表示的是当前正在运行的environment,当JOS刚刚启动,第一个environment运行之前,curenv的值为NULL.

接下来我们来阅读一下inc/env.h文件

 

  • env_tf: 用来在切换环境时保存各种register的值,以便之后恢复现场.
  • env_link: 用于构成一个链表结构,指向喜爱一个空闲的 environment.
  • env_id: 用于唯一标识使用当前这个Env structure(也就是envs数组中的某个位置)的environment的ID.当这个environment终止时,envs数组中的用一个位置可能会被re-allocate一个新的environment,但是env_id是不同的.虽然env_id不同,但是env_id的最后10bit是用来标识在envs的下标的,如果使用的是envs数组中的同一个位置,这部分是相同的.具体可以参考inc/env.h.
  • env_parent_id: 创建这个environment的environment 的env_id. 就是父进程id…
  • env_type:  用于区分不同种类的环境.对于大部分环境,类型都是ENV_TYPE_USER.
  • env_status: 用来标识当前这个environment的状态.
    • ENV_FREE: 标识一个environment是inactive的,因此在env_free_list上.
    • ENV_RUNNABLE: 标识一个environment等待运行在处理器上.
    • ENV_RUNNING:标识正在运行
    • ENV_NOT_RUNNABLE: 标识一个目前active的环境,但是没有准备好运行,原因可能是正在等待一个其他environment的交互.
    • ENV_DYING: 可以类比”僵尸进程”
  • env_pgdir: 当前这个environment的page directory.

 

Allocating the Environments Array

Modify  mem_init() in kern/pmap.c to allocate and map the  envs array. This array consists of exactly  NENV instances of the  Env structure allocated much like how you allocated the  pages array. Also like the  pages array, the memory backing  envsshould also be mapped user read-only at  UENVS (defined in inc/memlayout.h) so user processes can read from this array.

You should run your code and make sure  check_kern_pgdir() succeeds.

和mem_init中申请pages的空间是一样的.

 

后面有空再写

Creating and Running Environments

由于现在JOS还没有一个文件系统,因此将可执行文件以ELF格式嵌入到kernel中.

练习2:

In the file env.c, finish coding the following functions:

env_init()
Initialize all of the  Env structures in the  envs array and add them to the  env_free_list. Also calls  env_init_percpu, which configures the segmentation hardware with separate segments for privilege level 0 (kernel) and privilege level 3 (user).
env_setup_vm()
Allocate a page directory for a new environment and initialize the kernel portion of the new environment’s address space.
region_alloc()
Allocates and maps physical memory for an environment
load_icode()
You will need to parse an ELF binary image, much like the boot loader already does, and load its contents into the user address space of a new environment.
env_create()
Allocate an environment with  env_alloc and call  load_icode to load an ELF binary into it.
env_run()
Start a given environment running in user mode.

As you write these functions, you might find the new cprintf verb  %e useful — it prints a description corresponding to an error code. For example,

will panic with the message “env_alloc: out of memory”.

env_init()是这些函数中最简单的一个,只需要注意为了保证env_free_list的顺序和envs array的相同,需要倒序遍历.

接下来看env_setup_vm函数,作用是为新的environment初始化一个page directory. 每个environment都有一个自己的page directory.

注释里提示用kern_pgdir作为一个template… 说实话完全没get到含义…查阅资料发现其实就是…copy过来的意思…orz  注意头文件string.h中可能有些有用的函数,比如memcpy

接下来是函数region_alloc,作用是为一个函数分配并映射物理内存.使用page_insert即可

接下里要实现的函数是load_icode(),作用是将ELF格式的二进制文件加载到新环境的用户地址空间.

这个函数是相对比较难实现的一个,可以参考boot/main.c中boot loader加载 kernel image的过程.区别在于,boot loader是从disk中加载kernel image,而load_icode()要加载的二进制文件已经在memory中了.

实现参考lab3 抢占式调度

 

 

 

 

 

 

 

 

【施工完毕】MIT 6.828 lab 2: Memory Management

2019年2月24:完成了除了”Challenge”以外的全部练习和问题. 总共花费15个小时.

2019年2月26:完成”Challenge 2″(应该是最简单的一个orz,只花了不到一个小时)

Part 1: Physical Page Management

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

一个问题是,

Ex 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.

READ MORE →

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
再次报错

解决办法: