pa3.2 part1

pa3.2 用户程序和系统调用

如何加载用户程序?

操作系统中, 加载用户程序是由loader模块负责的.

加载的过程:

可执行文件中的 *代码和数据* 放置在正确的 **内存** 位置
	NanOS -> AM -> NEmu
		loader() -> ramdisk_write()
		memcpy()
		movl -> vaddr_write -> paddr_write -> pmem[addr]=val
跳转到程序入口
	EIP := addr

为了实现loader()函数, 我们需要解决以下问题:

  1. 可执行文件在哪里? ramdisk偏移为0处
  2. 代码和数据在可执行文件的哪个位置?
  3. 代码和数据有多少?
  4. "正确的内存位置"在哪里? ((for x86) 0x300,0000)
操作系统的用户程序(Navy-apps) dummy.c OS._start -> dummy.main() -> OS.exit()
complie (linked to Newlib)  navy-apps/tests/dummy/build/dummy-**x86**
         <=>                nanos-lite/build/ramdisk.img
compile (embed) into NanOS (**x86-NEmu**)

ELF中采用program header table来管理segment, program header table的一个表项描述了一个segment的所有属性, 包括类型, 虚拟地址, 标志, 对齐方式, 以及文件内偏移量和segment大小. 根据这些信息, 我们就可以知道需要加载可执行文件的哪些字节了, 同时我们也可以看到, 加载一个可执行文件并不是加载它所包含的所有内容, 只要加载那些与运行时刻相关的内容就可以了, 例如调试信息和符号表就不必加载. 我们可以通过判断segment的Type属性是否为PT_LOAD来判断一个segment是否需要加载.

你需要找出每一个需要加载的segment的Offset, VirtAddr, FileSiz, MemSiz这些参数. 其中相对文件偏移Offset指出相应segment的内容从ELF文件的第Offset字节开始, 在文件中的大小为FileSiz, 它需要被分配到以VirtAddr为首地址的虚拟内存位置, 在内存中它占用大小为MemSiz. 也就是说, 这个segment使用的内存就是[VirtAddr, VirtAddr + MemSiz)这一连续区间, 然后将segment的内容从ELF文件中读入到这一内存区间, 并将[VirtAddr + FileSiz, VirtAddr + MemSiz)对应的物理区间清零.

ELF文件在ramdisk中, 框架代码提供了一些ramdisk相关的函数(在nanos-lite/src/ramdisk.c中定义), 你可以使用它们来实现loader的功能

TODO: 实现loader的功能, 来把用户程序加载到正确的内存位置, 然后执行用户程序

loader()函数在nanos-lite/src/loader.c中定义, 其中的pcb参数目前暂不使用, 可以忽略, 而因为ramdisk中目前只有一个文件, filename参数也可以忽略. 在下一个阶段实现文件系统之后, filename就派上用场了.
实现后, 在init_proc()中调用naive_uload(NULL, NULL), 它会调用你实现的loader来加载第一个用户程序, 然后跳转到用户程序中执行. 如果你的实现正确, 你会看到执行dummy程序时在Nanos-lite中触发了一个未处理的4号事件. 这说明loader已经成功加载dummy, 并且成功地跳转到dummy中执行了.

上一篇:ConcurrenHashMap源码(JDK1.7)


下一篇:google 浏览器设置成黑色背景