rCore-Tutorial
rCore-Tutorial copied to clipboard
实验四(上)题目:clone 线程时无法实现安全的栈拷贝
涉及文件
https://rcore-os.github.io/rCore-Tutorial-deploy/docs/lab-4/practice-1.html
相关段落
实验四(上)对 clone()
的要求是:
实验:实现线程的 clone()。目前的内核线程不能进行系统调用,所以我们先简化地实现为“按 C 进行 clone”。clone 后应当为目前的线程复制一份几乎一样的拷贝,新线程与旧线程同属一个进程,公用页表和大部分内存空间,而新线程的栈是一份拷贝。
遇到问题
新线程的栈和寄存器内都可能有对栈上地址的引用,拷贝栈不会更新这些引用。于是在新栈上继续执行的代码对局部变量的访问会错误地引用旧栈上的地址。
建议做以下修改之一:
- 定义
clone()
为拷贝当前进程而非线程。 - 要求显式指定新线程的入口地址,并重新分配空栈而非拷贝栈。
~~我认为实验是没有问题的,我已经完成了对线程的 clone
,并没有遇到这方面的问题~~
~~开辟新的栈,然后修改栈顶指针 sp
即可,不会用到 unsafe
的代码~~
我对该 issue 的理解不太正确,实验的确有问题
这应当是 fork 和 clone 语义的区别,如果是拷贝当前进程的话应当是 fork 而非 clone。如果是复制进程的话,应该可以使该进程的虚拟地址与原进程的虚拟地址相同,但对应于不同的物理地址页,此时复制栈之后就不会出现引用旧线程变量的问题,也不需要重新分配空栈,这样应该是可行的;重新分配空栈的话,应该也对应于新创建线程的 create 语义而不是 clone。
@wfly1998
未定义的行为并不总会导致可见的问题。
考虑以下代码(未测试):
let x: i32 = 42;
let x_ref = &x;
if clone_thread() == 0 {
assert!(&x as *const _ == x_ref as *const _, "path 1");
} else {
assert!(&x as *const _ == x_ref as *const _, "path 2");
}
在不开启任何编译优化的情况下,你认为是否会有一个 assert!
命中呢?
@yunwei37
是的,拷贝进程会完整复制虚拟地址空间,应该就没问题了
直接在同一地址空间复制栈还有一个危险,即使引用没有失效,也有可能出现两个mutable reference。悄无声息的发生data race,是一种implicit的unsafe。所以应该只有@losfair提出的两种方法可行,拷贝进程或者重新开一个栈……😢
@losfair
抱歉,是我理解的错误,我以为你说的无法实现安全的栈拷贝指的是会用到 unsafe
代码块,是我没有考虑到这一点
我查阅了 rCore 对 sys_clone 的实现,我发现 sys_clone 传入的参数中有新的线程指针 newtls
还有新的堆栈指针 newsp
,这说明新的线程并不是拷贝了原线程,而是启动了一条新线程并执行了其它函数
感谢指出问题,这确实是我们在沿用以前版本的实验题目时没有考虑到新版本框架的不同
题目会改为实现进程的 fork,而如果大家做了“并不安全的 clone”也不需要重新再做一次 fork。
突然意识到,改成 fork
进程后,实验六的挑战实验是不是就做不成了啊
我认为 clone
的缺陷是可以规避的,比如尽可能早地 clone
,并且在 clone
前不使用引用
突然意识到,改成
fork
进程后,实验六的挑战实验是不是就做不成了啊 我认为clone
的缺陷是可以规避的,比如尽可能早地clone
,并且在clone
前不使用引用
实验六会要求做 sys_fork
复制进程,同时也就会复制文件描述符,我认为没有问题
如果分配空栈 实验六的sys_open就会报错 ,所以第二种方法并不可取