[MIT6.S081]Lab1: Unix utilities
# [MIT6.S081]Lab1: Unix utilities
Lab: Xv6 and Unix utilities (mit.edu) (opens new window)
# 1、Sleep
直接调用 sleep
系统调用即可,注意没有参数时要报错,并且要 exit()
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
int main(int argc, char *argv[]) {
if (argc <= 1) {
fprintf(2, "sleep: missing parameter\n");
exit(1);
}
int n = atoi(argv[1]);
if (sleep(n) < 0) {
fprintf(2, "sleep: sleep error\n");
exit(1);
}
exit(0);
}
# 2、Pingpong
先在父进程中创建一个管道,文件描述符存在 pipe_fds
中,之后 fork 一份子进程,子进程有着父进程文件描述符的一份拷贝,接下来都是非常基础的系统调用。
要注意父进程需要 wait
,等待子进程结束才可以输出 received pong
。
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
int main(int argc, char *argv[]) {
int pipe_fds[2]; // 管道读写口 0写 1读
if (pipe(pipe_fds) < 0) {
fprintf(2, "pipe: error\n");
exit(1);
}
int pid = fork();
if (pid == 0) { // child process
char *ch = "";
read(pipe_fds[0], ch, 1);
fprintf(1, "%d: received ping\n", pid);
exit(0);
} else { // parent process
char *ch = "a";
write(pipe_fds[1], ch, 1);
wait(0);
fprintf(1, "%d: received pong\n", pid);
}
exit(0);
}
# 3、Primes
利用管道和进程来筛质数,每一个子进程筛某一个质数的倍数。
思路:
对于每一个 work
函数,参数是左管道的两个文件描述符,每次先读取第一个目前没有被筛掉,也就是当前这一轮的第一个数字,其一定是质数,将其输出。接着我们创建一个新的右管道,用来传递给下一轮进程当前未被筛掉的数字。每个 work
中创建的子进程进入下一轮筛数字,而父进程则执行将这一轮的数字继续读完,并把未被筛掉的数字通过右管道的形式传递给下一轮。
注意点:
- 文件描述符上限是 35 个,所以要将无需使用的文件描述符关闭,注释中有标注
- 父进程读取完这一轮的所有数字之后,一定要关闭写入描述符。因为当写口关闭后,
read
会返回 0,此时表示没有可以筛的数字了,应该直接关闭进程。 - 另外每个父进程都要等待子进程结束,父进程等待子进程,而子进程又等待子进程的子进程结束。
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
int cnt = 0;
void work(int pl[]) {
close(pl[1]); // 无需写入
int n;
if (read(pl[0], &n, sizeof(n)) == 0) { // 如果写口关闭了,read 将会返回 0,即最后一个数字读完后要退出进程
exit(0);
}
fprintf(1, "prime %d\n", n); // 第一个未被筛去的数字,一定是质数
int pr[2]; // 右管道,用来传递当前未被筛去的数组
pipe(pr);
if (fork() == 0) { // 子进程递归进行下一轮质数筛
work(pr);
} else {
close(pr[0]); // 父进程无需读入
int x = 0;
while (read(pl[0], &x, sizeof(x)) != 0) { //从 pl 读取剩余传递来的数字
if (x % n != 0) { // 如果未被筛去则进入下一轮筛
write(pr[1], &x, sizeof(x));
}
}
close(pr[1]); // 一定要关闭写入符,否则读第一个质数没读到不会变成 0,那么会一直递归下去了。
wait(0); // 父进程等待
}
exit(0);
}
int main(int argc, char *argv[]) {
int pipe_fds[2];
pipe(pipe_fds);
if (fork() == 0) {
close(pipe_fds[1]); // 子进程无需写入
work(pipe_fds);
} else {
close(pipe_fds[0]); // 父进程无需读入
for (int i = 2; i <= 35; i++) {
write(pipe_fds[1], &i, sizeof(i)); // 传递数字
}
close(pipe_fds[1]); // 一定要关闭写入符,否则读第一个质数不会变成 0,那么会一直递归下去了。
wait(0); // 一定要等待
}
exit(0);
}
# 4、Find
大题内容都是仿照 ls.c
的,具体看注释即可
path 是当前目录的路径,buf 是添加文件名后的路径,每次将文件 cp 到 buf 后面进行判断,如果是 FILE 且与我们要的文件名相等则输出,如果是目录且不是 .
和 ..
,则递归查找下去。
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"
void fmtpath(char *path) {
char *p;
for (p = path + strlen(path); p >= path && *p != '/'; p--);
*(++p) = 0;
}
void find(char *path, char *fn) {
char buf[512], *p;
int fd;
struct dirent de;
struct stat st;
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;
}
// 读取当前路径下的所有数据
while (read(fd, &de, sizeof(de)) == sizeof(de)) {
strcpy(buf, path);
p = buf + strlen(buf);
*p++ = '/';
if (de.inum == 0) {
continue;
}
memcpy(p, de.name, DIRSIZ); // 将名字加在 buf 后面
p[DIRSIZ] = 0;
if (stat(buf, &st) < 0) {
fprintf(2, "ls: cannot stat %s\n", buf);
}
switch(st.type) {
case T_FILE: // 如果是文件
if (strcmp(p, fn) == 0) { // 文件名 和 fn 相同则输出
fprintf(1, "%s\n", buf);
}
break;
case T_DIR: // 如果是目录
if (strcmp(p, ".") != 0 && strcmp(p, "..") != 0) { // 且不是 . 和 ..
find(buf, fn); // 递归查询这个目录
}
}
}
close(fd);
}
int main(int argc, char *argv[]) {
if (argc < 3) {
fprintf(2, "find: miss parameter\n");
exit(1);
}
char *path = argv[1]; // path
char *fn = argv[2]; // file name
find(path, fn);
exit(0);
}
# 5、xargs
注释写的很详细了,主要是从标准输入里面读取参数然后将参数保存,然后创建子进程执行命令。
需要注意的点是每次标准输入中读取完一行的参数后要执行一次命令,并且初始参数不变!!!也就是代码中 init_para 之前的参数每次都要(这里我开始没注意,debug 了好久,也是对 xargs 理解不够吧)
#include "kernel/param.h"
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"
#define ARGLEN 50
int main(int argc, char *argv[]) {
char *cmd = argv[1];
int N = argc - 1;
char *para[MAXARG]; // 所有参数
for (int i = 0; i < N; i++) {
para[i] = argv[i + 1];
}
char **init_para = para + N; // init_para 指向右边指令参数的末尾
char arg[MAXARG * ARGLEN]; // 内存池,存放标准输入读取进来的参数
char *p = arg; // 用来在 arg 中存储参数,参数之间用 '\0' 分割
char ch;
int cnt = 0;
char **lp = init_para; // lp 用来移动并存储标准输入当前一行的参数
while (read(0, &ch, 1) != 0) {
if (ch == ' ' || ch == '\n') {
*p = 0; // 一个参数或者一行读完,0 分割
*lp++ = arg + ARGLEN * cnt; // lp 指向 arg 中当前所有参数存放的末尾
cnt++; // 参数个数加一
p = arg + ARGLEN * cnt; // p 指向了下一个参数的开头存放的位置
if (ch == '\n') { // 如果是换行符,则要执行 exec 了
*lp = 0; // para 以 0 为结尾作为参数结束的标志
if (fork() == 0) { //创建子进程执行 exec
exec(cmd, para);
exit(0);
} else {
wait(0);
lp = init_para; // lp 回到 init_para,重新读取下一行的所有参数
}
}
} else {
*p++ = ch;
}
}
exit(0);
}
# 实验结果
Last Updated: 3/4/2023, 5:38:14 PM