📖
R‘Notes
  • 关于本仓库/网站
  • Note
    • Golang的知识碎片
      • 关于Golang的一些碎片知识
    • LeetCode
      • LCR 121. 寻找目标值 - 二维数组
      • LCR 125. 图书整理 II
      • LCR 127. 跳跃训练
      • LCR 143. 子结构判断
      • LCR 159. 库存管理 III
      • LCR 161. 连续天数的最高销售额
      • LCR 170. 交易逆序对的总数
      • LCR 174. 寻找二叉搜索树中的目标节点
      • LeetCode--1. 两数之和
      • LeetCode--10. 正则表达式匹配
      • LeetCode--1004. 最大连续1的个数 III
      • LeetCode--101. 对称二叉树
      • LeetCode--102. 二叉树的层序遍历
      • LeetCode--1027. 最长等差数列
      • LeetCode--103. 二叉树的锯齿形层序遍历
      • LeetCode--1035. 不相交的线
      • LeetCode--104. 二叉树的最大深度
      • LeetCode--1044. 最长重复子串
      • LeetCode--1049. 最后一块石头的重量 II
      • LeetCode--105. 从前序与中序遍历序列构造二叉树
      • LeetCode--106. 从中序与后序遍历序列构造二叉树
      • LeetCode--110. 平衡二叉树
      • LeetCode--111. 二叉树的最小深度
      • LeetCode--112. 路径总和
      • LeetCode--113. 路径总和 II
      • LeetCode--1137. 第 N 个泰波那契数
      • LeetCode--114. 二叉树展开为链表
      • LeetCode--1143. 最长公共子序列
      • LeetCode--115. 不同的子序列
      • LeetCode--1191. K 次串联后最大子数组之和
      • LeetCode--120. 三角形最小路径和
      • LeetCode--121. 买卖股票的最佳时机
      • LeetCode--1218. 最长定差子序列
      • LeetCode--122. 买卖股票的最佳时机 II
      • LeetCode--1220. 统计元音字母序列的数目
      • LeetCode--123. 买卖股票的最佳时机 III
      • LeetCode--124. 二叉树中的最大路径和
      • LeetCode--125. 验证回文串
      • LeetCode--128. 最长连续序列
      • LeetCode--1289. 下降路径最小和 II
      • LeetCode--129. 求根节点到叶节点数字之和
      • LeetCode--1301. 最大得分的路径数目
      • LeetCode--1312. 让字符串成为回文串的最少插入次数
      • LeetCode--134. 加油站
      • LeetCode--135. 分发糖果
      • LeetCode--136. 只出现一次的数字
      • LeetCode--138. 随机链表的复制
      • LeetCode--139. 单词拆分
      • LeetCode--14. 最长公共前缀
      • LeetCode--141. 环形链表
      • LeetCode--142. 环形链表 II
      • LeetCode--143. 重排链表
      • LeetCode--144. 二叉树的前序遍历
      • LeetCode--145. 二叉树的后序遍历
      • LeetCode--146. LRU 缓存
      • LeetCode--148. 排序链表
      • LeetCode--15. 三数之和
      • LeetCode--151. 反转字符串中的单词
      • LeetCode--152. 最大乘积子数组【DP】
      • LeetCode--153. 寻找旋转排序数组中的最小值
      • LeetCode--155. 最小栈
      • LeetCode--1584. 连接所有点的最小费用,最小生成树模板题
      • LeetCode--1594. 矩阵的最大非负积
      • LeetCode--16. 最接近的三数之和
      • LeetCode--160. 相交链表
      • LeetCode--162. 寻找峰值
      • LeetCode--165. 比较版本号
      • LeetCode--169. 多数元素
      • LeetCode--174. 地下城游戏
      • LeetCode--1774. 最接近目标价格的甜点成本
      • LeetCode--179. 最大数
      • LeetCode--1824. 最少侧跳次数
      • LeetCode--188. 买卖股票的最佳时机 IV
      • LeetCode--189. 轮转数组
      • LeetCode--19. 删除链表的倒数第 N 个结点,关于删除链表会遇见的指针问题
      • LeetCode--1937. 扣分后的最大得分
      • LeetCode--1964. 找出到每个位置为止最长的有效障碍赛跑路线
      • LeetCode--198. 打家劫舍
      • LeetCode--199. 二叉树的右视图
      • LeetCode--2. 两数相加
      • LeetCode--20. 有效的括号
      • LeetCode--200. 岛屿数量
      • LeetCode--206. 反转链表
      • LeetCode--207. 课程表
      • LeetCode--208. 实现 Trie (前缀树)
      • LeetCode--209. 长度最小的子数组
      • LeetCode--21. 合并两个有序链表,关于链表的复习
      • LeetCode--210. 课程表 II
      • LeetCode--213. 打家劫舍 II
      • LeetCode--2140. 解决智力问题
      • LeetCode--215. 数组中的第K个最大元素
      • LeetCode--22. 括号生成
      • LeetCode--221. 最大正方形
      • LeetCode--2218. 从栈中取出 K 个硬币的最大面值和
      • LeetCode--224. 基本计算器
      • LeetCode--225. 用队列实现栈
      • LeetCode--226. 翻转二叉树
      • LeetCode--2266. 统计打字方案数
      • LeetCode--2267. 检查是否有合法括号字符串路径
      • LeetCode--227. 基本计算器 II
      • LeetCode--23. 合并 K 个升序链表【堆和分治】
      • LeetCode--230. 二叉搜索树中第 K 小的元素
      • LeetCode--2304. 网格中的最小路径代价
      • LeetCode--232. 用栈实现队列
      • LeetCode--2320. 统计放置房子的方式数
      • LeetCode--2321. 拼接数组的最大分数
      • LeetCode--2328. 网格图中递增路径的数目
      • LeetCode--233. 数字 1 的个数
      • LeetCode--234. 回文链表
      • LeetCode--236. 二叉树的最近公共祖先
      • LeetCode--239. 滑动窗口最大值,关于单调队列的复习
      • LeetCode--24. 两两交换链表中的节点
      • LeetCode--240. 搜索二维矩阵 II
      • LeetCode--2435. 矩阵中和能被 K 整除的路径
      • LeetCode--2466. 统计构造好字符串的方案数
      • LeetCode--25. K 个一组翻转链表
      • LeetCode--2533. 好二进制字符串的数量
      • LeetCode--256. 粉刷房子
      • LeetCode--2606. 找到最大开销的子字符串
      • LeetCode--265. 粉刷房子 II
      • LeetCode--2684. 矩阵中移动的最大次数
      • LeetCode--2787. 将一个数字表示成幂的和的方案数
      • LeetCode--279. 完全平方数【动态规划】
      • LeetCode--283. 移动零
      • LeetCode--287. 寻找重复数
      • LeetCode--2915. 和为目标值的最长子序列的长度
      • LeetCode--295. 数据流的中位数
      • LeetCode--297. 二叉树的序列化与反序列化
      • LeetCode--3. 无重复字符的最长子串
      • LeetCode--300. 最长递增子序列【DP+二分】
      • LeetCode--3082. 求出所有子序列的能量和
      • LeetCode--309. 买卖股票的最佳时机含冷冻期
      • LeetCode--31. 下一个排列
      • LeetCode--3180. 执行操作可获得的最大总奖励 I
      • LeetCode--3186. 施咒的最大总伤害
      • LeetCode--32. 最长有效括号【栈和dp】
      • LeetCode--322. 零钱兑换
      • LeetCode--328. 奇偶链表
      • LeetCode--329. 矩阵中的最长递增路径
      • LeetCode--33. 搜索旋转排序数组【直接二分】
      • LeetCode--337. 打家劫舍 III
      • LeetCode--3393. 统计异或值为给定值的路径数目
      • LeetCode--34. 在排序数组中查找元素的第一个和最后一个位置
      • LeetCode--3418. 机器人可以获得的最大金币数
      • LeetCode--343. 整数拆分
      • LeetCode--347. 前 K 个高频元素
      • LeetCode--347. 前 K 个高频元素Golang中的堆(containerheap)
      • LeetCode--3489. 零数组变换 IV
      • LeetCode--354. 俄罗斯套娃信封问题
      • LeetCode--377. 组合总和 Ⅳ
      • LeetCode--39. 组合总和
      • LeetCode--394. 字符串解码【栈】
      • LeetCode--395. 至少有 K 个重复字符的最长子串
      • LeetCode--4. 寻找两个正序数组的中位数
      • LeetCode--40. 组合总和 II
      • LeetCode--402. 移掉 K 位数字
      • LeetCode--41. 缺失的第一个正数
      • LeetCode--415. 字符串相加
      • LeetCode--416. 分割等和子集_494. 目标和【01背包】
      • LeetCode--42. 接雨水(单调栈和双指针)
      • LeetCode--426. 将二叉搜索树转化为排序的双向链表
      • LeetCode--43. 字符串相乘
      • LeetCode--437. 路径总和 III【前缀和】
      • LeetCode--44. 通配符匹配
      • LeetCode--440. 字典序的第K小数字
      • LeetCode--442. 数组中重复的数据
      • LeetCode--445. 两数相加 II
      • LeetCode--45. 跳跃游戏 II
      • LeetCode--450. 删除二叉搜索树中的节点
      • LeetCode--46. 全排列
      • LeetCode--460. LFU 缓存
      • LeetCode--468. 验证IP地址
      • LeetCode--470. 用 Rand7() 实现 Rand10()
      • LeetCode--474. 一和零
      • LeetCode--48. 旋转图像
      • LeetCode--498. 对角线遍历
      • LeetCode--5. 最长回文子串
      • LeetCode--50. Pow(x, n)
      • LeetCode--509. 斐波那契数
      • LeetCode--516. 最长回文子序列
      • LeetCode--518. 零钱兑换 II
      • LeetCode--529. 扫雷游戏题解C++广搜
      • LeetCode--53. 最大子数组和
      • LeetCode--54. 螺旋矩阵
      • LeetCode--540. 有序数组中的单一元素
      • LeetCode--543. 二叉树的直径
      • LeetCode--55. 跳跃游戏
      • LeetCode--556. 下一个更大元素 III
      • LeetCode--56. 合并区间
      • LeetCode--560. 和为 K 的子数组
      • LeetCode--572. 另一棵树的子树
      • LeetCode--59. 螺旋矩阵 II
      • LeetCode--61. 旋转链表
      • LeetCode--62. 不同路径
      • LeetCode--622. 设计循环队列
      • LeetCode--63. 不同路径 II
      • LeetCode--64. 最小路径和
      • LeetCode--646. 最长数对链
      • LeetCode--662. 二叉树最大宽度
      • LeetCode--673. 最长递增子序列的个数
      • LeetCode--678. 有效的括号字符串
      • LeetCode--679. 24 点游戏
      • LeetCode--69. x 的平方根
      • LeetCode--695. 岛屿的最大面积
      • LeetCode--7. 整数反转
      • LeetCode--70. 爬楼梯
      • LeetCode--704. 二分查找
      • LeetCode--712. 两个字符串的最小ASCII删除和
      • LeetCode--714. 买卖股票的最佳时机含手续费
      • LeetCode--718. 最长重复子数组
      • LeetCode--72. 编辑距离
      • LeetCode--739. 每日温度
      • LeetCode--74. 搜索二维矩阵
      • LeetCode--740. 删除并获得点数
      • LeetCode--746. 使用最小花费爬楼梯
      • LeetCode--75. 颜色分类
      • LeetCode--76. 最小覆盖子串
      • LeetCode--77. 组合
      • LeetCode--78. 子集
      • LeetCode--79. 单词搜索
      • LeetCode--790. 多米诺和托米诺平铺
      • LeetCode--8. 字符串转换整数 (atoi)
      • LeetCode--82. 删除排序链表中的重复元素 II
      • LeetCode--83. 删除排序链表中的重复元素
      • LeetCode--84. 柱状图中最大的矩形【单调栈】
      • LeetCode--85. 最大矩形
      • LeetCode--87. 扰乱字符串
      • LeetCode--879. 盈利计划
      • LeetCode--88. 合并两个有序数组
      • LeetCode--887. 鸡蛋掉落
      • LeetCode--91. 解码方法
      • LeetCode--912. 排序数组
      • LeetCode--918. 环形子数组的最大和
      • LeetCode--92. 反转链表 II
      • LeetCode--93. 复原 IP 地址
      • LeetCode--931. 下降路径最小和
      • LeetCode--94. 二叉树的中序遍历
      • LeetCode--958. 二叉树的完全性检验
      • LeetCode--97. 交错字符串
      • LeetCode--98. 验证二叉搜索树
      • LeetCode--983. 最低票价
      • LeetCode--LCR 140. 训练计划 II
      • NC--311.圆环回原点
      • NC--36进制加法
      • 补充题1. 排序奇升偶降链表
    • Redis
      • Redis基础部分
      • 在用Docker配置Redis哨兵节点的时候出现的错误及其解决
    • SQL学习记录
      • SQL碎片知识
      • 系统
        • MySQL学习笔记1【DQL和DCL】
        • MySQL学习笔记2【函数/约束/多表查询】
        • MySQL学习笔记3【事务】
        • MySQL学习笔记4【存储引擎和索引】
        • MySQL学习笔记5【SQL优化/视图/存储过程/触发器】
        • MySQL学习笔记6【锁】
        • MySQL学习笔记7【InnoDB】
    • x86汇编
      • 学习汇编随手记
    • 微服务相关
      • Nacos与gRPC
      • 【Golangnacos】nacos配置的增删查改,以及服务注册的golang实例及分析
    • 手搓
      • Whalebox(仿Docker)的爆诞
    • 操作系统
      • 操作系统碎片知识
      • MIT6.S081
        • MIT6.S081-lab1
        • MIT6.S081-lab2
        • MIT6.S081-lab3
        • MIT6.S081-lab3前置
        • MIT6.S081-lab4
        • MIT6.S081-lab4前置
        • MIT6.S081-lab5
        • MIT6.S081-lab5前置
        • MIT6.S081-lab7
        • MIT6.S081-lab7前置
        • MIT6.S081-lab8
        • MIT6.S081-lab8前置
        • MIT6.S081-lab9
        • MIT6.S081-环境搭建
    • 消息队列MQ
      • Kafka
    • 算法杂谈
      • 关于二分查找时的边界分类问题
    • 计组笔记
      • 计算机组成原理的学习笔记(1)--概述
      • 计算机组成原理的学习笔记(10)--CPU·其二 组合逻辑控制器和微程序
      • 计算机组成原理的学习笔记(11)--CPU·其三 中断和异常多处理器相关概念
      • 计算机组成原理的学习笔记(12)--总线和IO系统
      • 计算机组成原理的学习笔记(2)--数据的表示和运算·其一
      • 计算机组成原理的学习笔记(3)--数据表示与运算·其二 逻辑门和加减乘
      • 计算机组成原理的学习笔记(4)--数据的表示与运算·其三 补码的乘法以及原码补码的除法
      • 计算机组成原理的学习笔记(4)--数据的表示与运算·其三 补码的乘法以及原码补码的除法
      • 计算机组成原理的学习笔记(6)--存储器·其一 SRAMDRAMROM主存储器的初步认识
      • 计算机组成原理的学习笔记(7)--存储器·其二 容量扩展多模块存储系统外存Cache虚拟存储器
      • 计算机组成原理的学习笔记(8)--指令系统·其一 指令的组成以及数据寻址方式RISK和CISK
      • 计算机组成原理的学习笔记(9)--CPU·其一 CPU的基本概念流水线技术数据通路
