From 049deda839a1e23def7e1cc51820a40ec1794ada Mon Sep 17 00:00:00 2001 From: zhanghe Date: Wed, 3 Dec 2025 14:07:30 +0800 Subject: [PATCH] fix semantics of posix time function --- header/src/lib.rs | 10 +- kernel/src/syscall_handlers/mod.rs | 96 +++--- kernel/src/syscall_handlers/posix_timers.rs | 340 ++++++++++++++++++++ 3 files changed, 402 insertions(+), 44 deletions(-) create mode 100644 kernel/src/syscall_handlers/posix_timers.rs diff --git a/header/src/lib.rs b/header/src/lib.rs index 98fdc634..7a53fb2d 100644 --- a/header/src/lib.rs +++ b/header/src/lib.rs @@ -29,7 +29,6 @@ pub mod syscalls { ExitThread, AtomicWait, AtomicWake, - ClockGetTime, AllocMem, FreeMem, Write, @@ -76,7 +75,6 @@ pub mod syscalls { Recvmsg, GetAddrinfo, FreeAddrinfo, - NanoSleep, MqOpen, MqClose, MqUnlink, @@ -84,6 +82,14 @@ pub mod syscalls { MqTimedReceive, MqGetSetAttr, Ioctl, + ClockGetTime, + ClockSetTime, + ClockNanoSleep, + TimerCreate, + TimerDelete, + TimerGetTime, + TimerSetTime, + TimerGetOverrun, LastNR, } } diff --git a/kernel/src/syscall_handlers/mod.rs b/kernel/src/syscall_handlers/mod.rs index c63bbc1e..308df75c 100644 --- a/kernel/src/syscall_handlers/mod.rs +++ b/kernel/src/syscall_handlers/mod.rs @@ -38,8 +38,9 @@ use core::{ sync::atomic::AtomicUsize, }; use libc::{ - addrinfo, c_char, c_int, c_long, c_uint, c_ulong, c_void, clockid_t, mode_t, msghdr, off_t, - sigset_t, size_t, sockaddr, socklen_t, timespec, EBUSY, EINVAL, ESRCH, + addrinfo, c_char, c_int, c_long, c_uint, c_ulong, c_void, clockid_t, itimerspec, mode_t, + msghdr, off_t, sigevent, sigset_t, size_t, sockaddr, socklen_t, time_t, timer_t, timespec, + EBUSY, EINVAL, ESRCH, }; #[cfg(not(enable_vfs))] @@ -261,6 +262,7 @@ pub struct mq_attr { pub mq_curmsgs: c_long, pub pad: [c_long; 4], } + // For every syscall number in NR, we have to define a module to // handle the syscall request. `handle_context` serves as the // dispatcher if syscall is invoked via software interrupt. @@ -431,21 +433,44 @@ atomic_wake(addr: usize, count: *mut usize) -> c_long { }) }); -// Only for posix testsuite, we need to implement a stub for clock_gettime define_syscall_handler!( - clock_gettime(_clk_id: clockid_t, tp: *mut timespec) -> c_long { - if tp.is_null() { - return -1; - } - // Only support CLOCK_MONOTONIC. - let now = time::now(); - unsafe { - tp.write(timespec { - tv_sec: now.as_secs() as c_int, - tv_nsec: now.subsec_nanos() as c_int, - }); - } - 0 + clock_gettime(clk_id: clockid_t, tp: *mut timespec) -> c_long { + posix_timers::clock_gettime(clk_id, tp) as c_long +}); + +define_syscall_handler!( + clock_settime(clk_id: clockid_t, tp: *const timespec) -> c_long { + posix_timers::clock_settime(clk_id, tp) as c_long +}); + +define_syscall_handler!( + timer_create(clock_id: clockid_t, evp: *const sigevent, timerid: *mut timer_t) -> c_long { + posix_timers::timer_create(clock_id, evp, timerid) as c_long +}); + +define_syscall_handler!( + timer_delete(timerid: timer_t) -> c_long { + posix_timers::timer_delete(timerid) as c_long +}); + +define_syscall_handler!( + timer_gettime(timerid: timer_t, value: *mut itimerspec) -> c_long { + posix_timers::timer_gettime(timerid, value) as c_long +}); + +define_syscall_handler!( + timer_settime( + timerid: timer_t, + flags: c_int, + value: *const itimerspec, + ovalue: *mut itimerspec + ) -> c_long { + posix_timers::timer_settime(timerid, flags, value, ovalue) as c_long +}); + +define_syscall_handler!( + timer_getoverrun(timerid: timer_t) -> c_long { + posix_timers::timer_getoverrun(timerid) as c_long }); define_syscall_handler!( @@ -754,33 +779,13 @@ define_syscall_handler!( // Netdb syscall end define_syscall_handler!( - sys_clock_nanosleep( - clock_id: i32, - flags: i32, + clock_nanosleep( + clock_id: clockid_t, + flags: c_int, rqtp: *const timespec, rmtp: *mut timespec - ) -> c_int { - // TODO: Valid Clock Id - - // TODO: Valid rqtp - - // TODO: Implement absolute time sleep - let duration = timespec { - tv_sec: unsafe { rqtp.read().tv_sec }, - tv_nsec: unsafe { rqtp.read().tv_nsec }, - }; - - // TODO: Implement tv_nsec - let ticks = Tick(blueos_kconfig::CONFIG_TICKS_PER_SECOND as usize * - duration.tv_sec as usize + - duration.tv_nsec as usize * - blueos_kconfig::CONFIG_TICKS_PER_SECOND as usize / 1_000_000_000); - if ticks.0 == 0 { - scheduler::yield_me(); - } else { - scheduler::suspend_me_for::<()>(ticks,None); - } - 0 + ) -> c_long { + posix_timers::clock_nanosleep(clock_id, flags, rqtp, rmtp) as c_long } ); @@ -845,6 +850,13 @@ syscall_table! { (AtomicWait, atomic_wait), // For test only (ClockGetTime, clock_gettime), + (ClockSetTime, clock_settime), + (ClockNanoSleep, clock_nanosleep), + (TimerCreate, timer_create), + (TimerDelete, timer_delete), + (TimerGetTime, timer_gettime), + (TimerSetTime, timer_settime), + (TimerGetOverrun, timer_getoverrun), (AllocMem, alloc_mem), (FreeMem, free_mem), (Write, write), @@ -891,7 +903,6 @@ syscall_table! { (Recvmsg,recvmsg), (GetAddrinfo,getaddrinfo), (FreeAddrinfo,freeaddrinfo), - (NanoSleep,sys_clock_nanosleep), (MqOpen, mq_open), (MqClose, mq_close), (MqUnlink, mq_unlink), @@ -908,4 +919,5 @@ pub fn dispatch_syscall(ctx: &Context) -> usize { // Begin syscall modules. pub mod echo; +pub mod posix_timers; // End syscall modules. diff --git a/kernel/src/syscall_handlers/posix_timers.rs b/kernel/src/syscall_handlers/posix_timers.rs new file mode 100644 index 00000000..c54078a2 --- /dev/null +++ b/kernel/src/syscall_handlers/posix_timers.rs @@ -0,0 +1,340 @@ +// Copyright (c) 2026 vivo Mobile Communication Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// TODO: We should reuse the kernel timer and systick, and process signals in thread context. +// For now, keep minimal syscall stubs that preserve ABI and basic argument validation for posix timers. + +use crate::{ + scheduler, + thread::{self, Thread}, + time, +}; +use blueos_kconfig::CONFIG_TICKS_PER_SECOND; +use core::time::Duration; +use libc::{c_int, c_long, clockid_t, itimerspec, sigevent, time_t, timer_t, timespec, EINVAL}; + +pub const TIMER_ABSTIME: c_int = 1; +const NS_PER_SEC: i64 = 1_000_000_000; + +pub(crate) const CLOCK_REALTIME: clockid_t = 0; +pub(crate) const CLOCK_MONOTONIC: clockid_t = 1; +pub(crate) const CLOCK_PROCESS_CPUTIME_ID: clockid_t = 2; +pub(crate) const CLOCK_THREAD_CPUTIME_ID: clockid_t = 3; + +// Global realtime offset. +use crate::sync::spinlock::SpinLock; +#[cfg(target_has_atomic = "64")] +use core::sync::atomic::{AtomicI64, Ordering}; + +#[cfg(target_has_atomic = "64")] +static REALTIME_OFFSET_NS: AtomicI64 = AtomicI64::new(0); +#[cfg(not(target_has_atomic = "64"))] +static REALTIME_OFFSET_NS: SpinLock = SpinLock::new(0); + +fn duration_to_ns_saturated(d: Duration) -> i64 { + let nanos = d.as_nanos(); + if nanos > i64::MAX as u128 { + i64::MAX + } else { + nanos as i64 + } +} + +fn thread_cpu_cycles(thread: &Thread) -> u64 { + #[cfg(thread_stats)] + { + let mut total = thread.get_cycles(); + // Include the cycles accumulated since the thread started running. + let now = time::current_clock_cycles(); + total = total.saturating_add(now.saturating_sub(thread.start_cycles())); + total + } + #[cfg(not(thread_stats))] + { + // Fallback when per-thread cycle accounting is disabled. + let _ = thread; + time::current_clock_cycles() + } +} + +fn thread_cpu_time_ns(thread: &Thread) -> i64 { + let dur = time::from_clock_cycles(thread_cpu_cycles(thread)); + duration_to_ns_saturated(dur) +} + +fn current_thread_cpu_time_ns() -> i64 { + let current = scheduler::current_thread(); + thread_cpu_time_ns(¤t) +} + +fn current_process_cpu_time_ns() -> i64 { + // calling thread's CPU time as the process-wide accounting(aka. think an not multi-threaded process). + current_thread_cpu_time_ns() +} + +pub(crate) fn monotonic_time_ns() -> i64 { + let cycles = time::current_clock_cycles(); + let dur = time::from_clock_cycles(cycles); + duration_to_ns_saturated(dur) +} + +pub(crate) fn realtime_time_ns() -> i64 { + #[cfg(target_has_atomic = "64")] + let offset = REALTIME_OFFSET_NS.load(Ordering::Relaxed); + #[cfg(not(target_has_atomic = "64"))] + let offset = *REALTIME_OFFSET_NS.lock(); + monotonic_time_ns().saturating_add(offset) +} + +pub(crate) fn set_realtime_offset_ns(offset: i64) { + #[cfg(target_has_atomic = "64")] + REALTIME_OFFSET_NS.store(offset, Ordering::Relaxed); + #[cfg(not(target_has_atomic = "64"))] + { + *REALTIME_OFFSET_NS.lock() = offset; + } +} + +fn ns_to_timespec(total_ns: i64) -> timespec { + let mut seconds = total_ns / NS_PER_SEC; + let mut nanoseconds = total_ns % NS_PER_SEC; + if nanoseconds < 0 { + seconds -= 1; + nanoseconds += NS_PER_SEC; + } + timespec { + tv_sec: seconds as time_t, + tv_nsec: nanoseconds as c_long, + } +} + +fn timespec_to_ns(ts: ×pec) -> Result { + if ts.tv_nsec < 0 || ts.tv_nsec as i64 >= NS_PER_SEC { + return Err(-EINVAL as c_long); + } + let seconds = ts.tv_sec as i64; + let nanos = ts.tv_nsec as i64; + seconds + .checked_mul(NS_PER_SEC) + .and_then(|base| base.checked_add(nanos)) + .ok_or(-EINVAL as c_long) +} + +fn ticks_from_ns(ns: i64) -> usize { + if ns <= 0 { + return 0; + } + let ticks_per_second = CONFIG_TICKS_PER_SECOND as i64; + let seconds = ns / NS_PER_SEC; + let nanos = ns % NS_PER_SEC; + + let ticks_from_seconds = seconds.saturating_mul(ticks_per_second); + let fractional = (nanos + .saturating_mul(ticks_per_second) + .saturating_add(NS_PER_SEC - 1)) + / NS_PER_SEC; + + let total = ticks_from_seconds.saturating_add(fractional); + match usize::try_from(total) { + Ok(val) => val, + Err(_) => usize::MAX, + } +} + +fn sleep_for_ticks(ticks: usize) { + if ticks == 0 { + scheduler::yield_me(); + } else { + scheduler::suspend_me_for::<()>(time::Tick(ticks), None); + } +} + +pub(crate) fn is_supported_clock_id(clock_id: clockid_t) -> bool { + matches!( + clock_id, + CLOCK_REALTIME | CLOCK_MONOTONIC | CLOCK_PROCESS_CPUTIME_ID | CLOCK_THREAD_CPUTIME_ID + ) +} + +pub(crate) fn clock_gettime(clk_id: clockid_t, tp: *mut timespec) -> c_long { + if tp.is_null() { + return -EINVAL as c_long; + } + if !is_supported_clock_id(clk_id) { + return -EINVAL as c_long; + } + let time_ns: i64 = match clk_id { + CLOCK_REALTIME => realtime_time_ns(), + CLOCK_MONOTONIC => monotonic_time_ns(), + CLOCK_PROCESS_CPUTIME_ID => current_process_cpu_time_ns(), + CLOCK_THREAD_CPUTIME_ID => current_thread_cpu_time_ns(), + _ => monotonic_time_ns(), + }; + unsafe { + *tp = ns_to_timespec(time_ns); + } + 0 +} + +// don't allow set CLOCK_MONOTONIC, CLOCK_PROCESS_CPUTIME_ID, CLOCK_THREAD_CPUTIME_ID +pub(crate) fn clock_settime(clk_id: clockid_t, tp: *const timespec) -> c_long { + if tp.is_null() { + return -EINVAL as c_long; + } + if clk_id != CLOCK_REALTIME { + return -EINVAL as c_long; + } + let requested = unsafe { &*tp }; + let target_ns = match timespec_to_ns(requested) { + Ok(ns) => ns, + Err(errno) => return errno, + }; + let monotonic_ns = monotonic_time_ns(); + set_realtime_offset_ns(target_ns.saturating_sub(monotonic_ns)); + 0 +} + +fn validate_clock_for_sleep(clock_id: clockid_t) -> bool { + matches!(clock_id, CLOCK_MONOTONIC | CLOCK_REALTIME) +} + +fn remaining_ns_for_absolute(clock_id: clockid_t, target: ×pec) -> Result { + let target_ns = timespec_to_ns(target)?; + let now_ns = match clock_id { + CLOCK_REALTIME => realtime_time_ns(), + CLOCK_MONOTONIC => monotonic_time_ns(), + _ => return Err(-EINVAL as c_long), + }; + Ok(target_ns.saturating_sub(now_ns)) +} + +fn remaining_ns_for_relative(target: ×pec) -> Result { + let ns = timespec_to_ns(target)?; + if ns < 0 { + return Err(-EINVAL as c_long); + } + Ok(ns) +} + +fn clear_remaining_time(rmtp: *mut timespec) { + if rmtp.is_null() { + return; + } + unsafe { + (*rmtp).tv_sec = 0; + (*rmtp).tv_nsec = 0; + } +} + +pub(crate) fn clock_nanosleep( + clock_id: clockid_t, + flags: c_int, + rqtp: *const timespec, + rmtp: *mut timespec, +) -> c_long { + if rqtp.is_null() { + return -EINVAL as c_long; + } + if !validate_clock_for_sleep(clock_id) { + return -EINVAL as c_long; + } + let supported_flags = TIMER_ABSTIME; + if flags & !supported_flags != 0 { + return -EINVAL as c_long; + } + let request = unsafe { &*rqtp }; + let remaining_ns = if (flags & TIMER_ABSTIME) != 0 { + remaining_ns_for_absolute(clock_id, request) + } else { + remaining_ns_for_relative(request) + }; + let remaining_ns = match remaining_ns { + Ok(ns) => ns, + Err(errno) => return errno, + }; + if remaining_ns <= 0 { + clear_remaining_time(rmtp); + return 0; + } + let ticks = ticks_from_ns(remaining_ns); + sleep_for_ticks(ticks); + clear_remaining_time(rmtp); + 0 +} + +pub(crate) fn timer_create( + clock_id: clockid_t, + evp: *const sigevent, + timerid: *mut timer_t, +) -> c_long { + // Stub: accept creation request. + if timerid.is_null() { + return -EINVAL as c_long; + } + if !matches!(clock_id, CLOCK_REALTIME | CLOCK_MONOTONIC) { + return -EINVAL as c_long; + } + if !evp.is_null() { + return -EINVAL as c_long; + } + // TODO: actually create a timer and return a unique ID. + unsafe { *timerid = 1 as timer_t }; + 0 +} + +pub(crate) fn timer_delete(timerid: timer_t) -> c_long { + // Stub: treat any non-zero timerid as deletable. + if timerid == 0 { + return -EINVAL as c_long; + } + // TODO: actually delete the timer and free resources. + 0 +} + +pub(crate) fn timer_getoverrun(timerid: timer_t) -> c_long { + // Stub: no overrun accounting. + if timerid == 0 { + return -EINVAL as c_long; + } + // TODO: actually get the overrun count. + 0 +} + +pub(crate) fn timer_gettime(timerid: timer_t, value: *mut itimerspec) -> c_long { + if timerid == 0 || value.is_null() { + return -EINVAL as c_long; + } + // TODO: actually get the timer's remaining time and interval. + 0 +} + +pub(crate) fn timer_settime( + timerid: timer_t, + flags: c_int, + value: *const itimerspec, + ovalue: *mut itimerspec, +) -> c_long { + // Stub: validate pointers/flags + if timerid == 0 { + return -EINVAL as c_long; + } + if value.is_null() { + return -EINVAL as c_long; + } + if flags & !TIMER_ABSTIME != 0 { + return -EINVAL as c_long; + } + // TODO: actually set the timer and return the old value if requested. + 0 +}