Skip to content

Commit ebd3016

Browse files
committed
implement vfork
1 parent 74ddbef commit ebd3016

15 files changed

Lines changed: 182 additions & 45 deletions

File tree

src/arch/arm64/memory/fault.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ fn run_mem_fault_handler(
7474
}
7575

7676
fn handle_uacess_abort(exception: Exception, info: AbortIss, state: &mut ExceptionState) {
77-
match run_mem_fault_handler(current_work().vm.clone(), exception, info) {
77+
match run_mem_fault_handler(current_work().vm.shared_vm(), exception, info) {
7878
// We mapped in a page, the uacess handler can proceed.
7979
Ok(FaultResolution::Resolved) => (),
8080
// If the fault couldn't be resolved, signal to the uacess fixup that
@@ -124,7 +124,7 @@ pub fn handle_kernel_mem_fault(exception: Exception, info: AbortIss, state: &mut
124124
}
125125

126126
pub fn handle_mem_fault(ctx: &mut ProcessCtx, exception: Exception, info: AbortIss) {
127-
match run_mem_fault_handler(ctx.shared().vm.clone(), exception, info) {
127+
match run_mem_fault_handler(ctx.shared().vm.shared_vm(), exception, info) {
128128
Ok(FaultResolution::Resolved) => {}
129129
Ok(FaultResolution::Denied) => {
130130
ctx.task().process.deliver_signal(SigId::SIGSEGV);

src/arch/arm64/proc.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
use crate::process::Task;
22
use alloc::sync::Arc;
3-
use libkernel::memory::proc_vm::address_space::UserAddressSpace;
43

54
pub mod idle;
65
pub mod signal;
76
pub mod vdso;
87

98
pub fn context_switch(new: Arc<Task>) {
10-
new.vm
11-
.lock_save_irq()
12-
.mm_mut()
13-
.address_space_mut()
14-
.activate();
9+
new.vm.activate();
1510
}

src/drivers/fs/proc/task/task_file.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,8 @@ Threads:\t{tasks}\n",
109109
TaskFileType::Comm => format!("{name}\n", name = name.as_str()),
110110
TaskFileType::State => format!("{state}\n"),
111111
TaskFileType::Stat => {
112-
let vm = task.vm.lock_save_irq();
112+
let proc_vm = task.vm.shared_vm();
113+
let vm = proc_vm.lock_save_irq();
113114

114115
let mut vsize = 0;
115116
let mut startcode = 0;
@@ -217,7 +218,8 @@ Threads:\t{tasks}\n",
217218
TaskFileType::Root => task.root.lock_save_irq().1.as_str().to_string(),
218219
TaskFileType::Maps => {
219220
let mut output = String::new();
220-
let mut vm = task.vm.lock_save_irq();
221+
let proc_vm = task.vm.shared_vm();
222+
let mut vm = proc_vm.lock_save_irq();
221223

222224
for vma in vm.mm_mut().iter_vmas() {
223225
output.push_str(&format!(

src/memory/brk.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ use crate::sched::syscall_ctx::ProcessCtx;
2020
/// - On a successful resize, it returns the new break.
2121
/// - On a failed resize, it returns the current, unchanged break.
2222
pub async fn sys_brk(ctx: &ProcessCtx, addr: VA) -> Result<usize, Infallible> {
23-
let mut vm = ctx.shared().vm.lock_save_irq();
23+
let proc_vm = ctx.shared().vm.shared_vm();
24+
let mut vm = proc_vm.lock_save_irq();
2425

2526
// The query case `brk(0)` is special and is handled separately from modifications.
2627
if addr.is_null() {

src/memory/mincore.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ pub async fn sys_mincore(ctx: &ProcessCtx, start: u64, len: usize, vec: UA) -> R
3535
let mut buf: Vec<u8> = vec![0; pages];
3636

3737
{
38-
let mut vm_guard = ctx.shared().vm.lock_save_irq();
38+
let proc_vm = ctx.shared().vm.shared_vm();
39+
let mut vm_guard = proc_vm.lock_save_irq();
3940
let mm = vm_guard.mm_mut();
4041

4142
// Validate the entire region is covered by VMAs

src/memory/mmap.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,8 @@ pub async fn sys_mmap(
127127
};
128128

129129
// Lock the task and call the core memory manager to perform the mapping.
130-
let new_mapping_addr = ctx.shared().vm.lock_save_irq().mm_mut().mmap(
130+
let proc_vm = ctx.shared().vm.shared_vm();
131+
let new_mapping_addr = proc_vm.lock_save_irq().mm_mut().mmap(
131132
address_request,
132133
requested_len,
133134
permissions,
@@ -141,7 +142,8 @@ pub async fn sys_mmap(
141142
pub async fn sys_munmap(ctx: &ProcessCtx, addr: VA, len: usize) -> Result<usize> {
142143
let region = VirtMemoryRegion::new(addr, len);
143144

144-
let pages = ctx.shared().vm.lock_save_irq().mm_mut().munmap(region)?;
145+
let proc_vm = ctx.shared().vm.shared_vm();
146+
let pages = proc_vm.lock_save_irq().mm_mut().munmap(region)?;
145147

146148
// Free any physical frames that were unmapped.
147149
if !pages.is_empty() {
@@ -165,11 +167,8 @@ pub fn sys_mprotect(ctx: &ProcessCtx, addr: VA, len: usize, prot: u64) -> Result
165167
let perms = prot_to_perms(prot);
166168
let region = VirtMemoryRegion::new(addr, len);
167169

168-
ctx.shared()
169-
.vm
170-
.lock_save_irq()
171-
.mm_mut()
172-
.mprotect(region, perms)?;
170+
let proc_vm = ctx.shared().vm.shared_vm();
171+
proc_vm.lock_save_irq().mm_mut().mprotect(region, perms)?;
173172

174173
Ok(0)
175174
}

src/process/clone.rs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use super::owned::OwnedTask;
22
use super::ptrace::{PTrace, TracePoint, ptrace_stop};
3-
use super::{ITimers, Tid};
3+
use super::{ITimers, Tid, VmHandle};
44
use super::{
55
ctx::Context,
66
thread_group::signal::{AtomicSigSet, SigSet},
@@ -120,11 +120,14 @@ pub async fn sys_clone(
120120
};
121121

122122
let vm = if flags.contains(CloneFlags::CLONE_VM) {
123-
current_task.vm.clone()
123+
if flags.contains(CloneFlags::CLONE_THREAD) {
124+
current_task.vm.clone()
125+
} else {
126+
Arc::new(VmHandle::from_shared(current_task.vm.shared_vm()))
127+
}
124128
} else {
125-
Arc::new(SpinLock::new(
126-
current_task.vm.lock_save_irq().clone_as_cow()?,
127-
))
129+
let proc_vm = current_task.vm.shared_vm();
130+
Arc::new(VmHandle::new(proc_vm.lock_save_irq().clone_as_cow()?))
128131
};
129132

130133
let files = if flags.contains(CloneFlags::CLONE_FILES) {
@@ -195,8 +198,15 @@ pub async fn sys_clone(
195198
}
196199
};
197200

201+
if flags.contains(CloneFlags::CLONE_VFORK) {
202+
new_task.process.start_vfork();
203+
}
204+
198205
let desc = new_task.descriptor();
199206
let work = Work::new(Box::new(new_task));
207+
let vfork_process = flags
208+
.contains(CloneFlags::CLONE_VFORK)
209+
.then(|| work.process.clone());
200210

201211
TASK_LIST
202212
.lock_save_irq()
@@ -219,5 +229,9 @@ pub async fn sys_clone(
219229
copy_to_user(child_tidptr, desc.tid.value()).await?;
220230
}
221231

232+
if let Some(vfork_process) = vfork_process {
233+
vfork_process.wait_for_vfork_release().await;
234+
}
235+
222236
Ok(desc.tid.value() as _)
223237
}

src/process/exec.rs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -187,14 +187,7 @@ async fn exec_elf(
187187
ptrace_stop(ctx, TracePoint::Exec).await;
188188

189189
let user_ctx = ArchImpl::new_user_context(entry_addr, stack_ptr);
190-
let mut vm = ProcessVM::from_map(mem_map);
191-
192-
// We don't have to worry about actually calling for a full context switch
193-
// here. Parts of the old process that are replaced will go out of scope and
194-
// be cleaned up (open files, etc.); We don't need to preserve any extra
195-
// state. Simply activate the new process's address space.
196-
vm.mm_mut().address_space_mut().activate();
197-
190+
let vm = ProcessVM::from_map(mem_map);
198191
let new_comm = argv.first().map(|s| Comm::new(s.as_str()));
199192

200193
{
@@ -205,10 +198,15 @@ async fn exec_elf(
205198
}
206199

207200
current_task.ctx = Context::from_user_ctx(user_ctx);
208-
*current_task.vm.lock_save_irq() = vm;
201+
current_task.vm.replace(vm);
202+
current_task.vm.activate();
209203
*current_task.process.signals.lock_save_irq() = SignalActionState::new_default();
210204
}
211205

206+
// `CLONE_VFORK` parents must resume as soon as the child has stopped using
207+
// the shared address space, before any later async cleanup can block.
208+
ctx.shared().process.complete_vfork();
209+
212210
// Close all the CLOEXEC FDs.
213211
let mut fd_table = ctx.shared().fd_table.lock_save_irq().clone();
214212
fd_table.close_cloexec_entries().await;

src/process/exit.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ pub fn do_exit_group(task: &Arc<Task>, exit_code: ChildState) {
6262
// to wait for all the processes to have stopped execution before tearing
6363
// down the address-space, etc.
6464

65+
// If this process was created with `CLONE_VFORK`, the parent may resume as
66+
// soon as we are guaranteed not to run in the shared address space again.
67+
process.complete_vfork();
68+
6569
// Reparent children to `init`
6670
{
6771
let mut our_children = process.children.lock_save_irq();

src/process/mod.rs

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,49 @@ impl TaskDescriptor {
143143

144144
pub type ProcVM = ProcessVM<<ArchImpl as VirtualMemory>::ProcessAddressSpace>;
145145

146+
/// A per-task handle to a process address space.
147+
///
148+
/// Separate processes may temporarily share the same underlying `ProcVM`
149+
/// (e.g. `CLONE_VM` without `CLONE_THREAD`, including `CLONE_VFORK`) while
150+
/// still allowing one side to detach on `execve()` without affecting the
151+
/// other. Tasks in the same thread group share the same `VmHandle` so that an
152+
/// `execve()` updates the whole group consistently.
153+
pub struct VmHandle {
154+
current: SpinLock<Arc<SpinLock<ProcVM>>>,
155+
}
156+
157+
impl VmHandle {
158+
pub fn new(vm: ProcVM) -> Self {
159+
Self::from_shared(Arc::new(SpinLock::new(vm)))
160+
}
161+
162+
pub fn from_shared(vm: Arc<SpinLock<ProcVM>>) -> Self {
163+
Self {
164+
current: SpinLock::new(vm),
165+
}
166+
}
167+
168+
pub fn shared_vm(&self) -> Arc<SpinLock<ProcVM>> {
169+
self.current.lock_save_irq().clone()
170+
}
171+
172+
pub fn replace(&self, vm: ProcVM) {
173+
self.replace_shared(Arc::new(SpinLock::new(vm)));
174+
}
175+
176+
pub fn replace_shared(&self, vm: Arc<SpinLock<ProcVM>>) {
177+
*self.current.lock_save_irq() = vm;
178+
}
179+
180+
pub fn activate(&self) {
181+
self.shared_vm()
182+
.lock_save_irq()
183+
.mm_mut()
184+
.address_space_mut()
185+
.activate();
186+
}
187+
}
188+
146189
#[derive(Copy, Clone)]
147190
pub struct Comm([u8; 16]);
148191

@@ -183,7 +226,7 @@ pub struct Task {
183226
pub tid: Tid,
184227
pub comm: Arc<SpinLock<Comm>>,
185228
pub process: Arc<ThreadGroup>,
186-
pub vm: Arc<SpinLock<ProcVM>>,
229+
pub vm: Arc<VmHandle>,
187230
pub cwd: Arc<SpinLock<(Arc<dyn Inode>, PathBuf)>>,
188231
pub root: Arc<SpinLock<(Arc<dyn Inode>, PathBuf)>>,
189232
pub creds: SpinLock<Credentials>,
@@ -276,8 +319,10 @@ impl Task {
276319
Box::into_pin(fut).await?;
277320
}
278321

322+
let proc_vm = self.vm.shared_vm();
323+
279324
{
280-
let mut vm = self.vm.lock_save_irq();
325+
let mut vm = proc_vm.lock_save_irq();
281326

282327
if let Some(pa) = vm.mm_mut().address_space_mut().translate(va) {
283328
let region = pa.pfn.as_phys_range();
@@ -302,7 +347,7 @@ impl Task {
302347
}
303348

304349
// Try to handle the fault.
305-
match handle_demand_fault(self.vm.clone(), va, access_kind)? {
350+
match handle_demand_fault(proc_vm.clone(), va, access_kind)? {
306351
// Resolved the fault. Try again
307352
FaultResolution::Resolved => continue,
308353
FaultResolution::Denied => return Err(KernelError::Fault),

0 commit comments

Comments
 (0)