由 GitBook 提供支持
在本页
  • 0.前置
  • 第零章
  • 1. Sleep
  • 2. PingPong
  • 3. Primes
  • 4. Find
  • 5. Xargs
  1. Note
  2. 操作系统
  3. MIT6.S081

MIT6.S081-lab1

注:实验环境在我的汇编随手记的末尾部分有搭建教程。

0.前置

第零章

xv6为我们提供了多种系统调用,其中,exec将从某个文件里读取内存镜像(这确实是一个好的说法),并且将其替换到调用它的内存空间,也就是这个打开的文件(一切皆文件)替换了当前的进程,exec仅仅是这样的功能,同时,执行完成之后,exec并不会返回当前的调用进程,而是执行我们已经加载好的指令!

如果你阅读过手写docker或者类似讲过相关概念的书,你一定会知道,我们执行命令事实上是通过创建一个子进程,再在子进程中exec我们需要的命令!

exec并不是执行程序这个操作的全部,而只是将当前进程替换为某个可执行文件的工具,它需要结合 fork 使用,才是完整的执行命令的流程。

而命令执行完成,我们的子进程就会调用exit,使得我们的父进程从wait中返回。

**文件描述符是啥?**一切皆文件,我们的文件描述符可以是管道,文件,目录,socket的抽象,但是,值得注意的是,文件描述符并不代表了这个文件,而是指向这个文件的"指针",使得我们可以对其进行访问,我们可以获取多个指向同一个文件的文件描述符,并且能够对其进行写入,读取操作。

