mirror of
https://github.com/duguosheng/6.S081-All-in-one.git
synced 2026-02-02 18:39:38 +08:00
449 lines
11 KiB
Markdown
449 lines
11 KiB
Markdown
# lab1: Util
|
||
|
||
## sleep
|
||
|
||
这个简单,不多说了
|
||
|
||
```c
|
||
#include "kernel/types.h"
|
||
#include "user/user.h"
|
||
|
||
int main(int argc, char const *argv[])
|
||
{
|
||
if (argc != 2) { //参数错误
|
||
fprintf(2, "usage: sleep <time>\n");
|
||
exit(1);
|
||
}
|
||
sleep(atoi(argv[1]));
|
||
exit(0);
|
||
}
|
||
```
|
||
|
||
## pingpong
|
||
|
||
使用两个管道进行父子进程通信,需要注意的是如果管道的写端没有`close`,那么管道中数据为空时对管道的读取将会阻塞。因此对于不需要的管道描述符,要尽可能早的关闭。
|
||
|
||
```c
|
||
#include "kernel/types.h"
|
||
#include "user/user.h"
|
||
|
||
#define RD 0 //pipe的read端
|
||
#define WR 1 //pipe的write端
|
||
|
||
int main(int argc, char const *argv[]) {
|
||
char buf = 'P'; //用于传送的字节
|
||
|
||
int fd_c2p[2]; //子进程->父进程
|
||
int fd_p2c[2]; //父进程->子进程
|
||
pipe(fd_c2p);
|
||
pipe(fd_p2c);
|
||
|
||
int pid = fork();
|
||
int exit_status = 0;
|
||
|
||
if (pid < 0) {
|
||
fprintf(2, "fork() error!\n");
|
||
close(fd_c2p[RD]);
|
||
close(fd_c2p[WR]);
|
||
close(fd_p2c[RD]);
|
||
close(fd_p2c[WR]);
|
||
exit(1);
|
||
} else if (pid == 0) { //子进程
|
||
close(fd_p2c[WR]);
|
||
close(fd_c2p[RD]);
|
||
|
||
if (read(fd_p2c[RD], &buf, sizeof(char)) != sizeof(char)) {
|
||
fprintf(2, "child read() error!\n");
|
||
exit_status = 1; //标记出错
|
||
} else {
|
||
fprintf(1, "%d: received ping\n", getpid());
|
||
}
|
||
|
||
if (write(fd_c2p[WR], &buf, sizeof(char)) != sizeof(char)) {
|
||
fprintf(2, "child write() error!\n");
|
||
exit_status = 1;
|
||
}
|
||
|
||
close(fd_p2c[RD]);
|
||
close(fd_c2p[WR]);
|
||
|
||
exit(exit_status);
|
||
} else { //父进程
|
||
close(fd_p2c[RD]);
|
||
close(fd_c2p[WR]);
|
||
|
||
if (write(fd_p2c[WR], &buf, sizeof(char)) != sizeof(char)) {
|
||
fprintf(2, "parent write() error!\n");
|
||
exit_status = 1;
|
||
}
|
||
|
||
if (read(fd_c2p[RD], &buf, sizeof(char)) != sizeof(char)) {
|
||
fprintf(2, "parent read() error!\n");
|
||
exit_status = 1; //标记出错
|
||
} else {
|
||
fprintf(1, "%d: received pong\n", getpid());
|
||
}
|
||
|
||
close(fd_p2c[WR]);
|
||
close(fd_c2p[RD]);
|
||
|
||
exit(exit_status);
|
||
}
|
||
}
|
||
```
|
||
|
||
## primes
|
||
|
||
这个感觉还是有些难度的,它的思想是多进程版本的递归,不断地将左邻居管道中的数据筛选后传送给右邻居,每次传送的第一个数据都将是一个素数。
|
||
|
||
具体还是看代码吧,里面注释应该还是比较清楚的
|
||
|
||
```c
|
||
#include "kernel/types.h"
|
||
#include "user/user.h"
|
||
|
||
#define RD 0
|
||
#define WR 1
|
||
|
||
const uint INT_LEN = sizeof(int);
|
||
|
||
/**
|
||
* @brief 读取左邻居的第一个数据
|
||
* @param lpipe 左邻居的管道符
|
||
* @param pfirst 用于存储第一个数据的地址
|
||
* @return 如果没有数据返回-1,有数据返回0
|
||
*/
|
||
int lpipe_first_data(int lpipe[2], int *dst)
|
||
{
|
||
if (read(lpipe[RD], dst, sizeof(int)) == sizeof(int)) {
|
||
printf("prime %d\n", *dst);
|
||
return 0;
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
/**
|
||
* @brief 读取左邻居的数据,将不能被first整除的写入右邻居
|
||
* @param lpipe 左邻居的管道符
|
||
* @param rpipe 右邻居的管道符
|
||
* @param first 左邻居的第一个数据
|
||
*/
|
||
void transmit_data(int lpipe[2], int rpipe[2], int first)
|
||
{
|
||
int data;
|
||
// 从左管道读取数据
|
||
while (read(lpipe[RD], &data, sizeof(int)) == sizeof(int)) {
|
||
// 将无法整除的数据传递入右管道
|
||
if (data % first)
|
||
write(rpipe[WR], &data, sizeof(int));
|
||
}
|
||
close(lpipe[RD]);
|
||
close(rpipe[WR]);
|
||
}
|
||
|
||
/**
|
||
* @brief 寻找素数
|
||
* @param lpipe 左邻居管道
|
||
*/
|
||
void primes(int lpipe[2])
|
||
{
|
||
close(lpipe[WR]);
|
||
int first;
|
||
if (lpipe_first_data(lpipe, &first) == 0) {
|
||
int p[2];
|
||
pipe(p); // 当前的管道
|
||
transmit_data(lpipe, p, first);
|
||
|
||
if (fork() == 0) {
|
||
primes(p); // 递归的思想,但这将在一个新的进程中调用
|
||
} else {
|
||
close(p[RD]);
|
||
wait(0);
|
||
}
|
||
}
|
||
exit(0);
|
||
}
|
||
|
||
int main(int argc, char const *argv[])
|
||
{
|
||
int p[2];
|
||
pipe(p);
|
||
|
||
for (int i = 2; i <= 35; ++i) //写入初始数据
|
||
write(p[WR], &i, INT_LEN);
|
||
|
||
if (fork() == 0) {
|
||
primes(p);
|
||
} else {
|
||
close(p[WR]);
|
||
close(p[RD]);
|
||
wait(0);
|
||
}
|
||
|
||
exit(0);
|
||
}
|
||
```
|
||
|
||
## find
|
||
|
||
感觉没什么好说的0.0,代码基本上都是COPY的*ls.c*中的内容
|
||
|
||
```c
|
||
#include "kernel/types.h"
|
||
|
||
#include "kernel/fs.h"
|
||
#include "kernel/stat.h"
|
||
#include "user/user.h"
|
||
|
||
void find(char *path, const char *filename)
|
||
{
|
||
char buf[512], *p;
|
||
int fd;
|
||
struct dirent de;
|
||
struct stat st;
|
||
|
||
if ((fd = open(path, 0)) < 0) {
|
||
fprintf(2, "find: cannot open %s\n", path);
|
||
return;
|
||
}
|
||
|
||
if (fstat(fd, &st) < 0) {
|
||
fprintf(2, "find: cannot fstat %s\n", path);
|
||
close(fd);
|
||
return;
|
||
}
|
||
|
||
//参数错误,find的第一个参数必须是目录
|
||
if (st.type != T_DIR) {
|
||
fprintf(2, "usage: find <DIRECTORY> <filename>\n");
|
||
return;
|
||
}
|
||
|
||
if (strlen(path) + 1 + DIRSIZ + 1 > sizeof buf) {
|
||
fprintf(2, "find: path too long\n");
|
||
return;
|
||
}
|
||
strcpy(buf, path);
|
||
p = buf + strlen(buf);
|
||
*p++ = '/'; //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) {
|
||
fprintf(2, "find: cannot stat %s\n", buf);
|
||
continue;
|
||
}
|
||
//不要在“.”和“..”目录中递归
|
||
if (st.type == T_DIR && strcmp(p, ".") != 0 && strcmp(p, "..") != 0) {
|
||
find(buf, filename);
|
||
} else if (strcmp(filename, p) == 0)
|
||
printf("%s\n", buf);
|
||
}
|
||
|
||
close(fd);
|
||
}
|
||
|
||
int main(int argc, char *argv[])
|
||
{
|
||
if (argc != 3) {
|
||
fprintf(2, "usage: find <directory> <filename>\n");
|
||
exit(1);
|
||
}
|
||
find(argv[1], argv[2]);
|
||
exit(0);
|
||
}
|
||
```
|
||
|
||
## xargs
|
||
|
||
之前这个题目我是做错的,但是仍然通过了测试。需要注意的是题目中要求为每一行执行一个命令。之前刷力扣的时候处理字符串好几次用到了有限状态自动机,虽然写的代码比较多,但是只要搞清楚逻辑,这种方法反而比较容易写出来。
|
||
|
||
xv6中的`echo`命令并不能输出换行符,例如在xv6和linux中执行命令
|
||
|
||
xv6执行`echo "1\n2"`输出
|
||
|
||
```text
|
||
"1\n2"
|
||
```
|
||
|
||
linux执行`echo -e "1\n2"`输出(`-e`启用转义)
|
||
|
||
```text
|
||
1
|
||
2
|
||
```
|
||
|
||
因此没有想到如何在xv6中验证`xargs`的正确性,于是我将类似(类似是因为头文件不同,且将`exec`替换为了`execvp`)的代码*xargs.c*在linux下编译并测试运行,如下图所示:第一条命令是使用linux中`xargs`的输出,第二条命令是使用自己写的`xargs`的输出,二者是一致的。
|
||
|
||

