[MIT 6.S081] Lab1: Xv6 and Unix utilities
- Lab Guidance: Lab: Xv6 and Unix utilities
- Lab Code: https://github.com/VastRock-Huang/xv6-labs-2020/tree/util
Boot xv6 (easy)
步骤
- 获取实验用的 xv6 源码并切换到 util 分支
$ 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 (easy)
步骤
- 编写
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
部分:
测试
pingpong (easy)
步骤
- 编写代码
该代码主要是管道 pipe 的应用, 注意管道是单工的, 因此父子进程相互通信需要创建两个管道.
#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
部分:
测试
primes (M/hard)
步骤
- 编写代码
质数的求解方法本质上就是经典的"筛法", 此实验中通过多个进程来达到并行处理. 进程相当于上图的芯片, 而用于进程间通信的管道就模拟导线进行数字传递. 下面算法中, 个人使用了递归来达到构建迭代的子进程的目的.
#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
部分
测试
find (moderate)
步骤
- 编写代码
代码思路主要参考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 (moderate)
步骤
- 编写代码
该部分实验耗时较长, 重点之一在于xargs
指令的用法, 详见 命令教程 - 阮一峰的网络日志, 其中读取输入是通过标准输入 stdin 进行的, 在代码上体现为read()
函数.
另一方面是参数的解析, 下面代码考虑了xargs
后面无参数时默认以echo
作为指令. 此外, 另一个比较困难的地方在于"空格",“回车”,以及"输入结束"三种情况的处理. 网上有些代码没有考虑输入结束, 也就是read()
返回 0 的情况.
具体说来, 本题个人主要想到有以下两个方法:
- 第 1 种方法是对"空格",“回车”,以及"输入结束"三种情况都进行处理. 也就是在读取输入时会根据空格将参数进行划分.
#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);
}
- 第 2 种方法仅对"回车"以及"输入结束"两种种情况都进行处理. 也就是在读取输入时不根据空格将参数进行划分, 直接作为 1 个参数, 实际测试证明确实是可行的, 空格应该会被
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
部分