应用比如cat指令,cat并不关心你的文件描述符指向的是什么,使得我们可以轻松的实现cat指令,所以文件描述符是一个很棒的抽象。

甚至,fork和文件描述符可以实现我们的重定向,比如当我们fork一个进程之后,关闭子进程的文件描述符0(标准输入),然后重新打开一个我们指定的文件,文件描述符0指向的是我们指定的文件,也就是说,我们的标准输入来自于文件,而不是键盘了!然后我们执行cat,就会打印出我们的文件内容,指令为:cat < test.txt。

一般来说,通过dup和fork产生的文件描述符都将共享同一个偏移量,但是有一些特殊情况,这里不详细说了。

管道

这段代码值得分析,先创建一个管道,读端文件描述符为p[0],写端为p[1],在我们的子进程中,先将文件描述符0(标准输入)关闭,然后调用我们的dup将文件描述符p[0]复制到标准输入中,此时,我们的wc就可以从文件中读取数据了,然后,我们还需要子进程的写端,因为子进程中,写端是无用的,如果不关闭,我们在wc进程中的read将会阻塞,无法返回。

而在父进程中,指向写入,然后关闭就行了。

int p[2];
char *argv[2];
argv[0] = "wc";
argv[1] = 0;
pipe(p);
if(fork() == 0) {
    close(0);
    dup(p[0]);
    close(p[0]);
    close(p[1]);
    exec("/bin/wc", argv);
} else {
    write(p[1], "hello world\n", 12);
    close(p[0]);
    close(p[1]);
}

