6.S081-Lab 1: Xv6 and Unix utilities

更好的阅读体验

  • 官方材料:https://pdos.csail.mit.edu/6.S081/2021/labs/util.html

  • 参考资料:

    • https://mit-public-courses-cn-translatio.gitbook.io/mit6-s081/

    • https://th0ar.gitbooks.io/xv6-chinese/content/

    • https://blog.miigon.net/posts/s081-ending/

    • https://wangchujiang.com/linux-command/

Boot xv6 (easy)

$ git clone git://g.csail.mit.edu/xv6-labs-2021
Cloning into 'xv6-labs-2021'...
...
$ cd xv6-labs-2021
$ git checkout util
Branch 'util' set up to track remote branch 'util' from 'origin'.
Switched to a new branch 'util'

Build and run xv6:

$ make qemu
riscv64-unknown-elf-gcc    -c -o kernel/entry.o kernel/entry.S
riscv64-unknown-elf-gcc -Wall -Werror -O -fno-omit-frame-pointer -ggdb -DSOL_UTIL -MD -mcmodel=medany -ffreestanding -fno-common -nostdlib -mno-relax -I. -fno-stack-protector -fno-pie -no-pie   -c -o kernel/start.o kernel/start.c
...  
riscv64-unknown-elf-ld -z max-page-size=4096 -N -e main -Ttext 0 -o user/_zombie user/zombie.o user/ulib.o user/usys.o user/printf.o user/umalloc.o
riscv64-unknown-elf-objdump -S user/_zombie > user/zombie.asm
riscv64-unknown-elf-objdump -t user/_zombie | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$/d' > user/zombie.sym
mkfs/mkfs fs.img README  user/xargstest.sh user/_cat user/_echo user/_forktest user/_grep user/_init user/_kill user/_ln user/_ls user/_mkdir user/_rm user/_sh user/_stressfs user/_usertests user/_grind user/_wc user/_zombie 
nmeta 46 (boot, super, log blocks 30 inode blocks 13, bitmap blocks 1) blocks 954 total 1000
balloc: first 591 blocks have been allocated
balloc: write bitmap block at sector 45
qemu-system-riscv64 -machine virt -bios none -kernel kernel/kernel -m 128M -smp 3 -nographic -drive file=fs.img,if=none,format=raw,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0

xv6 kernel is booting

hart 2 starting
hart 1 starting
init: starting sh
$ 

ls 命令:

$ ls
.              1 1 1024
..             1 1 1024
README         2 2 2059
xargstest.sh   2 3 93
cat            2 4 24256
echo           2 5 23080
forktest       2 6 13272
grep           2 7 27560
init           2 8 23816
kill           2 9 23024
ln             2 10 22880
ls             2 11 26448
mkdir          2 12 23176
rm             2 13 23160
sh             2 14 41976
stressfs       2 15 24016
usertests      2 16 148456
grind          2 17 38144
wc             2 18 25344
zombie         2 19 22408
console        3 20 0

xv6没有ps命令,Ctrl-p命令可以让kernel打印每个进程的信息

退出 qemu : Ctrl-a x.

编程样例

在实现对应功能后,需要更新Makefile的UPROGS部分,例如实现了sleep.c后,要在UPROGS处追加:

$U/_sleep\
  1. copy.c:
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

int
main() {
    char buf[64];

    while(1) {
        int n = read(0, buf, sizeof(buf));
        if(n <= 0) break;
        write(1, buf, n);
    }
    exit(0);
}
  • 第一个参数是文件描述符,指向一个之前打开的文件。Shell会确保默认情况下,当一个程序启动时,文件描述符0连接到console的输入,文件描述符1连接到了console的输出。所以我可以通过这个程序看到console打印我的输入。当然,这里的程序会预期文件描述符已经被Shell打开并设置好。这里的0,1文件描述符是非常普遍的Unix风格,许多的Unix系统都会从文件描述符0读取数据,然后向文件描述符1写入数据。

  • read的第二个参数是指向某段内存的指针,程序可以通过指针对应的地址读取内存中的数据,这里的指针就是代码中的buf参数。在代码第10行,程序在栈里面申请了64字节的内存,并将指针保存在buf中,这样read可以将数据保存在这64字节中。

  • read的第三个参数是代码想读取的最大长度,sizeof(buf)表示,最多读取64字节的数据,所以这里的read最多只能从连接到文件描述符0的设备,也就是console中,读取64字节的数据。

  1. open.c:
