XV6实验-Lab0 Utilities

文章目录

  • EXERCISE 0 源代码阅读
  • EXERCISE 1 运行xv6
  • EXERCISE 2 sleep
    • XV6 book Chapter1
      • Operating system interfaces
      • 1.1 Processes and memory
      • 1.2 I/O and File descriptors
      • 1.3 Pipes
      • 1.4 File system
      • 1.5 Real world
    • 目标
    • 提示
  • EXERCISE 3 pingpong
    • 目标
    • 提示
    • 体会
  • EXERCISE 4 primes
    • 目的
    • 提示
  • EXERCISE 5 find
    • 目的
    • 提示
  • EXERCISE 6 xargs
    • 目的
    • 提示
  • 参考

EXERCISE 0 源代码阅读

阅读下面两个源代码,理解xv6及其系统调用
syscall.h
是对xv6里常见的21个系统调用的宏定义。定义指向实现函数的系统调用向量的位置。
在syscall.c中外部定义(extern)一个链接内核和shell的函数。

// System call numbers
#define SYS_fork    1
#define SYS_exit    2
#define SYS_wait    3
#define SYS_pipe    4
#define SYS_read    5
#define SYS_kill    6
#define SYS_exec    7
#define SYS_fstat   8
#define SYS_chdir   9
#define SYS_dup    10
#define SYS_getpid 11
#define SYS_sbrk   12
#define SYS_sleep  13
#define SYS_uptime 14
#define SYS_open   15
#define SYS_write  16
#define SYS_mknod  17
#define SYS_unlink 18
#define SYS_link   19
#define SYS_mkdir  20
#define SYS_close  21

sysproc.c
与进程创建、退出等相关的系统调用函数的实现。

#include "types.h" // typedef uint, ushort, uchar; uint8, uint16, uint32, uint64; pde_t;
#include "riscv.h"
#include "defs.h" // struct & functions
#include "date.h" // struct rtcdate{uint second, minute, hour, day, month, year; }
#include "param.h" // define NPROC NCPU NOFILE NFILE NINODE NDEV ROOTDEV MAXARG MAXOPBLOCKS LOGSIZE NBUF FSSIZE MAXPATH
#include "memlayout.h" // define UART0 UART0_IRQ VIRTIO0 VIRTIO_IRQ PLIC PLIC_PRIORITY PLIC_PENDING PLIC_MENABLE PLIC_SENABLE PLIC_MPRIORITY PLIC_SPRIORITY PLIC_MCLAIM PLIC_SCLAIM KERNBASE PHYSTOP TRAMPOLINE KSTACK TRAPFRAME
#include "spinlock.h" // struct spinlock{uint locked; char *name; struct cpu *cpu;}
#include "proc.h" // struct context{uint64 ra, sp, s0~s11;} cpu{proc *proc; context *context; int noff, intena;} trapframe{uint64 kernel_satp, kernel_sp, kernel_trap, epc, kernel_hartid, ra, sp, gp, tp, t0~t2, s0~s1, a0~a7, s2~s11, t3~t6;} enum procstate {UNUSED, SLEEPING, RUNNABLE, RUNNING, ZOMBIE} proc{spinlock lock; procstate state; void *chan; int killed, xstate, pid; uint64 kstack, sz; page_table_t pagetable; trapframe *trapframe; context context; file *ofile[NOFILE]; inode *cwd; char name[16]}

uint64
sys_exit(void)
{
  int n;
  if(argint(0, &n) < 0)
    return -1;
  exit(n);
  return 0;  // not reached
}

uint64
sys_getpid(void)
{
  return myproc()->pid;
}

uint64
sys_fork(void)
{
  return fork();
}

uint64
sys_wait(void)
{
  uint64 p;
  if(argaddr(0, &p) < 0)
    return -1;
  return wait(p);
}

uint64
sys_sbrk(void)
{
  int addr;
  int n;

  if(argint(0, &n) < 0)
    return -1;
  addr = myproc()->sz;
  if(growproc(n) < 0)
    return -1;
  return addr;
}

uint64
sys_sleep(void)
{
  int n;
  uint ticks0;

  if(argint(0, &n) < 0)
    return -1;
  acquire(&tickslock);
  ticks0 = ticks;
  while(ticks - ticks0 < n){
    if(myproc()->killed){
      release(&tickslock);
      return -1;
    }
    sleep(&ticks, &tickslock);
  }
  release(&tickslock);
  return 0;
}

