Lab util: Unix utilities
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
, anduser/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 thesleep
system call (look forsys_sleep
),user/user.h
for the C definition ofsleep
callable from a user program, anduser/usys.S
for the assembler code that jumps from user code into the kernel forsleep
.Make sure
main
callsexit()
in order to exit your program.Add your
sleep
program toUPROGS
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.
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user.h"
int
main(int argc, char *argv[]) {
/*必须包含参数*/
if (argc < 2) {
fprintf(2,"error");
exit(1);
}
int time = atoi(argv[1]);
sleep(time);
exit(0);
}
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 "<pid>: received ping", where <pid> 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 "<pid>: 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, andwrite
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 inuser/ulib.c
,user/printf.c
, anduser/umalloc.c
.
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
int
main(int argc, char *argv[]) {
int p1[2], p2[2];
pipe(p1);
pipe(p2);
char* a[2] = {"a",0};
/*子进程*/
if (fork() == 0) {
close(p1[1]);
read(p1[0],a,2);
/*子进程打印*/
fprintf(1,"%d: received ping\n",getpid());
/*子进程发送数据*/
close(p2[0]);
write(p2[1],a,1);
close(p2[1]);
} else {/*父进程*/
close(p1[0]);
/*父进程发送数据*/
write(p1[1],a,1);
close(p1[1]);
/*父进程打印数据*/
close(p2[1]);
read(p2[0],a,2);
fprintf(1,"%d: received pong\n",getpid());
close(p2[0]);
}
exit(0);
}
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)
int
s 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.
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
void child_process(int p[2]);
int main(int argc, char *argv[]) {
int p[2];
pipe(p);
int s = 0;
if (fork() == 0) {
child_process(p);
} else {
close(p[0]);
for (int i = 2; i <= 35; ++i) {
s = i;
write(p[1], &s, 4);
//fprintf(1, "process:%d write:%d\n", getpid(), s);
}
close(p[1]);
wait(0);
}
exit(0);
}
void child_process(int p[2]) {
close(p[1]);
int rd = 0;
/*读到流末端,退出*/
if (read(p[0], &rd, 4) == 0) {
//printf("process:%d read end\n",getpid());
exit(0);
} else {
//printf("process %d prime first %d\n", getpid(), rd);
int new_p[2];
pipe(new_p);
if (fork() == 0) {
//printf("fork new process");
child_process(new_p);
} else {
int first = rd;
printf("prime %d\n",rd);
while (read(p[0], &rd, 4) != 0) {
//printf("process:%d read:%d\n",getpid(),rd);
if (rd % first == 0) {
continue;
} else {
write(new_p[1], &rd, 4);
//printf("process:%d write:%d\n",getpid(),rd);
}
}
}
close(new_p[0]);
close(p[0]);
close(new_p[1]);
wait(0);
exit(0);
}
}
这题做了挺久,最后发现第36行的pipe()没加上去
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.
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"
/*获取文件名*/
char *filename(char *path);
/*在文件夹中查询文件*/
void findInFolders(char *folderPath, int fd, char *name);
int main(int argc, char *argv[]) {
/*如果参数个数小于3,报错*/
if (argc < 3) {
printf("parameter mismatch\n");
exit(1);
}
char *path = argv[1], *name = argv[2];
int fd;
struct stat st;
if ((fd = open(path, 0)) < 0) {
printf("find: cannot open %s\n", path);
}
if (fstat(fd, &st) < 0) {
printf("find: cannot stat %s\n", path);
close(fd);
}
switch (st.type) {
case T_FILE:
if (strcmp(filename(path), name) == 0) {
printf("%s\n", path);
}
break;
case T_DIR:
findInFolders(path, fd, name);
}
exit(0);
}
void findInFolders(char *folderPath, int fd, char *name) {
struct stat st;
char buf[512];
struct dirent de;
char *p, *fname;
if (strlen(folderPath) + 1 + DIRSIZ + 1 > sizeof buf) {
printf("find: folderPath too long\n");
return;
}
strcpy(buf, folderPath);
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("find: cannot stat %s\n", buf);
continue;
}
fname = filename(buf);
if (strcmp(fname, "..") == 0 || strcmp(fname, ".") == 0) {
continue;
}
switch (st.type) {
case T_FILE:
if (strcmp(fname, name) == 0) {
printf("%s\n", buf);
}
break;
case T_DIR:
;
int chf = open(buf,0);
findInFolders(buf, chf, name);
close(chf);
break;
}
}
close(fd);
}
char *filename(char *path) {
char *p;
// Find first character after last slash.
for (p = path + strlen(path); p >= path && *p != '/'; p--);
p++;
// Return blank-padded name.
return p;
}
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
.
The following example illustrates xarg's behavior:
$ echo hello too | xargs echo bye
bye hello too
$
Note that the command here is "echo bye" and the additional arguments are "hello too", making the command "echo bye hello too", which outputs "bye hello too".
Please note that xargs on UNIX makes an optimization where it will feed more than argument to the command at a time. We don't expect you to make this optimization. To make xargs on UNIX behave the way we want it to for this lab, please run it with the -n option set to 1. For instance
$ echo "1\n2" | xargs -n 1 echo line
line 1
line 2
$
Some hints:
Use
fork
andexec
to invoke the command on each line of input. Usewait
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.
#include "kernel/types.h"
#include "kernel/stat.h"
#include "kernel/fcntl.h"
#include "kernel/param.h"
#include "user/user.h"
int readLine(char *readArg);
int main(int argc, char *argv[]) {
char arg[MAXARG];
char *args[argc + 1];
for (int i = 1; i < argc; ++i) {
args[i - 1] = argv[i];
}
/*反复从标准输入流中读取*/
while (readLine(arg) != -1) {
args[argc - 1] = arg;
args[argc] = 0;
if (fork() == 0) {
exec(args[0], args);
printf("exec error\n");
exit(0);
}
else {
wait(0);
}
}
exit(0);
}
/*从标准输入读取一行,并返回读取长度,如果读至末尾,返回-1*/
int readLine(char *readArg) {
char r;
int cnt = 0;
/*每次读取单个字符*/
while (read(0, &r, sizeof r) != 0) {
if (r == '\n') {
readArg[cnt] = '\0';
return cnt;
}
readArg[cnt++] = r;
}
return -1;
}
int exec(char file, char argv[]) //Load a file and execute it with arguments; only returns if error.
exec() 函数第一个参数是要调用的文件,一开始以为可以随便填,一直输出exec error.
Q.E.D.