Skip to content

Commit 31398a8

Browse files
authored
Merge pull request #75 from ryanbreen/feature/interactive-shell-phases-2-4
feat(fork): implement full fork with memory copying and coreutils
2 parents acc7a36 + a43d6d4 commit 31398a8

32 files changed

+3682
-394
lines changed

kernel/src/interrupts/context_switch.rs

Lines changed: 80 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -502,12 +502,39 @@ fn switch_to_thread(
502502
}
503503

504504
// Now deliver the signal (modifies interrupt_frame and saved_regs)
505-
if crate::signal::delivery::deliver_pending_signals(
505+
let signal_result = crate::signal::delivery::deliver_pending_signals(
506506
process,
507507
interrupt_frame,
508508
saved_regs,
509-
) {
510-
log::info!("Signal delivered to thread {}", thread_id);
509+
);
510+
511+
// Handle signal result
512+
match signal_result {
513+
crate::signal::delivery::SignalDeliveryResult::Terminated(n) => {
514+
log::info!("Signal terminated process, thread {}", thread_id);
515+
// Process was terminated - notify parent after releasing locks
516+
// We need to return from this function and let the locks drop naturally
517+
// but first save the notification data
518+
// We have the notification info but can't call notify while holding locks.
519+
// The notification will be handled by the timer interrupt when it
520+
// eventually switches to the parent process, because:
521+
// 1. The child is now marked as terminated
522+
// 2. When the parent's waitpid resumes, it will find the terminated child
523+
// 3. The scheduler will see the parent is unblocked
524+
// However, this path is rare (signal terminating a process whose parent
525+
// is blocked in waitpid *at the exact same time*).
526+
// The parent notification happens in the other code paths.
527+
log::debug!(
528+
"Signal termination in blocked_in_syscall path: parent {} will be notified when resumed",
529+
n.parent_pid.as_u64()
530+
);
531+
// Just return - RAII will release the locks
532+
return;
533+
}
534+
crate::signal::delivery::SignalDeliveryResult::Delivered => {
535+
log::info!("Signal delivered to thread {}", thread_id);
536+
}
537+
crate::signal::delivery::SignalDeliveryResult::NoAction => {}
511538
}
512539
} else {
513540
// NO PENDING SIGNALS: Resume at kernel HLT loop
@@ -764,7 +791,7 @@ fn restore_userspace_thread_context(
764791
let guard_option = process_manager_guard.or_else(|| crate::process::try_manager());
765792

766793
// Track if signal termination happened (for parent notification after borrow ends)
767-
let mut signal_termination_info: Option<(crate::process::ProcessId, crate::process::ProcessId)> = None;
794+
let mut signal_termination_info: Option<crate::signal::delivery::ParentNotification> = None;
768795

769796
if let Some(mut manager_guard) = guard_option {
770797
if let Some(ref mut manager) = *manager_guard {
@@ -854,53 +881,44 @@ fn restore_userspace_thread_context(
854881
);
855882
}
856883

857-
// Save parent info BEFORE delivering signal, in case the signal
858-
// terminates the process and we need to notify parent
859-
let child_pid = pid;
860-
let parent_pid = process.parent;
861-
862-
if crate::signal::delivery::deliver_pending_signals(
884+
// Deliver pending signals
885+
let signal_result = crate::signal::delivery::deliver_pending_signals(
863886
process,
864887
interrupt_frame,
865888
saved_regs,
866-
) {
867-
// Signal was delivered and frame was modified
868-
// If process was terminated, save info for parent notification
869-
if process.is_terminated() {
889+
);
890+
891+
match signal_result {
892+
crate::signal::delivery::SignalDeliveryResult::Terminated(notification) => {
893+
// Signal terminated the process
870894
crate::task::scheduler::set_need_resched();
871-
// Save for later parent notification
872-
if let Some(ppid) = parent_pid {
873-
signal_termination_info = Some((child_pid, ppid));
895+
// Save notification for later (after manager lock is released)
896+
signal_termination_info = Some(notification);
897+
setup_idle_return(interrupt_frame);
898+
crate::task::scheduler::switch_to_idle();
899+
// Don't return here - fall through to handle notification
900+
}
901+
crate::signal::delivery::SignalDeliveryResult::Delivered => {
902+
// Signal was delivered and frame was modified
903+
if process.is_terminated() {
904+
// Process was terminated (somehow?)
905+
crate::task::scheduler::set_need_resched();
906+
setup_idle_return(interrupt_frame);
907+
crate::task::scheduler::switch_to_idle();
874908
}
875909
}
910+
crate::signal::delivery::SignalDeliveryResult::NoAction => {}
876911
}
877912
}
878913
}
879914
}
880915
}
881916

882917
// Now process borrow has ended - notify parent if signal terminated a child
883-
if let Some((child_pid, parent_pid)) = signal_termination_info {
884-
if let Some(parent_process) = manager.get_process_mut(parent_pid) {
885-
use crate::signal::constants::SIGCHLD;
886-
parent_process.signals.set_pending(SIGCHLD);
887-
888-
// Get parent's main thread ID to wake it if blocked on waitpid
889-
let parent_thread_id = parent_process.main_thread.as_ref().map(|t| t.id());
890-
891-
log::info!(
892-
"Signal termination: sent SIGCHLD to parent {} for child {} exit",
893-
parent_pid.as_u64(),
894-
child_pid.as_u64()
895-
);
896-
897-
// Wake up the parent thread if it's blocked on waitpid
898-
if let Some(parent_tid) = parent_thread_id {
899-
scheduler::with_scheduler(|sched| {
900-
sched.unblock_for_child_exit(parent_tid);
901-
});
902-
}
903-
}
918+
// Drop manager guard first to avoid deadlock when notifying parent
919+
drop(manager_guard);
920+
if let Some(notification) = signal_termination_info {
921+
crate::signal::delivery::notify_parent_of_termination_deferred(&notification);
904922
}
905923
}
906924
} else {
@@ -1058,11 +1076,11 @@ fn check_and_deliver_signals_for_current_thread(
10581076
};
10591077

10601078
// Track if signal termination happened (for parent notification after borrow ends)
1061-
let mut signal_termination_info: Option<(crate::process::ProcessId, crate::process::ProcessId)> = None;
1079+
let mut signal_termination_info: Option<crate::signal::delivery::ParentNotification> = None;
10621080

10631081
if let Some(ref mut manager) = *manager_guard {
10641082
// Find the process for this thread
1065-
if let Some((pid, process)) = manager.find_process_by_thread_mut(current_thread_id) {
1083+
if let Some((_pid, process)) = manager.find_process_by_thread_mut(current_thread_id) {
10661084
// Note: Debug logging removed from hot path - use GDB if debugging is needed
10671085
if crate::signal::delivery::has_deliverable_signals(process) {
10681086
// Switch to process's page table for signal delivery
@@ -1080,52 +1098,41 @@ fn check_and_deliver_signals_for_current_thread(
10801098
}
10811099
}
10821100

1083-
// Save parent info BEFORE delivering signal, in case the signal
1084-
// terminates the process and we need to notify parent
1085-
let child_pid = pid;
1086-
let parent_pid = process.parent;
1087-
10881101
// Deliver signals
1089-
if crate::signal::delivery::deliver_pending_signals(
1102+
let signal_result = crate::signal::delivery::deliver_pending_signals(
10901103
process,
10911104
interrupt_frame,
10921105
saved_regs,
1093-
) {
1094-
// Signal was delivered (may have terminated process)
1095-
if process.is_terminated() {
1106+
);
1107+
1108+
match signal_result {
1109+
crate::signal::delivery::SignalDeliveryResult::Terminated(notification) => {
1110+
// Signal terminated the process
10961111
crate::task::scheduler::set_need_resched();
1097-
// Save for later parent notification (after borrow ends)
1098-
if let Some(ppid) = parent_pid {
1099-
signal_termination_info = Some((child_pid, ppid));
1112+
signal_termination_info = Some(notification);
1113+
setup_idle_return(interrupt_frame);
1114+
crate::task::scheduler::switch_to_idle();
1115+
// Don't return here - fall through to handle notification
1116+
}
1117+
crate::signal::delivery::SignalDeliveryResult::Delivered => {
1118+
if process.is_terminated() {
1119+
crate::task::scheduler::set_need_resched();
1120+
setup_idle_return(interrupt_frame);
1121+
crate::task::scheduler::switch_to_idle();
11001122
}
11011123
}
1124+
crate::signal::delivery::SignalDeliveryResult::NoAction => {}
11021125
}
11031126
}
11041127
}
11051128
// process borrow has ended here
11061129

1107-
// Notify parent if signal terminated a child
1108-
if let Some((child_pid, parent_pid)) = signal_termination_info {
1109-
if let Some(parent_process) = manager.get_process_mut(parent_pid) {
1110-
use crate::signal::constants::SIGCHLD;
1111-
parent_process.signals.set_pending(SIGCHLD);
1112-
1113-
// Get parent's main thread ID to wake it if blocked on waitpid
1114-
let parent_thread_id = parent_process.main_thread.as_ref().map(|t| t.id());
1130+
// Drop manager guard first to avoid deadlock when notifying parent
1131+
drop(manager_guard);
11151132

1116-
log::info!(
1117-
"Signal termination (no-switch path): sent SIGCHLD to parent {} for child {} exit",
1118-
parent_pid.as_u64(),
1119-
child_pid.as_u64()
1120-
);
1121-
1122-
// Wake up the parent thread if it's blocked on waitpid
1123-
if let Some(parent_tid) = parent_thread_id {
1124-
scheduler::with_scheduler(|sched| {
1125-
sched.unblock_for_child_exit(parent_tid);
1126-
});
1127-
}
1128-
}
1133+
// Notify parent if signal terminated a child
1134+
if let Some(notification) = signal_termination_info {
1135+
crate::signal::delivery::notify_parent_of_termination_deferred(&notification);
11291136
}
11301137
}
11311138
}

kernel/src/main.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -672,6 +672,9 @@ fn kernel_main_continue() -> ! {
672672
log::info!("=== IPC TEST: fcntl() syscall functionality ===");
673673
test_exec::test_fcntl();
674674

675+
// Test close-on-exec (O_CLOEXEC) behavior
676+
test_exec::test_cloexec();
677+
675678
// Test pipe2() syscall
676679
log::info!("=== IPC TEST: pipe2() syscall functionality ===");
677680
test_exec::test_pipe2();
@@ -704,6 +707,26 @@ fn kernel_main_continue() -> ! {
704707
log::info!("=== SIGNAL TEST: Ctrl-C (SIGINT) signal delivery ===");
705708
test_exec::test_ctrl_c();
706709

710+
// Test fork memory isolation (CoW semantics)
711+
log::info!("=== FORK TEST: Fork memory isolation (CoW semantics) ===");
712+
test_exec::test_fork_memory();
713+
714+
// Test fork state copying (copy_process_state)
715+
log::info!("=== FORK TEST: Fork state copying (FD, signals, pgid, sid) ===");
716+
test_exec::test_fork_state();
717+
718+
// Test fork pending signal non-inheritance (POSIX)
719+
log::info!("=== SIGNAL TEST: fork pending signal non-inheritance ===");
720+
test_exec::test_fork_pending_signal();
721+
722+
// Test argv support in exec syscall
723+
log::info!("=== EXEC TEST: argv support ===");
724+
test_exec::test_argv();
725+
726+
// Test exec with argv (fork+exec with arguments)
727+
log::info!("=== EXEC TEST: exec with argv ===");
728+
test_exec::test_exec_argv();
729+
707730
// Test getdents64 syscall for directory listing
708731
log::info!("=== FS TEST: getdents64 directory listing ===");
709732
test_exec::test_getdents();

0 commit comments

Comments
 (0)