xv6-labs-2024 lab1

lab-1

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

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的教科书,那里面确实能够学到很多东西,给我的感觉就是比其他我看过的任何操作系统课对小白都要友好得多。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/900518.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

属性修改器 (AttributeModifier)

主页面设置组件 import { MyButtonModifier } from ../datastore/MyButtonModifier;Entry ComponentV2 struct MainPage {// 支持用状态装饰器修饰&#xff0c;行为和普通的对象一致Local modifier: MyButtonModifier new MyButtonModifier();build() {Column() {Button(&quo…

【 <二> 丹方改良:Spring 时代的 JavaWeb】之 Spring Boot 中的监控:使用 Actuator 实现健康检查

<前文回顾> 点击此处查看 合集 https://blog.csdn.net/foyodesigner/category_12907601.html?fromshareblogcolumn&sharetypeblogcolumn&sharerId12907601&sharereferPC&sharesourceFoyoDesigner&sharefromfrom_link <今日更新> 一、引子&…

类和对象(下篇)(详解)

【本节目标】 1. 再谈构造函数 2. Static成员 3. 友元 4. 内部类 5. 再次理解封装 1. 再谈构造函数 1.1 构造函数体赋值 在创建对象时&#xff0c;编译器通过调用构造函数&#xff0c;给对象中各个成员变量一个合适的初始值。 #include <iostream> using name…

高精度算法

高精度加法 输入两个数&#xff0c;输出他们的和&#xff08;高精度&#xff09; 输入样例 111111111111111111111111111111 222222222222222222222222222222 输出样例 333333333333333333333333333333 #include <bits/stdc.h> using namespace std;string a,b; in…

Linux开发中注意哪些操作系统安全

在 Linux 开发中&#xff0c;确保操作系统的安全至关重要。以下是一些需要注意的方面&#xff1a; 用户管理与权限控制 合理设置用户权限&#xff1a;为不同的用户和用户组分配适当的权限&#xff0c;遵循最小权限原则。避免给普通用户过多的权限&#xff0c;以免他们误操作或…

x64dbg调试python解释器

可以先写个input()这会让dbg中断在ntdll模块中&#xff0c;查看调用堆栈在系统调用结束后的打断点 然后直接断到PyObject_Vectorcall函数

✅ Ultralytics YOLO验证(Val)时自动输出COCO指标(AP):2025最新配置与代码详解 (小白友好 + B站视频)

✅ YOLO获取COCO指标(3)&#xff1a;验证(Val) 启用 COCO API 评估&#xff08;自动输出AP指标&#xff09;| 发论文必看&#xff01; | Ultralytics | 小白友好 文章目录 一、问题定位二、原理分析三、解决方案与实践案例步骤 1: 触发 COCO JSON 保存步骤 2: 确保 self.is_coc…

【嵌入式学习3】基于python的tcp客户端、服务器

目录 1、tcp客户端 2、tcp服务器 3、服务器多次连接客户端、多次接收信息 1、tcp客户端 """ tcp:客户端 1. 导入socket模块 2. 创建socket套接字 3. 建立tcp连接(和服务端建立连接) 4. 开始发送数据(到服务端) 5. 关闭套接字 """ import soc…

Linux: network: 两台直连的主机业务不通

前提环境,有一个产品的设定是两个主机之间必须是拿网线直连。但是设备管理者可能误将设置配错,不是直连。 最近遇到一个问题,说一个主机发的包,没有到对端,一开始怀疑设定的bond设备的问题,检查了bond的设置状态,发现没有问题,就感觉非常的奇怪。后来就开始怀疑两个主机…

COMSOL固体力学接触

目录 一、接触非线性及接触面积计算 一、接触非线性及接触面积计算 COMSOL接触非线性及接触面积计算_哔哩哔哩_bilibili 形成联合体&#xff0c;在定义处右键选择“建立接触对” 位移dz使用参数化扫描。 接触选择定义中设置的接触对&#xff0c;选择罚函数&#xff0c;摩擦设置…

22.OpenCV轮廓匹配原理介绍与使用

OpenCV轮廓匹配原理介绍与使用 1. 轮廓匹配的基本概念 轮廓匹配&#xff08;Contour Matching&#xff09;是计算机视觉中的一种重要方法&#xff0c;主要用于比较两个轮廓的相似性。它广泛应用于目标识别、形状分析、手势识别等领域。 在 OpenCV 中&#xff0c;轮廓匹配主要…

oracle 快速创建表结构

在 Oracle 中快速创建表结构&#xff08;仅复制表结构&#xff0c;不复制数据&#xff09;可以通过以下方法实现&#xff0c;适用于需要快速复制表定义或生成空表的场景 1. 使用 CREATE TABLE AS SELECT (CTAS) 方法 -- 复制源表的全部列和数据类型&#xff0c;但不复制数据 C…

若依原理笔记

代码生成器 源码分析 查询数据库列表 导入表结构 生成代码 修改generator.yml配置文件 代码生成器增强 Velocity模版引擎 基础语法-变量 Lombok集成 E:\javaProject\dkd-parent\dkd-generator\src\main\resources\vm\java\domain.java.vm package ${packageName}.domain;#fo…

Ansible的使用

##### Ansible使用环境 - 控制节点 - 安装Ansible软件 - Python环境支持&#xff1a;Python>2.6 - 必要的模块&#xff1a;如PyYAML等 - 被控节点 - 启用SSH服务 - 允许控制节点登录&#xff0c;通常设置免密登录 - Python环境支持 http://www.ansible.com/ …

C++ 提高编程:模板与 STL 深度剖析

摘要&#xff1a;本文深入探讨 C 提高编程中的模板编程与标准模板库&#xff08;STL&#xff09;相关内容。详细阐述模板编程中函数模板和类模板的概念、语法、特性及应用案例&#xff1b;对 STL 的诞生背景、基本概念、六大组件进行剖析&#xff0c;并对常用容器、函数对象、常…

C++(类模板的运用)

使用vector实现一个简单的本地注册登录系统 注册&#xff1a;将账号密码存入vector里面&#xff0c;注意防重复判断 登录&#xff1a;判断登录的账号密码是否正确 #include <iostream> #include <vector> #include <fstream> #include <sstream> usi…

【大模型】DeepSeek+蓝耕MaaS平台+海螺AI生成高质量视频实战详解

目录 一、前言 二、蓝耘智能云MaaS平台介绍 2.1 蓝耘智算平台是什么 2.2 平台优势 2.3 平台核心能力 三、海螺AI视频介绍 3.1 海螺AI视频是什么 3.2 海螺AI视频主要功能 3.3 海螺AI视频应用场景 3.4 海螺AI视频核心优势 3.5 项目git地址 四、蓝耘MaaS平台DeepSeek海…

接口自动化学习二:session自动管理cookie

session自动管理cookie&#xff1a; cookie中的数据&#xff0c;都是session提供的 实现步骤&#xff1a; 1.创建session对象&#xff1b;my_sessionrequests.Session() 2.使用session实例&#xff0c;调用get方法&#xff0c;发送获取验证码请求&#xff08;不需要提取cookie&…

C++类型转换详解

目录 一、内置 转 内置 二、内置 转 自定义 三、自定义 转 内置 四、自定义 转 自定义 五、类型转换规范化 1.static_case 2.reinterpret_cast 3.const_cast 4.dynamic_cast 六、RTTI 一、内置 转 内置 C兼容C语言&#xff0c;在内置类型之间转换规则和C语言一样的&am…

QEMU源码全解析 —— 块设备虚拟化(17)

接前一篇文章:QEMU源码全解析 —— 块设备虚拟化(16) 本文内容参考: 《趣谈Linux操作系统》 —— 刘超,极客时间 《QEMU/KVM源码解析与应用》 —— 李强,机械工业出版社 《KVM实战 —— 原理、进阶与性能调优》—— 任永杰 程舟,机械工业出版社