管道比临时文件强大得多,管道支持自动销毁,支持发送任意长度的数据,支持同步地进程间通信。

文件系统

我看这部分主要讲的是文件就是一棵树,前面没啥好说的

mknod表示创建一个设备文件,其元信息标志他是一个设备,并且记录了主设备号和辅设备号,他们确定了唯一设备,当进程打开这个文件的时候,内核会将读写操作转发到相应的设备上,而不是文件系统。

fstat可以通过文件描述符获取他所指向的文件的信息。

这里的一个概念也挺有意思的,就是文件名和文件有很大的区别,一个文件可以有多个文件名,一个文件名同一时刻指向一个文件(inode),比如说下面:

open("a", O_CREATE|O_WRONGLY);
link("a", "b");

这里创建了一个文件,然后通过link使得这个文件既叫a,又叫b,但是,此时我们如果执行unlink('a'),我们的inode和磁盘空间并不会被清空,因为此时我们的文件名b还指向它,所以一个文件的的inode和磁盘空间只有link数量为0的时候才会被清除

所以

fd = open("/tmp/xyz", O_CREATE|O_RDWR);
unlink("/tmp/xyz");

是创建一个临时inode的最好方式。

1. Sleep

挺简单的,应该就是让我们提升自信心的,先fork一个子进程,在子进程中调用sleep,父进程等待。

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(1, "Usage: sleep seconds\n");
        exit(1);
    }   
    int pid = fork();
    if (pid == 0) {
        unsigned int seconds = atoi(argv[1]);
        sleep(seconds * 10);
        exit(0);
    } else {
        wait(0);
    }
    exit(0);
}