#include "kernel/types.h"
#include "user/user.h"
#include "kernel/fcntl.h"

int
main()
{
    int fd = open("output.txt", O_WRONLY | O_CREATE);
    write(fd, "ooo\n", 4);
    exit(0);
}
  • 代码中的第8行,执行了open系统调用,将文件名output.txt作为参数传入,第二个参数是一些标志位,用来告诉open系统调用在内核中的实现:我们将要创建并写入一个文件。open系统调用会返回一个新分配的文件描述符,这里的文件描述符是一个小的数字,可能是2,3,4或者其他的数字。

  • 之后,这个文件描述符作为第一个参数被传到了write,write的第二个参数是数据的指针,第三个参数是要写入的字节数。数据被写入到了文件描述符对应的文件中。

  • 文件描述符本质上对应了内核中的一个表单数据。内核维护了每个运行进程的状态,内核会为每一个运行进程保存一个表单,表单的key是文件描述符。这个表单让内核知道,每个文件描述符对应的实际内容是什么。这里比较关键的点是,每个进程都有自己独立的文件描述符空间,所以如果运行了两个不同的程序,对应两个不同的进程,如果它们都打开一个文件,它们或许可以得到相同数字的文件描述符,但是因为内核为每个进程都维护了一个独立的文件描述符空间,这里相同数字的文件描述符可能会对应到不同的文件。

  1. fork.c:
#include "kernel/types.h"
#include "user/user.h"

int
main()
{
    int pid;
    pid = fork();
    printf("fork() returned %d\n", pid);

    if(pid == 0) {
        printf("child\n");
    } else {
        printf("parent\n");
    }
    exit(0);
}
  • fork会拷贝当前进程的内存,并创建一个新的进程,这里的内存包含了进程的指令和数据。之后,我们就有了两个拥有完全一样内存的进程。fork系统调用在两个进程中都会返回,在原始的进程中,fork系统调用会返回大于0的整数,这个是新创建进程的ID。而在新创建的进程中,fork系统调用会返回0。所以即使两个进程的内存是完全一样的,我们还是可以通过fork的返回值区分旧进程和新进程。

  • 在第11行,你可以看到代码检查pid。如果pid等于0,那么这必然是子进程。在我们的例子中,调用进程通常称为父进程,父进程看到的pid必然大于0。所以父进程会打印“parent”,子进程会打印“child”。之后两个进程都会退出。

$ fork
fork() returned 5
parent
$ fork() returned 0
child
  1. redirected.c:
#include "kernel/types.h"
#include "kernel/fcntl.h"
#include "user/user.h"

int main()
{
    int pid;
    pid = fork();
    if(pid == 0) {
        close(1);
        open("output.txt", O_WRONLY|O_CREATE);

        char *argv[] = {"echo", "this", "is", "redirected", "echo", 0};
        exec("echo", argv);
        printf("exec failed!\n");
        exit(1);
    } else {
        wait((int*) 0);
    }
    exit(0);
}
$ redirected
$ cat output.txt
this is redirected echo

sleep (easy)

Implement the UNIX program sleep for xv6; your sleep should pause for a user-specified number of ticks. A tick is a notion of time defined by the xv6 kernel, namely the time between two interrupts from the timer chip. Your solution should be in the file user/sleep.c.

