category
type
status
date
slug
summary
tags
password
Property
Mar 15, 2026 03:14 PM
icon
Lab1『🎉』
脑溢血的vscode函数定义跳转配置
配置gdb调试环境
pingpong
- 管道特性的思考
只要管道还有至少一个写端未被关闭,即使当前管道为空,读进程也会认为未来可能有数据写入,于是选择阻塞而非放弃。
1. API 设计约定
在 Unix/Linux 系统中,
pipe() 系统调用的设计规范就是:pipefd[0]:读端(read end)
pipefd[1]:写端(write end)
这个约定源于早期的 Unix 设计,已经成为 POSIX 标准的一部分。
2. 记忆方法
0像字母 "O"(代表 "Output from pipe" 或 "Open for reading")
1像数字 "1"(代表 "Input to pipe" 或 "One-way in")
更直观的记忆:
0= stdin(标准输入,用于读)
1= stdout(标准输出,用于写)
primes实验,管道在fork链中的开关情况
如果进程链是 p1 → p2 → p3(即 p1 创建 p2,p2 创建 p3),管道在 p1 中创建,随后 p2 关闭了写端,那么 p3 不会拥有该管道的写端文件描述符。
详细分析:
- 初始状态
p1 调用
pipe(fd),获得两个打开的文件描述符:fd[0](读端)和 fd[1](写端)。- p1 fork 出 p2
p2 继承 p1 的文件描述符副本,因此 p2 也拥有
fd[0] 和 fd[1],两者都处于打开状态。- p2 关闭写端
p2 调用
close(fd[1]),关闭自己持有的写端文件描述符。此时 p2 的文件描述符表中,写端已关闭,但读端 fd[0] 仍打开。- p2 fork 出 p3
p3 继承的是 p2 当前的文件描述符表副本。由于 p2 已关闭写端,p3 只会继承到打开的读端
fd[0],而不会继承写端 fd[1](因为它在 p2 的文件描述符表中已关闭)。关键点:
- 文件描述符的继承是复制当前状态:子进程获得的是父进程调用
fork()时文件描述符表的快照。
- 管道的写端是否仍然存在:虽然 p3 自己没有写端,但管道的写端可能仍在其他进程中打开(例如 p1 可能仍未关闭写端)。不过,p3 自身无法进行写入操作,因为它没有写端的文件描述符。
- 管道生命周期:只要至少有一个写端保持打开(无论在哪个进程),管道的读端就不会看到 EOF。如果 p1 也关闭了写端,那么当所有写端关闭后,读端在读取完剩余数据后会收到 EOF。
结论:
p3 从 p2 继承的文件描述符中没有写端,因此 p3 拥有的管道写端处于关闭状态
本实验代码实现
- fork系统调用

xargs
此实验涉及到Linux命令xargs的相关知识,建议先去学习一下这个命令的工作原理再做实验,否则寸步难行
实验结果

Lab2『🚀』
Perl脚本文件
主要用于自动生成系统调用存根代码
编写并添加新系统调用的步骤
用户空间
- 在user.h中声明系统调用跳板
- 在usys.pl写明系统调用接口
内核空间
- 写明系统调用号
- 在syscall.c全局声明系统调用,并将其与系统调用号关联
跟踪系统调用的核心逻辑
trace是父进程,exec执行被跟踪的系统调用子进程,子进程通过在fork时复制父进程的跟踪掩码,然后在执行系统调用时通过比对掩码和系统调用号来确定当前系统调用是否被跟踪。

Lab3 『important⚡⚡』
要明确一个前提:本项目实现的是一个os,而底层的如MMU「Memory Management Unit」由RISC-V架构提供的硬件自动完成虚拟地址到物理地址的转换,Sv39模式,通过在satp寄存器中设置根页表的物理地址和对应页表中的页表项即可。 这正是copyin_new和copyin的区别,copyin_new采用时『在lab3中的task3完成后』已经将用户空间的数据映射到用户的内核空间页表中,进而可以在内核空间中直接通过使用用户空间的虚拟地址来获取对应的数据。而copyin不行,故只能通过软件模拟页面的访问来在内核空间获取用户空间对应虚拟地址中的数据。


