diff --git a/README.md b/README.md index 71022aa..bcdf515 100644 --- a/README.md +++ b/README.md @@ -1,110 +1,36 @@ -# 01 Lab 赛道 -Repo to store 2025 Hackthon contest entries. +# 黑客松比赛作品 -> Ask: samuka007@dragonos.org +姓名:郑达均(qianliq) -## 背景 -关键词:operating system, computer network +参加项目:01-lab 赛道-赛题一(MIT6.S081) -提供给大家一个学习的机会和契机,鼓励大家尝试自己尚未有机会开始接触的lab! +## 作品介绍 -> [!WARNING] -> 本赛道有五个赛题,请选择其中一个赛题参赛! +按照MIT6.S081课程的指引,完成 lab1 和 lab2(本部分不包含lab0(环境安装和调试)!) -## 规定 -- 1 人一组,完成一项 Lab 赛题 -- 最终lab学习成果及文档需要提交 PR 到当前仓库 - 或提交至 https://gitea.scutosc.cn/ 并发送邮件告知评委 -- 选择 DragonOS 系的题目有加分 +其中 xv6 系统运行在 ubuntu 系统上,使用 qemu 虚拟化 -## 综合评分细则 -1. 创新性与难度(25%) - 1. 完成的实验难度 - 2. 实验的新颖度 - 3. 对现实问题的指导意义 -2. 功能完备性(55%) - 1. 完成了实验 / 实现了预期功能(35%) - 2. 需要实践测试 - 1. 实践有启发性,实践的可复现性、应用性(15%) - 3. 需要写代码的 - 1. 运行性能(10%) - 2. 代码质量(5%) -3. 展示效果(20%) - 1. 文档展示 - 2. 路演效果 +代码均通过 lab 中的 grade-lab-*** 测试 -## 操作系统方向 +## 文件结构 -### 赛题一:MIT 6.S081: Operating System Engineering +``` +├── assets/ +│ └── *.png # 存放实验相关的图片资源(如流程图、截图等) +│ +├── lab1_util 解析.md # Lab1 核心代码解析与题目分析 +│ - 内容包括: +│ - 实验目标概述 +│ - 题目解答思路与实现 +│ +├── lab2_syscall 解析.md # Lab2 核心代码解析与题目分析 +│ - 内容包括: +│ - 实验目标概述 +│ - 从实验提示和参考资料中获得的信息解析 +│ - 题目解答思路与实现 +│ +``` -#### 背景 -麻省理工学院大名鼎鼎的 PDOS 实验室开设的面向MIT本科生的操作系统课程。 +本仓库地址:https://github.com/qianliq/01-lab-hackthon-MIT6.S081 -#### 任务 - -参考: -- https://csdiy.wiki/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/MIT6.S081/ -- https://pdos.csail.mit.edu/6.828/2021/schedule.html - -> [!NOTE] -> 要求:完成两个及以上的 Lab 题目 - -题目见绿色 Assignment 字体后的链接 -![题目见绿色 Assignment 字体后的链接](https://github.com/user-attachments/assets/c348efcb-1c71-4472-bf03-e487aee96ce1) - - -### 赛题二:DragonOS 系统调用 & 修复 - -#### 背景 -DragonOS 是使用 Rust 编写的,以 Linux 架构作为参考的操作系统。 - -Repo: https://github.com/DragonOS-Community/DragonOS - -当前 DragonOS 内,有不少系统调用,由于各种原因 ( C 与 Rust 语言差异等 ) ,导致调用时常不符合语义/行为不一致。 - -#### 任务 -- 参考 https://bbs.dragonos.org.cn/t/topic/460 (以及最新引入的系统调用注册表, - 参考 https://github.com/DragonOS-Community/DragonOS/pull/1164 )在 DragonOS - 内添加一个新的系统调用,并在用户态成功调用 -- 寻找内核中行为与预期有出入的系统调用,建议寻找静态、接口不一致的错误 - -#### 参考 -一个内核中静态分析得出行为不一致的 Shutdown 错误: - -https://github.com/DragonOS-Community/DragonOS/issues/887 - -## 网络方向 - -### 赛题三:CS144: Introduction to Computer Networking - -#### 背景 -CS144 通过提前为网卡硬件、TCP 等组件定义抽象,提供了一套精巧设计的框架,为同学们理解网络模型提供了恰到好处的“题目”, -即,通过框架可以一窥整个计算机网络实现的意图与设计,又得以从重复的工作中抽离出来,将时间投入到理解网络模型与底层原理 -当中来。 - -#### 任务 -https://csdiy.wiki/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/CS144/ - -努力完成一个 TCP 协议栈吧! -- 实现至 checkpoint 3 及以上 - -### 赛题四:DragonOS Socket Implementation -#### 背景 -DragonOS 网络子系统中,Inet 协议簇依赖于 [smoltcp] 库实现。 -在Repo https://github.com/Samuka007/dragonos-berkeley-socket 中将网络子系统相关 -的框架实现抽离了出来,基于 tap 设备与 Linux Epoll 机制模拟中断,允许在用户空间测试 -[`Inet 协议簇`](https://github.com/Samuka007/dragonos-berkeley-socket/tree/master/src/socket/inet) -的功能。 - -#### 任务 -目前实现的协议栈涉及TCP多层状态机的转换,由于 Rust 的借用机制,以及 [smoltcp] 的 Socket 接口不完全与 -Berkeley 定义相一致,因此实现上会出现一些冗余的情况,让各层状态机之间的转换变得复杂。 - -本实验的任务是尝试基于已有的网卡抽象,参考现有的 Inet 协议簇 实现,基于 [smoltcp] 自己实现 TCP Socket, -并尝试在现有的 TCP 实现上予以优化。 - -## 其他方向 -自选实验,需要体现技术栈深度 - - -[smoltcp]: https://crates.io/crates/smoltcp +完成的完整 xv6 代码仓库:https://github.com/qianliq/xv6-labs-learning diff --git a/assets/ sysinfo.png b/assets/ sysinfo.png new file mode 100644 index 0000000..9b9212a Binary files /dev/null and b/assets/ sysinfo.png differ diff --git a/assets/ trace.png b/assets/ trace.png new file mode 100644 index 0000000..fa17139 Binary files /dev/null and b/assets/ trace.png differ diff --git a/assets/find.png b/assets/find.png new file mode 100644 index 0000000..785c5fb Binary files /dev/null and b/assets/find.png differ diff --git a/assets/image-20250608135132945.png b/assets/image-20250608135132945.png new file mode 100644 index 0000000..697b017 Binary files /dev/null and b/assets/image-20250608135132945.png differ diff --git a/assets/pingpong.png b/assets/pingpong.png new file mode 100644 index 0000000..b1f78dd Binary files /dev/null and b/assets/pingpong.png differ diff --git a/assets/primes.png b/assets/primes.png new file mode 100644 index 0000000..68945a0 Binary files /dev/null and b/assets/primes.png differ diff --git a/assets/sleep.png b/assets/sleep.png new file mode 100644 index 0000000..97eee8d Binary files /dev/null and b/assets/sleep.png differ diff --git a/assets/xargs.png b/assets/xargs.png new file mode 100644 index 0000000..64c01ef Binary files /dev/null and b/assets/xargs.png differ diff --git "a/lab1_util \350\247\243\346\236\220.md" "b/lab1_util \350\247\243\346\236\220.md" new file mode 100644 index 0000000..f4418fa --- /dev/null +++ "b/lab1_util \350\247\243\346\236\220.md" @@ -0,0 +1,383 @@ +# 完成记录 + +使用 grade-lab-util 文件检测 + +## sleep + +![sleep](./assets/sleep.png) + +## pingpong + +![pingpong](./assets/pingpong.png) + +## primes + +![primes](./assets/primes.png) + +## find + +![find](./assets/find.png) + +## xargs + +![xargs](./assets/xargs.png) + + + +# 逐题解析 + +## sleep + +目标:让进程休眠指定的 tick 数量 + +当前在 kernel 中已经有 sleep 函数,需要的是在 user 部分调用 + +同时需要在 makefile 上增加编译这个 sleep.c 文件 + +### 主要代码 + +```c +int main(int argc, char *argv[]) { + if(argc != 2){ + printf("Usage: sleep \n"); + exit(0); + } + int ticks = atoi(argv[1]); + if(ticks < 0){ + printf("sleep: ticks must be non-negative\n"); + exit(0); + } + sleep(ticks); + exit(0); +} +``` + +代码说明: + +通过检查参数的长度判断是否合理 + +如果 sleep ticks 是负数同理 + +## pingpong + +目标:父子进程通过管道互发一个字节,实现“乒乓”通信。 + +当前已经提供了管道 pipe,阻塞的 read 和 write ,以及创建子进程的 fork 函数。 + +使用这四个函数构建一个管道互发 + +### 主要代码 + +```c +int main() { + int p1[2], p2[2]; + pipe(p1); pipe(p2); + int pid = fork(); + if(pid == 0){ + // child + char buf; + close(p1[1]); close(p2[0]); + read(p1[0], &buf, 1); + printf("%d: received ping\n", getpid()); + write(p2[1], &buf, 1); + close(p1[0]); close(p2[1]); + exit(0); + } else { + // parent + char buf = 'A'; + close(p1[0]); close(p2[1]); + write(p1[1], &buf, 1); + read(p2[0], &buf, 1); + printf("%d: received pong\n", getpid()); + close(p1[1]); close(p2[0]); + wait(0); + exit(0); + } +} +``` + +- **p1 管道** :子进程通过 `p1[0]` 读取父进程发送的数据。 +- **p2 管道** :子进程通过 `p2[1]` 发送数据回父进程。 + +``` +父进程 子进程 + | | + | 写入 p1[1] | + | --------------------> | + | | + | <-------------------- | + | 读取 p2[0] | + | | +``` + +## primes + +目标:使用递归实现一个素数筛 + +需要在不同子进程之间传递消息,使用管道来传递 + +### 主要代码 + +```c +#include "kernel/types.h" +#include "user/user.h" + +// 素数筛法的递归函数 +// pfd[2]:当前进程的管道描述符(pfd[0]为读端口,pfd[1]为写端口) +void sieve(int pfd[2]) { + // 关闭当前进程的写端口,因为只从父进程接收数据 + close(pfd[1]); + + int prime; + // 从管道读取一个整数(会阻塞直到有数据到达) + // 如果读取到的数据长度不等于int大小,说明管道已关闭,结束进程 + if (read(pfd[0], &prime, sizeof(prime)) != sizeof(prime)) { + close(pfd[0]); + exit(0); + } + + // 当前读取的值是素数,输出结果 + printf("prime %d\n", prime); + + // 创建新的管道用于传递非prime倍数的数给下一层筛子 + int next, cfd[2]; + pipe(cfd); + + // 创建子进程负责处理下一层筛法 + int pid = fork(); + if (pid == 0) { // 子进程逻辑 + close(pfd[0]); + sieve(cfd); // 递归调用筛法函数 + exit(0); + } else { // 父进程逻辑(当前筛子层) + while (read(pfd[0], &next, sizeof(next)) == sizeof(next)) { + // 如果不是当前素数的倍数,传递给下一层筛子 + if (next % prime != 0) + write(cfd[1], &next, sizeof(next)); // 写入下一层管道 + } + + // 所有数据处理完毕,清理资源 + close(pfd[0]); + close(cfd[1]); + wait(0); + exit(0); + } +} + +int main() { + int pfd[2]; // 管道描述符数组 + pipe(pfd); + + int pid = fork(); + if (pid == 0) { // 子进程负责启动筛法 + sieve(pfd); + exit(0); + } else { // 父进程负责注入初始数据 + close(pfd[0]); // 关闭读端口,只负责写入 + + // 向管道注入初始数据:2到35的整数序列 + for (int i = 2; i <= 35; i++) + write(pfd[1], &i, sizeof(i)); + + close(pfd[1]); // 数据写入完成,关闭写端口 + wait(0); // 等待子进程处理完成 + exit(0); + } +} +``` + +代码解析: + +最上层的父进程不计算素数,只负责将初始数据注入管道,让子进程依次读取 + +![image-20250608135132945](./assets/image-20250608135132945.png) + +筛出的数直接输出,不需要单独记录 + +## find + +目标:递归查找目录树下所有指定名字的文件。 + +注意是查找文件,也就是说,遇到目录就递归,遇到文件就判断 + +注意,需要跳过当前目录“.”和上级目录“..” + +### 主要代码 + +```c +void find(char *path, char *target) { + int fd; // 文件描述符,用于打开目录 + struct stat st; // 用于保存文件/目录的状态信息 + struct dirent de; // 目录条目结构体,包含文件名和 inode 编号 + char buf[512]; // 缓冲区,用于构造当前遍历到的文件路径 + char *p; // 指针,指向 buf 中路径拼接的位置 + + // 打开指定路径对应的目录文件 + if((fd = open(path, 0)) < 0) + return; + + // 获取该路径对应文件的状态信息(是否为目录) + if(fstat(fd, &st) < 0){ + close(fd); + return; + } + + // 如果不是目录,则不继续处理 + if(st.type != T_DIR){ + close(fd); + return; + } + + // 将原始路径复制到缓冲区中 + strcpy(buf, path); + + // p 指向路径字符串末尾,准备追加文件名 + p = buf + strlen(buf); + + // 如果路径末尾没有斜杠 '/',则添加一个 + if(*(p-1) != '/') *p++ = '/'; + + // 循环读取目录下的每一个条目 + while(read(fd, &de, sizeof(de)) == sizeof(de)){ + // 跳过无效条目(inum 为 0 表示未使用) + if(de.inum == 0) + continue; + + // 跳过 "." 和 ".." 这两个特殊目录项 + if(strcmp(de.name, ".") == 0 || strcmp(de.name, "..") == 0) + continue; + + // 将当前目录项的文件名拷贝到路径缓冲区中 + memmove(p, de.name, DIRSIZ); + p[DIRSIZ] = 0; + + // 清理路径字符串中多余的空字符 '\0'(因为 DIRSIZ 可能比实际文件名长) + for(int i = strlen(buf)-1; i > 0 && buf[i] == '\0'; i--) + buf[i] = 0; + + // 获取当前文件的状态信息 + if(stat(buf, &st) < 0) + continue; + + // 如果是目录,则递归调用 find 查找该子目录 + if(st.type == T_DIR) + find(buf, target); + + // 如果文件名匹配目标文件名,输出完整路径 + if(strcmp(de.name, target) == 0) + printf("%s\n", buf); + } + + close(fd); +} + +int main(int argc, char *argv[]) { + // 检查命令行参数数量是否正确(必须提供路径和文件名) + if(argc != 3){ + fprintf(2, "Usage: find \n"); + exit(1); + } + + // 调用 find 函数开始查找 + find(argv[1], argv[2]); + + exit(0); +} +``` + +在递归的过程需要记住递归的路径,并添加到绝对路径中传入下一层递归 + +## xagrs + +目标:从标准输入读取每一行,作为参数追加到命令后并执行。 + +首先用到了管道符号,即把前面的内容(或返回值)作为后面的输入 + +xargs 则是转换成命令行输入而非标准化输入 + +还需要注意,实验要求中对换行符的要求 + +```bash +$ echo "1\n2" | xargs -n 1 echo line +line 1 +line 2 +$ +``` + +即使用换行符来表示不同的命令批次 + +以及,实验要求,每行的执行都应该是一个子进程,然后最后的父进程需要等待所有子进程运行。 + +### 主要代码 + +```c +int main(int argc, char *argv[]) { + char buf[512]; // 缓冲区,用于存储用户输入的一行命令 + char *args[MAXARG]; // 参数数组,用于传递给 exec 的命令参数 + int i; + + // 将命令行参数(除了程序名本身)复制到 args 数组中 + for(i = 1; i < argc && i < MAXARG - 1; i++) + args[i - 1] = argv[i]; + + int n = i - 1; // n 表示当前已有的参数数量 + int idx = 0; // 用于追踪 buf 中写入的位置 + + char ch; + // 从标准输入逐字符读取内容,直到遇到 EOF(文件结束) + while(read(0, &ch, 1) == 1){ + if(ch == '\n'){ // 遇到换行符表示一行命令结束 + buf[idx] = 0; // 在字符串末尾添加 null 终止符 + if(idx > 0){ // 如果这一行不是空行 + + int m = 0, j = 0; + // 解析 buf 中的命令参数,按空格分割 + while(j < idx){ + // 跳过前面的空格 + while(buf[j] == ' ' && j < idx) + j++; + + if(j >= idx) break; // 如果已经处理完所有字符,跳出循环 + + args[n + m] = &buf[j]; // 记录当前参数的起始地址 + + // 找到下一个空格或字符串结尾,插入 null 终止符 + while(buf[j] != ' ' && buf[j] != 0 && j < idx) j++; + + buf[j] = 0; // 将空格替换为字符串结束符 + j++; + m++; + } + + args[n + m] = 0; + + // 创建一个子进程来执行命令 + if(fork() == 0){ + exec(args[0], args); + exit(1); // 如果 exec 失败,退出子进程 + } else { + // 父进程等待子进程执行完成 + wait(0); + } + } + idx = 0; + } else { + // 当前字符不是换行符,继续写入缓冲区 + if(idx < sizeof(buf) - 1) + buf[idx++] = ch; + } + } + + exit(0); +} +``` + +代码解析 + +需要做的就是把命令按照回车分割读入,然后执行 + +需要注意,在实现的时候,管道符号前面的内容的标准化输入可以用过文件描述符 0 来读取到 + +下面的原文内容: + +> By convention, a process reads from file descriptor 0 (standard input), writes output to file descriptor 1 (standard output), and writes error messages to file descriptor 2 (standard error). diff --git "a/lab2_syscall \350\247\243\346\236\220.md" "b/lab2_syscall \350\247\243\346\236\220.md" new file mode 100644 index 0000000..f936888 --- /dev/null +++ "b/lab2_syscall \350\247\243\346\236\220.md" @@ -0,0 +1,172 @@ +# 完成记录 + +使用 grade-lab-syscall 文件检测 + +## trace + +![ trace](./assets/ trace.png) + +## sysinfo + +![ sysinfo](./assets/ sysinfo.png) + + + +# 逐题解析 + +## trace + +目标:跟踪某个命令调用了指定的系统调用(使用 mask 和系统调用的指定编号记录),并且需要记录其子进程的调用 + +在实验提示中写明了,需要在多个文件中添加内容,实现增加一个系统调用 + +同时,为了让子进程也能知道自己需要监控哪个系统调用,此处采用在进程结构体中增加一个 tracemask 的值的方式,并且注意需要同步修改 fork 实现,让 fork 的时候从父进程正确读取 tracemask + +最后,在 syscall 的时候判断,tracemask 是否有效(默认全为 0,不是 0 则表示有效,需要监控),有效的时候就在 syscall 的同时输入调用的细节。 + +### 主要代码 + +#### 新增系统调用号 + +在 `kernel/syscall.h` 中添加: + +```c +#define SYS_trace 22 +``` + +#### 进程结构体 + +在 `kernel/proc.h` 的 `struct proc` 中添加: + +```c +int tracemask; +``` + +#### 系统调用实现 + +在 `kernel/sysproc.c` 中实现: + +```c +uint64 sys_trace(void) { + int mask; + if(argint(0, &mask) < 0) + return -1; + myproc()->tracemask = mask; + return 0; +} +``` + +#### fork 继承的时候同步tracemask + +在 `kernel/proc.c` 的 fork 实现中添加: + +```c +np->tracemask = p->tracemask; +``` + +#### syscall 处跟踪输出 + +在 `kernel/syscall.c` 的 syscall() 函数中增加: + +```c +if(p->tracemask & (1 << num)){ + printf("%d: syscall %s -> %d\n", p->pid, syscall_names[num], ret); +} +``` + + + +## sysinfo + +目标:sysinfo 系统调用用于获取当前系统的空闲内存字节数和活跃进程数。用户传入 struct sysinfo 指针,内核填充数据。 + +同样,和trace 一样修改必要的系统调用信息。并且,用户态需要同时声明 sysinfo 命令,并且在用户态说明这个 sysinfo 指针的结构(不然不知道数据结果,用户没法读取了) + +在实现层面,需要做三件事:统计空闲的内存字节数、统计活跃的进程数、将信息读取和写入 + +前两个实现在提示中写到 + +> - To collect the amount of free memory, add a function to `kernel/kalloc.c` +> - To collect the number of processes, add a function to `kernel/proc.c` + +可以参考这两个文件 + +分析可以知道,kalloc 中管理了一个空闲内存的链表,所以链表的长度就是空闲内存数量 + +在 proc 中,有类似进程池的形式,然后遍历这个池中,state **不为** UNUSED 的进程并计数 + +### 主要代码 + +#### 系统调用号 + +在 `kernel/syscall.h`: + +```c +#define SYS_sysinfo 23 +``` + +#### 统计空闲内存 + +在 `kernel/kalloc.c`: + +```c +uint64 count_freemem(void) { + struct run *r; + uint64 n = 0; + acquire(&kmem.lock); + r = kmem.freelist; + while(r){ + n += PGSIZE; + r = r->next; + } + release(&kmem.lock); + return n; +} +``` + +#### 统计活跃进程数 + +在 `kernel/proc.c`: + +```c +uint64 count_proc(void) { + struct proc *p; + uint64 n = 0; + for(p = proc; p < &proc[NPROC]; p++){ + if(p->state != UNUSED) + n++; + } + return n; +} +``` + +#### 系统调用实现 + +在 `kernel/sysproc.c`: + +```c +uint64 sys_sysinfo(void) { + uint64 addr; + if(argaddr(0, &addr) < 0) + return -1; + struct sysinfo info; + info.freemem = count_freemem(); + info.nproc = count_proc(); + if(copyout(myproc()->pagetable, addr, (char*)&info, sizeof(info)) < 0) + return -1; + return 0; +} +``` + +#### 注册系统调用 + +在 `kernel/syscall.c`: + +```c +extern uint64 sys_sysinfo(void); +[SYS_sysinfo] sys_sysinfo, +``` + +代码解析: + +接受和写入结构体可以参考系统的其他代码部分