Some hints:

  • Before you start coding, read Chapter 1 of the xv6 book.
  • Look at some of the other programs in user/ (e.g., user/echo.c, user/grep.c, and user/rm.c) to see how you can obtain the command-line arguments passed to a program.
  • If the user forgets to pass an argument, sleep should print an error message.
  • The command-line argument is passed as a string; you can convert it to an integer using atoi (see user/ulib.c).
  • Use the system call sleep.
  • See kernel/sysproc.c for the xv6 kernel code that implements the sleep system call (look for sys_sleep), user/user.h for the C definition of sleep callable from a user program, and user/usys.S for the assembler code that jumps from user code into the kernel for sleep.
  • Make sure main calls exit() in order to exit your program.
  • Add your sleep program to UPROGS in Makefile; once you’ve done that, make qemu will compile your program and you’ll be able to run it from the xv6 shell.
  • Look at Kernighan and Ritchie’s book The C programming language (second edition) (K&R) to learn about C.

sleep.c:

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

int
main(int argc, char *argv[])
{
    if(argc < 2) {
        fprintf(2, "Please enter a number!\n");
        exit(1);
    }
    int time = atoi(argv[1]);
    sleep(time);
    exit(0);
}

测试:

$ sudo ./grade-lab-util sleep
make: “kernel/kernel”已是最新。
== Test sleep, no arguments == sleep, no arguments: OK (1.5s) 
== Test sleep, returns == sleep, returns: OK (0.9s) 
== Test sleep, makes syscall == sleep, makes syscall: OK (0.9s) 

pingpong (easy)

Write a program that uses UNIX system calls to ‘‘ping-pong’’ a byte between two processes over a pair of pipes, one for each direction. The parent should send a byte to the child; the child should print “: received ping”, where is its process ID, write the byte on the pipe to the parent, and exit; the parent should read the byte from the child, print “: received pong”, and exit. Your solution should be in the file user/pingpong.c.

Some hints:

  • Use pipe to create a pipe.
  • Use fork to create a child.
  • Use read to read from the pipe, and write to write to the pipe.
  • Use getpid to find the process ID of the calling process.
  • Add the program to UPROGS in Makefile.
  • User programs on xv6 have a limited set of library functions available to them. You can see the list in user/user.h; the source (other than for system calls) is in user/ulib.c, user/printf.c, and user/umalloc.c.

管道参考资料:

  • https://blog.csdn.net/qq_42914528/article/details/82023408

pipe 输入为长度为2的 int 数组 p, 其中 p[0] 为对应的输入文件描述符,p[1] 为对应的输出文件描述符

样例:

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]);
}

pingpong.c:

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

#define STDIN_FILENO  0
#define STDOUT_FILENO 1
#define STDDER_FILENO 2

#define READEND 0   // 0 为用于从管道读取数据的文件描述符
#define WRITEEND 1  // 1 为用于向管道写入数据的文件描述符

int main(int argc, char const *argv[]) {
    // 创建管道会得到一个长度为 2 的 int 数组
    int Parent2Child[2], Child2Parent[2];
    pipe(Parent2Child); // 创建用于 父进程 -> 子进程 的管道
    pipe(Child2Parent); // 创建用于 子进程 -> 父进程 的管道

    if(fork() != 0) {   // parent process
        char buf[2]; 

        // 1. 父进程 首先向 子进程 发送字节
        if(write(Parent2Child[WRITEEND], "!", 1) != 1) {
            fprintf(STDDER_FILENO, "failed to write in parent");
            exit(1);
        }
        close(Parent2Child[WRITEEND]);
        wait(0);

        // 2. 父进程 发送完成后,开始等待 子进程 的回复
        if(read(Child2Parent[READEND], buf, 1) != 1) {
            fprintf(STDDER_FILENO, "failed to read in parent");
            exit(1);
        }

        // 5. 子进程 收到数据, read 返回, 输出 pong
        printf("%d: received pong\n", getpid());
        close(Child2Parent[READEND]);
    } else {            // child process
        char buf[2];

        // 3. 子进程 读取管道, 收到 父进程 发送的字节数据
        if(read(Parent2Child[READEND], buf, 1) != 1) {
            fprintf(STDDER_FILENO, "failed to read in child\n");
            exit(1);
        }
        close(Parent2Child[READEND]);
        printf("%d: received ping\n", getpid());
        
        // 4. 子进程 通过 子->父 管道,将字节送回 父进程
        if(write(Child2Parent[WRITEEND], buf, 1) != 1) {
            fprintf(STDDER_FILENO, "failed to write in child\n");
            exit(1);
        }
        close(Child2Parent[WRITEEND]);
    }
    exit(0);
}

