From 47b743fc9a4c572a2bdbb37b60951f433224c5da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Ccqwhfhh=E2=80=9D?= <“2503600166@qq.com”> Date: Sat, 30 May 2026 14:58:24 +0800 Subject: [PATCH 1/5] feat(starry-kernel): implement TCP_INFO sockopt --- os/StarryOS/kernel/src/syscall/net/opt.rs | 107 +++++++++++++++++++++- os/arceos/modules/axnet-ng/src/options.rs | 94 ++++++++++++++++++- os/arceos/modules/axnet-ng/src/tcp.rs | 96 ++++++++++++++++++- 3 files changed, 289 insertions(+), 8 deletions(-) diff --git a/os/StarryOS/kernel/src/syscall/net/opt.rs b/os/StarryOS/kernel/src/syscall/net/opt.rs index 9a5ed2e39b..ccc6c8e46f 100644 --- a/os/StarryOS/kernel/src/syscall/net/opt.rs +++ b/os/StarryOS/kernel/src/syscall/net/opt.rs @@ -1,6 +1,12 @@ use ax_errno::{AxError, AxResult, LinuxError}; -use axnet::options::{Configurable, GetSocketOption, SetSocketOption}; -use linux_raw_sys::net::{IPPROTO_IPV6, IPV6_V6ONLY, socklen_t}; +use axnet::options::{ + Configurable, GetSocketOption, SetSocketOption, TcpInfo, TcpInfoOptions, TcpState, +}; +use linux_raw_sys::net::{ + IPPROTO_IPV6, IPV6_V6ONLY, TCP_INFO, TCPI_OPT_ECN, TCPI_OPT_ECN_SEEN, TCPI_OPT_SACK, + TCPI_OPT_SYN_DATA, TCPI_OPT_TIMESTAMPS, TCPI_OPT_WSCALE, socklen_t, tcp_info, +}; +use starry_vm::vm_write_slice; use crate::{ file::{FileLike, Socket, netlink::NetlinkSocket}, @@ -18,6 +24,93 @@ fn read_int_sockopt(optval: UserConstPtr, optlen: socklen_t) -> AxResult().get_as_ref()?) } +fn tcp_state_to_linux(state: TcpState) -> u8 { + match state { + TcpState::Established => 1, + TcpState::SynSent => 2, + TcpState::SynReceived => 3, + TcpState::FinWait1 => 4, + TcpState::FinWait2 => 5, + TcpState::TimeWait => 6, + TcpState::Closed => 7, + TcpState::CloseWait => 8, + TcpState::LastAck => 9, + TcpState::Listen => 10, + TcpState::Closing => 11, + } +} + +fn tcp_options_to_linux(options: TcpInfoOptions) -> u8 { + let mut raw = 0u8; + if options.contains(TcpInfoOptions::TIMESTAMPS) { + raw |= TCPI_OPT_TIMESTAMPS as u8; + } + if options.contains(TcpInfoOptions::SACK) { + raw |= TCPI_OPT_SACK as u8; + } + if options.contains(TcpInfoOptions::WSCALE) { + raw |= TCPI_OPT_WSCALE as u8; + } + if options.contains(TcpInfoOptions::ECN) { + raw |= TCPI_OPT_ECN as u8; + } + if options.contains(TcpInfoOptions::ECN_SEEN) { + raw |= TCPI_OPT_ECN_SEEN as u8; + } + if options.contains(TcpInfoOptions::SYN_DATA) { + raw |= TCPI_OPT_SYN_DATA as u8; + } + raw +} + +fn to_linux_tcp_info(info: TcpInfo) -> tcp_info { + // SAFETY: linux-raw-sys tcp_info is a plain C data record. An all-zero + // value is a valid baseline before the supported fields are filled below. + let mut raw = unsafe { core::mem::zeroed::() }; + raw.tcpi_state = tcp_state_to_linux(info.state); + raw.tcpi_ca_state = info.ca_state; + raw.tcpi_retransmits = info.retransmits; + raw.tcpi_probes = info.probes; + raw.tcpi_backoff = info.backoff; + raw.tcpi_options = tcp_options_to_linux(info.options); + raw.set_tcpi_snd_wscale(info.snd_wscale); + raw.set_tcpi_rcv_wscale(info.rcv_wscale); + raw.tcpi_rto = info.rto_micros; + raw.tcpi_ato = info.ato_micros; + raw.tcpi_snd_mss = info.snd_mss; + raw.tcpi_rcv_mss = info.rcv_mss; + raw.tcpi_notsent_bytes = info.notsent_bytes; + raw.tcpi_pmtu = info.pmtu; + raw.tcpi_advmss = info.advmss; + raw.tcpi_snd_cwnd = info.snd_cwnd; + raw.tcpi_reordering = info.reordering; + raw.tcpi_rcv_space = info.rcv_space; + raw.tcpi_snd_wnd = info.snd_wnd; + raw.tcpi_rcv_wnd = info.rcv_wnd; + raw +} + +fn write_tcp_info(socket: &Socket, optval: UserPtr, optlen: &mut socklen_t) -> AxResult<()> { + let mut info = TcpInfo::default(); + socket.get_option(GetSocketOption::TcpInfo(&mut info))?; + + let write_len = (*optlen as usize).min(size_of::()); + *optlen = write_len as socklen_t; + if write_len == 0 { + return Ok(()); + } + + let raw = to_linux_tcp_info(info); + // SAFETY: raw lives for the whole copy and is viewed as its C byte layout. + let raw_bytes = unsafe { + core::slice::from_raw_parts( + (&raw as *const tcp_info).cast::(), + size_of::(), + ) + }; + Ok(vm_write_slice(optval.as_ptr(), &raw_bytes[..write_len])?) +} + mod conv { use ax_errno::{AxError, AxResult}; use axnet::options::UnixCredentials; @@ -110,7 +203,6 @@ macro_rules! call_dispatch { (PROTO_TCP, TCP_KEEPINTVL) => TcpKeepInterval as Int, (PROTO_TCP, TCP_KEEPCNT) => TcpKeepCount as Int, (PROTO_TCP, TCP_USER_TIMEOUT) => TcpUserTimeout as Int, - (PROTO_TCP, TCP_INFO) => TcpInfo, // TODO: stub, returns empty struct (PROTO_IP, IP_TTL) => Ttl as Int, (PROTO_IP, IP_RECVERR) => RecvErr as IntBool, // TODO: hardcoded false, no errqueue support @@ -198,6 +290,11 @@ pub fn sys_getsockopt( return Ok(0); } + if level == PROTO_TCP && optname == TCP_INFO { + write_tcp_info(&socket, optval, optlen)?; + return Ok(0); + } + macro_rules! dispatch { ($which:ident) => { socket.get_option(GetSocketOption::$which(get(optval, optlen)?))?; @@ -269,6 +366,10 @@ pub fn sys_setsockopt( } let socket = Socket::from_fd(fd)?; + if level == PROTO_TCP && optname == TCP_INFO { + return Err(AxError::from(LinuxError::ENOPROTOOPT)); + } + if level == IPPROTO_IPV6 as u32 && optname == IPV6_V6ONLY { // TODO: Store and enforce IPV6_V6ONLY once native IPv6 sockets exist. let _ = *get::(optval, optlen)?; diff --git a/os/arceos/modules/axnet-ng/src/options.rs b/os/arceos/modules/axnet-ng/src/options.rs index 608aa79a67..9b3e798dac 100644 --- a/os/arceos/modules/axnet-ng/src/options.rs +++ b/os/arceos/modules/axnet-ng/src/options.rs @@ -4,6 +4,98 @@ use core::time::Duration; use ax_errno::{AxError, AxResult, LinuxError}; use enum_dispatch::enum_dispatch; +/// Linux-like TCP connection state reported by TCP_INFO. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +pub enum TcpState { + /// No active TCP connection. + #[default] + Closed, + /// Passive listener. + Listen, + /// SYN sent, waiting for a matching SYN+ACK. + SynSent, + /// SYN received, waiting for the final ACK. + SynReceived, + /// Fully established connection. + Established, + /// Local endpoint has closed and sent FIN. + FinWait1, + /// Local FIN has been acknowledged. + FinWait2, + /// Remote endpoint has closed first. + CloseWait, + /// Both endpoints have closed simultaneously. + Closing, + /// Waiting for ACK of the local FIN after remote close. + LastAck, + /// Waiting for delayed packets to expire. + TimeWait, +} + +bitflags::bitflags! { + /// Negotiated TCP options reported by TCP_INFO. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] + pub struct TcpInfoOptions: u8 { + /// TCP timestamps are enabled. + const TIMESTAMPS = 1 << 0; + /// Selective ACK is enabled. + const SACK = 1 << 1; + /// Window scaling is enabled. + const WSCALE = 1 << 2; + /// Explicit congestion notification is enabled. + const ECN = 1 << 3; + /// ECN has been seen on the connection. + const ECN_SEEN = 1 << 4; + /// SYN data was used by the connection. + const SYN_DATA = 1 << 5; + } +} + +/// Transport-independent TCP_INFO snapshot. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +pub struct TcpInfo { + /// Current TCP state. + pub state: TcpState, + /// Congestion-avoidance state. Zero means open. + pub ca_state: u8, + /// Number of unacknowledged retransmits. + pub retransmits: u8, + /// Number of pending keepalive or zero-window probes. + pub probes: u8, + /// Exponential backoff counter. + pub backoff: u8, + /// Negotiated TCP options. + pub options: TcpInfoOptions, + /// Send window scale. + pub snd_wscale: u8, + /// Receive window scale. + pub rcv_wscale: u8, + /// Retransmission timeout in microseconds. + pub rto_micros: u32, + /// Delayed ACK timeout in microseconds. + pub ato_micros: u32, + /// Send maximum segment size. + pub snd_mss: u32, + /// Receive maximum segment size. + pub rcv_mss: u32, + /// Bytes currently queued for transmit. + pub notsent_bytes: u32, + /// Advertised path MTU. + pub pmtu: u32, + /// Advertised maximum segment size. + pub advmss: u32, + /// Current send congestion window, in segments. + pub snd_cwnd: u32, + /// Packet reordering tolerance. + pub reordering: u32, + /// Available receive buffer space. + pub rcv_space: u32, + /// Current send window estimate. + pub snd_wnd: u32, + /// Current receive window estimate. + pub rcv_wnd: u32, +} + macro_rules! define_options { ($($name:ident($value:ty),)*) => { /// Operation to get a socket option. @@ -75,7 +167,7 @@ define_options! { TcpKeepInterval(u32), TcpKeepCount(u32), TcpUserTimeout(u32), - TcpInfo(()), + TcpInfo(TcpInfo), // ---- IP level options (IP_*) ---- Ttl(u8), diff --git a/os/arceos/modules/axnet-ng/src/tcp.rs b/os/arceos/modules/axnet-ng/src/tcp.rs index faf5284846..15763622da 100644 --- a/os/arceos/modules/axnet-ng/src/tcp.rs +++ b/os/arceos/modules/axnet-ng/src/tcp.rs @@ -24,7 +24,7 @@ use crate::{ consts::{TCP_RX_BUF_LEN, TCP_TX_BUF_LEN}, general::GeneralOptions, get_service, - options::{Configurable, GetSocketOption, SetSocketOption}, + options::{Configurable, GetSocketOption, SetSocketOption, TcpInfo, TcpInfoOptions, TcpState}, poll_interfaces, state::*, }; @@ -43,6 +43,10 @@ const TCP_USER_TIMEOUT_DEFAULT_MS: u32 = 0; const TCP_KEEPIDLE_MAX_SECS: u32 = 32767; const TCP_KEEPINTVL_MAX_SECS: u32 = 32767; const TCP_KEEPCNT_MAX: u32 = 127; +const TCP_INFO_DEFAULT_MSS: u32 = 1460; +const TCP_INFO_DEFAULT_PMTU: u32 = 1500; +const TCP_INFO_INITIAL_RTO_MICROS: u32 = 1_000_000; +const TCP_INFO_DEFAULT_REORDERING: u32 = 3; /// A TCP socket that provides POSIX-like APIs. pub struct TcpSocket { @@ -141,6 +145,45 @@ impl TcpSocket { Duration::from_secs(self.keep_idle_secs.load(Ordering::Relaxed) as u64) } + fn tcp_info_snapshot(&self) -> TcpInfo { + self.with_smol_socket(|socket| { + let send_capacity = saturating_u32(socket.send_capacity()); + let send_queue = saturating_u32(socket.send_queue()); + let recv_capacity = saturating_u32(socket.recv_capacity()); + let recv_queue = saturating_u32(socket.recv_queue()); + let send_space = send_capacity.saturating_sub(send_queue); + let recv_space = recv_capacity.saturating_sub(recv_queue); + let snd_mss = TCP_INFO_DEFAULT_MSS; + let cwnd_segments = send_capacity.div_ceil(snd_mss).max(1); + + let mut options = TcpInfoOptions::empty(); + if socket.timestamp_enabled() { + options |= TcpInfoOptions::TIMESTAMPS; + } + + TcpInfo { + state: tcp_state_info(socket.state()), + options, + rto_micros: socket + .timeout() + .map(duration_micros_u32) + .unwrap_or(TCP_INFO_INITIAL_RTO_MICROS), + ato_micros: socket.ack_delay().map(duration_micros_u32).unwrap_or(0), + snd_mss, + rcv_mss: snd_mss, + notsent_bytes: send_queue, + pmtu: TCP_INFO_DEFAULT_PMTU, + advmss: snd_mss, + snd_cwnd: cwnd_segments, + reordering: TCP_INFO_DEFAULT_REORDERING, + rcv_space: recv_space, + snd_wnd: send_space, + rcv_wnd: recv_space, + ..Default::default() + } + }) + } + fn bound_endpoint(&self) -> AxResult { let endpoint = *self.bound_endpoint.lock(); if endpoint.port == 0 { @@ -266,8 +309,8 @@ impl Configurable for TcpSocket { O::ReceiveBuffer(size) => { **size = TCP_RX_BUF_LEN; } - O::TcpInfo(_) => { - // TODO(mivik): implement TCP_INFO + O::TcpInfo(info) => { + **info = self.tcp_info_snapshot(); } _ => return Ok(false), } @@ -629,6 +672,30 @@ impl Drop for TcpSocket { } } +fn saturating_u32(value: usize) -> u32 { + value.min(u32::MAX as usize) as u32 +} + +fn duration_micros_u32(value: Duration) -> u32 { + value.total_micros().min(u32::MAX as u64) as u32 +} + +fn tcp_state_info(state: smol::State) -> TcpState { + match state { + smol::State::Closed => TcpState::Closed, + smol::State::Listen => TcpState::Listen, + smol::State::SynSent => TcpState::SynSent, + smol::State::SynReceived => TcpState::SynReceived, + smol::State::Established => TcpState::Established, + smol::State::FinWait1 => TcpState::FinWait1, + smol::State::FinWait2 => TcpState::FinWait2, + smol::State::CloseWait => TcpState::CloseWait, + smol::State::Closing => TcpState::Closing, + smol::State::LastAck => TcpState::LastAck, + smol::State::TimeWait => TcpState::TimeWait, + } +} + const fn empty_endpoint() -> IpListenEndpoint { IpListenEndpoint { addr: None, @@ -800,10 +867,31 @@ mod tests { use super::*; use crate::{ - options::{Configurable, SetSocketOption}, + options::{Configurable, GetSocketOption, SetSocketOption, TcpState}, test_support::{LOCAL_ADDR, LOCAL_MASK, PEER_ADDR, PEER_MASK, init_split_route_network}, }; + #[test] + fn tcp_info_reports_default_socket_metrics() { + init_split_route_network(); + + let socket = TcpSocket::new(); + let mut info = TcpInfo::default(); + + socket + .get_option(GetSocketOption::TcpInfo(&mut info)) + .unwrap(); + + assert_eq!(info.state, TcpState::Closed); + assert_eq!(info.snd_mss, TCP_INFO_DEFAULT_MSS); + assert_eq!(info.rcv_mss, TCP_INFO_DEFAULT_MSS); + assert_eq!(info.pmtu, TCP_INFO_DEFAULT_PMTU); + assert_eq!(info.notsent_bytes, 0); + assert_eq!(info.snd_wnd, saturating_u32(TCP_TX_BUF_LEN)); + assert_eq!(info.rcv_space, saturating_u32(TCP_RX_BUF_LEN)); + assert_eq!(info.rcv_wnd, saturating_u32(TCP_RX_BUF_LEN)); + } + #[test] fn connect_uses_peer_route_for_device_mask() { init_split_route_network(); From e8594acb0ab85c1f0577de4f69863f3a3ef9fddb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Ccqwhfhh=E2=80=9D?= <“2503600166@qq.com”> Date: Sat, 30 May 2026 15:54:32 +0800 Subject: [PATCH 2/5] fix(axtask): keep previous task alive during switch --- os/arceos/modules/axtask/src/run_queue.rs | 28 ++++++++++------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/os/arceos/modules/axtask/src/run_queue.rs b/os/arceos/modules/axtask/src/run_queue.rs index cc203e4512..53a0a71df7 100644 --- a/os/arceos/modules/axtask/src/run_queue.rs +++ b/os/arceos/modules/axtask/src/run_queue.rs @@ -1,7 +1,5 @@ use alloc::{collections::VecDeque, sync::Arc}; use core::mem::MaybeUninit; -#[cfg(feature = "smp")] -use core::ptr::NonNull; use ax_hal::percpu::this_cpu_id; use ax_kernel_guard::BaseGuard; @@ -34,12 +32,13 @@ percpu_static! { EXITED_TASKS: VecDeque = VecDeque::new(), WAIT_FOR_EXIT: WaitQueue = WaitQueue::new(), IDLE_TASK: LazyInit = LazyInit::new(), - /// Stores a raw pointer to the previous task running on this CPU. - /// The pointer is valid only within the window between `switch_to` storing it - /// and `clear_prev_task_on_cpu` consuming it — both in the same non-preemptible - /// call chain, so the task cannot be freed while the pointer is held. + /// Keeps the previous task alive while this CPU is completing a context switch. + /// + /// The slot is set before `CurrentTask::set_current()` drops the current + /// percpu strong reference, and is consumed by the next task when it clears + /// the previous task's `on_cpu` flag. #[cfg(feature = "smp")] - PREV_TASK: Option> = None, + PREV_TASK: Option = None, } /// An array of references to run queues, one for each CPU, indexed by cpu_id. @@ -702,13 +701,12 @@ impl AxRunQueue { let prev_ctx_ptr = prev_task.ctx_mut_ptr(); let next_ctx_ptr = next_task.ctx_mut_ptr(); - // Store a raw pointer to prev_task in PREV_TASK. - // Safety: prev_task is alive (Arc held on caller's stack) and will - // remain so through clear_prev_task_on_cpu() below. + // Keep prev_task alive after set_current() drops the old percpu + // current-task reference. The next running task will consume this + // slot in clear_prev_task_on_cpu(). #[cfg(feature = "smp")] { - *PREV_TASK.current_ref_mut_raw() = - Some(NonNull::new(Arc::as_ptr(&prev_task) as *mut _).unwrap()); + *PREV_TASK.current_ref_mut_raw() = Some(prev_task.clone()); } // The strong reference count of `prev_task` will be decremented by 1, @@ -783,12 +781,10 @@ pub(crate) fn migrate_entry(migrated_task: AxTaskRef) { /// Clear the `on_cpu` field of previous task running on this CPU. #[cfg(feature = "smp")] pub(crate) unsafe fn clear_prev_task_on_cpu() { - let prev = unsafe { PREV_TASK.current_ref_mut_raw() } + let prev_task = unsafe { PREV_TASK.current_ref_mut_raw() } .take() .expect("PREV_TASK should have been set by switch_to"); - // Safety: prev_task's Arc is still alive on the caller's stack at this point - // (switch_to has not yet returned), so the pointer is valid. - unsafe { prev.as_ref() }.set_on_cpu(false); + prev_task.set_on_cpu(false); } pub(crate) fn init() { let cpu_id = this_cpu_id(); From c63e509b788ad288d4a2818150bd6430b8bcefe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Ccqwhfhh=E2=80=9D?= <“2503600166@qq.com”> Date: Sat, 30 May 2026 20:26:10 +0800 Subject: [PATCH 3/5] test(starryos): autorun dup2 qemu case --- .../starryos/normal/qemu-smp1/test-dup2/c/CMakeLists.txt | 1 + .../test-dup2/c/profile.d/99-test-dup2-autorun.sh | 9 +++++++++ 2 files changed, 10 insertions(+) create mode 100644 test-suit/starryos/normal/qemu-smp1/test-dup2/c/profile.d/99-test-dup2-autorun.sh diff --git a/test-suit/starryos/normal/qemu-smp1/test-dup2/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/test-dup2/c/CMakeLists.txt index 2c5f328ae9..4a2dcf6edb 100644 --- a/test-suit/starryos/normal/qemu-smp1/test-dup2/c/CMakeLists.txt +++ b/test-suit/starryos/normal/qemu-smp1/test-dup2/c/CMakeLists.txt @@ -7,3 +7,4 @@ add_executable(test-dup2 src/main.c) target_include_directories(test-dup2 PRIVATE src) target_compile_options(test-dup2 PRIVATE -Wall -Wextra -Werror) install(TARGETS test-dup2 RUNTIME DESTINATION usr/bin) +install(PROGRAMS profile.d/99-test-dup2-autorun.sh DESTINATION etc/profile.d) diff --git a/test-suit/starryos/normal/qemu-smp1/test-dup2/c/profile.d/99-test-dup2-autorun.sh b/test-suit/starryos/normal/qemu-smp1/test-dup2/c/profile.d/99-test-dup2-autorun.sh new file mode 100644 index 0000000000..120145c02f --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-dup2/c/profile.d/99-test-dup2-autorun.sh @@ -0,0 +1,9 @@ +if [ "${AXBUILD_TEST_DUP2_AUTORUN_DONE:-0}" = "1" ]; then + return 0 2>/dev/null || exit 0 +fi + +export AXBUILD_TEST_DUP2_AUTORUN_DONE=1 + +if [ -x /usr/bin/test-dup2 ]; then + /usr/bin/test-dup2 +fi From 48990298db95554c910fee2dba3773bb0b5ea64b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Ccqwhfhh=E2=80=9D?= <“2503600166@qq.com”> Date: Sat, 30 May 2026 20:45:36 +0800 Subject: [PATCH 4/5] test(starryos): relax apk curl fetch timeout --- .../normal/qemu-smp1/apk-curl/qemu-aarch64.toml | 17 ++++++++++------- .../qemu-smp1/apk-curl/qemu-loongarch64.toml | 17 ++++++++++------- .../normal/qemu-smp1/apk-curl/qemu-riscv64.toml | 17 ++++++++++------- .../normal/qemu-smp1/apk-curl/qemu-x86_64.toml | 17 ++++++++++------- 4 files changed, 40 insertions(+), 28 deletions(-) diff --git a/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-aarch64.toml b/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-aarch64.toml index fa435061c3..91ce6629c5 100644 --- a/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-aarch64.toml +++ b/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-aarch64.toml @@ -17,13 +17,14 @@ uefi = false to_bin = true shell_prefix = "root@starry:" shell_init_cmd = ''' -fetch_timeout=60 +fetch_timeout=180 repo_file=/etc/apk/repositories original_repos="$(cat "$repo_file")" try_apk_curl() { mirror="$1" label="$2" + fetch_timeout="$3" probe_url="$mirror/MIRRORS.txt" printf '%s\n' "$original_repos" | \ sed "s#http://[^/]*/alpine/#$mirror/#g;s#https://[^/]*/alpine/#$mirror/#g" > "$repo_file" @@ -43,14 +44,16 @@ try_apk_curl() { i=0 for repo in \ - "https://mirrors.cernet.edu.cn/alpine cernet" \ - "https://dl-cdn.alpinelinux.org/alpine upstream" + "https://mirrors.cernet.edu.cn/alpine cernet 60" \ + "https://dl-cdn.alpinelinux.org/alpine upstream $fetch_timeout" do i=$((i + 1)) - mirror=${repo% *} - label=${repo#* } + set -- $repo + mirror=$1 + label=$2 + fetch_timeout=$3 echo "APK_CURL_ATTEMPT_$i" - if try_apk_curl "$mirror" "$label"; then + if try_apk_curl "$mirror" "$label" "$fetch_timeout"; then echo 'APK_CURL_TEST_PASSED' exit 0 fi @@ -60,4 +63,4 @@ echo 'APK_CURL_TEST_FAILED' ''' success_regex = ["(?m)^APK_CURL_TEST_PASSED\\s*$"] fail_regex = ['(?i)\bpanic(?:ked)?\b', "(?m)^lockdep fatal violation\\s*$", "(?m)^APK_CURL_TEST_FAILED\\s*$"] -timeout = 420 +timeout = 720 diff --git a/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-loongarch64.toml b/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-loongarch64.toml index 9186c9d732..3afab59525 100644 --- a/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-loongarch64.toml +++ b/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-loongarch64.toml @@ -19,13 +19,14 @@ to_bin = true uefi = false shell_prefix = "root@starry:" shell_init_cmd = ''' -fetch_timeout=60 +fetch_timeout=180 repo_file=/etc/apk/repositories original_repos="$(cat "$repo_file")" try_apk_curl() { mirror="$1" label="$2" + fetch_timeout="$3" probe_url="$mirror/MIRRORS.txt" printf '%s\n' "$original_repos" | \ sed "s#http://[^/]*/alpine/#$mirror/#g;s#https://[^/]*/alpine/#$mirror/#g" > "$repo_file" @@ -45,14 +46,16 @@ try_apk_curl() { i=0 for repo in \ - "https://mirrors.cernet.edu.cn/alpine cernet" \ - "https://dl-cdn.alpinelinux.org/alpine upstream" + "https://mirrors.cernet.edu.cn/alpine cernet 60" \ + "https://dl-cdn.alpinelinux.org/alpine upstream $fetch_timeout" do i=$((i + 1)) - mirror=${repo% *} - label=${repo#* } + set -- $repo + mirror=$1 + label=$2 + fetch_timeout=$3 echo "APK_CURL_ATTEMPT_$i" - if try_apk_curl "$mirror" "$label"; then + if try_apk_curl "$mirror" "$label" "$fetch_timeout"; then echo 'APK_CURL_TEST_PASSED' exit 0 fi @@ -62,4 +65,4 @@ echo 'APK_CURL_TEST_FAILED' ''' success_regex = ["(?m)^APK_CURL_TEST_PASSED\\s*$"] fail_regex = ['(?i)\bpanic(?:ked)?\b', "(?m)^lockdep fatal violation\\s*$", "(?m)^APK_CURL_TEST_FAILED\\s*$"] -timeout = 420 +timeout = 720 diff --git a/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-riscv64.toml b/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-riscv64.toml index b7e1379b10..a67299ec08 100644 --- a/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-riscv64.toml +++ b/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-riscv64.toml @@ -17,13 +17,14 @@ uefi = false to_bin = true shell_prefix = "root@starry:" shell_init_cmd = ''' -fetch_timeout=60 +fetch_timeout=180 repo_file=/etc/apk/repositories original_repos="$(cat "$repo_file")" try_apk_curl() { mirror="$1" label="$2" + fetch_timeout="$3" probe_url="$mirror/MIRRORS.txt" printf '%s\n' "$original_repos" | \ sed "s#http://[^/]*/alpine/#$mirror/#g;s#https://[^/]*/alpine/#$mirror/#g" > "$repo_file" @@ -43,14 +44,16 @@ try_apk_curl() { i=0 for repo in \ - "https://mirrors.cernet.edu.cn/alpine cernet" \ - "https://dl-cdn.alpinelinux.org/alpine upstream" + "https://mirrors.cernet.edu.cn/alpine cernet 60" \ + "https://dl-cdn.alpinelinux.org/alpine upstream $fetch_timeout" do i=$((i + 1)) - mirror=${repo% *} - label=${repo#* } + set -- $repo + mirror=$1 + label=$2 + fetch_timeout=$3 echo "APK_CURL_ATTEMPT_$i" - if try_apk_curl "$mirror" "$label"; then + if try_apk_curl "$mirror" "$label" "$fetch_timeout"; then echo 'APK_CURL_TEST_PASSED' exit 0 fi @@ -60,4 +63,4 @@ echo 'APK_CURL_TEST_FAILED' ''' success_regex = ["(?m)^APK_CURL_TEST_PASSED\\s*$"] fail_regex = ['(?i)\bpanic(?:ked)?\b', "(?m)^lockdep fatal violation\\s*$", "(?m)^APK_CURL_TEST_FAILED\\s*$"] -timeout = 420 +timeout = 720 diff --git a/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-x86_64.toml b/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-x86_64.toml index e80188d877..abc5c41b94 100644 --- a/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-x86_64.toml +++ b/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-x86_64.toml @@ -17,13 +17,14 @@ uefi = false to_bin = false shell_prefix = "root@starry:" shell_init_cmd = ''' -fetch_timeout=60 +fetch_timeout=180 repo_file=/etc/apk/repositories original_repos="$(cat "$repo_file")" try_apk_curl() { mirror="$1" label="$2" + fetch_timeout="$3" probe_url="$mirror/MIRRORS.txt" printf '%s\n' "$original_repos" | \ sed "s#http://[^/]*/alpine/#$mirror/#g;s#https://[^/]*/alpine/#$mirror/#g" > "$repo_file" @@ -43,14 +44,16 @@ try_apk_curl() { i=0 for repo in \ - "https://mirrors.cernet.edu.cn/alpine cernet" \ - "https://dl-cdn.alpinelinux.org/alpine upstream" + "https://mirrors.cernet.edu.cn/alpine cernet 60" \ + "https://dl-cdn.alpinelinux.org/alpine upstream $fetch_timeout" do i=$((i + 1)) - mirror=${repo% *} - label=${repo#* } + set -- $repo + mirror=$1 + label=$2 + fetch_timeout=$3 echo "APK_CURL_ATTEMPT_$i" - if try_apk_curl "$mirror" "$label"; then + if try_apk_curl "$mirror" "$label" "$fetch_timeout"; then echo 'APK_CURL_TEST_PASSED' exit 0 fi @@ -60,4 +63,4 @@ echo 'APK_CURL_TEST_FAILED' ''' success_regex = ["(?m)^APK_CURL_TEST_PASSED\\s*$"] fail_regex = ['(?i)\bpanic(?:ked)?\b', "(?m)^lockdep fatal violation\\s*$", "(?m)^APK_CURL_TEST_FAILED\\s*$"] -timeout = 420 +timeout = 720 From 684210986b0e51d0c62f37cce9104e76b2bbceb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Ccqwhfhh=E2=80=9D?= <“2503600166@qq.com”> Date: Wed, 3 Jun 2026 10:35:20 +0800 Subject: [PATCH 5/5] fix(axtask): restore raw previous-task handoff --- os/arceos/modules/axtask/src/run_queue.rs | 28 +++++++++++++---------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/os/arceos/modules/axtask/src/run_queue.rs b/os/arceos/modules/axtask/src/run_queue.rs index c100db5f00..36dc37456c 100644 --- a/os/arceos/modules/axtask/src/run_queue.rs +++ b/os/arceos/modules/axtask/src/run_queue.rs @@ -1,5 +1,7 @@ use alloc::{collections::VecDeque, sync::Arc}; use core::mem::MaybeUninit; +#[cfg(feature = "smp")] +use core::ptr::NonNull; use ax_hal::percpu::this_cpu_id; use ax_kernel_guard::BaseGuard; @@ -32,13 +34,12 @@ percpu_static! { EXITED_TASKS: VecDeque = VecDeque::new(), WAIT_FOR_EXIT: WaitQueue = WaitQueue::new(), IDLE_TASK: LazyInit = LazyInit::new(), - /// Keeps the previous task alive while this CPU is completing a context switch. - /// - /// The slot is set before `CurrentTask::set_current()` drops the current - /// percpu strong reference, and is consumed by the next task when it clears - /// the previous task's `on_cpu` flag. + /// Stores a raw pointer to the previous task running on this CPU. + /// The pointer is valid only within the window between `switch_to` storing it + /// and `clear_prev_task_on_cpu` consuming it — both in the same non-preemptible + /// call chain, so the task cannot be freed while the pointer is held. #[cfg(feature = "smp")] - PREV_TASK: Option = None, + PREV_TASK: Option> = None, } /// An array of references to run queues, one for each CPU, indexed by cpu_id. @@ -707,12 +708,13 @@ impl AxRunQueue { let prev_ctx_ptr = prev_task.ctx_mut_ptr(); let next_ctx_ptr = next_task.ctx_mut_ptr(); - // Keep prev_task alive after set_current() drops the old percpu - // current-task reference. The next running task will consume this - // slot in clear_prev_task_on_cpu(). + // Store a raw pointer to prev_task in PREV_TASK. + // Safety: prev_task is alive (Arc held on caller's stack) and will + // remain so through clear_prev_task_on_cpu() below. #[cfg(feature = "smp")] { - *PREV_TASK.current_ref_mut_raw() = Some(prev_task.clone()); + *PREV_TASK.current_ref_mut_raw() = + Some(NonNull::new(Arc::as_ptr(&prev_task) as *mut _).unwrap()); } // The strong reference count of `prev_task` will be decremented by 1, @@ -787,10 +789,12 @@ pub(crate) fn migrate_entry(migrated_task: AxTaskRef) { /// Clear the `on_cpu` field of previous task running on this CPU. #[cfg(feature = "smp")] pub(crate) unsafe fn clear_prev_task_on_cpu() { - let prev_task = unsafe { PREV_TASK.current_ref_mut_raw() } + let prev = unsafe { PREV_TASK.current_ref_mut_raw() } .take() .expect("PREV_TASK should have been set by switch_to"); - prev_task.set_on_cpu(false); + // Safety: prev_task's Arc is still alive on the caller's stack at this point + // (switch_to has not yet returned), so the pointer is valid. + unsafe { prev.as_ref() }.set_on_cpu(false); } pub(crate) fn init() { let cpu_id = this_cpu_id();