uint64
sys_kill(void)
{
  int pid;

  if(argint(0, &pid) < 0)
    return -1;
  return kill(pid);
}

// return how many clock tick interrupts have occurred
// since start.
uint64
sys_uptime(void)
{
  uint xticks;

  acquire(&tickslock);
  xticks = ticks;
  release(&tickslock);
  return xticks;
}

sysproc.c中的系统调用函数都没有参数,而内部的kill, sleep函数却含有参数。这是因为在XV6中无法将参数从用户级函数传递到内核级函数,例如:XV6中用argint()函数来传递整数。

argint(); // 传递整数
argptr(); //传递指针
argstr(); // 传递字符串起始地址

EXERCISE 1 运行xv6

安装环境:参考官方文档xv6-tools
克隆xv6仓库: git://g.csail.mit.edu/xv6-labs-2020
运行xv6:make qemu
退出qemu:Ctrl-a + x
自我评分:make grade

EXERCISE 2 sleep

XV6 book Chapter1

Operating system interfaces

xv6:一个kernel,许多进程(指令、数据、堆栈)
内容:processes, memory, file descriptors, pipes, a file system.
shell:a user program. xv6 shell的实现在user/sh.c:1中可以找到。
Xv6 system calls
If not otherwise stated, these calls return 0 for no error, and -1 if there’s an error.

int fork();
int exit(int status);
int wait(int *status);
int kill(int pid);
int getpid();
int sleep(int n);
int exec(char *file, char *argv[]);
char *sbrk(int n);
int open(char *file, char *argv[]);
int write(int fd, char *buf, int n);
int read(int fd, char *buf, int n);
int close(int fd);
int dup(int fd);
int pipe(int p[]);
int chdir(char *dir);
int mkdir(char *dir);
int mknod(char *file, int, int);
int fstat(int fd, struct stat *st);
int stat(char *file, struct stat *st);
int link(char *file1, char *file2);
int unlink(char *file);

1.1 Processes and memory

fork创建一个子进程,内容和父进程相同。父级fork返回子级的PID,子级fork返回0.

ELF格式。exec有两个参数:包含可执行参数的文件名和字符串参数数组。

shell结构:
main(user/sh.c:145): 用getcmd读一行输入;调用fork,创建shell进程的副本;父进程调用wait,子进程执行命令。
runcmd(user/sh.c:58):执行实际命令
exec(user/sh.c:78):执行指令。
shell在实现I/O重定向时利用了分离。
sbrk(n):提供更多的内存(malloc可能调用)。返回新内存的位置。

	pid = wait ((int *) 0);
	printf("child %d is done\n", pid);
}	else if(pid == 0){
	printf("child: exiting\n");
	exit(0);
}	else{
	printf("fork error\n");
}

1.2 I/O and File descriptors

文件描述符:一个小的整数,表示进程可以读取或写入的内核管理对象。指向的对象成为文件。0-标准输入,1-标准输出,2-标准错误。它们约定了cat的简单实现。
read(fd, buf, n):从fd文件描述符,读取最多n个字节,拷贝进buf,返回读取的字节数。
read从当前文件偏移量中读取数据,然后按照读取的字节数前进偏移量,随后的进程返回第一次读取返回的字节之后的字节;没有更多字节可读取时,read返回0,代表文件结束。
write(fd, buf, n)从buf写入n个字节进到fd文件中,返回写入字节数。同read,每个write从上一个偏移量停止的地方开始。

close释放一个文件描述符,使它可以被将来的open、pipe或dup重用(见下文)。新分配的文件描述符总是当前进程中编号最小的未使用描述符。
fork复制父级的文件描述符表及其内存,以便子级从与父级完全相同的打开文件开始。系统调用exec替换调用进程的内存,但保留其文件表。此行为允许shell通过分叉、重新打开子级中选定的文件描述符,然后调用exec来运行新程序来实现I/O重定向。

open: file control(fcntl) (kernel/fcntl.h:1-5): O_RDONLY, O_WRONLY, O_RDWR, O_CREATE, O_TRUNC。只读、只写、读写、创建、删节文件长度至0。

