MIT-6.S081 Copy on Write Fork
思路
思路相对来说比较简单,copy on write 主要实现是效果是在 fork 的时候并不要为子进程额外分配物理地址,而是与父进程共享物理地址,并且将物理地址的页表项(即最后一级页表项)设为不可写,这样,如果父进程与子进程如果不被写的话就可以完全正常执行,当设计到写操作时,硬件的 MMU 将会检测到页表项不可写,此时会产生 Page Fault 异常,而操作系统在异常处理中需要重新分配私有的物理页,并将原有的物理页拷贝到私有物理页并进行重新映射,并将页表项重新设置为可写即可。而原有的物理页则可能被多个进程所引用,所以由引用计数来决定是否被释放,具体过程见实现部分。
实现
首先用户进程在执行 fork 的时候子进程不需要对父进程物理页面进行实际拷贝,实际修改是在 uvmcopy 这个函数里面:
123456789101112131415161718192021222324252627282930intuvmcopy(pagetable_t old, pagetable_t new, uint64 sz){ // 将父进程页表内容拷贝到子进程中 ...
MIT-6.S081 User Level Threads
Warmup: RISC-V Assembly
Which registers contain arguments to functions? For example, which register holds 13 in main’s call to printf?
在 main 函数中 a0, a1, a2 寄存器保存着调用参数, 13 放在 a2 中。
Where is the function call to f from main? Where is the call to g? (Hint: the compiler may inline functions.)
在 main 函数中直接将调用 f 的结果计算了出来:
1224: 4635 li a2,1326: 45b1 li a1,12
At what address is the function printf located?
1230: 00000097 auipc ra,0x034: 620080e7 ...
2021年终总结
好像之前从来没有写过年终总结,这次主要是上网看到了很多人都写了年终总结的博客,于是趁着疫情隔离在家写一下去年的年终总结。
今年应该是我技术进步最大的一年,由于上大学前从来没学过计算机,从之前只会写一些 Web,连基础数据结构都写不利索,碰到一些比较难的东西就直接不会了,要不然就直接放弃,要不然就简单进行一个代码的抄。在今年的时候我已经使用 Rust 写了一个操作系统(虽然一堆 bug),一个简单的五级流水的 CPU(照书抄),一个简单的 TCP/IP 协议栈(参考 Linux),完成了半个 Raft 协议(MIT 6.824 课程,小学期大作业完成得早开始做的,后来没时间就鸽了),半个 DBMS(CMU 15-445,目前在做,已经做到 Hash Join Executor 了)。目前我对于系统软件和硬件已经具有了最基本的了解,大概不会像去年那样面对大作业的无力感了。
随之而来的则是各种焦虑,由于我大一大二时本身对于加权分数就不怎么看重,而且有几门学分很高的课,虽然当时我已经好好准备了,但依然拿到了比较低的分,干脆之后就更放飞了自我,于是学分绩就更难看了。然而大一大二一直不太在乎,到大 ...
xv6-riscv中的KPTI机制
KPTI 简述
KPTI (Kernel Page Table Isolation) 机制最初的主要目的是为了缓解 KASLR 的绕过以及 CPU 侧信道攻击。
在 KPTI 机制中,内核态空间的内存和用户态空间的内存的隔离进一步得到了增强。
内核态中的页表包括用户空间内存的页表和内核空间内存的页表。
用户态的页表只包括用户空间内存的页表以及必要的内核空间内存的页表,如用于处理系统调用、中断等信息的内存。
简而言之,KPTI 机制通过在系统调用等由用户态陷入内核态的过程中切换页表,使得内核态不能直接访问用户态的虚拟内存和执行用户态的代码段来避免熔断安全漏洞。
xv6-riscv 中的 KPTI
上下文切换
关于用户态陷入内核态的上下文切换,定义在 kernel/trampoline.S 中的 uservec 中:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970u ...
RISC -V N扩展
增加的 CSRs
用户状态寄存器(ustatus)
123456UXLEN-1 5 4 3 1 0┌────────┬──────┬──────┬─────┐│ WPRI │ UPIE │ WPRI │ UIE │└────────┴──────┴──────┴─────┘ UXLEN-5 1 3 1
ustatus 是一个 UXLEN 位长的可读写寄存器,记录和控制硬件线程当前的工作状态。
用户态中断使能位 UIE 为零时,用户态中断被禁用。为了向用户态陷入处理程序提供原子性,UIE 中的值在用户态中断发生时被复制到 UPIE ,且 UIE 被置为零。
UIE 和 UPIE 是 mstatus 和 sstatus 中对应位的镜像。
进入用户态中断处理函数之前的特权级只可能是用户态,所以不需要 UPP 位。
指令 URET 用于从用户态陷入状态中返回。URET 将 UPIE 复制回 UIE,然后将 UPIE 置位,最后将 uepc 拷贝至 pc。
在 UPIE/UIE 栈弹出后置位 UPIE 是为了启用中断,以及帮助发现代码中的 ...
io_uring 阅读笔记
io_uring 简述
对于 io_uring 的异步请求有两个重要的操作:提交请求、完成所提交的请求。
对于 IO 事件的提交,应用程序是生产者而内核是消费者;而对于完成事件来说,内核是生产者而应用程序是消费者。因此我们需要一对环(rings) 提供高性能的 channel 用于在内核和应用程序中的通信,这对环就是新的接口的核心: io_uring,它们被命名为 submission queue(SQ), completion queue(CQ),这两个数据结构构造了新接口的基础。
io_uring 的数据结构
首先我们看一下 (completion queue event)CQE 的数据结构定义:
12345struct io_uring_cqe { __u64 user_data; __s32 res; __u32 flags;}
首先 io_uring_cqe 有一个 user_data 的域,这个域是被最初的提交的请求时就被携带的,能够携带任何用来表明这是哪个请求的信息,最基础的使用就是使用原始请求的指针,内核将不会修改这个域,它仅仅 ...
在Ubuntu中升级Linux内核
为了使用 Linux 中的 io_uring 特性,我将 Ubuntu 18.04 中的内核进行升级。
首先访问 Ubuntu内核网址 然后选择我们需要升级的 linux 内核版本,在这里我们选择最新的稳定的 linux 内核的版本,然后进入网页:,可以看到如下页面:
注意到 linux 内核分为通用版和低延迟版,这里我们选择通用版进行安装,执行一下命令:
1234wget https://kernel.ubuntu.com/~kernel-ppa/mainline/v5.14/amd64/linux-headers-5.14.0-051400-generic_5.14.0-051400.202108292331_amd64.debwget https://kernel.ubuntu.com/~kernel-ppa/mainline/v5.14/amd64/linux-headers-5.14.0-051400_5.14.0-051400.202108292331_all.debwget https://kernel.ubuntu.com/~kernel-ppa/mainline/v ...
飞书Rust实习面试
一面
首先聊项目,主要让我讲了讲我 xv6-rust 的项目,然后讲到我用 Buddy System Allocator 替换了 xv6-riscv 的内存分配算法的时候,他问了我关于 Buddy System 算法相关的内容,我说伙伴内存分配算法使用多个链表和位图维护,然后举了个例子,说如果想去分配 512 bytes 的内存但是512 bytes 的链表为空,则去 1024 bytes 去找,如果 1024 bytes 的链表也为空,则向 2048 bytes 去找,找到了的话就把其分成两个 1024 bytes 的内存块,其中一块加入 1024 bytes 的链表中,另一块分为两块 512 bytes 的,一块供程序使用,另一块加入链表中,并更新位图。当回收时更新位图,并判断是否存在连续内存可以合并。
之后面试官问我伙伴内存系统解决了什么问题,我说可以有效减少内存碎片,然后他问伙伴内存分配算法真的可以完全解决吗,我说应该不可以,然后他说为啥不可以,我想了会没说上来,然后他说比如 513 bytes 这种,我说这种可以 2 的幂次对齐进行分配(但当时我记错了,我应该说 slab ...
Rust String 用法总结
String + String -> String:
123let s1 = "0";let s2 = "1";let s = format!("{}{}", s1, s2);
String + &str -> String:
123let s1: &str;let s: String;s.push_str(s1);
&str + &str -> String:
1234let s1: &str;let s2: &str;let s = String::from_str(s1);s.push_str(s2);
String -> &str:
123let s: String;let s1: &str = s.as_str();let s2: &mut str = s.as_mut_str();
String -> &[u8]:
12let s: String; ...