kernel/vm.c xv6的虚拟内存机制,难点,高频提问点 准备工作:阅读xv6第三章pdos.csail.mit.edu
,翻译:pdos.csail.mit.edu
第三章 页表 · 6.S081 All-In-One
,阅读kernel/vm.c源码第三章 页表 · 6.S081 All-In-One
页表是操作系统为每个进程提供私有地址空间和内存的机制。页表决定了内存地址的含义,以及物理内存的哪些部分可以访问。它们允许xv6隔离不同进程的地址空间,并将它们复用到单个物理内存上。页表还提供了一层抽象(a level of indirection),这允许xv6执行一些特殊操作:映射相同的内存到不同的地址空间中(a trampoline page),并用一个未映射的页面保护内核和用户栈区。本章的其余部分介绍了RISC-V硬件提供的页表以及xv6如何使用它们。
术语解释
- Page Table Entries/PTE:页表条目
- Physical Page Number/PPN 物理页码:44bit「页框号44位」
virtual address的高25位不使用,只使用低39位,其中中27位作为虚页号

RISC-V使用的是三级页表结构

satp寄存器存储根页表的物理地址,每个CPU有一个satp
lab-3/task2
- 内核获取user space 的数据只能通过将对应虚拟地址转换为对应物理地址才可以使用
前置知识:
『在进行此实验前的系统功能』xv6中进程在用户态下都有各自的用户态页表,但是通过系统调用进入内核态时要切换到使用内核态下的页表,这个内核态的页表是全局共享的。
架构缺点:- 进程可能会意外或者恶意的访问其他进程的内核数据,会影响系统中其他进程的稳定性
- 每次创建和删除进程是都需要小心更新共享的页表条目,确保不同进程之间内存不会冲突或被错误覆盖,无疑增加系统的复杂性和开销
- xv6中虚拟地址,在用户空间和内核空间中的不同使用情况:
- 定义 :程序代码中使用的地址
- 特点 :程序看到的是连续的、独立的地址空间
- 范围 :0 到 2^39 - 1(约 512GB,Sv39 模式)
- 隔离性 :每个进程有独立的虚拟地址空间
- 定义 :实际硬件内存的地址
- 特点 :真实的内存地址,直接对应硬件内存单元
- 范围 :0 到 实际物理内存大小(如 128MB)
- 共享性 :所有进程共享物理内存
空间类型 | 使用地址类型 | 原因 |
用户空间 | 虚拟地址 | 通过页表转换,实现隔离和保护 |
内核空间 | 虚拟地址 | 采用直接映射策略 |
虚拟地址(Virtual Address) :
物理地址(Physical Address) :
进程切换的每个进程的页表切换机制⚡⚡:
实验目的:
确保每个进程进入内核态之后,都有自己独立的内核页表
实验大体流程
- 修改vm.c中kv相关程序,以供进程的内核页面的使用
- 将每个进程内核栈的预分配推迟到进程创建时创建进程的内核页表后再分配
- 在进程的调度scheduler()中实现在进程被调度前设置satp寄存器的内容为要调度进程的内核页表物理地址『因为此时待调度进程处于内核态』
- 在进程结束时先回收分配给该进程的内核栈,然后回收分配给其的内核页面。『freeproc(), proc.c』
实验结果

