没啥复杂的,按照教程来就可以了,基本没踩坑。
描述: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.
指导(可以称得上是保姆级了。。。):
按照教程即可。代码如下:
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
int main(int argc, char *argv[])
{
int t;
if (argc <= 1 || argc > 2) {
fprintf(2, "usage: sleep \n" );
exit(1);
}
t = atoi(argv[1]);
sleep(t);
exit(0);
}
实现sleep
命令没啥好讲的,实际上好玩的是sleep syscall的实现,即如何从用户态陷入内核空间,进而完成sleep
命令的。
首先在user.h
中声明了sleep
这个函数,在usys.S
中看到,首先.global sleep
在全局符号表中注册函数名sleep
,之后调用sleep
这个系统调用时就会执行以下这段riscv汇编代码:
.global sleep
sleep:
li a7, SYS_sleep
ecall
ret
这段汇编的意思也很明确,就是把SYS_sleep
的值放入a7
寄存器中,记录系统调用的类型,之后的ecall
指令产生一个异常,此时陷入内核态,然后查看kernel/syscall.c
可知SYS_sleep
和sys_sleep
存在映射关系,syscall
函数可以根据SYS_
值的不同执行不同的sys_
函数(kernel/syscall.c:140)。
void
syscall(void)
{
int num;
struct proc *p = myproc();
num = p->trapframe->a7;
if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
p->trapframe->a0 = syscalls[num]();
} else {
printf("%d %s: unknown sys call %d\n",
p->pid, p->name, num);
p->trapframe->a0 = -1;
}
}
反正目前看kernel/syscall.c
里的syscall
实现,是通过中断实现的(trapframe
),具体的实现会等到后面的实验再详细解释。
以上是一个基本的理解,更详细的了解估计在后面的课程中会涉及。
参考:
描述: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.
指导:
之前实现过unix的版本,直接把代码copy稍作改动即可。
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
int main()
{
int p[2], q[2];
pipe(p), pipe(q);
if (fork() > 0) {
close(p[0]);
close(q[1]);
char x[1];
write(p[1], "0", 1);
read(q[0], x, 1);
printf("%d: received pong\n", getpid());
close(p[1]);
close(q[0]);
wait(0);
exit(0);
} else {
close(p[1]);
close(q[0]);
char x[1];
read(p[0], x, 1);
printf("%d: received ping\n", getpid());
write(q[1], x, 1);
close(p[0]);
close(q[1]);
exit(0);
}
return 0;
}
主要涉及管道的基本使用。难度不大。
描述: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.
指导:
首先需要了解两种素数筛法,一种埃氏筛,另一种是欧式筛,相关知识可以查阅基础数论书籍,这里不做赘述。
下面是描述的链接中给出的图,非常直观的解释了本题通过管道使用埃氏筛的思路:
在父进程中接收数据,输出第一个数据(一定是素数,埃氏筛中证明了这一点),并将这个数作为当前进程的一个公因子,之后在父进程中处理后续数据,如果可以被公因子整除,说明一定不是素数,因此drop掉,将满足条件的数据作为子进程的输入,直到输入的数据为空。
我们不难发现,这个过程是一个标准的递归调用过程。而且父进程中需要按顺序完成以下任务:
fork
一个子进程并建立数据传输管道,将管道的读出端文件描述符作为参数传递;exit
;因此,经过以上的分析,相信写出代码并不困难,添加了相关注释的代码如下:
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#define MIN_PRIME 2
#define MAX_NUM 35
/*
* input data to child process
* @param in_fd input file descriptor
* @return none
*/
void foo(int in_fd)
{
int prime;
int num;
/*
* first num must be prime
*/
if (read(in_fd, &prime, sizeof(int)) <= 0)
exit(0);
printf("prime %d\n", prime);
/*
* if no data on in_fd, exit normally.
* Because: "You should create the processes
* in the pipeline only as they are needed."
*/
if (read(in_fd, &num, sizeof(int)) <= 0)
exit(0);
int p[2];
pipe(p);
if (fork() > 0) {
close(p[0]);
do {
if (num % prime)
write(p[1], &num, sizeof(int));
} while (read(in_fd, &num, sizeof(int)) > 0);
} else {
close(p[1]);
foo(p[0]);
}
close(p[0]);
close(p[1]);
wait(0);
exit(0);
}
int main(int argc, char *argv[])
{
int i;
if (argc > 1) {
fprintf(2, "usage: primes\n");
exit(1);
}
int p[2];
pipe(p);
if (fork() > 0) {
close(p[0]);
for (i = MIN_PRIME ; i <= MAX_NUM ; i ++) {
/*
* directly write 32-bit (4-byte) ints
* to the pipes
*/
write(p[1], &i, sizeof(int));
}
} else {
close(p[1]);
foo(p[0]);
}
close(p[0]);
close(p[1]);
wait(0);
exit(0);
}
描述: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.
指导:
可以参考ls.c的代码,读懂了不难写出find.c。
代码如下,仅供参考:
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"
// DIRSIZ: file and directory max name length
/*
* get name from path (e.g. getname("abc/ab/a") = "a")
* @param path file path
* @return pointer to name
*/
char*
getname(char *path)
{
static char buf[DIRSIZ + 1];
char *p;
for (p = path + strlen(path) ; p >= path && *p != '/' ; p --);
p ++;
memmove(buf, p, strlen(p));
// Note: buf is static, need to process \0
*(buf + strlen(p)) = 0;
return buf;
}
void
find(char *path, char *name)
{
int fd;
char buf[512], *p;
struct stat st;
struct dirent de;
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:
if (strcmp(name, getname(path)) == 0) {
printf("%s\n", path);
}
break;
case T_DIR:
if (strlen(path) + 1 + DIRSIZ + 1 > sizeof buf) {
fprintf(2, "%s too long\n", path);
break;
}
strcpy(buf, path);
p = buf + strlen(buf);
*p ++ = '/';
while (read(fd, &de, sizeof(de)) == sizeof(de)) {
if (de.inum == 0)
continue;
// skip "." and ".."
if (strcmp(de.name, ".") == 0)
continue;
if (strcmp(de.name, "..") == 0)
continue;
memmove(p, de.name, DIRSIZ);
p[DIRSIZ] = 0;
find(buf, name);
}
break;
}
close(fd);
}
int
main(int argc, char *argv[])
{
if (argc != 3) {
fprintf(2, "usage: find \n" );
exit(1);
}
if (fork() == 0) {
find(argv[1], argv[2]);
exit(0);
}
wait(0);
exit(0);
}
描述: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.
指导:
难度在于理解xargs
命令的作用,最常见的应用就是把一个命令的输出当做另一个命令的参数。。。理解了之后就很好写了,代码如下:
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/param.h"
int
main(int argc, char *argv[])
{
char buf[32], *p;
char *my_argv[MAXARG + 1];
int cnt = 0;
if (argc < 2) {
fprintf(2, "usage: xargs [ ]" );
exit(1);
}
if (fork() == 0) {
for (int i = 1 ; i < argc ; i ++) {
my_argv[cnt] = (char*)malloc(32);
memmove(my_argv[cnt ++], argv[i], sizeof(argv[i]));
}
char c;
p = buf;
while (read(0, &c, 1) == 1) {
if (c == '\n') {
*p = 0;
my_argv[cnt] = (char*)malloc(32);
memmove(my_argv[cnt ++], buf, sizeof(buf));
memset(buf, 0, sizeof(buf));
p = buf;
} else {
if (p < buf + sizeof(buf)) {
*p++ = c;
} else {
fprintf(2, "arg too long (>32 bytes)\n");
exit(1);
}
}
}
my_argv[cnt] = (char*)malloc(32);
my_argv[cnt] = 0;
exec(my_argv[0], my_argv);
}
wait(0);
exit(0);
}
神奇的是可以提交!不过肯定没人评分就是了hhh
这次实验用了12小时左右,时间比较久,整体上对系统调用特别是管道,fork
,exec
这些系统调用都比较熟悉了,难度适中。
Write an uptime program that prints the uptime in terms of ticks using the uptime system call. (easy)
待续
Support regular expressions in name matching for find. grep.c has some primitive support for regular expressions. (easy)
待续
The xv6 shell (user/sh.c) is just another user program and you can improve it. It is a minimal shell and lacks many features found in real shell. For example, modify the shell to not print a $ when processing shell commands from a file (moderate), modify the shell to support wait (easy), modify the shell to support lists of commands, separated by “;” (moderate), modify the shell to support sub-shells by implementing “(” and “)” (moderate), modify the shell to support tab completion (easy), modify the shell to keep a history of passed shell commands (moderate), or anything else you would like your shell to do. (If you are very ambitious, you may have to modify the kernel to support the kernel features you need; xv6 doesn’t support much.)
待续