测试:

$ sudo ./grade-lab-util pingpong
make: “kernel/kernel”已是最新。
== Test pingpong == pingpong: OK (1.0s) 

primes (moderate)/(hard)

Write a concurrent version of prime sieve using pipes. This idea is due to Doug McIlroy, inventor of Unix pipes. The picture halfway down this page and the surrounding text explain how to do it. Your solution should be in the file user/primes.c.

Your goal is to use pipe and fork to set up the pipeline. The first process feeds the numbers 2 through 35 into the pipeline. For each prime number, you will arrange to create one process that reads from its left neighbor over a pipe and writes to its right neighbor over another pipe. Since xv6 has limited number of file descriptors and processes, the first process can stop at 35.

Some hints:

  • Be careful to close file descriptors that a process doesn’t need, because otherwise your program will run xv6 out of resources before the first process reaches 35.
  • Once the first process reaches 35, it should wait until the entire pipeline terminates, including all children, grandchildren, &c. Thus the main primes process should only exit after all the output has been printed, and after all the other primes processes have exited.
  • Hint: read returns zero when the write-side of a pipe is closed.
  • It’s simplest to directly write 32-bit (4-byte) ints to the pipes, rather than using formatted ASCII I/O.
  • You should create the processes in the pipeline only as they are needed.
  • Add the program to UPROGS in Makefile.

文档链接:https://swtch.com/~rsc/thread/

图解:

多线程埃氏筛

p = get a number from left neighbor
print p
loop:
    n = get a number from left neighbor
    if (p does not divide n)
        send n to right neighbor

单线程埃氏筛CPP实现:

  • primes数组记录素数
  • st数组标记数字是否为素数
  • 每次遇到素数 i 则将范围 [ 2 ∗ i , n ] [2 * i, n] [2i,n]i 的倍数全部标记
#include<iostream>
using namespace std;

const int N = 1e6 + 10;

int primes[N], cnt;
bool st[N];

void get_primes(int n) {
    for(int i = 2; i <= n; i ++ )
        if(!st[i]) {
            primes[cnt ++ ] = i;
            for(int j = i + i; j <= n; j += i)
                st[j] = true;
        }
}

int main() {
    int n;
    cin >> n;
    get_primes(n);
    cout << cnt << endl;
    for(int i = 0; i < cnt; i ++ ) cout << primes[i] << " ";
    cout << endl;
    return 0;
}

primes.c:

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

#define STDDER_FILENO 2

#define READEND 0
#define WRITEEND 1

// 一次 sieve 调用是一个筛子阶段,会从 pleft 获取并输出一个素数 p,筛除 p 的所有倍数
// 同时创建下一 pipeline 的进程以及相应输入管道 pright,进行下一轮的素数筛
void sieve(int pleft[2]) { // pleft 是来自该 pipeline 左端进程的输入管道
	int prime, cur;
	
	// 读第一个数,必然是素数
	if(read(pleft[READEND], &prime, sizeof(prime)) != sizeof(prime)){
		fprintf(STDDER_FILENO, "Child process failed to read\n");
		exit(1);
	}
	printf("prime %d\n", prime);

	if (read(pleft[READEND], &cur, sizeof(cur))){
		int pright[2];
		pipe(pright); // 创建用于输出到下一 pipeline 的进程的输出管道 pright

		if(fork() == 0) {				// 子进程 (下一个 pipeline)		
			close(pright[WRITEEND]); 	// 子进程对 pright 进行读,不需要写
			close(pleft[READEND]); 		// pleft 为 父进程 的输入管道,子进程用不到
			sieve(pright); 				// 父进程 -> 子进程,开始下个素数筛	
		} else {						// 父进程 (当前 pipeline)
			close(pright[READEND]); 	// 父进程 只需对 pright 进行写
			do{
                if (cur % prime != 0){	// 筛去当前pipeline的prime 倍数
                    write(pright[WRITEEND], &cur, sizeof(cur)); //其余数字进入下一轮筛选
                }
            } while(read(pleft[READEND], &cur, sizeof(cur)));	// 从当前 pipline 左端读取数字

            close(pleft[READEND]);		//关闭 父进程 读
            close(pright[WRITEEND]);	//关闭 子进程 写
			wait(0); // 等待该进程的子进程完成,也就是下一 pipeline
		}
	}
	exit(0);
}

