From bce23a63bd81dc0267d84d3f939e25890463a1d9 Mon Sep 17 00:00:00 2001 From: Fabing Li Date: Fri, 21 Mar 2025 03:41:11 +0000 Subject: [PATCH] Add sys_signalfd/sys_signalfd implementation --- docs/src/kernel/linux-compatibility.md | 4 +- kernel/src/process/signal/events.rs | 4 + kernel/src/process/signal/sig_mask.rs | 19 +- kernel/src/syscall/arch/riscv.rs | 2 + kernel/src/syscall/arch/x86.rs | 3 + kernel/src/syscall/mod.rs | 1 + kernel/src/syscall/signalfd.rs | 369 +++++++++++++++++++++ test/syscall_test/Makefile | 1 + test/syscall_test/blocklists/signalfd_test | 6 + 9 files changed, 404 insertions(+), 5 deletions(-) create mode 100644 kernel/src/syscall/signalfd.rs create mode 100644 test/syscall_test/blocklists/signalfd_test diff --git a/docs/src/kernel/linux-compatibility.md b/docs/src/kernel/linux-compatibility.md index c0336f75a..cb2cccfd2 100644 --- a/docs/src/kernel/linux-compatibility.md +++ b/docs/src/kernel/linux-compatibility.md @@ -302,14 +302,14 @@ provided by Linux on x86-64 architecture. | 279 | move_pages | ❌ | | 280 | utimensat | ✅ | | 281 | epoll_pwait | ✅ | -| 282 | signalfd | ❌ | +| 282 | signalfd | ✅ | | 283 | timerfd_create | ❌ | | 284 | eventfd | ✅ | | 285 | fallocate | ✅ | | 286 | timerfd_settime | ❌ | | 287 | timerfd_gettime | ❌ | | 288 | accept4 | ✅ | -| 289 | signalfd4 | ❌ | +| 289 | signalfd4 | ✅ | | 290 | eventfd2 | ✅ | | 291 | epoll_create1 | ✅ | | 292 | dup3 | ✅ | diff --git a/kernel/src/process/signal/events.rs b/kernel/src/process/signal/events.rs index 85b399795..eef9a5c0a 100644 --- a/kernel/src/process/signal/events.rs +++ b/kernel/src/process/signal/events.rs @@ -13,6 +13,10 @@ impl SigEvents { pub fn new(sig_num: SigNum) -> Self { Self(sig_num) } + + pub fn sig_num(&self) -> SigNum { + self.0 + } } impl Events for SigEvents {} diff --git a/kernel/src/process/signal/sig_mask.rs b/kernel/src/process/signal/sig_mask.rs index d3fd99cfb..32d44d839 100644 --- a/kernel/src/process/signal/sig_mask.rs +++ b/kernel/src/process/signal/sig_mask.rs @@ -118,6 +118,14 @@ impl> ops::SubAssign for SigSet { } } +impl ops::Not for SigSet { + type Output = Self; + + fn not(self) -> Self { + SigSet { bits: !self.bits } + } +} + impl SigSet { pub fn new_empty() -> Self { SigSet { bits: 0 } @@ -139,9 +147,14 @@ impl SigSet { self.bits.count_ones() as usize } - pub fn contains(&self, set: impl Into) -> bool { - let set = set.into(); - self.bits & set.bits == set.bits + pub fn contains(&self, other: impl Into) -> bool { + let other = other.into(); + self.bits & other.bits == other.bits + } + + pub fn intersects(&self, other: impl Into) -> bool { + let other = other.into(); + self.bits & other.bits != 0 } } diff --git a/kernel/src/syscall/arch/riscv.rs b/kernel/src/syscall/arch/riscv.rs index 1132cb03d..b943b487f 100644 --- a/kernel/src/syscall/arch/riscv.rs +++ b/kernel/src/syscall/arch/riscv.rs @@ -115,6 +115,7 @@ use crate::syscall::{ setuid::sys_setuid, shutdown::sys_shutdown, sigaltstack::sys_sigaltstack, + signalfd::sys_signalfd4, socket::sys_socket, socketpair::sys_socketpair, stat::{sys_fstat, sys_fstatat}, @@ -182,6 +183,7 @@ impl_syscall_nums_and_dispatch_fn! { SYS_PWRITEV = 70 => sys_pwritev(args[..4]); SYS_SENDFILE64 = 71 => sys_sendfile(args[..4]); SYS_PSELECT6 = 72 => sys_pselect6(args[..6]); + SYS_SIGNALFD4 = 74 => sys_signalfd4(args[..4]); SYS_READLINKAT = 78 => sys_readlinkat(args[..4]); SYS_NEWFSTATAT = 79 => sys_fstatat(args[..4]); SYS_NEWFSTAT = 80 => sys_fstat(args[..2]); diff --git a/kernel/src/syscall/arch/x86.rs b/kernel/src/syscall/arch/x86.rs index 4de933e61..8d598e31c 100644 --- a/kernel/src/syscall/arch/x86.rs +++ b/kernel/src/syscall/arch/x86.rs @@ -124,6 +124,7 @@ use crate::syscall::{ setuid::sys_setuid, shutdown::sys_shutdown, sigaltstack::sys_sigaltstack, + signalfd::{sys_signalfd, sys_signalfd4}, socket::sys_socket, socketpair::sys_socketpair, stat::{sys_fstat, sys_fstatat, sys_lstat, sys_stat}, @@ -320,9 +321,11 @@ impl_syscall_nums_and_dispatch_fn! { SYS_SET_ROBUST_LIST = 273 => sys_set_robust_list(args[..2]); SYS_UTIMENSAT = 280 => sys_utimensat(args[..4]); SYS_EPOLL_PWAIT = 281 => sys_epoll_pwait(args[..6]); + SYS_SIGNALFD = 282 => sys_signalfd(args[..3]); SYS_EVENTFD = 284 => sys_eventfd(args[..1]); SYS_FALLOCATE = 285 => sys_fallocate(args[..4]); SYS_ACCEPT4 = 288 => sys_accept4(args[..4]); + SYS_SIGNALFD4 = 289 => sys_signalfd4(args[..4]); SYS_EVENTFD2 = 290 => sys_eventfd2(args[..2]); SYS_EPOLL_CREATE1 = 291 => sys_epoll_create1(args[..1]); SYS_DUP3 = 292 => sys_dup3(args[..3]); diff --git a/kernel/src/syscall/mod.rs b/kernel/src/syscall/mod.rs index 617c0f72b..2cb006243 100644 --- a/kernel/src/syscall/mod.rs +++ b/kernel/src/syscall/mod.rs @@ -131,6 +131,7 @@ mod setsockopt; mod setuid; mod shutdown; mod sigaltstack; +mod signalfd; mod socket; mod socketpair; mod stat; diff --git a/kernel/src/syscall/signalfd.rs b/kernel/src/syscall/signalfd.rs new file mode 100644 index 000000000..f5704dfdc --- /dev/null +++ b/kernel/src/syscall/signalfd.rs @@ -0,0 +1,369 @@ +// SPDX-License-Identifier: MPL-2.0 + +//! signalfd implementation for Linux compatibility +//! +//! The signalfd mechanism allows receiving signals via file descriptor, +//! enabling better integration with event loops. +//! See https://man7.org/linux/man-pages/man2/signalfd.2.html + +use core::sync::atomic::{AtomicBool, Ordering}; + +use bitflags::bitflags; + +use super::SyscallReturn; +use crate::{ + events::{IoEvents, Observer}, + fs::{ + file_handle::FileLike, + file_table::{get_file_fast, FdFlags, FileDesc}, + utils::{CreationFlags, InodeMode, InodeType, Metadata, StatusFlags}, + }, + prelude::*, + process::{ + posix_thread::AsPosixThread, + signal::{ + constants::{SIGKILL, SIGSTOP}, + sig_mask::{AtomicSigMask, SigMask}, + signals::Signal, + PollHandle, Pollable, Pollee, SigEvents, SigEventsFilter, + }, + Gid, Uid, + }, + time::clocks::RealTimeClock, +}; + +/// Creates a new signalfd or updates an existing one according to the given mask +pub fn sys_signalfd( + fd: FileDesc, + mask_ptr: Vaddr, + sizemask: usize, + ctx: &Context, +) -> Result { + sys_signalfd4(fd, mask_ptr, sizemask, 0, ctx) +} + +/// Creates a new signalfd or updates an existing one according to the given mask and flags +pub fn sys_signalfd4( + fd: FileDesc, + mask_ptr: Vaddr, + sizemask: usize, + flags: i32, + ctx: &Context, +) -> Result { + debug!( + "fd = {}, mask = {:x}, sizemask = {}, flags = {}", + fd, mask_ptr, sizemask, flags + ); + + if sizemask != core::mem::size_of::() { + return Err(Error::with_message(Errno::EINVAL, "invalid mask size")); + } + + let mut mask = ctx.user_space().read_val::(mask_ptr)?; + mask -= SIGKILL; + mask -= SIGSTOP; + + let flags = SignalFileFlags::from_bits(flags as u32) + .ok_or_else(|| Error::with_message(Errno::EINVAL, "invalid flags"))?; + + let fd_flags = if flags.contains(SignalFileFlags::O_CLOEXEC) { + FdFlags::CLOEXEC + } else { + FdFlags::empty() + }; + + let non_blocking = flags.contains(SignalFileFlags::O_NONBLOCK); + + let new_fd = if fd == -1 { + create_new_signalfd(ctx, mask, non_blocking, fd_flags)? + } else { + update_existing_signalfd(ctx, fd, mask, non_blocking)? + }; + + Ok(SyscallReturn::Return(new_fd as _)) +} + +fn create_new_signalfd( + ctx: &Context, + mask: SigMask, + non_blocking: bool, + fd_flags: FdFlags, +) -> Result { + let atomic_mask = AtomicSigMask::new(mask); + let signal_file = SignalFile::new(atomic_mask, non_blocking); + + register_observer(ctx, &signal_file, mask)?; + + let file_table = ctx.thread_local.file_table().borrow(); + let fd = file_table.write().insert(signal_file, fd_flags); + Ok(fd) +} + +fn update_existing_signalfd( + ctx: &Context, + fd: FileDesc, + new_mask: SigMask, + non_blocking: bool, +) -> Result { + let mut file_table = ctx.thread_local.file_table().borrow_mut(); + let file = get_file_fast!(&mut file_table, fd); + let signal_file = file + .downcast_ref::() + .ok_or_else(|| Error::with_message(Errno::EINVAL, "File descriptor is not a signalfd"))?; + + if signal_file.mask().load(Ordering::Relaxed) != new_mask { + signal_file.update_signal_mask(new_mask)?; + } + signal_file.set_non_blocking(non_blocking); + Ok(fd) +} + +fn register_observer(ctx: &Context, signal_file: &Arc, mask: SigMask) -> Result<()> { + let filter = SigEventsFilter::new(mask); + + ctx.posix_thread + .register_sigqueue_observer(signal_file.observer_ref(), filter); + + Ok(()) +} + +bitflags! { + /// Signal file descriptor creation flags + struct SignalFileFlags: u32 { + const O_CLOEXEC = CreationFlags::O_CLOEXEC.bits(); + const O_NONBLOCK = StatusFlags::O_NONBLOCK.bits(); + } +} + +/// Signal file implementation +/// +/// Represents a file that can be used to receive signals +/// as readable events. +struct SignalFile { + /// Atomic signal mask for filtering signals + signals_mask: AtomicSigMask, + /// I/O event notifier + pollee: Pollee, + /// Non-blocking mode flag + non_blocking: AtomicBool, + /// Weak reference to self as an observer + weak_self: Weak>, +} + +impl SignalFile { + /// Create a new signalfd instance + fn new(mask: AtomicSigMask, non_blocking: bool) -> Arc { + Arc::new_cyclic(|weak_ref| { + let weak_self = weak_ref.clone() as Weak>; + Self { + signals_mask: mask, + pollee: Pollee::new(), + non_blocking: AtomicBool::new(non_blocking), + weak_self, + } + }) + } + + fn mask(&self) -> &AtomicSigMask { + &self.signals_mask + } + + fn observer_ref(&self) -> Weak> { + self.weak_self.clone() + } + + fn update_signal_mask(&self, new_mask: SigMask) -> Result<()> { + if let Some(thread) = current_thread!().as_posix_thread() { + thread.unregister_sigqueue_observer(&self.weak_self); + let filter = SigEventsFilter::new(new_mask); + thread.register_sigqueue_observer(self.weak_self.clone(), filter); + } + self.signals_mask.store(new_mask, Ordering::Relaxed); + Ok(()) + } + + fn set_non_blocking(&self, non_blocking: bool) { + self.non_blocking.store(non_blocking, Ordering::Relaxed); + } + + fn is_non_blocking(&self) -> bool { + self.non_blocking.load(Ordering::Relaxed) + } + + /// Check current readable I/O events + fn check_io_events(&self) -> IoEvents { + let current = current_thread!(); + let Some(thread) = current.as_posix_thread() else { + return IoEvents::empty(); + }; + + let mask = self.signals_mask.load(Ordering::Relaxed); + if thread.sig_pending().intersects(mask) { + IoEvents::IN + } else { + IoEvents::empty() + } + } + + /// Attempt non-blocking read operation + fn try_read(&self, writer: &mut VmWriter) -> Result { + let current = current_thread!(); + let thread = current + .as_posix_thread() + .ok_or_else(|| Error::with_message(Errno::ESRCH, "Not a POSIX thread"))?; + + // Mask is inverted to get the signals that are not blocked + let mask = !self.signals_mask.load(Ordering::Relaxed); + let max_signals = writer.avail() / core::mem::size_of::(); + let mut count = 0; + + for _ in 0..max_signals { + match thread.dequeue_signal(&mask) { + Some(signal) => { + writer.write_val(&signal.to_signalfd_siginfo())?; + count += 1; + self.pollee.invalidate(); + } + None => break, + } + } + + if count == 0 { + return_errno!(Errno::EAGAIN); + } + Ok(count * core::mem::size_of::()) + } +} + +impl Observer for SignalFile { + // TODO: Fix signal notifications. + // Child processes do not inherit the parent's observer mechanism for signal event notifications. + // `sys_poll` with blocking mode gets stuck if the signal is received after polling. + fn on_events(&self, events: &SigEvents) { + if self + .signals_mask + .load(Ordering::Relaxed) + .contains(events.sig_num()) + { + self.pollee.notify(IoEvents::IN); + } + } +} + +impl Pollable for SignalFile { + fn poll(&self, mask: IoEvents, poller: Option<&mut PollHandle>) -> IoEvents { + self.pollee + .poll_with(mask, poller, || self.check_io_events()) + } +} + +impl FileLike for SignalFile { + fn read(&self, writer: &mut VmWriter) -> Result { + if writer.avail() < core::mem::size_of::() { + return_errno_with_message!(Errno::EINVAL, "Buffer too small for siginfo structure"); + } + + if self.is_non_blocking() { + self.try_read(writer) + } else { + self.wait_events(IoEvents::IN, None, || self.try_read(writer)) + } + } + + fn write(&self, _reader: &mut VmReader) -> Result { + return_errno_with_message!(Errno::EBADF, "signalfd does not support write operations"); + } + + fn status_flags(&self) -> StatusFlags { + if self.is_non_blocking() { + StatusFlags::O_NONBLOCK + } else { + StatusFlags::empty() + } + } + + fn set_status_flags(&self, new_flags: StatusFlags) -> Result<()> { + self.set_non_blocking(new_flags.contains(StatusFlags::O_NONBLOCK)); + Ok(()) + } + + fn metadata(&self) -> Metadata { + let now = RealTimeClock::get().read_time(); + Metadata { + dev: 0, + ino: 0, + size: 0, + blk_size: 0, + blocks: 0, + atime: now, + mtime: now, + ctime: now, + type_: InodeType::NamedPipe, + mode: InodeMode::from_bits_truncate(0o400), + nlinks: 1, + uid: Uid::new_root(), + gid: Gid::new_root(), + rdev: 0, + } + } +} + +impl Drop for SignalFile { + // TODO: Fix signal notifications. See `on_events` method. + fn drop(&mut self) { + if let Some(thread) = current_thread!().as_posix_thread() { + thread.unregister_sigqueue_observer(&self.weak_self); + } + } +} + +#[repr(C)] +#[derive(Debug, Copy, Clone, Pod)] +struct SignalfdSiginfo { + ssi_signo: u32, + ssi_errno: i32, + ssi_code: i32, + ssi_pid: u32, + ssi_uid: u32, + ssi_fd: i32, + ssi_tid: u32, + ssi_band: u32, + ssi_overrun: u32, + ssi_trapno: u32, + ssi_status: i32, + ssi_int: i32, + ssi_ptr: u64, + ssi_utime: u64, + ssi_stime: u64, + ssi_addr: u64, + _pad: [u8; 48], +} + +trait ToSignalfdSiginfo { + fn to_signalfd_siginfo(&self) -> SignalfdSiginfo; +} + +impl ToSignalfdSiginfo for Box { + fn to_signalfd_siginfo(&self) -> SignalfdSiginfo { + let siginfo = self.to_info(); + SignalfdSiginfo { + ssi_signo: siginfo.si_signo as _, + ssi_errno: siginfo.si_errno, + ssi_code: siginfo.si_code, + ssi_pid: 0, + ssi_uid: 0, + ssi_fd: 0, + ssi_tid: 0, + ssi_band: 0, + ssi_overrun: 0, + ssi_trapno: 0, + ssi_status: 0, + ssi_int: 0, + ssi_ptr: 0, + ssi_utime: 0, + ssi_stime: 0, + ssi_addr: 0, + _pad: [0; 48], + } + } +} diff --git a/test/syscall_test/Makefile b/test/syscall_test/Makefile index 84476946b..12b3e2e18 100644 --- a/test/syscall_test/Makefile +++ b/test/syscall_test/Makefile @@ -44,6 +44,7 @@ TESTS ?= \ sendfile_test \ sigaction_test \ sigaltstack_test \ + signalfd_test \ stat_test \ stat_times_test \ statfs_test \ diff --git a/test/syscall_test/blocklists/signalfd_test b/test/syscall_test/blocklists/signalfd_test new file mode 100644 index 000000000..3c415e77e --- /dev/null +++ b/test/syscall_test/blocklists/signalfd_test @@ -0,0 +1,6 @@ +Signalfd.Ppoll +Signalfd/SignalfdTest.Blocking/kSignoMax +Signalfd/SignalfdTest.ThreadGroup/kSigno +Signalfd/SignalfdTest.ThreadGroup/kSignoMax +Signalfd/SignalfdTest.Poll/kSigno +Signalfd/SignalfdTest.Poll/kSignoMax \ No newline at end of file