操作系统:Pintos Project2 详解

前情提要

在上个月的紧赶慢赶,连续一周持续4点睡觉以后,终于完成了斯坦福OS的pintos2 project。网上中文资源太少,也没有所谓的详解,我就大概出来献一下丑。

我们的代码开源在 http://victoryang00.xyz:5012/victoryang/pintos-team-20

credit:https://static1.squarespace.com/static/5b18aa0955b02c1de94e4412/t/5b85fad2f950b7b16b7a2ed6/1535507195196/Pintos+Guide

需要改动的文件集中在userprog 文件夹下,官方给出的需要改动的行数:

 threads/thread.c     |   13 
 threads/thread.h     |   26 +
 userprog/exception.c |    8 
 userprog/process.c   |  247 ++++++++++++++--
 userprog/syscall.c   |  468 ++++++++++++++++++++++++++++++-
 userprog/syscall.h   |    1 
 6 files changed, 725 insertions(+), 38 deletions(-)

为了通过测试,你需要做五件事:

•传递可执行文件名称,而不是线程_create和文件sys_open的原始文件名。 •正确设置堆栈
•实现简单形式的process_wait
•使用putbuf为STDOUT_FILENO实现write syscall
•实施出口syscall。
这将使您开始通过userprog的前几个测试。

args测试集

这个测试集要求把值传入到 esp 中,非常像OS实现的脚本程序,比普通的python 中的 args.append() 多加一个地址处理来翻译。

pintos -v -k -T 60 --filesys-size=2 -p tests/userprog/args-none -a args-none -- -q -f run args-none
pintos -v -k -T 60 --filesys-size=2 -p tests/userprog/args-single -a args-single -- -q -f run 'args-single onearg'
pintos -v -k -T 60 --filesys-size=2 -p tests/userprog/args-multiple -a args-multiple -- -q -f run 'args-multiple some arguments for you!'
pintos -v -k -T 60 --filesys-size=2 -p tests/userprog/args-many -a args-many -- -q -f run 'args-many a b c d e f g h i j k l m n o p q r s t u v'
pintos -v -k -T 60 --filesys-size=2 -p tests/userprog/args-dbl-space -a args-dbl-space -- -q -f run 'args-dbl-space two spaces!'

大概解释一下脚本的含义,贵校用的测试机上只有bochs,makefile.vars 中不用修改。 之前遇到启动 makefile 参数的问题,改这个文件就行。 pintos 命令是一个 perl 脚本,-v -k -T 60 是启动参数, --filesys-size=2 定义文件系统大小。名字不超过14,最大16个文件。 -p 载入文件, -a 载入测试名。 -q -f 为 format 以后 run, 最后是在系统中运行的命令。所有的源代码可以在 .c 文件中看到,如果有某个 testcase 没过就去看。

学长做 check 的时候问过这样一个问题,proj1 和 2 最大的 testcase 区别是什么?proj1 是直接调用写好的程序,而 proj2 传入参数。可以清楚的看到传入参数的格式:

user_program arg1 arg2 arg3 arg4

稍稍看看 args 最大的数量,最多 50 个参数,那直接开一个数组实现,这样的好处是直接通过空格看多少 args ,读的时候看位数就好。

(args) argv[20] = 't'
(args) argv[21] = 'u'
(args) argv[22] = 'v'
(args) argv[23] = null
(args) end

当用户程序开始被执行的时候是先调用process.c 再调用 thread_create() 。注意到该线程被命名为原始文件名:

tid = thread_create(file_name, PRI_DEFAULT, start_process, fn_copy);

你不希望线程具有原始文件名。相反,你希望线程的名称为可执行文件的名称。您将需要从file_name中提取可执行文件名称,然后将其传递。fd(file descriptor就上线了,好处是直接读取数组位置)

tid = thread_create {exec_name,PRI_DEFAULT,start_process,fn_copy;

另请注意fn_copy。这是原始文件名的副本,并作为辅助参数传递。这将派上用场。该线程将运行的函数是start_process,该函数接受参数void * file_name_。 fn_copy作为此参数传递,使您可以在此函数中访问完整的原始文件名的副本。这将派上用场。

如果您查看start_process函数,将看到一个load函数;此功能是在用户程序中加载所有数据的位置。在此加载功能中,Pintos将尝试加载描述。从参数中的指定文件执行用户程序,等待带有指定tid的子进程在继续执行之前完成。

在装入函数中,您还将找到一个名为setup_stack的函数。在此功能中,您将为每个用户程序设置堆栈。

接下来看pintos guide 上的对 stack pointer 的描述,一开始需要先初始化 esp 来存值。首先要说的就是如何debug 因为你一上来一个testcase 都过不了。先介绍两个关于内存读入的函数和一个工具。

memset and memcpy

假设要将一条信息写入指针的目标,就用memset。例如,假设我们要编写字符a,执行以下操作:

#include <stdio.h>
int main(void)
{
   char my_string[] = "XSCI350";
   printf("Original: %s\n", my_string); 
   //ptr points to the start of the string 
   char* ptr = my_string;
   //memset(void pointer to data to modify, write data, size of data in bytes)
   memset(static_cast<void*>(ptr), ’C’, sizeof(char)); printf("After: %s\n", my_string);
   return 0;
}

输出是:

Original: XSCI350
After:    CSCI350

这不是要写单个数据,而是要写一个数据字符串。 这时就要使用 memcpy。 例如,假设我们要编写字符串CSCI350,将执行以下操作:

#include <stdio.h>
int main(void)
{
    char my_string[] = "ABCD123";
    printf("Original: %s\n", my_string); //ptr points to the start of the string

    char* ptr = my_string;
    //memcpy(void pointer to data to modify, write data, size of data in bytes)
    memset(static_cast<void*>(ptr), ’CSCI350’, sizeof(char) * 7);
    printf("After: %s\n", my_string);
    return 0; 
}

输出是:

Original: ABCD123
After:    CSCI350

Hex Dump

它允许你打印出指定堆栈的地址和地址内容。 使用十六进制转储的原型是:

static void hex_dump((uintptr_t)**, void**, int, bool)

这里有一个 hexdump 的案例

接下来实现 process.c 的第一个函数 setup_stack 。

这是双指针,因为你将要进行指针操作,并且由于你希望这些修改是全局的,而不仅仅是在此功能的范围内,因此你将获得一个指向堆栈指针的指针(为指针传递一个指针)。 void** esp 将内容写到你要解引用的堆栈中

按照文档,一开始定义为 PHYS_BASE , 也就是 0xffffffff 。

这里是按 stack 的习惯写入。上面的CSCI350应该写为053ICSC。

上面是一个小测试,我们可以用 hexdump 来检查它输出的到底是什么。

剩下的就是反转了,没什么花样,不过除了我们实现的方法,还有更笨的直接取反。

恶心的testcase

这次的 multioom 是个很恶心的 testcase,相当于一个压力测试,先读入 args 到不能读,再继续不断地 syscall, 就是看你的程序会不会自己 halt 或者退出。

算是一个比较极端的例子,不过只要能跑其他的,这个只是看看你的上界对不对而已。