int main(int argc, char *argv[]) {
	// 主进程
	int input_pipe[2];
    int start = 2, end = 35;
	pipe(input_pipe); // 输入管道,输入 2 到 35 之间的所有整数。

	if(fork() == 0) {
		// 第一个 pipeline 的子进程
		close(input_pipe[WRITEEND]); // 子进程只需要读输入
		sieve(input_pipe);
	} else {
		// 主进程
		close(input_pipe[READEND]);  // 同上
		int i;
		for(i = start; i <= end; i ++ ) { 
			if (write(input_pipe[WRITEEND], &i, sizeof(i)) != sizeof(i)){
                fprintf(STDDER_FILENO, "first process failed to write %d into the pipe!\n", i);
                exit(1);
            }
		}
		close(input_pipe[WRITEEND]);
		wait(0);
	}
	exit(0);
}

测试:

$ sudo ./grade-lab-util primes
make: “kernel/kernel”已是最新。
== Test primes == primes: OK (0.8s) 

find (moderate)

Write a simple version of the UNIX find program: find all the files in a directory tree with a specific name. Your solution should be in the file user/find.c.

Some hints:

  • Look at user/ls.c to see how to read directories.
  • Use recursion to allow find to descend into sub-directories.
  • Don’t recurse into “.” and “…”.
  • Changes to the file system persist across runs of qemu; to get a clean file system run make clean and then make qemu.
  • You’ll need to use C strings. Have a look at K&R (the C book), for example Section 5.5.
  • Note that == does not compare strings like in Python. Use strcmp() instead.
  • Add the program to UPROGS in Makefile.

stat.h:

#define T_DIR     1   // Directory
#define T_FILE    2   // File
#define T_DEVICE  3   // Device

struct stat {
  int dev;     // File system's disk device	//文件系统设备号
  uint ino;    // Inode number				//Inode 值
  short type;  // Type of file				//文件类型
  short nlink; // Number of links to file	//文件被链接数
  uint64 size; // Size of file in bytes		//文件大小
};

dirent结构体:

inum是说这个文件占了几个inode,name是这个文件的名字。

// Directory is a file containing a sequence of dirent structures.
#define DIRSIZ 14

struct dirent {
  ushort inum;	
  char name[DIRSIZ];
};

ls.c:

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


// 返回 path 最后一个 ‘/’ 后的 字符串
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;

	// 打开 path 文件
	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:

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

#define STDDER_FILENO 2
#define O_RDONLY 0

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

	// 打开 path 文件
	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:
			fprintf(STDDER_FILENO, "Usage: find <dir> <file>\n");
			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 || strcmp(de.name, ".") == 0 || strcmp(de.name, "..") == 0)
					continue;
				memmove(p, de.name, DIRSIZ);
				p[DIRSIZ] = 0;
				if (stat(buf, &st) < 0)
				{
					printf("find: cannot stat %s\n", buf);
					continue;
				}
				if(st.type == T_DIR) 
				{
					find(buf, file);
				} else if (st.type == T_FILE) {
					if(strcmp(de.name, file) == 0) {
						printf("%s\n", buf);
					}
				}
			}
			break;
	}

	close(fd);
}