QUES:为什么fork和exec是分开的?为了方便I/O重定位。

dup复制一个现有的文件描述符,返回一个引用相同底层I/O对象的新文件描述符。两个文件描述符共享一个偏移量,就像fork复制的文件描述符一样。

	char buf[512];
	int n;
	for(;;){
		n = read(0, buf, sizof buf);
		if(n == 0) break;
		if(n<0){
			fprintf(2, "read error\n");
			exit(1);
		}
		if(write(1,buf,n)!=n){
			fprintf(2, "write error\n");
			exit(1);
		}
	}
--
	if(fork() == 0){
		write(1,"hello ", 6);
		exit(0);
	} else {
		wait(0);
		write(1, "world\n", 6);
	}
--
	fd = dup(1);
	write(1, "hello ", 6);
	write(fd, "world\n", 6);

1.3 Pipes

pipe:给进程提供一种通信方式:数据写入管道的一段,可以从另一端读取。

如果没有数据可用,管道上的读取将等待写入数据或所有引用写入端的文件描述符被关闭;在后一种情况下,read将返回0,就像数据文件的结尾已经到达一样。

管道 临时文件
自动清理自己 因为文件重定向,必须小心删除/tmp/xyz
传递任意长的数据流 需要磁盘上有足够的可用空间来存储所有数据
允许并行执行管道阶段 第一个程序在第二个程序启动前完成
阻塞读写在进程间通信更有效 非阻塞语义
	int p[2];
	char *argv[2];
	argv[0] = "wc";
	argv[1] = 0;
	pipe(p);
	if(fork() == 0) {
		close(0);
		dup(p[0]);
		close(p[0]);
		close(p[1]);
		exec("/bin/wc", argv);
	} else {
		close(p[0]);
		write(p[1], "hello world\n", 12);
		close(p[1]);
	}

1.4 File system

数据文件:未解释的字节数组
目录:对数据文件和其他目录的命名引用
:从一个名为root的特殊目录(根目录)开始
两种方式打开文件:

chdir("/a"); chdir("b"); open("c",O_RDONLY);

open("/a/b/c", O_RDONLY);

mkdir(""):创建新目录directory
open("",O_CREATE):创建新数据文件data file
mknod("", major device numbers, minor device numbers):创建新的设备文件device file 主设备号和次设备号唯一地标识内核设备。打开设备文件时,内核将读写系统调用转到内核设备实现,而不是传递到文件系统。

fstat:从文件描述符引用的inode检索信息。stat.h(kernel/stat.h)
link:创建另一个拥有与现有文件相同的inode的文件系统名称。
unlink:移除一个名称。文件链接计数为0且没有文件描述符引用它时,释放文件的inode和保存其内容的磁盘空间

mkdir, ln, rm都是用户级的程序。
例外是cd,是内置在shell(user/sh.c: 160)中的。因为cd会更改shell本身的当前工作目录,如果是常规命令的话,那么shell将派生一个子进程,子进程运行cd,cd只能修改子进程的工作目录,而不改变父进程shell的工作目录。

chdir("/a");
chdir("b");
open("c", O_RDONLY);
||
open("/a/b/c", O_RDONLY);
--
mkdir("/dir");
fd = open("dir/file", O_CREATE|O_WRONLY);
close(fd);
mknod("/console",1 ,1);
--
link("a", "b");
unlink("a");

1.5 Real world

UNIX引发了“软件工具”,shell是第一种“脚本语言”。
POSIX标准(可移植操作系统接口)
Unix通过一组文件名和文件描述符接口统一访问多种类型的资源(文件、目录、设备)

目标

实现UNIX程序sleep,暂停用户指定的tick数。
tick:xv6内核定义的时间概念,即定时器芯片的两次中断之间的时间。

提示

  1. 阅读xv6 book第一章(见前文)
  2. 参阅其他代码(echo.c, grep.c, rm.c)获取如何获取传递给程序的命令行参数
//echo.c
#include "kernel/types.h"	//定义类型
#include "kernel/stat.h"	//定义变量别名
#include "user/user.h"		//定义系统调用接口和一些C函数