lab-3/task3
实验目的:将进程在用户空间的页表信息映射到内核空间的页表种。 👾 attention! : 在本实验中用户空间在内核中的虚拟地址空间范围只有[0,PLIC)
task3的重要前置知识
exec()实现了将程序加载到当前被调度进程中以便使该进程能够执行该程序,由于进程在allocproc中创建时并没有绑定任何程序,故在exec中绑定相关程序后要释放旧的页面并设置新的页面。
思路:
- 先找到涉及进程用户页面修改的地方『exec,fork,growproc,userinit』,利用映射来达到进程的内核页面进行同步修改
- 细节处理:由于进程在内核空间中虚拟地址范围是[0,PLIC),故需要在exec中为进程加载程序时要注意处理进程的大小『针对虚拟地址来说』不要超过PLIC『不需要考虑CLINT,因为此地址只在内核启动时使用』
实验结果

本task遇到的问题
remap


score

实验后疑问🤯🫨
- lab3 的task2 done之后全局内核页表除了在系统启动时使用之外还会再被使用吗?
Lab 4 『🐌✨』
前置知识
trampoline中代码是同时在用户和内核空间中映射的。
trap的三种不同情况
traps from user space, traps from kernel space, and timer interrupts
涉及到的registers
- stevc:内核在此写入要进行陷入处理的程序的地址『supervisor trap vector base address register』
- sepc:从陷入返回,保存pc供
sret『中断返回指令』在陷入返回时将pc设置为sepc中的值。『supervisor exception program counter』
- scause: The RISC-V puts a number here that describes the reason for the trap.
- sscratch:内核在这里放一个值,该值在trap handling一开始就被使用 『supervisor scratch register』
- sstatus:
SIE:控制设备中断是否启用,如果内核清空SIE,RISC-V将推迟设备中断 SPP:指示trap来源『用户/内核』,进而控制sret的返回模式
从user space 陷入逻辑
task 1『Q&A』
- Which registers contain arguments to functions? For example, which register holds 13 in main's call to
printf?
a0~a7; a2li a1,12 12是f(8)+1的值,编译器进行了优化,直接计算出结果auipc a0,0x0功能:将20位立即数0x0左移动12位后与当前PC值相加,结果存入目标寄存器a0.auipc: add upper immediate to PC
- Where is the call to function
fin the assembly code for main? Where is the call tog? (Hint: the compiler may inline functions.)
- At what address is the function
printflocated?
ra=30H, 故printf地址为30H+600H=630H
- What value is in the register
rajust after thejalrtoprintfin main?
jalr指令的标准格式:jalr rd, offset(rs1),跳转到rs1+offset的位置,并将jalr指令的下一条指令的位置存储在rd中
此处
rd被省略了,ra是rd,故ra=0x34+4=0x38- Run the following code:
What is the output? Here's an ASCII table that maps bytes to characters.
The output depends on the fact that RISC-V is little-endian. If RISC-V were instead big-endian, what would you set
i to in order to yield the same output? Would you need to change 57616 to a different value?output: Hello World reason: 57616 = 0xello, 0x64=’d’, 0x6c=’l’, 0x72=’r’ 陷阱😒: 无论采用大端还是小端,57616的16进制格式化输出都是0xello, 大端只会影响i的转换,要想得到同样的输出i改为:0x726c6400
- In the following code, what is going to be printed after
'y='? (note: the answer is not a specific value.) Why does this happen?
y=的值取决于寄存器a2中存放的值
task 2 『backtrace』
stack layout fp 是fram pointer,即栈帧的地址,栈的地址是从高往低处生长的。 调用过程中的每个函数的栈的分配也是从高地址到低地址进行的。

task 3 『use trap implement cron job』
- core code
实验结果:

Lab 5 『🤠』
Lab 6 『todo』
- 作者:axiszql
- 链接:https://axiszql.cn/article/mit6S081_learn
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。


![[MIT-6.S081/Fall2020] 实验笔记零 Lab0: Preparation](https://www.notion.so/image/https%3A%2F%2Fgwzlchn.github.io%2Fimg%2F6.S081%2Fgdb.png?table=block&id=2e52458d-bf0c-8062-9e57-e9fe44971975&t=2e52458d-bf0c-8062-9e57-e9fe44971975)





