mirror of
https://github.com/hexagonal-sun/moss-kernel.git
synced 2026-06-11 02:16:01 -04:00
implement itimer syscalls
This commit is contained in:
@@ -102,8 +102,8 @@
|
||||
| 0x63 (99) | set_robust_list | (struct robust_list_head *head, size_t len) | __arm64_sys_set_robust_list | true |
|
||||
| 0x64 (100) | get_robust_list | (int pid, struct robust_list_head **head_ptr, size_t *len_ptr) | __arm64_sys_get_robust_list | false |
|
||||
| 0x65 (101) | nanosleep | (struct __kernel_timespec *rqtp, struct __kernel_timespec *rmtp) | __arm64_sys_nanosleep | true |
|
||||
| 0x66 (102) | getitimer | (int which, struct __kernel_old_itimerval *value) | __arm64_sys_getitimer | false |
|
||||
| 0x67 (103) | setitimer | (int which, struct __kernel_old_itimerval *value, struct __kernel_old_itimerval *ovalue) | __arm64_sys_setitimer | false |
|
||||
| 0x66 (102) | getitimer | (int which, struct __kernel_old_itimerval *value) | __arm64_sys_getitimer | partially |
|
||||
| 0x67 (103) | setitimer | (int which, struct __kernel_old_itimerval *value, struct __kernel_old_itimerval *ovalue) | __arm64_sys_setitimer | partially |
|
||||
| 0x68 (104) | kexec_load | (unsigned long entry, unsigned long nr_segments, struct kexec_segment *segments, unsigned long flags) | __arm64_sys_kexec_load | false |
|
||||
| 0x69 (105) | init_module | (void *umod, unsigned long len, const char *uargs) | __arm64_sys_init_module | false |
|
||||
| 0x6a (106) | delete_module | (const char *name_user, unsigned int flags) | __arm64_sys_delete_module | false |
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::{
|
||||
arch::{Arch, ArchImpl},
|
||||
clock::{
|
||||
clock::syscalls::{
|
||||
gettime::sys_clock_gettime,
|
||||
itimer::{sys_getitimer, sys_setitimer},
|
||||
settime::sys_clock_settime,
|
||||
timeofday::{sys_gettimeofday, sys_settimeofday},
|
||||
},
|
||||
@@ -514,6 +515,16 @@ pub async fn handle_syscall(mut ctx: ProcessCtx) {
|
||||
}
|
||||
0x63 => sys_set_robust_list(&mut ctx, TUA::from_value(arg1 as _), arg2 as _).await,
|
||||
0x65 => sys_nanosleep(TUA::from_value(arg1 as _), TUA::from_value(arg2 as _)).await,
|
||||
0x66 => sys_getitimer(&ctx, arg1 as _, TUA::from_value(arg2 as _)).await,
|
||||
0x67 => {
|
||||
sys_setitimer(
|
||||
&ctx,
|
||||
arg1 as _,
|
||||
TUA::from_value(arg2 as _),
|
||||
TUA::from_value(arg3 as _),
|
||||
)
|
||||
.await
|
||||
}
|
||||
0x70 => sys_clock_settime(arg1 as _, TUA::from_value(arg2 as _)).await,
|
||||
0x71 => sys_clock_gettime(&ctx, arg1 as _, TUA::from_value(arg2 as _)).await,
|
||||
0x73 => {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
pub mod gettime;
|
||||
pub mod realtime;
|
||||
pub mod settime;
|
||||
pub mod timeofday;
|
||||
pub mod syscalls;
|
||||
pub mod timespec;
|
||||
|
||||
pub enum ClockId {
|
||||
|
||||
@@ -5,7 +5,7 @@ use libkernel::{
|
||||
memory::address::TUA,
|
||||
};
|
||||
|
||||
use super::{ClockId, realtime::date, timespec::TimeSpec};
|
||||
use crate::clock::{ClockId, realtime::date, timespec::TimeSpec};
|
||||
use crate::drivers::timer::{Instant, now};
|
||||
use crate::sched::syscall_ctx::ProcessCtx;
|
||||
use crate::{drivers::timer::uptime, memory::uaccess::copy_to_user};
|
||||
177
src/clock/syscalls/itimer.rs
Normal file
177
src/clock/syscalls/itimer.rs
Normal file
@@ -0,0 +1,177 @@
|
||||
use crate::clock::timespec::TimeSpec;
|
||||
use crate::drivers::timer::{Instant, now, uptime};
|
||||
use crate::memory::uaccess::{UserCopyable, copy_from_user, copy_to_user};
|
||||
use crate::process::thread_group::signal::SigId;
|
||||
use crate::process::{ITimer, Task, Tid, find_task_by_tid};
|
||||
use crate::sched::syscall_ctx::ProcessCtx;
|
||||
use core::time::Duration;
|
||||
use libkernel::memory::address::TUA;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum ITimerType {
|
||||
Real = 0,
|
||||
Virtual = 1,
|
||||
Prof = 2,
|
||||
}
|
||||
|
||||
impl TryFrom<i32> for ITimerType {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: i32) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
0 => Ok(Self::Real),
|
||||
1 => Ok(Self::Virtual),
|
||||
2 => Ok(Self::Prof),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct ITimerVal {
|
||||
it_interval: TimeSpec,
|
||||
it_value: TimeSpec,
|
||||
}
|
||||
|
||||
impl ITimerVal {
|
||||
fn is_disabled(&self) -> bool {
|
||||
self.it_value.tv_sec == 0 && self.it_value.tv_nsec == 0
|
||||
}
|
||||
|
||||
fn is_oneshot(&self) -> bool {
|
||||
self.it_interval.tv_sec == 0 && self.it_interval.tv_nsec == 0
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ITimerVal {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
it_interval: TimeSpec {
|
||||
tv_sec: 0,
|
||||
tv_nsec: 0,
|
||||
},
|
||||
it_value: TimeSpec {
|
||||
tv_sec: 0,
|
||||
tv_nsec: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl UserCopyable for ITimerVal {}
|
||||
|
||||
pub fn itimer_irq_handler(tid: Tid, ty: ITimerType) -> Option<Instant> {
|
||||
let task = find_task_by_tid(tid)?;
|
||||
match ty {
|
||||
ITimerType::Real => {
|
||||
task.process.deliver_signal(SigId::SIGALRM);
|
||||
let mut timers = task.i_timers.lock_save_irq();
|
||||
if let Some(ref mut timer) = timers.real
|
||||
&& let Some(interval) = timer.interval
|
||||
{
|
||||
timer.next = now().unwrap() + interval;
|
||||
Some(timer.next)
|
||||
} else {
|
||||
timers.real = None;
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn getitimer(current_task: &Task, which: ITimerType) -> libkernel::error::Result<ITimerVal> {
|
||||
let now = match which {
|
||||
ITimerType::Real => uptime(),
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
Ok(current_task
|
||||
.i_timers
|
||||
.lock_save_irq()
|
||||
.real
|
||||
.map(|t| {
|
||||
let remaining = Duration::from(t.next) - now;
|
||||
let interval = t.interval.unwrap_or_default();
|
||||
ITimerVal {
|
||||
it_interval: TimeSpec {
|
||||
tv_sec: interval.as_secs() as _,
|
||||
tv_nsec: interval.subsec_nanos() as _,
|
||||
},
|
||||
it_value: TimeSpec {
|
||||
tv_sec: remaining.as_secs() as _,
|
||||
tv_nsec: remaining.subsec_nanos() as _,
|
||||
},
|
||||
}
|
||||
})
|
||||
.unwrap_or_default())
|
||||
}
|
||||
|
||||
/// <https://man7.org/linux/man-pages/man2/getitimer.2.html>
|
||||
pub async fn sys_getitimer(
|
||||
ctx: &ProcessCtx,
|
||||
which: i32,
|
||||
curr_value: TUA<ITimerVal>,
|
||||
) -> libkernel::error::Result<usize> {
|
||||
let timer_type =
|
||||
ITimerType::try_from(which).map_err(|_| libkernel::error::KernelError::InvalidValue)?;
|
||||
let value = getitimer(ctx.shared(), timer_type).await?;
|
||||
copy_to_user(curr_value, value).await?;
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
/// <https://man7.org/linux/man-pages/man2/setitimer.2.html>
|
||||
pub async fn sys_setitimer(
|
||||
ctx: &ProcessCtx,
|
||||
which: i32,
|
||||
new_value: TUA<ITimerVal>,
|
||||
old_value: TUA<ITimerVal>,
|
||||
) -> libkernel::error::Result<usize> {
|
||||
let timer_type =
|
||||
ITimerType::try_from(which).map_err(|_| libkernel::error::KernelError::InvalidValue)?;
|
||||
if !old_value.is_null() {
|
||||
let old_timer = getitimer(ctx.shared(), timer_type).await?;
|
||||
copy_to_user(old_value, old_timer).await?;
|
||||
}
|
||||
let new_timer = copy_from_user(new_value).await?;
|
||||
match timer_type {
|
||||
ITimerType::Real => {
|
||||
let current_task = ctx.shared();
|
||||
let mut timers = current_task.i_timers.lock_save_irq();
|
||||
let interval = if new_timer.is_oneshot() {
|
||||
None
|
||||
} else {
|
||||
Some(Duration::new(
|
||||
new_timer.it_interval.tv_sec as _,
|
||||
new_timer.it_interval.tv_nsec as _,
|
||||
))
|
||||
};
|
||||
let next = if new_timer.is_disabled() {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
now().unwrap()
|
||||
+ Duration::new(
|
||||
new_timer.it_value.tv_sec as _,
|
||||
new_timer.it_value.tv_nsec as _,
|
||||
),
|
||||
)
|
||||
};
|
||||
if timers.real.is_some() {
|
||||
crate::drivers::timer::SYS_TIMER
|
||||
.get()
|
||||
.unwrap()
|
||||
.remove_scheduled_itimer(current_task.tid(), ITimerType::Real);
|
||||
}
|
||||
if let Some(next) = next {
|
||||
timers.real = Some(ITimer { interval, next });
|
||||
crate::drivers::timer::SYS_TIMER
|
||||
.get()
|
||||
.unwrap()
|
||||
.schedule_itimer(current_task.tid(), ITimerType::Real, next);
|
||||
}
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
Ok(0)
|
||||
}
|
||||
4
src/clock/syscalls/mod.rs
Normal file
4
src/clock/syscalls/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
pub mod gettime;
|
||||
pub mod itimer;
|
||||
pub mod settime;
|
||||
pub mod timeofday;
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::timespec::TimeSpec;
|
||||
use crate::clock::realtime::{date, set_date};
|
||||
use crate::clock::timespec::TimeSpec;
|
||||
use crate::memory::uaccess::{UserCopyable, copy_from_user, copy_to_user};
|
||||
use core::time::Duration;
|
||||
use libkernel::{error::Result, memory::address::TUA};
|
||||
@@ -1,6 +1,8 @@
|
||||
use super::Driver;
|
||||
use crate::clock::syscalls::itimer::ITimerType;
|
||||
use crate::interrupts::{InterruptDescriptor, InterruptHandler};
|
||||
use crate::per_cpu_private;
|
||||
use crate::process::Tid;
|
||||
use crate::sync::OnceLock;
|
||||
use alloc::{collections::binary_heap::BinaryHeap, sync::Arc};
|
||||
use core::{
|
||||
@@ -74,8 +76,13 @@ enum WakeupKind {
|
||||
|
||||
/// This wake up is for the kernel's preemption mechanism.
|
||||
Preempt,
|
||||
|
||||
ITimer(Tid, ITimerType),
|
||||
}
|
||||
|
||||
unsafe impl Send for WakeupKind {}
|
||||
unsafe impl Sync for WakeupKind {}
|
||||
|
||||
struct WakeupEvent {
|
||||
when: Instant,
|
||||
what: WakeupKind,
|
||||
@@ -174,6 +181,9 @@ impl InterruptHandler for SysTimer {
|
||||
// Do nothing, the IRQ return-to-userspace code will
|
||||
// call schedule() for us.
|
||||
}
|
||||
WakeupKind::ITimer(tid, ty) => {
|
||||
crate::clock::syscalls::itimer::itimer_irq_handler(tid, ty);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// The next event is in the future, so we're done.
|
||||
@@ -231,6 +241,41 @@ impl SysTimer {
|
||||
.await;
|
||||
}
|
||||
|
||||
pub fn schedule_itimer(&self, tid: Tid, ty: ITimerType, when: Instant) {
|
||||
let mut wakeup_q = WAKEUP_Q.borrow_mut();
|
||||
|
||||
wakeup_q.push(WakeupEvent {
|
||||
when,
|
||||
what: WakeupKind::ITimer(tid, ty),
|
||||
});
|
||||
|
||||
// After pushing, we must update the hardware timer in case our
|
||||
// new event is the earliest one.
|
||||
if let Some(next_event) = wakeup_q.peek() {
|
||||
self.driver.schedule_interrupt(Some(next_event.when));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_scheduled_itimer(&self, tid: Tid, ty: ITimerType) {
|
||||
let mut wakeup_q = WAKEUP_Q.borrow_mut();
|
||||
|
||||
// Remove any matching itimer events from the queue.
|
||||
wakeup_q.retain(|event| {
|
||||
if let WakeupKind::ITimer(event_tid, event_ty) = event.what {
|
||||
!(event_tid == tid && event_ty == ty)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
// TODO: Handle cross-CPU cases where the itimer event might be on a different CPU's queue.
|
||||
|
||||
// After removing, we must update the hardware timer in case we removed
|
||||
// the earliest event.
|
||||
if let Some(next_event) = wakeup_q.peek() {
|
||||
self.driver.schedule_interrupt(Some(next_event.when));
|
||||
}
|
||||
}
|
||||
|
||||
/// Schedule a preemption event for the current CPU.
|
||||
pub fn schedule_preempt(&self, when: Instant) {
|
||||
let mut wake_q = WAKEUP_Q.borrow_mut();
|
||||
@@ -306,8 +351,12 @@ pub fn schedule_preempt(when: Instant) {
|
||||
}
|
||||
}
|
||||
|
||||
static SYS_TIMER: OnceLock<Arc<SysTimer>> = OnceLock::new();
|
||||
pub static SYS_TIMER: OnceLock<Arc<SysTimer>> = OnceLock::new();
|
||||
|
||||
per_cpu_private! {
|
||||
static WAKEUP_Q: BinaryHeap<WakeupEvent> = BinaryHeap::new;
|
||||
}
|
||||
|
||||
per_cpu_private! {
|
||||
static NEXT_WAKE_ID: u32 = u32::default;
|
||||
}
|
||||
|
||||
@@ -181,6 +181,7 @@ pub async fn sys_clone(
|
||||
fd_table: files,
|
||||
cwd,
|
||||
root,
|
||||
i_timers: SpinLock::new(*current_task.i_timers.lock_save_irq()),
|
||||
creds: SpinLock::new(creds),
|
||||
ptrace: SpinLock::new(ptrace),
|
||||
sig_mask: new_sigmask,
|
||||
|
||||
@@ -16,6 +16,7 @@ use alloc::{
|
||||
sync::{Arc, Weak},
|
||||
};
|
||||
use core::sync::atomic::{AtomicU32, AtomicUsize, Ordering};
|
||||
use core::time::Duration;
|
||||
use creds::Credentials;
|
||||
use fd_table::FileDescriptorTable;
|
||||
use libkernel::memory::proc_vm::address_space::{UserAddressSpace, VirtualMemory};
|
||||
@@ -162,6 +163,22 @@ impl Comm {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct ITimer {
|
||||
/// If interval is `None`, this timer is a one-shot timer.
|
||||
pub interval: Option<Duration>,
|
||||
/// Instant (wrt the needed clock) at which this timer will next expire.
|
||||
pub next: Instant,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default)]
|
||||
pub struct ITimers {
|
||||
pub real: Option<ITimer>,
|
||||
// virtual is a reserved keyword
|
||||
pub virtual_: Option<ITimer>,
|
||||
pub prof: Option<ITimer>,
|
||||
}
|
||||
|
||||
pub struct Task {
|
||||
pub tid: Tid,
|
||||
pub comm: Arc<SpinLock<Comm>>,
|
||||
@@ -170,6 +187,7 @@ pub struct Task {
|
||||
pub cwd: Arc<SpinLock<(Arc<dyn Inode>, PathBuf)>>,
|
||||
pub root: Arc<SpinLock<(Arc<dyn Inode>, PathBuf)>>,
|
||||
pub creds: SpinLock<Credentials>,
|
||||
pub i_timers: SpinLock<ITimers>,
|
||||
pub fd_table: Arc<SpinLock<FileDescriptorTable>>,
|
||||
pub ptrace: SpinLock<PTrace>,
|
||||
pub sig_mask: AtomicSigSet,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::{
|
||||
Comm, Task, Tid,
|
||||
Comm, ITimers, Task, Tid,
|
||||
creds::Credentials,
|
||||
ctx::{Context, UserCtx},
|
||||
fd_table::FileDescriptorTable,
|
||||
@@ -72,6 +72,7 @@ impl OwnedTask {
|
||||
creds: SpinLock::new(Credentials::new_root()),
|
||||
vm: Arc::new(SpinLock::new(vm)),
|
||||
fd_table: Arc::new(SpinLock::new(FileDescriptorTable::new())),
|
||||
i_timers: SpinLock::new(ITimers::default()),
|
||||
ptrace: SpinLock::new(PTrace::new()),
|
||||
utime: AtomicUsize::new(0),
|
||||
stime: AtomicUsize::new(0),
|
||||
@@ -102,6 +103,7 @@ impl OwnedTask {
|
||||
vm: Arc::new(SpinLock::new(
|
||||
ProcessVM::empty().expect("Could not create init process's VM"),
|
||||
)),
|
||||
i_timers: SpinLock::new(ITimers::default()),
|
||||
fd_table: Arc::new(SpinLock::new(FileDescriptorTable::new())),
|
||||
ptrace: SpinLock::new(PTrace::new()),
|
||||
last_account: AtomicUsize::new(0),
|
||||
|
||||
@@ -196,6 +196,59 @@ fn test_mincore() {
|
||||
|
||||
register_test!(test_mincore);
|
||||
|
||||
fn test_itimer() {
|
||||
use libc::{ITIMER_REAL, itimerval};
|
||||
use std::mem::MaybeUninit;
|
||||
|
||||
unsafe {
|
||||
// Set signal handler for SIGALRM to avoid process termination when the timer expires
|
||||
// We'll flip a bit in a static variable when the signal handler is called, and check that bit at the end of the test to verify the timer actually expired.
|
||||
static TIMER_EXPIRED: std::sync::atomic::AtomicBool =
|
||||
std::sync::atomic::AtomicBool::new(false);
|
||||
extern "C" fn sigalrm_handler(_signum: libc::c_int) {
|
||||
TIMER_EXPIRED.store(true, std::sync::atomic::Ordering::SeqCst);
|
||||
}
|
||||
let mut sa: libc::sigaction = std::mem::zeroed();
|
||||
sa.sa_sigaction = sigalrm_handler as *const () as usize;
|
||||
sa.sa_flags = libc::SA_RESTART;
|
||||
libc::sigemptyset(&mut sa.sa_mask);
|
||||
if libc::sigaction(libc::SIGALRM, &sa, std::ptr::null_mut()) != 0 {
|
||||
panic!("sigaction failed: {}", std::io::Error::last_os_error());
|
||||
}
|
||||
|
||||
let mut old_timer = MaybeUninit::<itimerval>::uninit();
|
||||
let new_timer = itimerval {
|
||||
it_interval: libc::timeval {
|
||||
tv_sec: 0,
|
||||
tv_usec: 0,
|
||||
},
|
||||
it_value: libc::timeval {
|
||||
tv_sec: 1,
|
||||
tv_usec: 0,
|
||||
},
|
||||
};
|
||||
let ret = libc::setitimer(
|
||||
ITIMER_REAL,
|
||||
&new_timer as *const itimerval,
|
||||
old_timer.as_mut_ptr(),
|
||||
);
|
||||
if ret != 0 {
|
||||
panic!("setitimer failed: {}", std::io::Error::last_os_error());
|
||||
}
|
||||
let old_timer = old_timer.assume_init();
|
||||
assert_eq!(old_timer.it_value.tv_sec, 0);
|
||||
assert_eq!(old_timer.it_value.tv_usec, 0);
|
||||
// Wait for 2 seconds to ensure the timer has time to expire
|
||||
std::thread::sleep(std::time::Duration::from_secs(2));
|
||||
assert!(
|
||||
TIMER_EXPIRED.load(std::sync::atomic::Ordering::SeqCst),
|
||||
"Expected timer to have expired and signal handler to have been called"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
register_test!(test_itimer);
|
||||
|
||||
fn run_test(test_fn: fn()) -> Result<(), i32> {
|
||||
// Fork a new process to run the test
|
||||
unsafe {
|
||||
@@ -238,10 +291,10 @@ fn main() {
|
||||
let start = std::time::Instant::now();
|
||||
let mut failures = 0;
|
||||
for test in inventory::iter::<Test> {
|
||||
if let Some(filter) = filter {
|
||||
if !test.test_text.contains(filter) {
|
||||
continue;
|
||||
}
|
||||
if let Some(filter) = filter
|
||||
&& !test.test_text.contains(filter)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
print!("{} ...", test.test_text);
|
||||
let _ = stdout().flush();
|
||||
|
||||
Reference in New Issue
Block a user