Files
2022-03-09 22:07:44 +08:00

449 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`的输出,二者是一致的。
![img](images/p3.png)
有限状态自动机主要就是一系列的状态转换,例如对于
```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_ENDfork后执行程序
...以此类推
```
```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);
}
```