xv6:一个简单的类Unix教学操作系统--操作系统接口--进程和内存

xv6进程由用户空间内存(指令,数据和栈)和仅对内核可见的每个进程的状况组成。xv6支持分时特性:在可用的CPU上不停地切换等待运行的进程。当一个进程退出执行时,xv6保存它的CPU寄存器,并在下次运行时恢复它们。内核通过进程标识符或pid与每个进程相关联。

进程可以使用fork系统调用创建一个新进程。Fork创建一个称为子进程的新进程,其内存内容与调用进程(称为父进程)完全相同。Fork在父、子进程中都会返回。在父进程中,fork返回子进程的pid;在子进程中返回零。例如,考虑用C语言[5]编写的下列程序片段:

1

int pid = fork();

2

if(pid > 0){

3

    printf("parent: child=%d\n", pid);

4

    pid = wait(0);

5

    printf("child %d is done\n", pid);

6

} else if(pid == 0){

7

    printf("child: exiting\n");

8

    exit(0);

9

} else {

10

    printf("fork error\n");

11

}

 

exit系统调用会让调用进程停止执行并释放如内存和打开的文件等资源。exit接受一个整型状态参数,通常0代表成功而1代表失败。wait系统调用返回当前进程的子进程pid并把子进程的状态复制到传递给wait的地址;如果调用者的子进程没有一个退出,则wait会等待直到有一个子进程退出。如果父进程不关心子进程的退出状态,它会传递一个0地址给wait。

 

在这个例子中,输出的行为:

parent: child=1234    注:1234仅为举例说明的值,实际运行时,可能是其他值。

child: exiting

输出顺序取决于父进程还是子进程先调用printf。子进程退出后且父进程的wait返回,父会打印

parent: child 1234 is done

尽管期初父子进程拥有相同的内存内容,但正在执行的父子进程却使用不同的内存和寄存器:改变其中一个某变量不会影响另一个,举例来说,当父进程wait的返回值已经存进pid,并不会改变子进程的pid。子进程pid仍然是0。

xv6:一个简单的类Unix教学操作系统--操作系统接口--进程和内存

注:在实际调试时,与文档的描述有点出入,比如,exit()和wait()无输入参数。程序运行结果如下。

xv6:一个简单的类Unix教学操作系统--操作系统接口--进程和内存

exec系统调用将调用进程的内存替换为文件系统中某个文件存储内存镜像。文件必须有一个特殊格式,详细说明了哪部分存指令,哪部分是数据,从哪个指令开始执行等等。xv6使用ELF格式,第3章会有详细说明。当exec成功执行,它并不会返回给调用程序,取而代之的是ELF文件头中声明的入口指令加载并执行。exec接受两个参数:可执行文件的名称和一个string*型的参数序列。比如:

char *argv[3];

argv[0] = "echo";

argv[1] = "hello";

argv[2] = 0;

exec("/bin/echo", argv);

printf("exec error\en");

这段程序使用 /bin/echo 的一个实例并以 echo hello 为参数运行,替换了调用程序。多数程序会忽略第一个参数,通常是程序的名称。

xv6的shell使用上述的调用以用户的名义运行程序。shell的主结构很简单;请看main(user/sh.c:145)。主循环通过getcmd读取一行用户的输入。然后调用fork,创建一个shell进程的副本。当子进程执行命令的时候,父进程调用wait。举例来说,如果用户已经输入了echo hello到shell中,会以“echo hello”为参数并调用runcmd。runcmd(user/sh.c:58)执行实际的命令。对于“echo hello”,它会调用exec(user/sh.c:78)。如果exec成功子进程会从echo执行指令,而不是runcmd。在某个时刻echo会调用exit,这会使得其父进程从wait返回,参看main(user/sh.c:145)。

xv6:一个简单的类Unix教学操作系统--操作系统接口--进程和内存

程序运行结果如下。

    xv6:一个简单的类Unix教学操作系统--操作系统接口--进程和内存

你可能在想为啥fork和exec不能合成一个调用呢;待会我们就会看到创建进程和加载程序分离调用在I/O重定向上有妙用。为了避免创建一个复制进程然后立即替换掉它带来的浪费,对于这种场景系统内核会借助虚拟内存技术比如copy-on-write等对fork的实现进行优化。

xv6 通常隐式地分配用户的内存空间:fork分配子进程需要拷贝父进程内存所需的内存,exec为可执行文件分配足够的内存空间。进程在运行时若需要更多内存(也许是malloc)可以调用sbrk(n)来将其数据内存增加n个字节;sbrk返回新内存的位置。

xv6没有用户这个概念当然更没有不同用户间的保护隔离措施。按照Unix的术语来说,所有的xv6进程都以root用户执行。