int
main(int argc, char *argv[])
{
  int i;

  for(i = 1; i < argc; i++){
    write(1, argv[i], strlen(argv[i]));
    if(i + 1 < argc){
      write(1, " ", 1);
    } else {
      write(1, "\n", 1);
    }
  }
  exit(0);
}
  1. 用户忘记传参,应当打印错误信息
  2. 命令行参数以字符串形式传递,使用atoi(user/ulib.c)转换成整数
  3. 使用sleep系统调用
  4. 参阅kernel/sysproc.c来寻找xv6的内核代码sys_sleep
uint64
sys_sleep(void)
{
	int n;
	uint ticks0;

	if(argint(0, &n) < 0)
		return -1;
	acquire(&tickslock);
	ticks0 = ticks;
	while(ticks - ticks0 < n){
		if(myproc()->killed){
			release(&tickslock);
			return -1;
		}
		sleep(&ticks, &tickslock);
	}
	release(&tickslock);
	return 0;
}
  1. 参阅user/user.h寻找用户程序可调用的sleep的C语言定义
int sleep(int);
  1. 参阅user/usys.S获取从用户代码跳转到内核以供sleep的汇编程序代码
//98~102
.global sleep
sleep:
	li a7, SYS_sleep
	ecall
	ret
  1. 确保main调用exit()来退出程序
  2. 添加sleep程序到Makefile中的UPROGS,然后用qemu编译程序即可使用xv6 shell运行。
//sleep.c
// Sleep function. Pause for a user-specified number of ticks.
#include "kernel/types.h"
#include "user/user.h"

int
main(int argc, char *argv[])
{
	int i;

	if(argc != 2){
		fprintf(2, "Usage: sleep needs 1 parameter, gets %d parameter(s) instead.\n", argc-1);
		exit(1);
	}
	
	i = atoi(argv[1]);
	if(i <= 0)
	    exit(1);
	sleep(i);
	exit(0);
}

修改Makefile中的UP: 152

$U/_sleep\
# this is changed when write sleep.c
  1. 测试
./grade-lab-util sleep
//or
make GRADEFLAGS=sleep grade

EXERCISE 3 pingpong

目标

实现程序pingpong,使用UNIX系统在两个进程之间通过管道传递一个byte
父进程将一个字节发送给子进程;子进程打印< pid>: received ping, 其中pid是其进程ID;子进程将字节发送给父进程,然后退出;父进程从子进程读取字节,打印< pid>: received pong, 然后退出。

提示

  1. 使用pipe来创建pipe
  2. 使用fork创建子进程
  3. 使用read来从pipe读取,write写入
  4. 使用getpid获得当前进程ID
#include "kernel/types.h"
#include "user/user.h"

int
main(int argc, char *argv[])
{
    int p1[2], p2[2];
    char buf[] = {'A'}; // one byte
    int n = sizeof(buf);

    pipe(p1);
    pipe(p2);
    if(fork() == 0) {

        close(p1[1]);
        close(p2[0]);
        if(read(p1[0],buf,n) != n){
            printf("Error: child read from p1 with parent failed.");
            exit(1);
        }
        printf("%d: received ping\n", getpid());
        if(write(p2[1],buf,n) != n){
            printf("Error: child write into p2 with parent failed.");
            exit(1);
        }
        exit(0);
    } else {
        close(p1[0]);
        close(p2[1]);
        if(write(p1[1],buf,n) != n){
            printf("Error: parent write into p1 with child failed.");
            exit(1);
        }
        if(read(p2[0],buf,n) != n){
            printf("Error: parent read from p2 with child failed.");
            exit(1);
        }
        printf("%d: received pong\n",getpid());
        wait(0);    //wait for child exit;
        exit(0);
    }
}
  1. 加入Makefile的UPROGS
    修改Makefile中的UP: 153
$U/_pingpong\
# this is changed when write pingpong.c
  1. xv6的用户程序有一个组有限的可用库函数,在user.h中可以看到函数列表;源(系统调用除外)在ulib.c, printf.c, umalloc.c
//ulib.c
char *strcpy(char *s, const char *t);
int strcmp(const char *p, const char*q);
uint strlen(const char *s);
void *memset(void *dst, int c, uint n);
char *strchr(const char *s, char c);
char *gets(char *buf, int max);
int stat(const char *n, struct stat *st);
int atoi(const char *s);
void *memmove(void *vdst, const void *vsrc, int n);
int memcmp(const void *s1, const void *s2, uint n);
void *memcpy(void *dst, const void *src, uint n);

