diff --git a/docs/src/kernel/linux-compatibility.md b/docs/src/kernel/linux-compatibility.md index 6be7115bc..6ba2b3711 100644 --- a/docs/src/kernel/linux-compatibility.md +++ b/docs/src/kernel/linux-compatibility.md @@ -303,11 +303,11 @@ provided by Linux on x86-64 architecture. | 280 | utimensat | ✅ | | 281 | epoll_pwait | ✅ | | 282 | signalfd | ✅ | -| 283 | timerfd_create | ❌ | +| 283 | timerfd_create | ✅ | | 284 | eventfd | ✅ | | 285 | fallocate | ✅ | -| 286 | timerfd_settime | ❌ | -| 287 | timerfd_gettime | ❌ | +| 286 | timerfd_settime | ✅ | +| 287 | timerfd_gettime | ✅ | | 288 | accept4 | ✅ | | 289 | signalfd4 | ✅ | | 290 | eventfd2 | ✅ | diff --git a/kernel/src/syscall/arch/riscv.rs b/kernel/src/syscall/arch/riscv.rs index c895a8502..63fccddac 100644 --- a/kernel/src/syscall/arch/riscv.rs +++ b/kernel/src/syscall/arch/riscv.rs @@ -126,6 +126,9 @@ use crate::syscall::{ tgkill::sys_tgkill, timer_create::{sys_timer_create, sys_timer_delete}, timer_settime::{sys_timer_gettime, sys_timer_settime}, + timerfd_create::sys_timerfd_create, + timerfd_gettime::sys_timerfd_gettime, + timerfd_settime::sys_timerfd_settime, truncate::{sys_ftruncate, sys_truncate}, umask::sys_umask, umount::sys_umount, @@ -191,6 +194,7 @@ impl_syscall_nums_and_dispatch_fn! { SYS_SYNC = 81 => sys_sync(args[..0]); SYS_FSYNC = 82 => sys_fsync(args[..1]); SYS_FDATASYNC = 83 => sys_fdatasync(args[..1]); + SYS_TIMERFD_CREATE = 85 => sys_timerfd_create(args[..2]); SYS_CAPGET = 90 => sys_capget(args[..2]); SYS_CAPSET = 91 => sys_capset(args[..2]); SYS_EXIT = 93 => sys_exit(args[..1]); @@ -293,6 +297,8 @@ impl_syscall_nums_and_dispatch_fn! { SYS_CLOCK_NANOSLEEP = 407 => sys_clock_nanosleep(args[..4]); SYS_TIMER_GETTIME = 408 => sys_timer_gettime(args[..2]); SYS_TIMER_SETTIME = 409 => sys_timer_settime(args[..4]); + SYS_TIMERFD_GETTIME = 410 => sys_timerfd_gettime(args[..2]); + SYS_TIMERFD_SETTIME = 411 => sys_timerfd_settime(args[..4]); SYS_UTIMENSAT = 412 => sys_utimensat(args[..4]); SYS_SEMTIMEDOP = 420 => sys_semtimedop(args[..4]); SYS_CLONE3 = 435 => sys_clone3(args[..2], &user_ctx); diff --git a/kernel/src/syscall/arch/x86.rs b/kernel/src/syscall/arch/x86.rs index 16fba25aa..956d845a0 100644 --- a/kernel/src/syscall/arch/x86.rs +++ b/kernel/src/syscall/arch/x86.rs @@ -142,6 +142,9 @@ use crate::syscall::{ time::sys_time, timer_create::{sys_timer_create, sys_timer_delete}, timer_settime::{sys_timer_gettime, sys_timer_settime}, + timerfd_create::sys_timerfd_create, + timerfd_gettime::sys_timerfd_gettime, + timerfd_settime::sys_timerfd_settime, truncate::{sys_ftruncate, sys_truncate}, umask::sys_umask, umount::sys_umount, @@ -342,8 +345,11 @@ impl_syscall_nums_and_dispatch_fn! { SYS_UTIMENSAT = 280 => sys_utimensat(args[..4]); SYS_EPOLL_PWAIT = 281 => sys_epoll_pwait(args[..6]); SYS_SIGNALFD = 282 => sys_signalfd(args[..3]); + SYS_TIMERFD_CREATE = 283 => sys_timerfd_create(args[..2]); SYS_EVENTFD = 284 => sys_eventfd(args[..1]); SYS_FALLOCATE = 285 => sys_fallocate(args[..4]); + SYS_TIMERFD_SETTIME = 286 => sys_timerfd_settime(args[..4]); + SYS_TIMERFD_GETTIME = 287 => sys_timerfd_gettime(args[..2]); SYS_ACCEPT4 = 288 => sys_accept4(args[..4]); SYS_SIGNALFD4 = 289 => sys_signalfd4(args[..4]); SYS_EVENTFD2 = 290 => sys_eventfd2(args[..2]); diff --git a/kernel/src/syscall/mod.rs b/kernel/src/syscall/mod.rs index bd3ba3e5e..a3e4b4fcc 100644 --- a/kernel/src/syscall/mod.rs +++ b/kernel/src/syscall/mod.rs @@ -4,6 +4,7 @@ //! The each sub module contains functions that handle real syscall logic. pub use clock_gettime::ClockId; use ostd::cpu::context::UserContext; +pub use timer_create::create_timer; use crate::{context::Context, cpu::LinuxAbi, prelude::*}; @@ -149,6 +150,9 @@ mod tgkill; mod time; mod timer_create; mod timer_settime; +mod timerfd_create; +mod timerfd_gettime; +mod timerfd_settime; mod truncate; mod umask; mod umount; diff --git a/kernel/src/syscall/timer_create.rs b/kernel/src/syscall/timer_create.rs index cd3691121..1c2ac7376 100644 --- a/kernel/src/syscall/timer_create.rs +++ b/kernel/src/syscall/timer_create.rs @@ -21,6 +21,7 @@ use crate::{ time::{ clockid_t, clocks::{BootTimeClock, MonotonicClock, RealTimeClock}, + Timer, }, }; @@ -103,7 +104,31 @@ pub fn sys_timer_create( ); }; - let process_timer_manager = current_process.timer_manager(); + let timer = create_timer(clockid, func, ctx)?; + + let timer_id = current_process.timer_manager().add_posix_timer(timer); + ctx.user_space().write_val(timer_id_addr, &timer_id)?; + Ok(SyscallReturn::Return(0)) +} + +pub fn sys_timer_delete(timer_id: usize, _ctx: &Context) -> Result { + let current_process = current!(); + let Some(timer) = current_process.timer_manager().remove_posix_timer(timer_id) else { + return_errno_with_message!(Errno::EINVAL, "invalid timer ID"); + }; + + timer.cancel(); + Ok(SyscallReturn::Return(0)) +} + +/// Creates a timer associated with the specified clock ID. +/// +/// This timer will invoke the given callback function (`func`) when it expires. +pub fn create_timer(clockid: clockid_t, func: F, ctx: &Context) -> Result> +where + F: Fn() + Send + Sync + 'static, +{ + let process_timer_manager = ctx.process.timer_manager(); let timer = if clockid >= 0 { let clock_id = ClockId::try_from(clockid)?; match clock_id { @@ -141,18 +166,5 @@ pub fn sys_timer_create( DynamicClockIdInfo::Fd(_) => unimplemented!(), } }; - - let timer_id = process_timer_manager.add_posix_timer(timer); - ctx.user_space().write_val(timer_id_addr, &timer_id)?; - Ok(SyscallReturn::Return(0)) -} - -pub fn sys_timer_delete(timer_id: usize, _ctx: &Context) -> Result { - let current_process = current!(); - let Some(timer) = current_process.timer_manager().remove_posix_timer(timer_id) else { - return_errno_with_message!(Errno::EINVAL, "invalid timer ID"); - }; - - timer.cancel(); - Ok(SyscallReturn::Return(0)) + Ok(timer) } diff --git a/kernel/src/syscall/timerfd_create.rs b/kernel/src/syscall/timerfd_create.rs new file mode 100644 index 000000000..6ce6c3a3a --- /dev/null +++ b/kernel/src/syscall/timerfd_create.rs @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MPL-2.0 + +use super::SyscallReturn; +use crate::{ + fs::file_table::FdFlags, + prelude::*, + time::{ + clockid_t, + timerfd::{TFDFlags, TimerfdFile}, + }, +}; + +pub fn sys_timerfd_create(clockid: clockid_t, flags: i32, ctx: &Context) -> Result { + let flags = TFDFlags::from_bits(flags as u32) + .ok_or_else(|| Error::with_message(Errno::EINVAL, "unknown flags"))?; + + let timerfd_file = TimerfdFile::new(clockid, flags, ctx)?; + + let fd = { + let file_table = ctx.thread_local.borrow_file_table(); + let mut file_table_locked = file_table.unwrap().write(); + let fd_flags = if flags.contains(TFDFlags::TFD_CLOEXEC) { + FdFlags::CLOEXEC + } else { + FdFlags::empty() + }; + file_table_locked.insert(Arc::new(timerfd_file), fd_flags) + }; + + Ok(SyscallReturn::Return(fd as _)) +} diff --git a/kernel/src/syscall/timerfd_gettime.rs b/kernel/src/syscall/timerfd_gettime.rs new file mode 100644 index 000000000..6b3e8fdfe --- /dev/null +++ b/kernel/src/syscall/timerfd_gettime.rs @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MPL-2.0 + +use super::SyscallReturn; +use crate::{ + fs::file_table::FileDesc, + prelude::*, + time::{itimerspec_t, timerfd::TimerfdFile, timespec_t}, +}; + +pub fn sys_timerfd_gettime( + fd: FileDesc, + itimerspec_addr: Vaddr, + ctx: &Context, +) -> Result { + if itimerspec_addr == 0 { + return_errno_with_message!(Errno::EINVAL, "invalid pointer to return value"); + } + + let file_table = ctx.thread_local.borrow_file_table(); + let file_table_locked = file_table.unwrap().read(); + let timerfd_file = file_table_locked.get_file(fd as _)?; + let timerfd_file = timerfd_file + .downcast_ref::() + .ok_or_else(|| Error::with_message(Errno::EINVAL, "the fd is not a timerfd"))?; + + let interval = timespec_t::from(timerfd_file.timer().interval()); + let remain = timespec_t::from(timerfd_file.timer().remain()); + let itimerspec = itimerspec_t { + it_interval: interval, + it_value: remain, + }; + ctx.user_space().write_val(itimerspec_addr, &itimerspec)?; + + Ok(SyscallReturn::Return(0)) +} diff --git a/kernel/src/syscall/timerfd_settime.rs b/kernel/src/syscall/timerfd_settime.rs new file mode 100644 index 000000000..f67540ec2 --- /dev/null +++ b/kernel/src/syscall/timerfd_settime.rs @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: MPL-2.0 + +use core::time::Duration; + +use super::SyscallReturn; +use crate::{ + fs::file_table::FileDesc, + prelude::*, + time::{itimerspec_t, timer::Timeout, timerfd::TimerfdFile, timespec_t, TIMER_ABSTIME}, +}; + +pub fn sys_timerfd_settime( + fd: FileDesc, + flags: i32, + new_itimerspec_addr: Vaddr, + old_itimerspec_addr: Vaddr, + ctx: &Context, +) -> Result { + let file_table = ctx.thread_local.borrow_file_table(); + let file_table_locked = file_table.unwrap().read(); + let timerfd_file = file_table_locked.get_file(fd as _)?; + let timerfd_file = timerfd_file + .downcast_ref::() + .ok_or_else(|| Error::with_message(Errno::EINVAL, "the fd is not a timerfd"))?; + + let user_space = ctx.user_space(); + let new_itimerspec = user_space.read_val::(new_itimerspec_addr)?; + let interval = Duration::try_from(new_itimerspec.it_interval)?; + let expire_time = Duration::try_from(new_itimerspec.it_value)?; + + let timer = timerfd_file.timer(); + if old_itimerspec_addr > 0 { + let old_interval = timespec_t::from(timer.interval()); + let remain = timespec_t::from(timer.remain()); + let old_itimerspec = itimerspec_t { + it_interval: old_interval, + it_value: remain, + }; + user_space.write_val(old_itimerspec_addr, &old_itimerspec)?; + } + + timer.set_interval(interval); + timer.cancel(); + // Clear `ticks` after cancel the timer to ensure that `ticks` is zero + // when the timer is rearmed. + timerfd_file.clear_ticks(); + + const TFD_TIMER_CANCEL_ON_SET: i32 = 2; + if expire_time != Duration::ZERO { + let timeout = if flags == 0 { + Timeout::After(expire_time) + } else if flags == TIMER_ABSTIME { + Timeout::When(expire_time) + } else if flags == TFD_TIMER_CANCEL_ON_SET { + unimplemented!() + } else { + unreachable!() + }; + timer.set_timeout(timeout); + } + + Ok(SyscallReturn::Return(0)) +} diff --git a/kernel/src/time/mod.rs b/kernel/src/time/mod.rs index 896c58ec2..45b1bb9eb 100644 --- a/kernel/src/time/mod.rs +++ b/kernel/src/time/mod.rs @@ -14,6 +14,7 @@ pub mod clocks; mod core; mod softirq; mod system_time; +pub mod timerfd; pub mod wait; pub type clockid_t = i32; diff --git a/kernel/src/time/timerfd.rs b/kernel/src/time/timerfd.rs new file mode 100644 index 000000000..996ff63e9 --- /dev/null +++ b/kernel/src/time/timerfd.rs @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: MPL-2.0 + +use core::sync::atomic::{AtomicU64, Ordering}; + +use super::clockid_t; +use crate::{ + events::IoEvents, + fs::{ + file_handle::FileLike, + utils::{CreationFlags, InodeMode, InodeType, Metadata, StatusFlags}, + }, + prelude::*, + process::{ + signal::{PollHandle, Pollable, Pollee}, + Gid, Uid, + }, + syscall::create_timer, + time::{clocks::RealTimeClock, Timer}, +}; + +/// A file-like object representing a timer that can be used with file descriptors. +pub struct TimerfdFile { + timer: Arc, + ticks: Arc, + pollee: Pollee, + flags: SpinLock, +} + +bitflags! { + /// The flags useds for timerfd-related operations. + pub struct TFDFlags: u32 { + const TFD_CLOEXEC = CreationFlags::O_CLOEXEC.bits(); + const TFD_NONBLOCK = StatusFlags::O_NONBLOCK.bits(); + } +} + +impl TimerfdFile { + /// Creates a new `TimerfdFile` instance. + pub fn new(clockid: clockid_t, flags: TFDFlags, ctx: &Context) -> Result { + let ticks = Arc::new(AtomicU64::new(0)); + let pollee = Pollee::new(); + + let timer = { + let ticks = ticks.clone(); + let pollee = pollee.clone(); + + let expired_fn = move || { + ticks.fetch_add(1, Ordering::Release); + pollee.notify(IoEvents::IN); + }; + create_timer(clockid, expired_fn, ctx) + }?; + + Ok(TimerfdFile { + timer, + ticks, + pollee, + flags: SpinLock::new(flags), + }) + } + + /// Gets the associated timer. + pub fn timer(&self) -> &Arc { + &self.timer + } + + /// Clears the tick count. + pub fn clear_ticks(&self) { + self.ticks.store(0, Ordering::Release); + } + + fn is_nonblocking(&self) -> bool { + self.flags.lock().contains(TFDFlags::TFD_NONBLOCK) + } + + fn try_read(&self, writer: &mut VmWriter) -> Result<()> { + let ticks = self.ticks.fetch_and(0, Ordering::AcqRel); + + if ticks == 0 { + return_errno_with_message!(Errno::EAGAIN, "the counter is zero"); + } + + writer.write_fallible(&mut ticks.as_bytes().into())?; + + Ok(()) + } + + fn check_io_events(&self) -> IoEvents { + let mut events = IoEvents::empty(); + + if self.ticks.load(Ordering::Acquire) != 0 { + events |= IoEvents::IN; + } + + events + } +} + +impl Pollable for TimerfdFile { + fn poll(&self, mask: IoEvents, poller: Option<&mut PollHandle>) -> IoEvents { + self.pollee + .poll_with(mask, poller, || self.check_io_events()) + } +} + +impl FileLike for TimerfdFile { + fn read(&self, writer: &mut VmWriter) -> Result { + let read_len = core::mem::size_of::(); + + if writer.avail() < read_len { + return_errno_with_message!(Errno::EINVAL, "buf len is less len u64 size"); + } + + if self.is_nonblocking() { + self.try_read(writer)?; + } else { + self.wait_events(IoEvents::IN, None, || self.try_read(writer))?; + } + + Ok(read_len) + } + + fn write(&self, _reader: &mut VmReader) -> Result { + return_errno_with_message!(Errno::EINVAL, "the file is not valid for writing"); + } + + fn status_flags(&self) -> StatusFlags { + if self.is_nonblocking() { + StatusFlags::O_NONBLOCK + } else { + StatusFlags::empty() + } + } + + fn set_status_flags(&self, new_flags: StatusFlags) -> Result<()> { + let mut flags = self.flags.lock(); + + if new_flags.contains(StatusFlags::O_NONBLOCK) { + *flags |= TFDFlags::TFD_NONBLOCK; + } else { + *flags &= !TFDFlags::TFD_NONBLOCK; + } + + Ok(()) + } + + fn metadata(&self) -> Metadata { + // This is a dummy implementation. + // TODO: Add "anonymous inode fs" and link the file to it. + 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(0o200), + nlinks: 1, + uid: Uid::new_root(), + gid: Gid::new_root(), + rdev: 0, + } + } +}