int main(int argc, char *argv[])
{
	if(argc != 3) {
		fprintf(STDDER_FILENO, "Usage: find <dir> <file>\n");
		exit(1);
	}

	find( argv[1], argv[2]);
	exit(0);
}

测试:

$ sudo ./grade-lab-util find 
make: “kernel/kernel”已是最新。
== Test find, in current directory == find, in current directory: OK (1.3s) 
== Test find, recursive == find, recursive: OK (1.1s) 

xargs (moderate)

Write a simple version of the UNIX xargs program: read lines from the standard input and run a command for each line, supplying the line as arguments to the command. Your solution should be in the file user/xargs.c.

Some hints:

  • Use fork and exec to invoke the command on each line of input. Use wait in the parent to wait for the child to complete the command.
  • To read individual lines of input, read a character at a time until a newline (‘\n’) appears.
  • kernel/param.h declares MAXARG, which may be useful if you need to declare an argv array.
  • Add the program to UPROGS in Makefile.
  • Changes to the file system persist across runs of qemu; to get a clean file system run make clean and then make qemu.

xargs效果示例:

$ echo hello too | xargs echo bye
bye hello too
$
$ echo "1\n2" | xargs -n 1 echo line
line 1
line 2
$
$ find . b | xargs grep hello

xargs命令介绍:

  • https://wangchujiang.com/linux-command/c/xargs.html
  • http://www.ruanyifeng.com/blog/2019/08/xargs-tutorial.html

整体思路:

  • xargs 命令传入的参数保存至数组,形式为cmd, arg[0], … , arg[k - 1]
  • 解析输入参数,根据 \n 将参数划分至多行;
  • 对每行参数根据空格划分,更新参数数组为 cmd, arg[0], … , arg[k - 1], arg[k], … , arg[k + l - 1]
  • 利用 exec 调用函数;
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/param.h"

#define STDDER_FILENO 2

int readline(char *new_argv[32], int cur){
	char buf[1024];
	int n = 0;
	while(read(0, buf + n, 1)){
		if (n == 1023) {
			fprintf(STDDER_FILENO, "argument is too long\n");
			exit(1);
		}
		if (buf[n] == '\n') break;
		n ++ ;
	}

	buf[n] = 0;
	if (n == 0) return 0;
	int offset = 0;
	while(offset < n) {
		new_argv[cur ++ ] = buf + offset;
		while(buf[offset] != ' ' && offset < n) {
			offset ++ ;
		}
		while(buf[offset] == ' ' && offset < n) {
			buf[offset ++ ] = 0;
		}
	}
	return cur;
}

int main(int argc, char const *argv[]) {
	if (argc < 2) {
		fprintf(STDDER_FILENO, "Usage: xargs command (arg ...)\n");
		exit(1);
	}

	char *command = malloc(strlen(argv[1]) + 1);
	char *new_argv[MAXARG];
	strcpy(command, argv[1]);
	
    for (int i = 1; i < argc; i ++ ) {
		new_argv[i - 1] = malloc(strlen(argv[i]) + 1);
		strcpy(new_argv[i - 1], argv[i]);
	}

	int cur;
	while((cur = readline(new_argv, argc - 1)) != 0) {
		new_argv[cur] = 0;
		if(fork() == 0) {
			exec(command, new_argv);
			fprintf(STDDER_FILENO, "exec failed\n");
			exit(1);
		}
		wait(0);
	}
	exit(0);
}

测试:

