Pārlūkot izejas kodu

feat: add select and pselect6 syscall (#1229)

* feat: add select and pselect6 syscall

Signed-off-by: Godones <chenlinfeng25@outlook.com>

* fix: fix the select compile error

Signed-off-by: Godones <chenlinfeng25@outlook.com>

---------

Signed-off-by: Godones <chenlinfeng25@outlook.com>
linfeng 3 nedēļas atpakaļ
vecāks
revīzija
8aa3b7cdfb

+ 5 - 2
kernel/src/filesystem/poll.rs

@@ -164,7 +164,10 @@ impl Syscall {
     }
 }
 
-fn do_sys_poll(poll_fds: &mut [PollFd], timeout: Option<Instant>) -> Result<usize, SystemError> {
+pub fn do_sys_poll(
+    poll_fds: &mut [PollFd],
+    timeout: Option<Instant>,
+) -> Result<usize, SystemError> {
     let ep_file = EventPoll::create_epoll_file(FileMode::empty())?;
 
     let ep_file = Arc::new(ep_file);
@@ -177,7 +180,7 @@ fn do_sys_poll(poll_fds: &mut [PollFd], timeout: Option<Instant>) -> Result<usiz
 }
 
 /// 计算超时的时刻
-fn poll_select_set_timeout(timeout_ms: u64) -> Option<Instant> {
+pub fn poll_select_set_timeout(timeout_ms: u64) -> Option<Instant> {
     Some(Instant::now() + Duration::from_millis(timeout_ms))
 }
 

+ 2 - 0
kernel/src/filesystem/vfs/syscall/mod.rs

@@ -60,6 +60,8 @@ pub mod sys_mount;
 pub mod sys_umount2;
 
 pub mod symlink_utils;
+mod sys_pselect6;
+mod sys_select;
 #[cfg(target_arch = "x86_64")]
 mod sys_symlink;
 mod sys_symlinkat;

+ 51 - 0
kernel/src/filesystem/vfs/syscall/sys_pselect6.rs

@@ -0,0 +1,51 @@
+use alloc::vec::Vec;
+
+use system_error::SystemError;
+
+use crate::{
+    arch::{ipc::signal::SigSet, syscall::nr::SYS_PSELECT6},
+    filesystem::vfs::syscall::sys_select::common_sys_select,
+    ipc::signal::set_user_sigmask,
+    syscall::{
+        table::{FormattedSyscallParam, Syscall},
+        user_access::UserBufferReader,
+    },
+};
+
+pub struct SysPselect6;
+impl Syscall for SysPselect6 {
+    fn num_args(&self) -> usize {
+        6
+    }
+
+    fn handle(
+        &self,
+        args: &[usize],
+        _frame: &mut crate::arch::interrupt::TrapFrame,
+    ) -> Result<usize, SystemError> {
+        let sigmask_ptr = args[5];
+        let mut sigmask: Option<SigSet> = None;
+        if sigmask_ptr != 0 {
+            let sigmask_reader =
+                UserBufferReader::new(sigmask_ptr as *const SigSet, size_of::<SigSet>(), true)?;
+            sigmask.replace(*sigmask_reader.read_one_from_user(0)?);
+        }
+        if let Some(mut sigmask) = sigmask {
+            set_user_sigmask(&mut sigmask);
+        }
+        common_sys_select(args[0], args[1], args[2], args[3], args[4])
+    }
+
+    fn entry_format(&self, args: &[usize]) -> Vec<crate::syscall::table::FormattedSyscallParam> {
+        vec![
+            FormattedSyscallParam::new("nfds", format!("{}", args[0])),
+            FormattedSyscallParam::new("readfds", format!("{:#x}", args[1])),
+            FormattedSyscallParam::new("writefds", format!("{:#x}", args[2])),
+            FormattedSyscallParam::new("exceptfds", format!("{:#x}", args[3])),
+            FormattedSyscallParam::new("timeout", format!("{:#x}", args[4])),
+            FormattedSyscallParam::new("sigmask", format!("{:#x}", args[5])),
+        ]
+    }
+}
+
+syscall_table_macros::declare_syscall!(SYS_PSELECT6, SysPselect6);

+ 285 - 0
kernel/src/filesystem/vfs/syscall/sys_select.rs

@@ -0,0 +1,285 @@
+//! Reference https://github.com/asterinas/asterinas/blob/main/kernel/src/syscall/select.rs
+use alloc::vec::Vec;
+use system_error::SystemError;
+
+#[cfg(target_arch = "x86_64")]
+use crate::arch::syscall::nr::SYS_SELECT;
+use crate::{
+    filesystem::{
+        epoll::EPollEventType,
+        poll::{do_sys_poll, poll_select_set_timeout, PollFd},
+    },
+    syscall::{
+        table::{FormattedSyscallParam, Syscall},
+        user_access::{UserBufferReader, UserBufferWriter},
+    },
+    time::{syscall::PosixTimeval, Instant},
+};
+// Maximum number of file descriptors in a set
+const FD_SETSIZE: usize = 1024;
+const USIZE_BITS: usize = core::mem::size_of::<usize>() * 8;
+/// See https://man7.org/linux/man-pages/man2/select.2.html
+pub struct SysSelect;
+
+impl Syscall for SysSelect {
+    fn num_args(&self) -> usize {
+        5
+    }
+
+    fn handle(
+        &self,
+        args: &[usize],
+        _frame: &mut crate::arch::interrupt::TrapFrame,
+    ) -> Result<usize, SystemError> {
+        common_sys_select(args[0], args[1], args[2], args[3], args[4])
+    }
+
+    fn entry_format(&self, args: &[usize]) -> Vec<crate::syscall::table::FormattedSyscallParam> {
+        vec![
+            FormattedSyscallParam::new("nfds", format!("{}", args[0])),
+            FormattedSyscallParam::new("readfds", format!("{:#x}", args[1])),
+            FormattedSyscallParam::new("writefds", format!("{:#x}", args[2])),
+            FormattedSyscallParam::new("exceptfds", format!("{:#x}", args[3])),
+            FormattedSyscallParam::new("timeout", format!("{:#x}", args[4])),
+        ]
+    }
+}
+
+pub fn common_sys_select(
+    nfds: usize,
+    readfds_addr: usize,
+    writefds_addr: usize,
+    exceptfds_addr: usize,
+    timeout_ptr: usize,
+) -> Result<usize, SystemError> {
+    // log::debug!(
+    //     "common_sys_select called with nfds = {}, readfds_addr = {:#x}, writefds_addr = {:#x}, exceptfds_addr = {:#x}, timeout_ptr = {:#x}",
+    //     nfds, readfds_addr, writefds_addr, exceptfds_addr, timeout_ptr
+    // );
+    let mut timeout: Option<Instant> = None;
+    if timeout_ptr != 0 {
+        let tsreader = UserBufferReader::new(
+            timeout_ptr as *const PosixTimeval,
+            size_of::<PosixTimeval>(),
+            true,
+        )?;
+        let ts = *tsreader.read_one_from_user::<PosixTimeval>(0)?;
+        let timeout_ms = ts.tv_sec * 1000 + ts.tv_usec as i64 / 1000;
+        if timeout_ms >= 0 {
+            timeout = poll_select_set_timeout(timeout_ms as u64);
+        }
+    }
+    do_sys_select(
+        nfds as isize,
+        readfds_addr as *const FdSet,
+        writefds_addr as *const FdSet,
+        exceptfds_addr as *const FdSet,
+        timeout,
+    )
+}
+
+fn do_sys_select(
+    nfds: isize,
+    readfds_addr: *const FdSet,
+    writefds_addr: *const FdSet,
+    exceptfds_addr: *const FdSet,
+    timeout: Option<Instant>,
+) -> Result<usize, SystemError> {
+    if nfds < 0 || nfds as usize > FD_SETSIZE {
+        return Err(SystemError::EINVAL);
+    }
+    let get_fdset = |fdset_addr: *const FdSet| -> Result<Option<FdSet>, SystemError> {
+        let fdset = if fdset_addr.is_null() {
+            None
+        } else {
+            let fdset_buf = UserBufferReader::new(fdset_addr, size_of::<FdSet>(), true)?;
+            let fdset = *fdset_buf.read_one_from_user::<FdSet>(0)?;
+            Some(fdset)
+        };
+        Ok(fdset)
+    };
+    let mut readfds = get_fdset(readfds_addr)?;
+    let mut writefds = get_fdset(writefds_addr)?;
+    let mut exceptfds = get_fdset(exceptfds_addr)?;
+
+    // log::debug!(
+    //     "nfds = {}, readfds = {:?}, writefds = {:?}, exceptfds = {:?}, timeout = {:?}",
+    //     nfds,
+    //     readfds,
+    //     writefds,
+    //     exceptfds,
+    //     timeout
+    // );
+
+    let num_revents = do_select(
+        nfds as usize,
+        readfds.as_mut(),
+        writefds.as_mut(),
+        exceptfds.as_mut(),
+        timeout,
+    )?;
+
+    let set_fdset = |fdset_addr: *const FdSet, fdset: Option<FdSet>| -> Result<(), SystemError> {
+        if let Some(fdset) = fdset {
+            let mut fdset_buf =
+                UserBufferWriter::new(fdset_addr as *mut FdSet, size_of::<FdSet>(), true)?;
+            fdset_buf.copy_one_to_user(&fdset, 0)?;
+        }
+        Ok(())
+    };
+
+    set_fdset(readfds_addr, readfds)?;
+    set_fdset(writefds_addr, writefds)?;
+    set_fdset(exceptfds_addr, exceptfds)?;
+
+    // log::info!("num_revents = {}", num_revents);
+    Ok(num_revents)
+}
+
+fn do_select(
+    nfds: usize,
+    mut readfds: Option<&mut FdSet>,
+    mut writefds: Option<&mut FdSet>,
+    mut exceptfds: Option<&mut FdSet>,
+    timeout: Option<Instant>,
+) -> Result<usize, SystemError> {
+    let mut poll_fds = {
+        let mut poll_fds = Vec::with_capacity(nfds);
+        for fd in 0..nfds {
+            let events = {
+                let readable = readfds.as_ref().is_some_and(|fds| fds.is_set(fd));
+                let writable = writefds.as_ref().is_some_and(|fds| fds.is_set(fd));
+                let except = exceptfds.as_ref().is_some_and(|fds| fds.is_set(fd));
+                convert_rwe_to_events(readable, writable, except)
+            };
+
+            if events.is_empty() {
+                continue;
+            }
+
+            let poll_fd = PollFd {
+                fd: fd as i32,
+                events: events.bits() as _,
+                revents: 0,
+            };
+            poll_fds.push(poll_fd);
+        }
+        poll_fds
+    };
+    if let Some(fds) = readfds.as_mut() {
+        fds.clear();
+    }
+    if let Some(fds) = writefds.as_mut() {
+        fds.clear();
+    }
+    if let Some(fds) = exceptfds.as_mut() {
+        fds.clear();
+    }
+
+    // call the underlying poll syscall
+    let num_revents = do_sys_poll(&mut poll_fds, timeout)?;
+    if num_revents == 0 {
+        return Ok(0);
+    }
+
+    let mut total_revents = 0;
+    for poll_fd in &poll_fds {
+        let fd = poll_fd.fd as usize;
+        let revents = poll_fd.revents;
+        let revents = EPollEventType::from_bits_truncate(revents as u32);
+        let (readable, writable, except) = convert_events_to_rwe(revents)?;
+        if let Some(ref mut fds) = readfds
+            && readable
+        {
+            fds.set(fd)?;
+            total_revents += 1;
+        }
+        if let Some(ref mut fds) = writefds
+            && writable
+        {
+            fds.set(fd)?;
+            total_revents += 1;
+        }
+        if let Some(ref mut fds) = exceptfds
+            && except
+        {
+            fds.set(fd)?;
+            total_revents += 1;
+        }
+    }
+    Ok(total_revents)
+}
+
+/// Converts `select` RWE input to `poll` I/O event input
+/// according to Linux's behavior.
+fn convert_rwe_to_events(readable: bool, writable: bool, except: bool) -> EPollEventType {
+    let mut events = EPollEventType::empty();
+    if readable {
+        events |= EPollEventType::EPOLLIN;
+    }
+    if writable {
+        events |= EPollEventType::EPOLLOUT;
+    }
+    if except {
+        events |= EPollEventType::EPOLLPRI;
+    }
+    events
+}
+
+/// Converts `poll` I/O event results to `select` RWE results
+/// according to Linux's behavior.
+fn convert_events_to_rwe(events: EPollEventType) -> Result<(bool, bool, bool), SystemError> {
+    if events.contains(EPollEventType::EPOLLNVAL) {
+        return Err(SystemError::EBADF);
+    }
+
+    let readable = events
+        .intersects(EPollEventType::EPOLLIN | EPollEventType::EPOLLHUP | EPollEventType::EPOLLERR);
+    let writable = events.intersects(EPollEventType::EPOLLOUT | EPollEventType::EPOLLERR);
+    let except = events.contains(EPollEventType::EPOLLPRI);
+    Ok((readable, writable, except))
+}
+
+#[derive(Debug, Clone, Copy)]
+#[repr(C)]
+struct FdSet {
+    fds_bits: [usize; FD_SETSIZE / USIZE_BITS],
+}
+
+impl FdSet {
+    /// Equivalent to FD_SET.
+    pub fn set(&mut self, fd: usize) -> Result<(), SystemError> {
+        if fd >= FD_SETSIZE {
+            return Err(SystemError::EINVAL);
+        }
+        self.fds_bits[fd / USIZE_BITS] |= 1 << (fd % USIZE_BITS);
+        Ok(())
+    }
+
+    /// Equivalent to FD_CLR.
+    #[expect(unused)]
+    pub fn unset(&mut self, fd: usize) -> Result<(), SystemError> {
+        if fd >= FD_SETSIZE {
+            return Err(SystemError::EINVAL);
+        }
+        self.fds_bits[fd / USIZE_BITS] &= !(1 << (fd % USIZE_BITS));
+        Ok(())
+    }
+
+    /// Equivalent to FD_ISSET.
+    pub fn is_set(&self, fd: usize) -> bool {
+        if fd >= FD_SETSIZE {
+            return false;
+        }
+        (self.fds_bits[fd / USIZE_BITS] & (1 << (fd % USIZE_BITS))) != 0
+    }
+
+    /// Equivalent to FD_ZERO.
+    pub fn clear(&mut self) {
+        for slot in self.fds_bits.iter_mut() {
+            *slot = 0;
+        }
+    }
+}
+#[cfg(target_arch = "x86_64")]
+syscall_table_macros::declare_syscall!(SYS_SELECT, SysSelect);

+ 1 - 0
kernel/src/lib.rs

@@ -23,6 +23,7 @@
 #![feature(vec_into_raw_parts)]
 #![feature(linkage)]
 #![feature(panic_can_unwind)]
+#![feature(let_chains)]
 #![allow(
     static_mut_refs,
     non_local_definitions,

+ 20 - 0
user/apps/test_select/Makefile

@@ -0,0 +1,20 @@
+ifeq ($(ARCH), x86_64)
+	CROSS_COMPILE=x86_64-linux-musl-
+else ifeq ($(ARCH), riscv64)
+	CROSS_COMPILE=riscv64-linux-musl-
+endif
+
+CC=$(CROSS_COMPILE)gcc
+
+
+all:
+	$(CC) -static -o test_select main.c
+
+.PHONY: install clean
+install: all
+	mv test_select $(DADK_CURRENT_BUILD_DIR)/test_select
+
+clean:
+	rm test_select *.o
+
+fmt:

+ 87 - 0
user/apps/test_select/main.c

@@ -0,0 +1,87 @@
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/eventfd.h>
+#include <sys/select.h>
+#include <sys/wait.h>
+#include <time.h>
+#include <unistd.h>
+
+// 创建 eventfd 并返回 fd
+int create_eventfd() {
+  int fd = eventfd(0, EFD_NONBLOCK);
+  if (fd == -1) {
+    perror("eventfd");
+    exit(EXIT_FAILURE);
+  }
+  return fd;
+}
+
+// 子线程或子进程模拟事件发生
+void trigger_event(int efd, unsigned int delay_sec) {
+  printf("[trigger] Writing eventfd after %u seconds...\n", delay_sec);
+  sleep(delay_sec);
+  uint64_t val = 1;
+  if (write(efd, &val, sizeof(val)) != sizeof(val)) {
+    perror("write eventfd");
+    exit(EXIT_FAILURE);
+  }
+  printf("[trigger] Event written to eventfd.\n");
+}
+
+int main() {
+  int efd = create_eventfd();
+
+  pid_t pid = fork();
+  if (pid < 0) {
+    perror("fork");
+    exit(1);
+  }
+
+  if (pid == 0) {
+    // 子进程:触发事件
+    trigger_event(efd, 3);
+    close(efd);
+    exit(0);
+  }
+
+  // 父进程:使用 select 等待事件发生
+  printf("[select_test] Waiting for event...\n");
+
+  fd_set rfds;
+  FD_ZERO(&rfds);
+  FD_SET(efd, &rfds);
+
+  int maxfd = efd + 1;
+  struct timeval timeout;
+  timeout.tv_sec = 5;
+  timeout.tv_usec = 0;
+
+  int ret = select(maxfd, &rfds, NULL, NULL, &timeout);
+  if (ret < 0) {
+    perror("select");
+    exit(1);
+  }
+  printf("[select_test] select returned: %d\n", ret);
+
+  if (FD_ISSET(efd, &rfds)) {
+    printf("[select_test] Event occurred on eventfd.\n");
+    uint64_t val;
+    if (read(efd, &val, sizeof(val)) != sizeof(val)) {
+      perror("read");
+      exit(1);
+    }
+    printf("[select_test] Received eventfd value: %lu\n", val);
+  }
+
+  // wait for child process to finish
+  int status;
+  waitpid(pid, &status, 0);
+  printf("[parent] Child exited with status: %d\n", WEXITSTATUS(status));
+  close(efd);
+
+  return 0;
+}

+ 46 - 0
user/dadk/config/test_select_0_1_1.toml

@@ -0,0 +1,46 @@
+# 用户程序名称
+name = "test_select"
+# 版本号
+version = "0.1.0"
+# 用户程序描述信息
+description = "简单的select测试程序"
+# (可选)默认: false 是否只构建一次,如果为true,DADK会在构建成功后,将构建结果缓存起来,下次构建时,直接使用缓存的构建结果
+build-once = false
+#  (可选) 默认: false 是否只安装一次,如果为true,DADK会在安装成功后,不再重复安装
+install-once = false
+# 目标架构
+# 可选值:"x86_64", "aarch64", "riscv64"
+target-arch = ["x86_64"]
+# 任务源
+[task-source]
+# 构建类型
+# 可选值:"build-from_source", "install-from-prebuilt"
+type = "build-from-source"
+# 构建来源
+# "build_from_source" 可选值:"git", "local", "archive"
+# "install_from_prebuilt" 可选值:"local", "archive"
+source = "local"
+# 路径或URL
+source-path = "user/apps/test_select"
+# 构建相关信息
+[build]
+# (可选)构建命令
+build-command = "make install -j $(nproc)"
+# 安装相关信息
+[install]
+# (可选)安装到DragonOS的路径
+in-dragonos-path = "/bin"
+# 清除相关信息
+[clean]
+# (可选)清除命令
+clean-command = "make clean"
+# (可选)依赖项
+# 注意:如果没有依赖项,忽略此项,不允许只留一个[[depends]]
+# [[depends]]
+# name = "depend1"
+# version = "0.1.1"
+# (可选)环境变量
+# 注意:如果没有环境变量,忽略此项,不允许只留一个[[envs]]
+# [[envs]]
+# key = "PATH"
+# value = "/usr/bin"