2. PingPong

大部分都是前置内容,也就是教材里面讲过的,需要创建两个管道,以供来相互通信,注意关闭读写端的时机:

#include "kernel/types.h"
#include "user/user.h"

int main(int argc, char *argv[]) {
    int p1[2];
    int p2[2];
    pipe(p1);
    pipe(p2);
    int pid1 = fork();
    if (pid1 == 0) {
        close(p1[1]);
        close(p2[0]);
        char buf[1];
        read(p1[0], buf, 1);
        close(p1[0]);
        printf("%d: received ping\n", getpid());
        write(p2[1], "x", 1);
        close(p2[1]);
        exit(0);
    }
    int pid2 = fork();
    if (pid2 == 0) {
        close(p1[0]);
        close(p2[1]);
        write(p1[1], "x", 1);
        close(p1[1]);
        char buf[1];
        read(p2[0], buf, 1);
        close(p2[0]);
        printf("%d: received pong\n", getpid());
        write(p1[1], "x", 1);
        close(p1[1]);
        exit(0);
    }
}

3. Primes

我去,这个lab真牛逼,最核心的点就是dup去复用我们的管道,让管道可以及时地被释放,这真的很重要!否则你的程序大概率只能跑到40左右的数字(血的教训),另外就是实验要求使用埃拉托色尼筛法,这一点我最开始也搞不懂要怎么去在管道之间传递这个数字,其实就是pipe不熟悉,还是问了gpt才明白,可以一个一个传,然后一个一个读取。