//printf.c
static void putc(int fd, char c);
static void printint(int fd, int xx, int base, int sgn);
static void printptr(int fd, uint64 x);
void vprintf(int fd, const char *fmt, va_list ap);
void fprintf(int fd, const char *fmt, ...);
void printf(const char *fmt, ...);

//umalloc.c
void free(void *ap);
static Header* morecore(uint nu);
void *malloc(uint nbytes);

体会

pipe函数用来打开管道,要记得在进程中关闭用不到的管道端。
父进程推出前要记得wait子进程exit。

EXERCISE 4 primes

目的

用pipes编写并发版本的主筛,用pipe和fork建立管道。参阅贝尔实验室和CSP线程中的图片了解如何实现。
第一个进程将2到35压入管道,为每个素数创建一个进程,通过管道从其左邻居读取,并在另一管道上向其右邻居写入。第一个进程可以在35处停止。

提示

//伪代码描述
p = get a number from left neighbor
print p
loop:
	n = get a number from left neighbor
	if (p does not divid n)
		send n to right neighbor

XV6实验-Lab0 Utilities_第1张图片

  1. 了解如何用pipe构建并发版本的主筛
  2. 小心关闭进程不需要的文件描述符,否则程序将在第一个进程到达35之前用尽xv6资源
  3. 第一个进程到达35后,应当等待直到整个管道终止,包括所有子代、孙代等等。仅在打印完所有输出之后以及在所有其他进程都退出之后才应退出主进程。
  4. read在管道写侧关闭时返回0
  5. 32 bit(or4 byte) int直接写入管道是最简单的方法,不需要使用ASCII I/O
  6. 只在需要时在管道中创建进程
  7. 添加到UPROGS里。
//Primes Function. Print prime numbers from 2~35

#include "kernel/types.h"
#include "user/user.h"

void primes(int p[2]){
    int n, prime;
    int p1[2];
    close(p[1]);
    if(read(p[0], &prime, 4) == 0){
        close(p[0]);
        exit(1);
    } else {
        printf("prime %d\n", prime);
        pipe(p1);
        if(fork() == 0) {
            close(p[0]);
            primes(p1);
        } else {
            close(p1[0]);
            while(1){
                if(read(p[0],&n, 4) == 0) break;
                if(n % prime != 0) write(p1[1], &n, 4);
            }
            close(p[0]);
            close(p1[1]);
            wait(0);
        }
    }
    exit(0);
}

int
main(int argc, char *argv[]){
    if(argc != 1){
        fprintf(2," Usage: error.\n");
        exit(1);
    }
    int n;
    int p[2];
    pipe(p);
    if(fork() == 0) {
        primes(p);
    } else {
        close(p[0]);
        for(n = 2 ; n <= 35 ; n ++ ){
            write(p[1], &n, 4);
        }
        close(p[1]);
        wait(0);
    }
    exit(0);
}

EXERCISE 5 find

目的

写一个简单find.c程序,在目录树里根据一个特定名称找到文件。

提示

  1. 参阅user/ls.c获悉如何读取目录
  2. 使用递归来允许find下降到子目录中
  3. 不要递归’.‘和’…’
  4. 运行make clean; make qemu
  5. C字符串相关代码
  6. ==不能比较字符串,应该使用strcmp()函数
  7. 添加到Makefile中
//here's ls.c
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"

char*
fmtname(char *path)
{
  static char buf[DIRSIZ+1];
  char *p;

  // Find first character after last slash.
  for(p=path+strlen(path); p >= path && *p != '/'; p--)
    ;
  p++;

  // Return blank-padded name.
  if(strlen(p) >= DIRSIZ)
    return p;
  memmove(buf, p, strlen(p));
  memset(buf+strlen(p), ' ', DIRSIZ-strlen(p));
  return buf;
}