|
||
|
||
有限状态自动机主要就是一系列的状态转换,例如对于
|
||
|
||
```text
|
||
1 \n 23
|
||
0123 456
|
||
```
|
||
|
||
来说,第一行是待读取的字符串,第二行是字符下标,起始时状态为`S_WAIT`,状态转换如下
|
||
|
||
```text
|
||
读取到0处的空格 状态由S_WAIT变为S_WAIT,继续等待参数到来(arg_beg前移)
|
||
读取到1处的字符1 状态由S_WAIT变为S_ARG,开始读取参数(arg_beg不动)
|
||
读取到2处的空格 状态由S_ARG变为S_ARG_END,储存参数地址(将lines[arg_beg]的地址存入x_argv中)
|
||
读取到3处的换行 状态由S_ARG_END变为S_LINE_END,fork后执行程序
|
||
...以此类推
|
||
```
|
||
|
||
```c
|
||
#include "kernel/types.h"
|
||
#include "user/user.h"
|
||
#include "kernel/param.h"
|
||
|
||
#define MAXSZ 512
|
||
// 有限状态自动机状态定义
|
||
enum state {
|
||
S_WAIT, // 等待参数输入,此状态为初始状态或当前字符为空格
|
||
S_ARG, // 参数内
|
||
S_ARG_END, // 参数结束
|
||
S_ARG_LINE_END, // 左侧有参数的换行,例如"arg\n"
|
||
S_LINE_END, // 左侧为空格的换行,例如"arg \n""
|
||
S_END // 结束,EOF
|
||
};
|
||
|
||
// 字符类型定义
|
||
enum char_type {
|
||
C_SPACE,
|
||
C_CHAR,
|
||
C_LINE_END
|
||
};
|
||
|
||
/**
|
||
* @brief 获取字符类型
|
||
*
|
||
* @param c 待判定的字符
|
||
* @return enum char_type 字符类型
|
||
*/
|
||
enum char_type get_char_type(char c)
|
||
{
|
||
switch (c) {
|
||
case ' ':
|
||
return C_SPACE;
|
||
case '\n':
|
||
return C_LINE_END;
|
||
default:
|
||
return C_CHAR;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 状态转换
|
||
*
|
||
* @param cur 当前的状态
|
||
* @param ct 将要读取的字符
|
||
* @return enum state 转换后的状态
|
||
*/
|
||
enum state transform_state(enum state cur, enum char_type ct)
|
||
{
|
||
switch (cur) {
|
||
case S_WAIT:
|
||
if (ct == C_SPACE) return S_WAIT;
|
||
if (ct == C_LINE_END) return S_LINE_END;
|
||
if (ct == C_CHAR) return S_ARG;
|
||
break;
|
||
case S_ARG:
|
||
if (ct == C_SPACE) return S_ARG_END;
|
||
if (ct == C_LINE_END) return S_ARG_LINE_END;
|
||
if (ct == C_CHAR) return S_ARG;
|
||
break;
|
||
case S_ARG_END:
|
||
case S_ARG_LINE_END:
|
||
case S_LINE_END:
|
||
if (ct == C_SPACE) return S_WAIT;
|
||
if (ct == C_LINE_END) return S_LINE_END;
|
||
if (ct == C_CHAR) return S_ARG;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
return S_END;
|
||
}
|
||
|
||
|
||
/**
|
||
* @brief 将参数列表后面的元素全部置为空
|
||
* 用于换行时,重新赋予参数
|
||
*
|
||
* @param x_argv 参数指针数组
|
||
* @param beg 要清空的起始下标
|
||
*/
|
||
void clearArgv(char *x_argv[MAXARG], int beg)
|
||
{
|
||
for (int i = beg; i < MAXARG; ++i)
|
||
x_argv[i] = 0;
|
||
}
|
||
|
||
int main(int argc, char *argv[])
|
||
{
|
||
if (argc - 1 >= MAXARG) {
|
||
fprintf(2, "xargs: too many arguments.\n");
|
||
exit(1);
|
||
}
|
||
char lines[MAXSZ];
|
||
char *p = lines;
|
||
char *x_argv[MAXARG] = {0}; // 参数指针数组,全部初始化为空指针
|
||
|
||
// 存储原有的参数
|
||
for (int i = 1; i < argc; ++i) {
|
||
x_argv[i - 1] = argv[i];
|
||
}
|
||
int arg_beg = 0; // 参数起始下标
|
||
int arg_end = 0; // 参数结束下标
|
||
int arg_cnt = argc - 1; // 当前参数索引
|
||
enum state st = S_WAIT; // 起始状态置为S_WAIT
|
||
|
||
while (st != S_END) {
|
||
// 读取为空则退出
|
||
if (read(0, p, sizeof(char)) != sizeof(char)) {
|
||
st = S_END;
|
||
} else {
|
||
st = transform_state(st, get_char_type(*p));
|
||
}
|
||
|
||
if (++arg_end >= MAXSZ) {
|
||
fprintf(2, "xargs: arguments too long.\n");
|
||
exit(1);
|
||
}
|
||
|
||
switch (st) {
|
||
case S_WAIT: // 这种情况下只需要让参数起始指针前移
|
||
++arg_beg;
|
||
break;
|
||
case S_ARG_END: // 参数结束,将参数地址存入x_argv数组中
|
||
x_argv[arg_cnt++] = &lines[arg_beg];
|
||
arg_beg = arg_end;
|
||
*p = '\0'; // 替换为字符串结束符
|
||
break;
|
||
case S_ARG_LINE_END: // 将参数地址存入x_argv数组中同时执行指令
|
||
x_argv[arg_cnt++] = &lines[arg_beg];
|
||
// 不加break,因为后续处理同S_LINE_END
|
||
case S_LINE_END: // 行结束,则为当前行执行指令
|
||
arg_beg = arg_end;
|
||
*p = '\0';
|
||
if (fork() == 0) {
|
||
exec(argv[1], x_argv);
|
||
}
|
||
arg_cnt = argc - 1;
|
||
clearArgv(x_argv, arg_cnt);
|
||
wait(0);
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
|
||
++p; // 下一个字符的存储位置后移
|
||
}
|
||
exit(0);
|
||
}
|
||
``` |