$ git clone git://g.csail.mit.edu/xv6-labs-2020 $ cd xv6-labs-2020 $ git checkout util
2. 构建并运行 xv6
$ make qemu
3. 在 xv6 中使用命令测试
使用 ls
列出文件:
$ ls
使用 Ctrl
+p
查看运行进程(xv6 无 ps
命令)
使用 Ctrl
+a
x
退出 QWMU.
Ctrl
+a
, 再按 x
键.sleep.c
sleep
考虑了输入参数不为数字的情况. 此外, 对于sleep
后多个参数的情况也进行了考虑, 但是发现在 QEMU 中执行是失败的, 测试是通过的, 可以推测此处无需考虑多个参数的情况.#include "kernel/types.h" #include "kernel/stat.h" #include "user/user.h" int main(int argc, char *argv[]) { int ticks; // 需要1个参数 if(argc != 2) { fprintf(2, "Usage: sleep number\n"); exit(1); } ticks = atoi(argv[1]); // 判断 if(ticks == 0 && strcmp(argv[1],"0") != 0){ fprintf(2, "sleep: invalid time interval '%s'\n", argv[1]); exit(2); } sleep(ticks); exit(0); }
Makefile
文件中的 UPROGS
部分:#include "kernel/types.h" #include "kernel/stat.h" #include "user/user.h" int main() { // fd1用于父进程向子进程通信, fd2反之 int fd1[2], fd2[2]; char buf[1]; pipe(fd1); pipe(fd2); if(fork() == 0){ close(fd1[1]); read(fd1[0],buf,1); close(fd1[0]); printf("%d: received ping\n",getpid()); close(fd2[0]); write(fd2[1],buf,1); close(fd2[1]); } else { close(fd1[0]); write(fd1[1],"a",1); close(fd1[1]); close(fd2[1]); read(fd2[0],buf,1); close(fd2[0]); printf("%d: received pong\n", getpid()); } exit(0); }
Makefile
文件中的 UPROGS
部分:
#include "kernel/types.h" #include "kernel/stat.h" #include "user/user.h" int subProcess(int *oldFd) { // 关闭原管道写端 close(oldFd[1]); int fd[2]; int prime; int num; // 若能从原管道读到数据 if (read(oldFd[0], &prime, 4)) { // 第一个数据为质数,进行输出 printf("prime %d\n", prime); // 创建管道和子进程 pipe(fd); if (fork() == 0) { //子进程 // 递归调用 subProcess(fd); } else { // 父进程 // 关闭新管道读端 close(fd[0]); // 从原管道进行读取 while (read(oldFd[0], &num, 4)) { // 不能被记录的质数整除则写入新管道 if (num % prime != 0) { write(fd[1], &num, 4); } } // 此时父进程的原管道关闭, 则关闭原管道的读端 close(oldFd[0]); // 关闭新管道的写端 close(fd[1]); // 等待子进程结束 wait((int *) 0); } } else { // 此时说明原管道已关闭,第一个数字都读不出 // 不创建子进程直接关闭原管道读端 close(oldFd[0]); } exit(0); } int main() { int i; int fd[2]; pipe(fd); if (fork() == 0) { subProcess(fd); } else { // 父进程 close(fd[0]); // 遍历 2~35 写入管道写端 for (i = 2; i <= 35; ++i) { write(fd[1], &i, 4); } // 写完关闭管道写端并等待子进程结束 close(fd[1]); wait((int *) 0); } exit(0); }
Makefile
文件中的 UPROGS
部分
user/ls.c
, 需要使用 struct dirent
和 struct stat
两个和文档相关的结构体. 此处对于文件夹同样是利用递归来进行查找.#include "kernel/types.h" #include "kernel/stat.h" #include "user/user.h" #include "kernel/fs.h" void find(char *dir, char *file) { char buf[512] = {0}, *p; int fd; struct dirent de; struct stat st; // 打开目录 if ((fd = open(dir, 0)) < 0) { fprintf(2, "find: cannot open %s\n", dir); return; } // 判断路径长度 if (strlen(dir) + 1 + DIRSIZ + 1 > sizeof(buf)) { fprintf(2, "find: path too long\n"); close(fd); return; } strcpy(buf, dir); 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) { fprintf(2, "find: cannot stat %s\n", buf); continue; } // 是目录则递归遍历 if (st.type == T_DIR) { find(buf, file); } else if (strcmp(de.name, file) == 0) { //是文件则进行比较, 若与查找一致则输出 printf("%s\n", buf); } } close(fd); return; } int main(int argc, char *argv[]) { struct stat st; if (argc != 3) { fprintf(2, "Usage: find dir file\n"); exit(1); } // 获取查找目录的状态并判断是否为目录 if (stat(argv[1], &st) < 0) { fprintf(2, "find: cannot stat %s\n", argv[1]); exit(2); } if (st.type != T_DIR) { fprintf(2, "find: '%s' is not a directory\n", argv[1]); exit(3); } find(argv[1], argv[2]); exit(0); }
Makefile
文件中的 UPROGS
部分
xargs
指令的用法, 详见 命令教程 - 阮一峰的网络日志, 其中读取输入是通过标准输入 stdin 进行的, 在代码上体现为 read()
函数.xargs
后面无参数时默认以 echo
作为指令. 此外, 另一个比较困难的地方在于"空格",“回车”,以及"输入结束"三种情况的处理. 网上有些代码没有考虑输入结束, 也就是 read()
返回 0 的情况.#include "kernel/types.h" #include "kernel/stat.h" #include "user/user.h" #include "kernel/param.h" #define MAXLEN 32 int main(int argc, char *argv[]) { // 执行命令 char *path = "echo"; // 存放参数的数组和指针 char buf[MAXLEN * MAXARG] = {0},*p; // 参数的指针数组 char *params[MAXARG]; // xargs后命令所带的参数个数 int oriParamCnt = 0; // 参数序号 int paramIdx; // 参数长度 int paramLen; int i; // 临时记录读取字符 char ch; // read读取长度, 用于判断是否输入结束 int res; // 参数数量大于1 if (argc > 1) { // 提取指令 path = argv[1]; // 设置参数, 注意也需要带上指令的参数 for (i = 1; i < argc; ++i) { params[oriParamCnt++] = argv[i]; } } else { //参数唯一,即只有xargs // 即指令为echo params[oriParamCnt++] = path; } // 后续参数起始序号 paramIdx = oriParamCnt; p = buf; paramLen = 0; while (1) { res = read(0, p, 1); ch = *p; // 若读取的为一般字符 if (res != 0 && ch != ' ' && ch != '\n') { ++paramLen; ++p; continue; } // 未读取成功, 或者读取的是空格或回车 // 计算参数起始位置 params[paramIdx++] = p - paramLen; // 参数长度置0 paramLen = 0; // 设置字符结束符 *p = 0; ++p; // 若读取的参数超过上限 if (paramIdx == MAXARG && ch == ' ') { // 一直读到回车即下个命令为止 while ((res = read(0, &ch, 1)) != 0) { if (ch == '\n') { break; } } } // 若读取的不为空格, 即 res==0||ch=='\n' if (ch != ' ') { // 创建子进程执行命令 if (fork() == 0) { exec(path, params); exit(0); } else { // 父进程等待子进程 wait((int *) 0); // 重置参数序号和指针 paramIdx = oriParamCnt; p = buf; } } // 若输入读取完毕则退出 if (res == 0) { break; } } exit(0); }
exec()
执行的命令进行解析. 代码参考了 MIT 6.S081 2020 LAB1记录 - 知乎 中的 xargs 代码.#include "kernel/types.h" #include "kernel/stat.h" #include "user/user.h" #include "kernel/param.h" #define MAXLEN 32 int main(int argc, char *argv[]) { char *path = "echo"; char buf[MAXLEN * MAXARG] = {0}, *p; char *params[MAXARG]; int paramIdx = 0; int i; int len; // 读取原有参数, 与上一种方法相同 if (argc > 1) { if (argc + 1 > MAXARG) { fprintf(2, "xargs: too many ars\n"); exit(1); } path = argv[1]; for (i = 1; i < argc; ++i) { params[paramIdx++] = argv[i]; } } else { params[paramIdx++] = path; } p = buf; while (1) { // 读取字符知道结束或遇到回车 while (1) { len = read(0, p, 1); if (len == 0 || *p == '\n') { break; } ++p; } *p = 0; // 直接将其作为一个参数 params[paramIdx] = buf; // 创建子进程执行 if (fork() == 0) { exec(path, params); exit(0); } else { wait((int *) 0); p=buf; } if (len == 0) { break; } } exit(0); }
Makefile
文件中的 UPROGS
部分