void
ls(char *path)
{
  char buf[512], *p;
  int fd;
  struct dirent de;
  struct stat st;

  if((fd = open(path, 0)) < 0){
    fprintf(2, "ls: cannot open %s\n", path);
    return;
  }

  if(fstat(fd, &st) < 0){
    fprintf(2, "ls: cannot stat %s\n", path);
    close(fd);
    return;
  }

  switch(st.type){
  case T_FILE:
    printf("%s %d %d %l\n", fmtname(path), st.type, st.ino, st.size);
    break;

  case T_DIR:
    if(strlen(path) + 1 + DIRSIZ + 1 > sizeof buf){
      printf("ls: path too long\n");
      break;
    }
    strcpy(buf, path);
    p = buf+strlen(buf);
    *p++ = '/';
    while(read(fd, &de, sizeof(de)) == sizeof(de)){
      if(de.inum == 0)
        continue;
      memmove(p, de.name, DIRSIZ);
      p[DIRSIZ] = 0;
      if(stat(buf, &st) < 0){
        printf("ls: cannot stat %s\n", buf);
        continue;
      }
      printf("%s %d %d %d\n", fmtname(buf), st.type, st.ino, st.size);
    }
    break;
  }
  close(fd);
}

int
main(int argc, char *argv[])
{
  int i;

  if(argc < 2){
    ls(".");
    exit(0);
  }
  for(i=1; i<argc; i++)
    ls(argv[i]);
  exit(0);
}

find.c的代码结构和ls.c没有太大区别,但三去除了fmtname中对buf相关的操作。

// Find Function.

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"

char*
fmtname(char *path)
{
    char *p;

    //Find first character after last slash.
    for(p=path+strlen(path); p >= path && *p != '/'; p--)
      ;
    p++;
    return p;
}

void
find(char *path, char *filename)
{
    char buf[512], *p;
    int fd;
    struct dirent de;
    struct stat st;

    if((fd = open(path, 0)) < 0){
        fprintf(2, "find: cannot open %s\n", path);
        return;
    }//open the file
    
    if(fstat(fd, &st) < 0){
        fprintf(2, "find: cannot stat %s\n", path);
        close(fd);
        return;
    }//obtain some information about the file's stat

    switch(st.type){
        //fd is a file
        case T_FILE:
            if(strcmp(fmtname(path), filename) == 0){
                printf("%s\n", path);
            }
            break;
        // fd is a directory
        case T_DIR:
            if(strlen(path) + 1 + DIRSIZ + 1 > sizeof buf){
                printf("find: path too long\n");
                break;
            }
            strcpy(buf, path);
            p = buf+strlen(buf);
            *p++ = '/';
            // open the next file
            while(read(fd, &de, sizeof(de)) == sizeof(de)){//read to dirent
                if((de.inum == 0) || strcmp(de.name, ".") == 0 || strcmp(de.name, "..") == 0)
                    continue;
                memmove(p, de.name, DIRSIZ);
                p[DIRSIZ] = 0;
                find(buf, filename);
            }
            break;
    }
    close(fd);
}
int
main(int argc, char *argv[]){

    if(argc != 3){
        fprintf(2, "Usage: find directory filename.\n");
        exit(1);
    }
    find(argv[1], argv[2]);
    exit(0);
}

EXERCISE 6 xargs

目的

编写xargs.c程序,从标准输入中读取行,并为每行运行一个命令,将该行作为命令的参数。

提示

  1. 使用fork和exec在输入的每一行上调用命令。使用wait在父级等待子级完成。
  2. 要读取输入的各个行:一次读取一个字符,直到出现换行符’\n’
  3. 在param.h中声明了MAXARG,可以帮助声明argv数组
  4. make clean; make qemu
//Xargs Funtion.

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"
#include "kernel/param.h"

int
main(int argc, char *argv[])
{
    char buf[512];
    char *full_argv[MAXARG];
    int i;
    int len;
    if(argc < 2){
        fprintf(2, "Usage: xargs command\n");
        exit(1);
    }
    if(argc + 1 > MAXARG){
        fprintf(2, "Usage: too many args\n");
        exit(1);
    }

    for(i=1 ; i < argc ; i++){
        full_argv[i-1] = argv[i];
    }

    full_argv[argc] = 0;

    while (1) {
        i = 0;
        while (1) {
            len = read(0, &buf[i], 1);
            if(len <= 0 || buf[i] == '\n') break;
            i++;
        }
        if(i == 0) break;
        buf[i] = 0;
        full_argv[argc-1]=buf;
        if(fork() == 0){
            exec(full_argv[0], full_argv);
            exit(0);
        } else {
            wait(0);
        }
    }
    exit(0);
}

参考

xv6 book

你可能感兴趣的:(XV6,操作系统)