然后dup的使用也是参考了别人的blog,感觉自己就是菜。

总之感觉还是挺神奇的。

#include "kernel/types.h"
#include "user/user.h"

void primes(int p0[2]) __attribute__((noreturn));

int main(int argc, char *argv[]) {
    int p[2];
    pipe(p);
    int pid = fork();
    if (pid == 0) {
        //管道的关闭逻辑在primes函数中
        primes(p);
    } else {
        close(p[0]);
        for (int i = 2; i <= 280; i++) {
            write(p[1], &i, sizeof(i));
        }
        close(p[1]);
        wait(0);
    }
    exit(0);
}


void primes(int old_pipe[2]) {
    //及时释放管道
    close(0);
    dup(old_pipe[0]);
    close(old_pipe[0]);
    close(old_pipe[1]);


    int prime;
    if (read(0, &prime, sizeof(prime)) == 0) {
        close(0);
        exit(0);
    }
    printf("prime %d\n", prime);
    //新建管道,并fork子进程
    int new_pipe[2];
    pipe(new_pipe);
    int pid = fork();
    if (pid == 0) {
        primes(new_pipe);
    } else {
        close(new_pipe[0]);
        int num;
        while (read(0, &num, sizeof(num))) {
            if (num % prime != 0) {
                write(new_pipe[1], &num, sizeof(num));
            }
        }
        close(0);
        close(new_pipe[1]);
        wait(0);
    }
    exit(0);
}

