pintos project2 无法加载正确运行用户程序
我的系统版本
Ubuntu 12.04 AMD64的
gcc 4.6.3的。
装了pintos 和bochs
pintos project1 已经通过。27个test已经通过
现在的问题:
进入project2 后出现了用户文件加载失败。照网上的一个改法,改了后又出现page fault at c0000008.
下面先看一下是怎么遇到问题的。
我是照那个英文的官方文档做的:
1. 编译example里的文件,这是我们用的用户文件。
wyg@wyg-pc:~$ cd OSstep/pintos8/src/examples/
wyg@wyg-pc:~/OSstep/pintos8/src/examples$ make
完成后如下:
我们要用的是里的echo可执行文件
2. 进入src/userprog 文件夹
3. 然后make 编译好后在当前目录下出现了build 文件夹
4. 进入build文件夹: cd build
5. 制作大小为2M的虚拟硬盘:pintos-mkdisk filesys.dsk
--filesys-size=2
此时多了一个filesys.dsk,这就是虚拟硬盘了。
6.
化虚拟硬盘 pintos -f -q
出现上图说明成功。。
7. 复制echo文件到 pintos 的虚拟硬盘filesys.dsk
pintos -p ../../example/echo -a echo -- -q
执行后出现:
说明成功。
8. 接下来运行用户文件 pintos run echo
为什么不是pintos run ‘echo x’呢,因为我们还未实现参数分离功能,这样会把”echo x”当成一个文件名,显然不存在”echo x”这个文件,加载,必然失败。
看似成功了,其实不然,当然也有可能成功了,如果你已经成功,那就不必做任何修改。如何验证成功与否,后面会提到。
我就在这种失败的情况下写了好久,最后发现,根本没有加载echo文件。
9. 打开src/userprog/process.c
找到start_process (void *file_name_)
数
如下图修改:选中的是要新加的
然后make
再
pintos run echo
我是很不幸,出现 load user program failed-_-!
唉,program 打错了。。只是个输出提示而已。。
但已经证明的是用户程序echo根本没有加载。。。
10.于是我百度了下,原来是elf文件有问题。这个echo 是个elf文件,但是其中的入口地址是0x0000000. 而pintos 去不映射0x1000以下的内存,所以失败了。
11.找到load函数,经过调试发现是下图中的
if(validate_segment(&phdr, file))失败了,
12.所以进入validate_segment(&phdr, file)这个函数,找到问题所在了。原来是
if(phdr->p_vaddr>PGSIZE)失败了
于是我照网上据说,把if(phdr->p_vaddr<PGSIZE)改为if(phdr->p_offset<PGSIZE)
还要改load 函数中代码,如下图选中部分为新加的。
这样做是在pintos 内部改了用户程序的入口地址,以满足pintos 的要求。
这是我在网上看到的。
.我们在载入elf文件时需要做一系列检查,主要是为了确认elf文件的存储空间占用情况, 代码段入口等。这些信息一般都保存在elf文件的executable header和program header
里。在这里,我们遇到了一个小问题导致载入elf文件失败,需要载入的elf文件的header中标明,
第一个loader部分占用虚存从0x000000开始,然而pintos操作系统
默认虚存的低0x1000位 置不被映射,即不被映射,但是我们注意到,首个loader段实际用到的地址是从offset开
始的,正好是0x1000,那么函数validate_segment()的判别机制要有所修改,将 if (phdr>
p_vaddr < PGSIZE)改为 if (phdr>
p_offset < PGSIZE)。这样做的话可
以成功载入elf文件,不过这么做的后果是分配用户内存空间时,仍就会映射到虚存的,这是
pintos所不允许的,为了解决这个问题,我们在函数load()里做一下修改,如果我们发现读出的
mem_page==0,我们需要手动将其设置为0x1000,这样做实际上是将一个条件判断所作的工作手动
实现了,因为我们无法控制由make得到的elf文件,我们只能从load函数中做修改。
13.接下来还是make
pintos run echo
又有问题:出现了page fault at 0xc0000008
14. rights violation error reading page in user context
用户程序访问了内核的页,所以出现了page fault
15我们反汇编一下用户程序echo
方法是:objdump -d
../../example/echo >echo.txt
用vi编辑器打开生成的echo.txt文件 vim echo.txt
仔细观察发现问题了:
在08048130:<_start>:中,开始两行就有问题 sub $0x1c,%esp
mov 0x24(%esp),%eax
我也不知道这是干什么。
但是0x24-0x1c=8
不就是c0000008?
现在我们须要知道pintos的用户栈的范围
PHYS_BASE=0xC0000000 也就是3G
再看看pintos 源代码中设置用户栈的函数
在load()函数中有调用,如下图
找到setup_stack()函数的实现代码:
看这个函数发现,esp被设置成了PHYS_BASE (*esp=PHYS_BASE)
也就是说,用户的栈指针esp=0xc0000000
当然栈是向下扩展的,把数据压入栈只会让esp减小,不会越界访问大小PHYS_BASE的内存,可是
sub $0x1c,%esp //esp=esp-0x1c
mov 0x24(%esp),%eax //把内存(esp+0x24)这个地址里的内容装入eax
这两句,先把esp 减小0x1c,又要访问esp+0x24内存位置的数据。
那esp不就是0xc0000000-0x1c+0x24=0xc0000008了,这就是在用户程序中访问了核心内存,引发了page fault at c0000008.
解决
:
既然是这样,那我们在
setup_stack()函数中把esp减小8不就OK了?
看下图:
我减了0x20,只要大于8就行。
这样做会不会引发什么问题呢,我也不清楚,在此时:2013年8月25日17:26分我还不清楚,但是后续写代码中如果遇到问题再补充。
我的思考:
当代码从高优先级ring0到低优先级ring3切换时要切换栈,要复制ring0 的栈里的一部分东西到ring3的栈,此时我们这样做,可能会找不到ring0代码传给ring3代码的参数。。。
16(再次make
pintos run echo
出现了system call! 这里是由于我们没有实现系统调用,而printf函数要调用 write. 这个我们要后来实现。
17.
当然,经过几天的纠结,你可能还是不相信现在就OK了,我这里有一个证明方法:
虽然目前在echo 中不能使用printf 函数; 我已经想办法证明了echo这个用户程序已经被运行了。。 证明方法:我在echo.c中加入
了 int *t=0xC0000008; *t=3;编译.....弄好,运行后:出现了
page fault at 0xC0000008,无权访问这个位置。我又改了*t=0xC0000009,运行后出现page fault at 0xC0000009;这说明了*t=3;这句运行了,也就是说
运行到了echo.c中的代码。 我还在调试的时候 在
syscall_handler()
{
printf("system call"); //在这句设了断点
}
在gdb中用bt指令看了调用栈,发现, 如果在echo.c中有printf 函数,的确是在 write 这个函数中执行了int 0x30. 而删除了echo.c中所有printf函数,当然echo.c中其实那个main函数已经是空的了,此时在tell 这个函数中执行了int 0x30;
原因尚未搞清楚。。不过,每个用户程序结束时都要调用exit这个函数,里面也有int 0x30..总之现在可以确定的是,用户程序(echo)已经可以被加载并且执行了。。接下来应该可以进入任务了,完成那些系统调用。。。
18.这样的用户程序和main线程是同一优先线,将被交替执行
可能出现了用户程序还没运行完而main线程先结束,这就是为什么上而的system call~ 会出现在Execution ‘echo’ complete之后。
当然这是要通过实现process_wait()函数来解决父子进程同步问题。现在我们只要把用户进程优先级改高一些,做个测试。
看到,用户进程的优先级被设为了PRI_DEFAULT+1,而main线程的优先级为PRI_DEFAULT
还是make
pintos run echo
看到systmep call!已经在
Executing ‘echo’和 Execution of ‘echo’ complete.
之间了。。
-----------------2013年8月25日
今日阅读英文文档,猛然发现
3.2 Suggested Order of Implementation
We suggest first implementing the following, which can happen in
parallel:
? Argument passing (see Section 3.3.3 [Argument Passing], page 29).
Every user program will page fault immediately until argument passing is
implemented.
For now, you may simply wish to change
*esp = PHYS_BASE;
to
*esp = PHYS_BASE - 12;
in setup_stack().
真是欲哭无泪。。这么多天白折腾了。。
--------------------2013年8月26日星期一 7时56分51秒