$ sudo ./grade-lab-util xargs
make: “kernel/kernel”已是最新。
== Test xargs == xargs: OK (0.7s) 
$ vim time.txt # 写上自己花在这个 Lab 多少个小时的时间
$ make grade
== Test sleep, no arguments == 
$ make qemu-gdb
sleep, no arguments: OK (2.5s) 
== Test sleep, returns == 
$ make qemu-gdb
sleep, returns: OK (0.4s) 
== Test sleep, makes syscall == 
$ make qemu-gdb
sleep, makes syscall: OK (1.1s) 
== Test pingpong == 
$ make qemu-gdb
pingpong: OK (0.9s) 
== Test primes == 
$ make qemu-gdb
primes: OK (1.1s) 
== Test find, in current directory == 
$ make qemu-gdb
find, in current directory: OK (1.0s) 
== Test find, recursive == 
$ make qemu-gdb
find, recursive: OK (1.1s) 
== Test xargs == 
$ make qemu-gdb
xargs: OK (1.1s) 
== Test time == 
time: OK 
Score: 100/100
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: :xv6是一个基于Unix的操作系统,它是一个教学用途的操作系统,旨在教授操作系统的基本概念和实现。它是在MIT的x86架构上开发的,包括了Unix的一些基本功能,如进程管理、文件系统、内存管理等。xv6的源代码是公开的,可以用于学习和研究。 Unix utilitiesUnix操作系统的一些基本工具,如ls、cd、cp、mv、rm等。这些工具可以帮助用户管理文件和目录,执行各种操作。这些工具的实现是基于Unix的系统调用,可以通过编写C程序来调用这些系统调用实现相应的功能。这些工具是Unix操作系统的基础,也是其他操作系统的参考。 ### 回答2: lab: xv6 and unix utilities 实验是一项旨在帮助学生深入理解操作系统和 Unix 工具使用的实验。该实验分为两个部分,第一部分教授学生如何构建和运行 xv6 操作系统;第二部分则重点教授 Unix 工具的使用。 在 xv6 操作系统部分,学生将学习到操作系统内核的基本结构和实现原理。实验将引导学生理解内存管理、进程调度、系统调用等关键操作系统概念。此外,学生还将学习如何编写简单的 shell 以及如何通过修改 xv6 内核代码来实现新的系统调用和功能。 在 Unix 工具部分,学生将探索 Unix 系统广泛使用的常见工具。这些工具包括 vi 编辑器、grep、awk、sed 等。实验将介绍这些工具的基本使用方法以及它们在处理文本和数据时的实际应用。这部分实验还将让学生深入了解 shell 和 shell 脚本的编写,帮助他们在 Unix 环境轻松地编写脚本和自动化任务。 lab: xv6 and unix utilities 实验对计算机科学专业的学生具有重要意义。通过完成这个实验,学生将建立起对操作系统和 Unix 工具的深入理解,为他们成为一名优秀的软件工程师奠定坚实的基础。同时,这个实验还将为学生提供实践经验,让他们能够将所学知识应用到真实的软件开发和运维。 ### 回答3: Lab: xv6 and Unix Utilities是一个计算机科学领域的实验,旨在让学生深入了解Unix操作系统以及操作系统本身的自我管理机制。在这个实验,学生需要从零开始构建一个类似于Unix的操作系统,在这个操作系统,学生需要设计一些基本命令,例如ls,cat,grep等等,并且将它们与系统的底层API结合起来,以实现各种功能。此外,学生还需要了解和探索xv6这个开发工具,它是一个轻量级基于Unix的操作系统实现,具有一定的可移植性和简洁性,因此,它可以作为一个基础框架来实现一个完整的Unix操作系统。 这个实验的目标是让学生了解Unix的基本命令结构和API,以及操作系统内部的一些基本机制,例如进程管理,文件系统交互以及进程通信等等。此外,通过实现这些命令,学生还可以学到一些基本的C语言编程技能,例如文件操作,字符串处理以及进程管理等等。还可以学习到如何使用Git等版本控制工具,以及如何进行调试和测试代码的技巧。 在整个实验过程,学生需要有较强的自我管理能力和综合运用能力,因为在实现这些命令的同时,他们还需要和其他团队成员进行交流和合作,以及不断改进和完善他们的代码。总之,这个实验是一个非常有趣且富有挑战性的计算机科学课程,通过完成这个实验,学生可以更好地了解操作系统的构造和运作机制,以及如何设计和开发高效的系统级应用程序。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值