4. Find

实验hint,让我们可以从ls.c中知道怎么才可以展开当前目录,这部分完全是参考了ls.c里面的方法,知道了这一点,我们就很好做判断了。

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"

void find(char *path, char *filename);

int main(int argc, char *argv[]) {
    if (argc != 3) {
        fprintf(2, "Usage: find filename with path\n");
        exit(1);
    }
    //递归搜索
    find(argv[1], argv[2]);
    exit(0);
    
}

void find(char *path, 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 stat %s\n", path);
        close(fd);
        return;
    }
    switch (st.type) {
        case T_DIR:
            if (strlen(path) + 1 + DIRSIZ + 1 >= sizeof(buf)) {
                printf("find: path too long\n");
                break;
            }
            strcpy(buf, path);
            p = buf + strlen(buf);
            *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) {
                    printf("find: cannot stat %s\n", buf);
                    continue;
                }
                if (st.type == T_FILE && strcmp(de.name, filename) == 0) {
                    printf("%s\n", buf);
                }
                if (st.type == T_DIR && strcmp(de.name, ".") != 0 && strcmp(de.name, "..") != 0) {
                    find(buf, filename);
                }
            }
            break;
        default:
            if (strcmp(path, filename) == 0) {
                printf("%s\n", path);
            }
        }
    close(fd);
}

这里有一个很有意思很有意思的东西,我直接跳转到read的实现,实际上但是他会直接跳到qemu的文件里面,导致我以为我们的read是qemu封装好的,但是实际上并不是,read确确实实我们的xv6自己实现的!我们可以通过这样去追溯它的根源:

  1. 在/user/usys.S中,找到有关read的字段,可以看见,它调用了SYS_read。

  2. 回到/kernel/syscall.c,我们可以看见syscall_read的具体定义。

  3. 跳转,我们会发现,调用了fileread这个函数,继续跳转

  4. 在这里,会调用一个至关重要的函数,就是read()

  5. 跳转到这个函数里面,read就是我们读取数据的关键函数

嗯。。这个函数还是蛮复杂的,先做下一个实验吧


5. Xargs

我没用过xargs,最开始可以说是一头雾水,包括最开始做的时候,甚至还不知道可以传递多行参数,改了半天。

整体思路就是先将当前右侧的参数读取,然后循环从标准输入中读取数据,遇到换行符,则执行命令,然后重置当前的参数和缓冲区为初始状态。

#include "kernel/types.h"
#include "user/user.h"
#include "kernel/param.h"

int main(int argc, char *argv[]) {
    if (argc < 2) {
        fprintf(2, "Usage: xargs command [args...]\n");
        exit(1);
    }
    char *cmd = argv[1];
    char *args[MAXARG];
    int i, n = 0;

    // 复制参数
    for (i = 1; i < argc && n < MAXARG - 1; i++) {
        args[n++] = argv[i];
    }
    int end = n;
    //方便重置索引
    char buf[512];
    int m = 0;
    while (read(0, &buf[m], 1) == 1) {
        if (buf[m] == '\n') {
            buf[m] = 0;
            args[n++] = &buf[0];

            // 参数必须以 NULL 结尾
            args[n] = 0;

            int fd = fork();
            if (fd == 0) {
                exec(cmd, args);
                fprintf(2, "xargs: exec failed\n");
                exit(1);
            }
            wait(0);

            // 索引重置
            m = 0;
            n = end;
        } else {
            m++;
        }
    }
    exit(0);
}

lab1给我感觉倒是没有太多关于os的知识,更多的是需要你去熟悉这个xv6的大体是什么样的,给他添加一些组件。

目前来看,收获更多的还得是xv6的教科书,那里面确实能够学到很多东西,给我的感觉就是比其他我看过的任何操作系统课对小白都要友好得多。

上一页MIT6.S081下一页MIT6.S081-lab2

最后更新于2天前