关于 MIT 6.S081
这门课的前身是 MIT 著名的课程 6.828,MIT 的几位教授为了这门课曾专门开发了一个基于 x86 的教学用操作系统 JOS,被众多名校作为自己的操统课程实验。但随着 RISC-V 的横空出世,这几位教授又基于 RISC-V 开发了一个新的教学用操作系统 xv6,并开设了 MIT6.S081 这门课。由于 RISC-V 轻便易学的特点,学生不需要像此前 JOS 一样纠结于众多 x86 “特有的”为了兼容而遗留下来的复杂机制,而可以专注于操作系统层面的开发。
代码环境
Xv6 是在 x86 处理器上( x 即指 x86 )用 ANSI 标准 C 重新实现的 Unix 第六版(Unix V6,通常直接被称为 V6)。
学习笔记
记录学习这门课时的一些笔记,包括阅读相关材料的一些摘录,以及完成 lab 时的思路和代码。
Operating System Interfaces
这章主要介绍了操作系统提供的一些接口,通过这些接口,让用户可以去执行内核中的函数。这些函数称为系统调用。
shell 的工作原理
以 lab 中的 user/sh.c 为例
main 函数经过一些列的环境检查,fork 出一个子进程,子进程调用 exec 执行 shell 中给出的命令。
- getcmd 函数接受命令行中的语句,如 ls / 。
- 子进程执行 runcmd("ls /") 函数,父进程执行 wati(0) 函数等待子进程结束,并回收资源。
- 子进程在 runcmd 函数中执行到 exec(ls, /) 系统调用函数,完成命令。
过程如下图所示:
命令行中管道符工作原理
管道是内核中的一块 buffer , 以一对文件描述符的方式暴露给进程。
int p[2];
pipe(p);
p[0] // 负责将从管道读出数据
p[1] // 负责从管道写入数据
为了实现下图中划红线的命令:
xv6 的 shell 使用如下代码实现:
为了更好地理解(user/sh.c 100),也就是上图中所示的代码,写了下图近似代码通过打印日志来分析执行流程。
通过输出知道代码的执行过程的进程的父子关系如下图所示:
- 把标准输出关联到管道p[1],运行管道符左侧的命令,grep fork sh.c 会将结果写入的标准输出,但实际上是写到了 p[1];
- 把标准输入关联到管道pp[0],运行管道符右侧的命令(对于grep fork sh.c),其没有右侧命令,所以结束返回,如果有管道符如(b | c),则会继续创建子进程进行处理。
- 把标准输入关联到管道p[0],运行管道符右侧的命令(也就是 wc -l),wc -l 会从标准输出读入数据,但其实是从 p[0] 读入。
Other Note
这段讨论的是不使用子进程来执行 | 左右两边的命令,这么做会需要额外的代码来保证左右两边的命令执行的先后顺序。
Lab Unix Utilities
这个 lab 的主要任务是实现一些 xv6 的应用层工具。
sleep
实现 sleep 命令,实现代码如下图所示
在第 26 行会执行 sleep 系统调用,用于进入到系统的内核态执行 sleep 相关的内核函数,来完成进程的睡眠功能。
pingpong
申请 2 对管道,用于两个进程直接的通信。原理如下图所示:
注意关闭不使用的管道,xv6 所能使用的文件描述符有限。
primes
find
需要注意的是最后一行,当 dirent 的 inum == 0 的时候,说明这个目录是空的。
memove() 函数用于复制字符串,当源字符串和目的字符串的地址空间有重合的时候,可以保证源字符能不被破坏的复制到目的字符串。复制完成后,源字符串中的内容发生改变。
代码可仿照 ls.c 来进行实现。注意当遍历到子文件夹时,需要进行递归处理。
#include "kernel/types.h"
#include "user/user.h"
#include "kernel/fs.h"
#include "kernel/stat.h"
void find(char* path, char* file_name) {
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 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;
}
memmove(p, de.name, DIRSIZ); // full path ex. path/item_name
p[DIRSIZ] = 0;
if(stat(buf, &st) < 0) {
printf("find: cannot stat %s\n", buf);
continue;
}
switch(st.type) {
case T_FILE: {
if (strcmp(de.name, file_name) ==0) { // find the file
printf("%s\n", buf);
}
break;
}
case T_DIR: {
if (strcmp(de.name, ".") != 0 && strcmp(de.name, "..") != 0) {
find(buf, file_name); // recursive
}
break;
}
}
}
close(fd);
}
int main(int argc, char *argv[])
{
if (argc != 3) {
printf("There should be one path argument and one filename argument\n");
exit(0);
}
char* path_name = argv[1];
char* file_name = argv[2];
find(path_name, file_name);
exit(0);
}
xargs
从标准输入读入一行数据,作为 xargs 命令后的命令的输入。
echo hello too | xargs echo bye
# 在终端打印 byte hello too
- 如上所示代码,从左往右执行。执行完 echo hello too 会向标准输入写入 hello too,由于后接管道符,所以标准输入被重定向到一个管道当中。
- 随后执行 xargs 命令,其实现代码中的 argc 为 3,argv[] 为 "xargs", "echo", "byte"。
- 由于 xargs 要实现的功能为将前一个命令写入管道的字符作为其输入。
- 所以当执行 xargs 程序时,其实际的 argv 数组为 "xargs", "echo", "byte", "hello", "too"。
- 这个程序的实现最核心的是要为 xargs 命令组装新的 argv 数组。然后 fork 出一个子进程调用 exec 执行 argv[1] 所使用的命令(这里是 echo),然后将其新的参数数组 argv 也传入 exec 函数中。
#include "kernel/types.h"
#include "kernel/stat.h"
#include "kernel/param.h"
#include "user/user.h"
#define MAXLEN 100
int main(int argc, char* argv[])
{
if (argc <= 1) {
fprintf(2, "illegal argument number\n");
}
char* cmd = argv[1]; // 记录命令行输入的命令
char buf;
char argv_list[MAXARG][MAXLEN];
char* new_argv_list[MAXARG]; // 记录拼接完后的新参数列表
while(1) {
memset(argv_list, 0, MAXARG * MAXLEN);
for (int i = 1; i < argc; ++i) {
strcpy(argv_list[i-1], argv[i]); // 将除命令名之外的字符串,复制给 argv_list
}
int cur_argc = argc - 1;
int offset = 0;
int is_read = 0;
while((is_read = read(0, &buf, 1))) > 0) { // 逐个读入字符
if (buf == ' ') {
cur_argc++;
offset = 0;
continue;
}
if(buf == '\n') {
break; // 读完当前行
}
if (offset == MAXLEN) {
fprintf(2, "parameter too long\n");
}
if (cur_argc == MAXARG) {
fprintf(2, "too many argument\n");
}
// 将从 stdin中读取的参数,填到原 xargs 命令的参数后面
argv_list[cur_argc][offset++] = buf;
}
if (is_read <= 0) {
break; // 管道符之前的命令执行结果读取结束
}
// 组装新的 char* argv[] 参数
for (int i = 0; i <= cur_argc; ++i) {
new_argv_list[i] = argv_list[i];
}
if (fork() == 0) {
exec(cmd, new_argv_list);
exit(1);
}
wait(0);
}
exit(0);
}
References
- https://blog.csdn.net/u013577996/article/details/108680888
- https://fanxiao.tech/posts/MIT-6S081-notes/
- https://blog.miigon.net/posts/s081-lab1-unix-utilities/
- https://www.yichuny.page/posts/6s081lab-utilities/
词汇
the downside of 缺点
标签:xargs,name,MIT6,Utilities,Lab,管道,argv,path,buf From: https://www.cnblogs.com/qwerty-ll/p/16855574.html