Add sys_signalfd/sys_signalfd implementation

This commit is contained in:
Fabing Li 2025-03-21 03:41:11 +00:00 committed by Tate, Hongliang Tian
parent 7644ca1598
commit bce23a63bd
9 changed files with 404 additions and 5 deletions

View File

@ -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 | ✅ |

View File

@ -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 {}

View File

@ -118,6 +118,14 @@ impl<T: Into<SigSet>> ops::SubAssign<T> 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<Self>) -> bool {
let set = set.into();
self.bits & set.bits == set.bits
pub fn contains(&self, other: impl Into<Self>) -> bool {
let other = other.into();
self.bits & other.bits == other.bits
}
pub fn intersects(&self, other: impl Into<Self>) -> bool {
let other = other.into();
self.bits & other.bits != 0
}
}

View File

@ -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]);

View File

@ -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]);

View File

@ -131,6 +131,7 @@ mod setsockopt;
mod setuid;
mod shutdown;
mod sigaltstack;
mod signalfd;
mod socket;
mod socketpair;
mod stat;

View File

@ -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<SyscallReturn> {
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<SyscallReturn> {
debug!(
"fd = {}, mask = {:x}, sizemask = {}, flags = {}",
fd, mask_ptr, sizemask, flags
);
if sizemask != core::mem::size_of::<SigMask>() {
return Err(Error::with_message(Errno::EINVAL, "invalid mask size"));
}
let mut mask = ctx.user_space().read_val::<SigMask>(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<FileDesc> {
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<FileDesc> {
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::<SignalFile>()
.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<SignalFile>, 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<dyn Observer<SigEvents>>,
}
impl SignalFile {
/// Create a new signalfd instance
fn new(mask: AtomicSigMask, non_blocking: bool) -> Arc<Self> {
Arc::new_cyclic(|weak_ref| {
let weak_self = weak_ref.clone() as Weak<dyn Observer<SigEvents>>;
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<dyn Observer<SigEvents>> {
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<usize> {
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::<SignalfdSiginfo>();
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::<SignalfdSiginfo>())
}
}
impl Observer<SigEvents> 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<usize> {
if writer.avail() < core::mem::size_of::<SignalfdSiginfo>() {
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<usize> {
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<dyn Signal> {
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],
}
}
}

View File

@ -44,6 +44,7 @@ TESTS ?= \
sendfile_test \
sigaction_test \
sigaltstack_test \
signalfd_test \
stat_test \
stat_times_test \
statfs_test \

View File

@ -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