mirror of
https://github.com/DragonOS-Community/DragonOS.git
synced 2026-09-08 23:57:59 +08:00
feat(epoll): implement Linux-compatible epoll_pwait2 (#2261)
* feat(epoll): implement Linux-compatible epoll_pwait2 Implement syscall 441 with the native __kernel_timespec ABI and share the wait engine with epoll_wait and epoll_pwait. Previously, valid raw calls returned ENOSYS, preventing native event loops from using epoll_pwait2. Convert relative timeouts into a single monotonic nanosecond deadline at the syscall boundary, saturating oversized values at KTIME_MAX. Preserve NULL infinite waits and zero-timeout polls. Use existing timers as wakeup sources and verify the deadline before reporting expiry, without resetting the timeout budget on spurious readiness. Parse all six epoll_pwait arguments, validate non-NULL signal-mask sizes, and share temporary-mask handling with epoll_pwait2. Restore the saved mask on success, timeout and ordinary errors; preserve deferred restoration for EINTR so signal delivery and sigreturn retain the original mask. Check pending signals before expired nonzero deadlines, including 1 ns waits. Replace direct user-buffer slices with protected per-field event writes. Use the native epoll_event layout on each architecture without exposing padding. Preserve unsent entries after faults or maxevents truncation, return partial progress, and consume ET/ONESHOT state only after delivery. Requeue unprocessed entries ahead of delivered LT entries for fairness. Correct sigpending to intersect pending signals with the blocked mask, as required by Linux and exposed by the pending-signal regression test. Add 13 dunitest cases and register them in whitelist/no_skip. Cover timeout bounds, infinite and huge waits, errno ordering, six-argument ABI, temporary masks, SIGKILL/SIGSTOP, EINTR, protected writes and ready-list preservation. Validation: - Reproduced ENOSYS with raw syscall 441 on the unmodified SMP guest. - make kernel and formatting/diff checks passed. - All 13 new tests passed on Linux and a 4-vCPU x86_64 DragonOS guest. - All 21 gVisor epoll tests passed, including EpollPwait2Timeout. - All 11 existing epoll/poll/eventfd regression cases passed in the guest. Cross-architecture guest execution and CodeBuddy startup were not tested. Fixes #2238 Signed-off-by: longjin <longjin@dragonos.org>
This commit is contained in:
@@ -11,8 +11,9 @@ use crate::{
|
||||
},
|
||||
process::ProcessManager,
|
||||
time::{
|
||||
timekeeping::monotonic_now,
|
||||
timer::{next_n_us_timer_jiffies, Timer},
|
||||
Duration, Instant, PosixTimeSpec,
|
||||
PosixTimeSpec,
|
||||
},
|
||||
};
|
||||
use core::fmt::Debug;
|
||||
@@ -378,9 +379,9 @@ impl EventPoll {
|
||||
|
||||
pub fn epoll_wait(
|
||||
epfd: i32,
|
||||
epoll_event: &mut [EPollEvent],
|
||||
max_events: i32,
|
||||
timespec: Option<PosixTimeSpec>,
|
||||
deadline: Option<PosixTimeSpec>,
|
||||
output: &mut dyn FnMut(usize, &EPollEvent) -> Result<(), SystemError>,
|
||||
) -> Result<usize, SystemError> {
|
||||
let current_pcb = ProcessManager::current_pcb();
|
||||
let fd_table = current_pcb.fd_table();
|
||||
@@ -392,7 +393,7 @@ impl EventPoll {
|
||||
.ok_or(SystemError::EBADF)?;
|
||||
|
||||
drop(fd_table_guard);
|
||||
Self::epoll_wait_with_file(ep_file, epoll_event, max_events, timespec)
|
||||
Self::wait_with_output(ep_file, max_events, deadline, output)
|
||||
}
|
||||
|
||||
/// ## epoll_wait的具体实现
|
||||
@@ -401,6 +402,36 @@ impl EventPoll {
|
||||
epoll_event: &mut [EPollEvent],
|
||||
max_events: i32,
|
||||
timespec: Option<PosixTimeSpec>,
|
||||
) -> Result<usize, SystemError> {
|
||||
if max_events <= 0 || epoll_event.len() < max_events as usize {
|
||||
return Err(SystemError::EINVAL);
|
||||
}
|
||||
Self::wait_with_output(
|
||||
ep_file,
|
||||
max_events,
|
||||
timespec.map(Self::timeout_to_deadline),
|
||||
&mut |index, event| {
|
||||
epoll_event[index] = *event;
|
||||
Ok(())
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Convert a valid relative timeout once; zero remains the polling sentinel.
|
||||
pub(crate) fn timeout_to_deadline(timeout: PosixTimeSpec) -> PosixTimeSpec {
|
||||
if timeout.is_empty() {
|
||||
timeout
|
||||
} else {
|
||||
monotonic_now().saturating_add_ktime(&timeout)
|
||||
}
|
||||
}
|
||||
|
||||
/// Shared wait engine. The deadline is absolute CLOCK_MONOTONIC.
|
||||
fn wait_with_output(
|
||||
ep_file: Arc<File>,
|
||||
max_events: i32,
|
||||
deadline: Option<PosixTimeSpec>,
|
||||
output: &mut dyn FnMut(usize, &EPollEvent) -> Result<(), SystemError>,
|
||||
) -> Result<usize, SystemError> {
|
||||
let current_pcb = ProcessManager::current_pcb();
|
||||
|
||||
@@ -423,28 +454,14 @@ impl EventPoll {
|
||||
ep_guard.ready_state.clone()
|
||||
};
|
||||
|
||||
let mut timeout = false;
|
||||
let mut deadline: Option<Instant> = None;
|
||||
if let Some(timespec) = timespec {
|
||||
if !(timespec.tv_sec > 0 || timespec.tv_nsec > 0) {
|
||||
// 非阻塞情况
|
||||
timeout = true;
|
||||
} else {
|
||||
let timeout_us =
|
||||
(timespec.tv_sec * 1_000_000 + timespec.tv_nsec / 1_000) as u64;
|
||||
deadline = Some(Instant::now() + Duration::from_micros(timeout_us));
|
||||
}
|
||||
} else if timespec.is_none() {
|
||||
// 非阻塞情况
|
||||
timeout = false;
|
||||
}
|
||||
let mut timeout = deadline.is_some_and(|time| time.is_empty());
|
||||
// 判断epoll上有没有就绪事件(仅需 SpinLock)
|
||||
let mut available = Self::ep_events_available_rs(&rs_arc);
|
||||
|
||||
loop {
|
||||
if available {
|
||||
// 如果有就绪的事件,则直接返回就绪事件
|
||||
let sent = Self::ep_send_events(epoll.clone(), epoll_event, max_events)?;
|
||||
let sent = Self::ep_send_events(epoll.clone(), output, max_events)?;
|
||||
|
||||
// Linux 语义:阻塞等待时,被唤醒但没有可返回事件应继续等待。
|
||||
// 这会发生在并发读/状态变化导致 ready_list 中项目在 poll 时已不再就绪。
|
||||
@@ -463,10 +480,19 @@ impl EventPoll {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
if let Some(deadline) = deadline {
|
||||
if Instant::now() >= deadline {
|
||||
return Ok(0);
|
||||
}
|
||||
// 如果有未处理且未被屏蔽的信号则返回错误
|
||||
if current_pcb.has_pending_signal_fast()
|
||||
&& current_pcb.has_pending_not_masked_signal()
|
||||
{
|
||||
// Linux epoll_wait(2): interrupted by signal handler -> EINTR.
|
||||
// Returning ERESTARTSYS would cause userspace to restart the syscall
|
||||
// (SA_RESTART), which breaks gVisor's UnblockWithSignal expectation.
|
||||
return Err(SystemError::EINTR);
|
||||
}
|
||||
|
||||
if deadline.is_some_and(|time| monotonic_now().to_ktime_ns() >= time.to_ktime_ns())
|
||||
{
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
// 自旋等待一段时间(仅需 SpinLock)
|
||||
@@ -488,16 +514,6 @@ impl EventPoll {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 如果有未处理且未被屏蔽的信号则返回错误
|
||||
if current_pcb.has_pending_signal_fast()
|
||||
&& current_pcb.has_pending_not_masked_signal()
|
||||
{
|
||||
// Linux epoll_wait(2): interrupted by signal handler -> EINTR.
|
||||
// Returning ERESTARTSYS would cause userspace to restart the syscall
|
||||
// (SA_RESTART), which breaks gVisor's UnblockWithSignal expectation.
|
||||
return Err(SystemError::EINTR);
|
||||
}
|
||||
|
||||
// 还未等待到事件发生,则睡眠
|
||||
// 构造一次等待(先构造 Waiter/Waker,超时需要通过 Waker::wake 触发)
|
||||
let (waiter, waker) = Waiter::new_pair();
|
||||
@@ -505,11 +521,13 @@ impl EventPoll {
|
||||
// 注册定时器:用 waker.wake() 来触发 waiter 退出等待(而不是仅唤醒 PCB)
|
||||
let mut timer = None;
|
||||
if let Some(deadline) = deadline {
|
||||
let remain = deadline.saturating_sub(Instant::now());
|
||||
if remain == Duration::ZERO {
|
||||
let remain_ns = deadline
|
||||
.to_ktime_ns()
|
||||
.saturating_sub(monotonic_now().to_ktime_ns());
|
||||
if remain_ns == 0 {
|
||||
timeout = true;
|
||||
} else {
|
||||
let jiffies = next_n_us_timer_jiffies(remain.total_micros());
|
||||
let jiffies = next_n_us_timer_jiffies(remain_ns.div_ceil(1_000));
|
||||
let inner: Arc<Timer> =
|
||||
Timer::new(TimeoutWaker::new(waker.clone()), jiffies);
|
||||
timer = Some(inner);
|
||||
@@ -554,11 +572,12 @@ impl EventPoll {
|
||||
}
|
||||
|
||||
if let Some(timer) = timer {
|
||||
if timer.as_ref().timeout() {
|
||||
timeout = true;
|
||||
} else {
|
||||
timer.cancel();
|
||||
}
|
||||
// A coarse timer firing alone does not establish expiry.
|
||||
timeout = timer.timeout()
|
||||
&& deadline.is_some_and(|time| {
|
||||
monotonic_now().to_ktime_ns() >= time.to_ktime_ns()
|
||||
});
|
||||
timer.cancel();
|
||||
}
|
||||
|
||||
wait_res?;
|
||||
@@ -631,12 +650,9 @@ impl EventPoll {
|
||||
/// 3. ep_done_scan: 将 ovflist 合并回 ready_list,重入队水平触发项
|
||||
fn ep_send_events(
|
||||
epoll: LockedEventPoll,
|
||||
user_event: &mut [EPollEvent],
|
||||
output: &mut dyn FnMut(usize, &EPollEvent) -> Result<(), SystemError>,
|
||||
max_events: i32,
|
||||
) -> Result<usize, SystemError> {
|
||||
if user_event.len() < max_events as usize {
|
||||
return Err(SystemError::EINVAL);
|
||||
}
|
||||
let ep_guard = epoll.0.lock();
|
||||
let mut res: usize = 0;
|
||||
|
||||
@@ -645,9 +661,12 @@ impl EventPoll {
|
||||
|
||||
// Phase 2: 遍历偷取的列表(此时 ovflist 吸收并发回调)
|
||||
let mut push_back = Vec::new();
|
||||
for epitem in stolen {
|
||||
let mut pending = stolen.into_iter();
|
||||
let mut error = None;
|
||||
let mut remaining = Vec::new();
|
||||
for epitem in pending.by_ref() {
|
||||
if res >= max_events as usize {
|
||||
push_back.push(epitem);
|
||||
remaining.push(epitem);
|
||||
break;
|
||||
}
|
||||
let revents = epitem.ep_item_poll();
|
||||
@@ -666,7 +685,12 @@ impl EventPoll {
|
||||
data: epitem.event.lock_irqsave().data,
|
||||
};
|
||||
|
||||
user_event[res] = event;
|
||||
if let Err(err) = output(res, &event) {
|
||||
// Failed delivery must not consume ET/ONESHOT state.
|
||||
remaining.push(epitem);
|
||||
error = Some(err);
|
||||
break;
|
||||
}
|
||||
res += 1;
|
||||
|
||||
if is_oneshot {
|
||||
@@ -679,8 +703,17 @@ impl EventPoll {
|
||||
}
|
||||
|
||||
// Phase 3: 将 ovflist 合并回 ready_list,重入队水平触发项
|
||||
ep_guard.ep_done_scan(push_back);
|
||||
// Unprocessed entries precede delivered LT entries: a small maxevents
|
||||
// must rotate through all ready descriptors rather than starve the tail.
|
||||
remaining.extend(pending);
|
||||
remaining.extend(push_back);
|
||||
ep_guard.ep_done_scan(remaining);
|
||||
|
||||
if res == 0 {
|
||||
if let Some(err) = error {
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ pub mod fs;
|
||||
|
||||
/// 与C兼容的Epoll事件结构体
|
||||
#[derive(Copy, Clone, Default)]
|
||||
#[repr(packed)]
|
||||
#[cfg_attr(target_arch = "x86_64", repr(packed))]
|
||||
#[repr(C)]
|
||||
pub struct EPollEvent {
|
||||
/// 表示触发的事件
|
||||
@@ -32,6 +32,8 @@ impl Debug for EPollEvent {
|
||||
}
|
||||
|
||||
impl EPollEvent {
|
||||
pub(crate) const DATA_OFFSET: usize = core::mem::offset_of!(Self, data);
|
||||
|
||||
pub fn set_events(&mut self, events: u32) {
|
||||
self.events = events;
|
||||
}
|
||||
|
||||
@@ -1,49 +1,70 @@
|
||||
use crate::arch::ipc::signal::SigSet;
|
||||
use crate::filesystem::epoll::event_poll::EventPoll;
|
||||
use crate::filesystem::epoll::EPollEvent;
|
||||
use crate::mm::VirtAddr;
|
||||
use crate::syscall::user_access::UserBufferWriter;
|
||||
use crate::ipc::signal::{restore_saved_sigmask_unless, set_user_sigmask};
|
||||
use crate::mm::{access_ok, VirtAddr};
|
||||
use crate::syscall::user_access::{read_one_from_user_protected, write_one_to_user_protected};
|
||||
use crate::time::PosixTimeSpec;
|
||||
use system_error::SystemError;
|
||||
|
||||
/// System call handler for epoll_wait.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `epfd` - File descriptor of the epoll instance
|
||||
/// * `events` - User space address to store the events
|
||||
/// * `max_events` - Maximum number of events to return
|
||||
/// * `timeout` - Timeout in milliseconds, 0 for no wait, negative for infinite wait
|
||||
///
|
||||
/// # Returns
|
||||
/// Returns the number of events ready or an error if the operation fails.
|
||||
/// Convert the legacy millisecond ABI at the syscall boundary.
|
||||
pub(super) fn epoll_msec_deadline(timeout: i32) -> Option<PosixTimeSpec> {
|
||||
(timeout >= 0).then(|| {
|
||||
EventPoll::timeout_to_deadline(PosixTimeSpec::new(
|
||||
(timeout / 1000) as i64,
|
||||
(timeout % 1000) as i64 * 1_000_000,
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
/// `deadline` is absolute CLOCK_MONOTONIC; None waits forever, zero polls.
|
||||
pub(super) fn do_epoll_wait(
|
||||
epfd: i32,
|
||||
events: VirtAddr,
|
||||
max_events: i32,
|
||||
timeout: i32,
|
||||
deadline: Option<PosixTimeSpec>,
|
||||
) -> Result<usize, SystemError> {
|
||||
if max_events <= 0 || max_events as u32 > EventPoll::EP_MAX_EVENTS {
|
||||
return Err(SystemError::EINVAL);
|
||||
}
|
||||
|
||||
let mut timespec = None;
|
||||
if timeout == 0 {
|
||||
timespec = Some(PosixTimeSpec::new(0, 0));
|
||||
}
|
||||
|
||||
if timeout > 0 {
|
||||
let sec: i64 = timeout as i64 / 1000;
|
||||
let nsec: i64 = 1000000 * (timeout as i64 % 1000);
|
||||
|
||||
timespec = Some(PosixTimeSpec::new(sec, nsec))
|
||||
}
|
||||
|
||||
// 从用户传入的地址中拿到epoll_events
|
||||
let mut epds_writer = UserBufferWriter::new(
|
||||
events.as_ptr::<EPollEvent>(),
|
||||
// Like Linux access_ok(), only validate the address range here. Actual
|
||||
// writes can fault, including after another thread unmaps the destination.
|
||||
access_ok(
|
||||
events,
|
||||
max_events as usize * core::mem::size_of::<EPollEvent>(),
|
||||
true,
|
||||
)?;
|
||||
)
|
||||
.map_err(|_| SystemError::EFAULT)?;
|
||||
EventPoll::epoll_wait(epfd, max_events, deadline, &mut |index, event| unsafe {
|
||||
let address = events.data() + index * core::mem::size_of::<EPollEvent>();
|
||||
// Linux writes the fields separately. Never expose struct padding on
|
||||
// architectures where epoll_event has natural (rather than packed) layout.
|
||||
write_one_to_user_protected(VirtAddr::new(address), &event.events())?;
|
||||
write_one_to_user_protected(
|
||||
VirtAddr::new(address + EPollEvent::DATA_OFFSET),
|
||||
&event.data(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
let epoll_events = epds_writer.buffer::<EPollEvent>(0)?;
|
||||
return EventPoll::epoll_wait(epfd, epoll_events, max_events, timespec);
|
||||
pub(super) fn do_epoll_pwait(
|
||||
epfd: i32,
|
||||
events: VirtAddr,
|
||||
max_events: i32,
|
||||
deadline: Option<PosixTimeSpec>,
|
||||
sigmask: VirtAddr,
|
||||
sigsetsize: usize,
|
||||
) -> Result<usize, SystemError> {
|
||||
if !sigmask.is_null() {
|
||||
if sigsetsize != core::mem::size_of::<SigSet>() {
|
||||
return Err(SystemError::EINVAL);
|
||||
}
|
||||
let mut mask = SigSet::empty();
|
||||
unsafe { read_one_from_user_protected(sigmask, &mut mask)? };
|
||||
set_user_sigmask(&mut mask);
|
||||
}
|
||||
let result = do_epoll_wait(epfd, events, max_events, deadline);
|
||||
// EINTR must retain the temporary mask until signal delivery. The signal
|
||||
// frame saves the original mask, which sigreturn subsequently restores.
|
||||
restore_saved_sigmask_unless(result == Err(SystemError::EINTR));
|
||||
result
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@ mod epoll_utils;
|
||||
mod sys_epoll_create1;
|
||||
mod sys_epoll_ctl;
|
||||
mod sys_epoll_pwait;
|
||||
mod sys_epoll_pwait2;
|
||||
|
||||
pub mod symlink_utils;
|
||||
mod sys_copy_file_range;
|
||||
|
||||
@@ -32,8 +32,7 @@ impl Syscall for SysEpollCtlHandle {
|
||||
return Err(SystemError::EFAULT);
|
||||
}
|
||||
|
||||
// 还是一样的问题,C标准的epoll_event大小为12字节,而内核实现的epoll_event内存对齐后为16字节
|
||||
// 这样分别拷贝其实和整体拷贝差别不大,内核使用内存对其版本甚至可能提升性能
|
||||
// EPollEvent follows the native ABI: packed on x86_64, natural alignment elsewhere.
|
||||
let epds_reader = UserBufferReader::new(
|
||||
event.as_ptr::<EPollEvent>(),
|
||||
core::mem::size_of::<EPollEvent>(),
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
//! System call handler for epoll_pwait.
|
||||
|
||||
use super::epoll_utils::do_epoll_wait;
|
||||
use super::epoll_utils::{do_epoll_pwait, epoll_msec_deadline};
|
||||
use crate::arch::interrupt::TrapFrame;
|
||||
use crate::arch::ipc::signal::SigSet;
|
||||
use crate::arch::syscall::nr::SYS_EPOLL_PWAIT;
|
||||
use crate::ipc::signal::restore_saved_sigmask;
|
||||
use crate::ipc::signal::set_user_sigmask;
|
||||
use crate::mm::VirtAddr;
|
||||
use crate::syscall::table::FormattedSyscallParam;
|
||||
use crate::syscall::table::Syscall;
|
||||
use crate::syscall::user_access::UserBufferReader;
|
||||
use alloc::vec::Vec;
|
||||
use system_error::SystemError;
|
||||
|
||||
@@ -17,31 +14,24 @@ pub struct SysEpollPwaitHandle;
|
||||
|
||||
impl Syscall for SysEpollPwaitHandle {
|
||||
fn num_args(&self) -> usize {
|
||||
5
|
||||
6
|
||||
}
|
||||
|
||||
fn handle(&self, args: &[usize], _frame: &mut TrapFrame) -> Result<usize, SystemError> {
|
||||
let epfd = Self::epfd(args);
|
||||
let epoll_event = Self::epoll_event(args);
|
||||
let max_events = Self::max_events(args);
|
||||
let timespec = Self::timespec(args);
|
||||
let timeout = Self::timeout(args);
|
||||
let sigmask_addr = Self::sigmask_addr(args);
|
||||
|
||||
if sigmask_addr.is_null() {
|
||||
return do_epoll_wait(epfd, epoll_event, max_events, timespec);
|
||||
}
|
||||
let sigmask_reader =
|
||||
UserBufferReader::new(sigmask_addr, core::mem::size_of::<SigSet>(), true)?;
|
||||
let mut sigmask = sigmask_reader.read_one_from_user::<SigSet>(0)?;
|
||||
|
||||
set_user_sigmask(&mut sigmask);
|
||||
|
||||
let wait_ret = do_epoll_wait(epfd, epoll_event, max_events, timespec);
|
||||
|
||||
if wait_ret.is_err() && *wait_ret.as_ref().unwrap_err() != SystemError::EINTR {
|
||||
restore_saved_sigmask();
|
||||
}
|
||||
wait_ret
|
||||
do_epoll_pwait(
|
||||
epfd,
|
||||
epoll_event,
|
||||
max_events,
|
||||
epoll_msec_deadline(timeout),
|
||||
VirtAddr::new(sigmask_addr as usize),
|
||||
args[5],
|
||||
)
|
||||
}
|
||||
|
||||
fn entry_format(&self, args: &[usize]) -> Vec<FormattedSyscallParam> {
|
||||
@@ -49,11 +39,12 @@ impl Syscall for SysEpollPwaitHandle {
|
||||
FormattedSyscallParam::new("epfd", format!("{:#x}", Self::epfd(args) as usize)),
|
||||
FormattedSyscallParam::new("event", format!("{:#x}", Self::epoll_event(args).data())),
|
||||
FormattedSyscallParam::new("max_events", format!("{:#x}", Self::max_events(args))),
|
||||
FormattedSyscallParam::new("timespec", format!("{:#x}", Self::timespec(args))),
|
||||
FormattedSyscallParam::new("timeout", format!("{:#x}", Self::timeout(args))),
|
||||
FormattedSyscallParam::new(
|
||||
"sigmask_addr",
|
||||
format!("{:#x}", Self::sigmask_addr(args) as usize),
|
||||
),
|
||||
FormattedSyscallParam::new("sigsetsize", format!("{}", args[5])),
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -68,7 +59,7 @@ impl SysEpollPwaitHandle {
|
||||
fn max_events(args: &[usize]) -> i32 {
|
||||
args[2] as i32
|
||||
}
|
||||
fn timespec(args: &[usize]) -> i32 {
|
||||
fn timeout(args: &[usize]) -> i32 {
|
||||
args[3] as i32
|
||||
}
|
||||
fn sigmask_addr(args: &[usize]) -> *mut SigSet {
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
//! Linux epoll_pwait2: timespec timeout and a temporary signal mask.
|
||||
use super::epoll_utils::do_epoll_pwait;
|
||||
use crate::arch::{interrupt::TrapFrame, syscall::nr::SYS_EPOLL_PWAIT2};
|
||||
use crate::filesystem::epoll::event_poll::EventPoll;
|
||||
use crate::mm::VirtAddr;
|
||||
use crate::syscall::table::{FormattedSyscallParam, Syscall};
|
||||
use crate::syscall::user_access::read_one_from_user_protected;
|
||||
use crate::time::PosixTimeSpec;
|
||||
use alloc::vec::Vec;
|
||||
use system_error::SystemError;
|
||||
|
||||
pub struct SysEpollPwait2Handle;
|
||||
|
||||
impl Syscall for SysEpollPwait2Handle {
|
||||
fn num_args(&self) -> usize {
|
||||
6
|
||||
}
|
||||
|
||||
fn handle(&self, args: &[usize], _frame: &mut TrapFrame) -> Result<usize, SystemError> {
|
||||
let deadline = if args[3] == 0 {
|
||||
None
|
||||
} else {
|
||||
// __kernel_timespec consists of two signed 64-bit fields.
|
||||
let mut timeout = PosixTimeSpec::default();
|
||||
unsafe { read_one_from_user_protected(VirtAddr::new(args[3]), &mut timeout)? };
|
||||
if !timeout.is_valid_timeout() {
|
||||
return Err(SystemError::EINVAL);
|
||||
}
|
||||
Some(EventPoll::timeout_to_deadline(timeout))
|
||||
};
|
||||
do_epoll_pwait(
|
||||
args[0] as i32,
|
||||
VirtAddr::new(args[1]),
|
||||
args[2] as i32,
|
||||
deadline,
|
||||
VirtAddr::new(args[4]),
|
||||
args[5],
|
||||
)
|
||||
}
|
||||
|
||||
fn entry_format(&self, args: &[usize]) -> Vec<FormattedSyscallParam> {
|
||||
[
|
||||
"epfd",
|
||||
"events",
|
||||
"maxevents",
|
||||
"timeout",
|
||||
"sigmask",
|
||||
"sigsetsize",
|
||||
]
|
||||
.iter()
|
||||
.zip(args.iter())
|
||||
.map(|(name, value)| FormattedSyscallParam::new(name, format!("{:#x}", value)))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
syscall_table_macros::declare_syscall!(SYS_EPOLL_PWAIT2, SysEpollPwait2Handle);
|
||||
@@ -1,6 +1,6 @@
|
||||
//! System call handler for epoll_wait.
|
||||
|
||||
use super::epoll_utils::do_epoll_wait;
|
||||
use super::epoll_utils::{do_epoll_wait, epoll_msec_deadline};
|
||||
use crate::arch::interrupt::TrapFrame;
|
||||
use crate::arch::syscall::nr::SYS_EPOLL_WAIT;
|
||||
use crate::mm::VirtAddr;
|
||||
@@ -22,7 +22,7 @@ impl Syscall for SysEpollWaitHandle {
|
||||
let timeout = Self::timeout(args);
|
||||
let events = Self::events(args);
|
||||
|
||||
do_epoll_wait(epfd, events, max_events, timeout)
|
||||
do_epoll_wait(epfd, events, max_events, epoll_msec_deadline(timeout))
|
||||
}
|
||||
|
||||
fn entry_format(&self, args: &[usize]) -> Vec<FormattedSyscallParam> {
|
||||
|
||||
@@ -34,7 +34,8 @@ pub(super) fn do_kernel_rt_sigpending(
|
||||
let shared_pending_set = pcb.sighand().shared_pending_signal();
|
||||
|
||||
let mut result = pending_set.union(shared_pending_set);
|
||||
result = result.difference(blocked_set);
|
||||
// sigpending reports pending signals that are blocked by this thread.
|
||||
result = result.intersection(blocked_set);
|
||||
|
||||
user_buffer_writer.copy_one_to_user(&result, 0)?;
|
||||
|
||||
|
||||
@@ -21,3 +21,5 @@ normal/socket_ioctl_netdev_query
|
||||
normal/socket_ioctl_netdev_mutation
|
||||
normal/tcp_self_connect_semantics
|
||||
normal/poll_timeout_semantics
|
||||
|
||||
normal/epoll_pwait2_semantics
|
||||
|
||||
@@ -0,0 +1,352 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <sys/eventfd.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/wait.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
// The kernel ABI uses an eight-byte signal set, not libc's sigset_t.
|
||||
constexpr size_t kSigsetSize = sizeof(uint64_t);
|
||||
int pwait2(int fd, epoll_event* events, int count, const timespec* timeout,
|
||||
const uint64_t* mask = nullptr, size_t size = kSigsetSize) {
|
||||
return syscall(441, fd, events, count, timeout, mask, size);
|
||||
}
|
||||
volatile sig_atomic_t received = 0;
|
||||
void on_signal(int) { received = 1; }
|
||||
|
||||
class EpollPwait2 : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
// Bound even NULL/huge timeout regressions. The runner records a killed
|
||||
// test as a failure; no signal handler can silently turn it into a pass.
|
||||
alarm(15);
|
||||
epfd = epoll_create1(0);
|
||||
ASSERT_GE(epfd, 0);
|
||||
}
|
||||
void TearDown() override {
|
||||
if (child > 0) {
|
||||
kill(child, SIGKILL);
|
||||
waitpid(child, nullptr, 0);
|
||||
}
|
||||
if (mapping != MAP_FAILED) munmap(mapping, mapping_size);
|
||||
for (int fd : sources) close(fd);
|
||||
close(epfd);
|
||||
if (mask_saved) sigprocmask(SIG_SETMASK, &original, nullptr);
|
||||
if (handler_saved) sigaction(SIGUSR1, &old_action, nullptr);
|
||||
alarm(0);
|
||||
}
|
||||
int ready(uint32_t flags = 0) {
|
||||
int fd = eventfd(1, EFD_NONBLOCK);
|
||||
if (fd < 0) return -1;
|
||||
sources.push_back(fd);
|
||||
epoll_event ev = {};
|
||||
ev.events = EPOLLIN | flags;
|
||||
ev.data.fd = fd;
|
||||
if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) != 0) return -1;
|
||||
return fd;
|
||||
}
|
||||
void block_usr1() {
|
||||
sigset_t blocked;
|
||||
sigemptyset(&blocked);
|
||||
sigaddset(&blocked, SIGUSR1);
|
||||
ASSERT_EQ(0, sigprocmask(SIG_BLOCK, &blocked, &original));
|
||||
mask_saved = true;
|
||||
struct sigaction action = {};
|
||||
action.sa_handler = on_signal;
|
||||
action.sa_flags = SA_RESTART;
|
||||
sigemptyset(&action.sa_mask);
|
||||
ASSERT_EQ(0, sigaction(SIGUSR1, &action, &old_action));
|
||||
handler_saved = true;
|
||||
received = 0;
|
||||
}
|
||||
void expect_blocked() {
|
||||
sigset_t current;
|
||||
ASSERT_EQ(0, sigprocmask(SIG_SETMASK, nullptr, ¤t));
|
||||
EXPECT_EQ(1, sigismember(¤t, SIGUSR1));
|
||||
}
|
||||
int epfd = -1;
|
||||
pid_t child = -1;
|
||||
std::vector<int> sources;
|
||||
timespec zero = {};
|
||||
epoll_event out[4] = {};
|
||||
sigset_t original = {};
|
||||
struct sigaction old_action = {};
|
||||
bool mask_saved = false, handler_saved = false;
|
||||
void* mapping = MAP_FAILED;
|
||||
size_t mapping_size = 0;
|
||||
};
|
||||
|
||||
TEST_F(EpollPwait2, ZeroFiniteAndSubmillisecondTimeouts) {
|
||||
for (long ns : {0L, 1L, 500001L, 20000000L}) {
|
||||
timespec timeout = {0, ns}, before = {}, after = {};
|
||||
ASSERT_EQ(0, clock_gettime(CLOCK_MONOTONIC, &before));
|
||||
ASSERT_EQ(0, pwait2(epfd, out, 1, &timeout)) << errno;
|
||||
ASSERT_EQ(0, clock_gettime(CLOCK_MONOTONIC, &after));
|
||||
int64_t elapsed = (after.tv_sec - before.tv_sec) * INT64_C(1000000000) +
|
||||
after.tv_nsec - before.tv_nsec;
|
||||
EXPECT_GE(elapsed, ns);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(EpollPwait2, NullAndHugeTimeoutWakeOnNewReadiness) {
|
||||
timespec huge = {INT64_MAX, 999999999};
|
||||
for (const timespec* timeout : {static_cast<const timespec*>(nullptr), static_cast<const timespec*>(&huge)}) {
|
||||
int fd = eventfd(0, 0);
|
||||
ASSERT_GE(fd, 0);
|
||||
sources.push_back(fd);
|
||||
epoll_event ev = {};
|
||||
ev.events = EPOLLIN;
|
||||
ev.data.fd = fd;
|
||||
ASSERT_EQ(0, epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev));
|
||||
child = fork();
|
||||
ASSERT_GE(child, 0);
|
||||
if (child == 0) {
|
||||
usleep(20000);
|
||||
uint64_t one = 1;
|
||||
_exit(write(fd, &one, sizeof(one)) == sizeof(one) ? 0 : 1);
|
||||
}
|
||||
ASSERT_EQ(1, pwait2(epfd, out, 1, timeout));
|
||||
EXPECT_EQ(fd, out[0].data.fd);
|
||||
int status;
|
||||
ASSERT_EQ(child, waitpid(child, &status, 0));
|
||||
child = -1;
|
||||
EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);
|
||||
ASSERT_EQ(0, epoll_ctl(epfd, EPOLL_CTL_DEL, fd, nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(EpollPwait2, HugeTimeoutReturnsReadyEvent) {
|
||||
ASSERT_GE(ready(), 0);
|
||||
timespec huge = {INT64_MAX, 999999999};
|
||||
EXPECT_EQ(1, pwait2(epfd, out, 1, &huge));
|
||||
}
|
||||
|
||||
TEST_F(EpollPwait2, ValidationOrderAndSixArgumentPwait) {
|
||||
const timespec* bad_time = reinterpret_cast<const timespec*>(1);
|
||||
const uint64_t* bad_mask = reinterpret_cast<const uint64_t*>(1);
|
||||
uint64_t mask = 0;
|
||||
for (timespec invalid : {timespec{-1, 0}, timespec{0, -1}, timespec{0, 1000000000}}) {
|
||||
EXPECT_EQ(-1, pwait2(-1, out, 0, &invalid, bad_mask, 1));
|
||||
EXPECT_EQ(EINVAL, errno);
|
||||
}
|
||||
EXPECT_EQ(-1, pwait2(-1, out, 0, bad_time, bad_mask, 1));
|
||||
EXPECT_EQ(EFAULT, errno);
|
||||
EXPECT_EQ(-1, pwait2(-1, out, 1, &zero, bad_mask, 1));
|
||||
EXPECT_EQ(EINVAL, errno);
|
||||
EXPECT_EQ(-1, pwait2(-1, out, 1, &zero, bad_mask));
|
||||
EXPECT_EQ(EFAULT, errno);
|
||||
EXPECT_EQ(-1, pwait2(-1, out, 0, &zero));
|
||||
EXPECT_EQ(EINVAL, errno);
|
||||
EXPECT_EQ(-1, pwait2(-1, out, 1, &zero));
|
||||
EXPECT_EQ(EBADF, errno);
|
||||
int ordinary = eventfd(0, 0);
|
||||
ASSERT_GE(ordinary, 0);
|
||||
sources.push_back(ordinary);
|
||||
EXPECT_EQ(-1, pwait2(ordinary, out, 1, &zero));
|
||||
EXPECT_EQ(EINVAL, errno);
|
||||
EXPECT_EQ(-1, pwait2(epfd, out, INT_MAX, &zero));
|
||||
EXPECT_EQ(EINVAL, errno);
|
||||
EXPECT_EQ(0, pwait2(epfd, out, 1, &zero, nullptr, 123));
|
||||
EXPECT_EQ(-1, pwait2(epfd, out, 1, &zero, &mask, sizeof(sigset_t)));
|
||||
EXPECT_EQ(EINVAL, errno);
|
||||
EXPECT_EQ(-1, syscall(SYS_epoll_pwait, epfd, out, 1, 0, &mask, 1));
|
||||
EXPECT_EQ(EINVAL, errno);
|
||||
EXPECT_EQ(0, syscall(SYS_epoll_pwait, epfd, out, 1, 0, &mask, kSigsetSize));
|
||||
EXPECT_EQ(0, syscall(SYS_epoll_pwait, epfd, out, 1, 0, nullptr, 123));
|
||||
}
|
||||
|
||||
TEST_F(EpollPwait2, TemporaryMaskRestoredForSuccessTimeoutAndError) {
|
||||
block_usr1();
|
||||
uint64_t empty = 0;
|
||||
EXPECT_EQ(0, pwait2(epfd, out, 1, &zero, &empty));
|
||||
expect_blocked();
|
||||
EXPECT_EQ(-1, pwait2(-1, out, 1, &zero, &empty));
|
||||
EXPECT_EQ(EBADF, errno);
|
||||
expect_blocked();
|
||||
ASSERT_GE(ready(), 0);
|
||||
EXPECT_EQ(1, pwait2(epfd, out, 1, &zero, &empty));
|
||||
expect_blocked();
|
||||
EXPECT_EQ(1, syscall(SYS_epoll_pwait, epfd, out, 1, 0, &empty, kSigsetSize));
|
||||
expect_blocked();
|
||||
mapping_size = sysconf(_SC_PAGESIZE);
|
||||
mapping = mmap(nullptr, mapping_size, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
ASSERT_NE(MAP_FAILED, mapping);
|
||||
EXPECT_EQ(-1, pwait2(epfd, static_cast<epoll_event*>(mapping), 1, &zero, &empty));
|
||||
EXPECT_EQ(EFAULT, errno);
|
||||
expect_blocked();
|
||||
}
|
||||
|
||||
TEST_F(EpollPwait2, TemporarilyBlockedSignalIsDeliveredAfterFiniteWait) {
|
||||
block_usr1();
|
||||
sigset_t unblocked;
|
||||
sigemptyset(&unblocked);
|
||||
ASSERT_EQ(0, sigprocmask(SIG_SETMASK, &unblocked, nullptr));
|
||||
uint64_t mask = UINT64_C(1) << (SIGUSR1 - 1);
|
||||
for (bool legacy : {false, true}) {
|
||||
received = 0;
|
||||
child = fork();
|
||||
ASSERT_GE(child, 0);
|
||||
if (child == 0) {
|
||||
usleep(20000);
|
||||
_exit(kill(getppid(), SIGUSR1) == 0 ? 0 : 1);
|
||||
}
|
||||
timespec timeout = {0, 100000000}, before = {}, after = {};
|
||||
ASSERT_EQ(0, clock_gettime(CLOCK_MONOTONIC, &before));
|
||||
int result = legacy ? syscall(SYS_epoll_pwait, epfd, out, 1, 100, &mask, kSigsetSize)
|
||||
: pwait2(epfd, out, 1, &timeout, &mask);
|
||||
EXPECT_EQ(0, result) << errno;
|
||||
ASSERT_EQ(0, clock_gettime(CLOCK_MONOTONIC, &after));
|
||||
EXPECT_GE((after.tv_sec - before.tv_sec) * INT64_C(1000000000) +
|
||||
after.tv_nsec - before.tv_nsec, 100000000);
|
||||
EXPECT_EQ(1, received);
|
||||
sigset_t current;
|
||||
ASSERT_EQ(0, sigprocmask(SIG_SETMASK, nullptr, ¤t));
|
||||
EXPECT_EQ(0, sigismember(¤t, SIGUSR1));
|
||||
int status;
|
||||
ASSERT_EQ(child, waitpid(child, &status, 0));
|
||||
child = -1;
|
||||
EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(EpollPwait2, TemporaryMaskCannotBlockStopOrKill) {
|
||||
child = fork();
|
||||
ASSERT_GE(child, 0);
|
||||
if (child == 0) {
|
||||
uint64_t mask = (UINT64_C(1) << (SIGSTOP - 1)) | (UINT64_C(1) << (SIGKILL - 1));
|
||||
// Keep waiting across SIGCONT even if the kernel returns EINTR.
|
||||
for (;;) {
|
||||
int result = pwait2(epfd, out, 1, nullptr, &mask);
|
||||
if (result != -1 || errno != EINTR) _exit(1);
|
||||
}
|
||||
}
|
||||
usleep(20000);
|
||||
ASSERT_EQ(0, kill(child, SIGSTOP));
|
||||
int status;
|
||||
ASSERT_EQ(child, waitpid(child, &status, WUNTRACED));
|
||||
ASSERT_TRUE(WIFSTOPPED(status));
|
||||
EXPECT_EQ(SIGSTOP, WSTOPSIG(status));
|
||||
ASSERT_EQ(0, kill(child, SIGCONT));
|
||||
usleep(20000);
|
||||
ASSERT_EQ(0, kill(child, SIGKILL));
|
||||
ASSERT_EQ(child, waitpid(child, &status, 0));
|
||||
child = -1;
|
||||
ASSERT_TRUE(WIFSIGNALED(status));
|
||||
EXPECT_EQ(SIGKILL, WTERMSIG(status));
|
||||
}
|
||||
|
||||
TEST_F(EpollPwait2, PendingSignalPrecedesNonzeroTimeoutButNotZeroPoll) {
|
||||
block_usr1();
|
||||
uint64_t empty = 0;
|
||||
ASSERT_EQ(0, kill(getpid(), SIGUSR1));
|
||||
EXPECT_EQ(0, pwait2(epfd, out, 1, &zero, &empty));
|
||||
EXPECT_EQ(0, received);
|
||||
expect_blocked();
|
||||
sigset_t pending;
|
||||
ASSERT_EQ(0, sigpending(&pending));
|
||||
EXPECT_EQ(1, sigismember(&pending, SIGUSR1));
|
||||
for (long ns : {1L, 100L}) {
|
||||
if (ns == 100) {
|
||||
ASSERT_EQ(0, kill(getpid(), SIGUSR1));
|
||||
}
|
||||
received = 0;
|
||||
timespec timeout = {0, ns};
|
||||
EXPECT_EQ(-1, pwait2(epfd, out, 1, &timeout, &empty));
|
||||
EXPECT_EQ(EINTR, errno);
|
||||
EXPECT_EQ(1, received);
|
||||
expect_blocked();
|
||||
ASSERT_EQ(0, sigpending(&pending));
|
||||
EXPECT_EQ(0, sigismember(&pending, SIGUSR1));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(EpollPwait2, PendingSignalInterruptsWithRestartHandlerAndRestoresMask) {
|
||||
block_usr1();
|
||||
ASSERT_EQ(0, kill(getpid(), SIGUSR1));
|
||||
EXPECT_EQ(0, received);
|
||||
uint64_t empty = 0;
|
||||
timespec timeout = {1, 0};
|
||||
EXPECT_EQ(-1, pwait2(epfd, out, 1, &timeout, &empty));
|
||||
EXPECT_EQ(EINTR, errno);
|
||||
EXPECT_EQ(1, received);
|
||||
expect_blocked();
|
||||
received = 0;
|
||||
ASSERT_EQ(0, kill(getpid(), SIGUSR1));
|
||||
EXPECT_EQ(-1, syscall(SYS_epoll_pwait, epfd, out, 1, 1000, &empty, kSigsetSize));
|
||||
EXPECT_EQ(EINTR, errno);
|
||||
EXPECT_EQ(1, received);
|
||||
expect_blocked();
|
||||
}
|
||||
|
||||
TEST_F(EpollPwait2, ReadOnlyOutputDoesNotConsumeEdgeOrOneshot) {
|
||||
mapping_size = sysconf(_SC_PAGESIZE);
|
||||
mapping = mmap(nullptr, mapping_size, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
ASSERT_NE(MAP_FAILED, mapping);
|
||||
auto* readonly = static_cast<epoll_event*>(mapping);
|
||||
EXPECT_EQ(0, pwait2(epfd, readonly, 1, &zero));
|
||||
for (uint32_t flag : {uint32_t(EPOLLET), uint32_t(EPOLLONESHOT)}) {
|
||||
int fd = ready(flag);
|
||||
ASSERT_GE(fd, 0);
|
||||
EXPECT_EQ(-1, pwait2(epfd, readonly, 1, &zero));
|
||||
EXPECT_EQ(EFAULT, errno);
|
||||
EXPECT_EQ(1, pwait2(epfd, out, 1, &zero));
|
||||
EXPECT_EQ(fd, out[0].data.fd);
|
||||
EXPECT_EQ(0, pwait2(epfd, out, 1, &zero));
|
||||
ASSERT_EQ(0, epoll_ctl(epfd, EPOLL_CTL_DEL, fd, nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(EpollPwait2, CrossPagePartialSuccessPreservesUnwrittenEvent) {
|
||||
size_t page = sysconf(_SC_PAGESIZE);
|
||||
mapping_size = 2 * page;
|
||||
mapping = mmap(nullptr, mapping_size, PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
ASSERT_NE(MAP_FAILED, mapping);
|
||||
ASSERT_EQ(0, mprotect(static_cast<char*>(mapping) + page, page, PROT_NONE));
|
||||
ASSERT_GE(ready(EPOLLONESHOT), 0);
|
||||
ASSERT_GE(ready(EPOLLONESHOT), 0);
|
||||
// First event fits; the second event straddles the inaccessible page.
|
||||
auto* split = reinterpret_cast<epoll_event*>(static_cast<char*>(mapping) + page -
|
||||
sizeof(epoll_event) - 4);
|
||||
ASSERT_EQ(1, pwait2(epfd, split, 2, &zero));
|
||||
int delivered = split[0].data.fd;
|
||||
ASSERT_EQ(1, pwait2(epfd, out, 2, &zero));
|
||||
EXPECT_NE(delivered, out[0].data.fd);
|
||||
EXPECT_EQ(0, pwait2(epfd, out, 2, &zero));
|
||||
}
|
||||
|
||||
TEST_F(EpollPwait2, MaxeventsDoesNotStarveLevelTriggeredSources) {
|
||||
for (int i = 0; i < 3; ++i) ASSERT_GE(ready(), 0);
|
||||
std::vector<int> seen;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
ASSERT_EQ(1, pwait2(epfd, out, 1, &zero));
|
||||
for (int fd : seen) EXPECT_NE(fd, out[0].data.fd);
|
||||
seen.push_back(out[0].data.fd);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(EpollPwait2, MaxeventsPreservesRemainingEdgesAndLegacyWait) {
|
||||
for (int i = 0; i < 3; ++i) ASSERT_GE(ready(EPOLLET), 0);
|
||||
std::vector<int> seen;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
ASSERT_EQ(1, epoll_wait(epfd, out, 1, 0));
|
||||
for (int fd : seen) EXPECT_NE(fd, out[0].data.fd);
|
||||
seen.push_back(out[0].data.fd);
|
||||
}
|
||||
EXPECT_EQ(0, pwait2(epfd, out, 1, &zero));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -125,3 +125,5 @@ normal/sched_tracepoint
|
||||
normal/proc_mem_shared
|
||||
normal/ptrace_perm_dumpable
|
||||
normal/ptrace_x86_debug
|
||||
|
||||
normal/epoll_pwait2_semantics
|
||||
|
||||
Reference in New Issue
Block a user