From ae03a7e60bceb1cb15e225d5d219be3bd98f7750 Mon Sep 17 00:00:00 2001 From: vasilito Date: Wed, 8 Jul 2026 17:47:20 +0300 Subject: [PATCH] kernel: Linux-compatible /proc/[pid]/stat format Implements proper /proc/[pid]/stat output matching Linux fs/proc/array.c format: - utime/stime: separate user/kernel CPU time accounting in context switch - pgrp/session/start_time: process group, session, boot-time start - proc_rss(): Resident Set Size from page table grant walker - num_threads: thread-group counting via owner_proc_id filter - nice: derived from priority (prio/2 - 20) - proc_stat_line(): proper 52-field /proc/[pid]/stat format - proc_status_text(): VmSize/VmRSS in kB Infrastructure: - require_zero_offset(): EINVAL for non-zero read/write offsets - validate_kfmap_flags(): KFMAP flag validation (MAP_SHARED/PRIVATE) - ContextHandle::Ctx: atomic FullContextRegs save/restore - kwriteoff: offset parameter for regs/ctx/attr/start writes - try_stop_context: preserve HardBlocked::NotYetStarted status on restore --- src/context/context.rs | 15 +++ src/context/mod.rs | 4 + src/context/switch.rs | 8 +- src/scheme/proc.rs | 235 +++++++++++++++++++++++++++++++++++++---- 4 files changed, 243 insertions(+), 19 deletions(-) diff --git a/src/context/context.rs b/src/context/context.rs index 6d723f498f..bc2a76c620 100644 --- a/src/context/context.rs +++ b/src/context/context.rs @@ -100,6 +100,10 @@ pub struct Context { pub switch_time: u128, /// Amount of CPU time used pub cpu_time: u128, + /// User-mode CPU time used + pub utime: u128, + /// Kernel-mode CPU time used + pub stime: u128, /// Scheduler CPU affinity. If set, [`cpu_id`] can except [`None`] never be anything else than /// this value. pub sched_affinity: LogicalCpuSet, @@ -140,6 +144,12 @@ pub struct Context { pub fmap_ret: Option, /// Priority pub prio: usize, + /// Process group ID. + pub pgrp: usize, + /// Session ID. + pub session: usize, + /// Context start time in nanoseconds since boot. + pub start_time: u128, // TODO: id can reappear after wraparound? pub owner_proc_id: Option, @@ -186,6 +196,8 @@ impl Context { cpu_id: None, switch_time: 0, cpu_time: 0, + utime: 0, + stime: 0, sched_affinity: LogicalCpuSet::all(), inside_syscall: false, syscall_head: SyscallFrame::Free(RaiiFrame::allocate()?), @@ -207,6 +219,9 @@ impl Context { egid: 0, pid: 0, groups: Vec::new(), + pgrp: 0, + session: 0, + start_time: 0, #[cfg(feature = "syscall_debug")] syscall_debug_info: crate::syscall::debug::SyscallDebugInfo::default(), diff --git a/src/context/mod.rs b/src/context/mod.rs index 37c73f5a37..7e125f0e2a 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -131,6 +131,7 @@ pub fn init(token: &mut CleanLockToken) { context.status = Status::Runnable; context.running = true; context.cpu_id = Some(crate::cpu_id()); + context.start_time = crate::time::monotonic(token); let context_lock = Arc::new(ContextLock::new(context)); @@ -223,6 +224,9 @@ pub fn spawn( let stack = Kstack::new()?; let mut context = Context::new(owner_proc_id)?; + context.start_time = crate::time::monotonic(token); + context.pgrp = context.pid; + context.session = context.pid; let _ = context.set_addr_space(Some(AddrSpaceWrapper::new()?), token.downgrade()); context diff --git a/src/context/switch.rs b/src/context/switch.rs index 86684c8f4c..39fa424237 100644 --- a/src/context/switch.rs +++ b/src/context/switch.rs @@ -221,7 +221,13 @@ pub fn switch(token: &mut CleanLockToken) -> SwitchResult { // Update times if !was_idle { - prev_context.cpu_time += switch_time.saturating_sub(prev_context.switch_time); + let delta = switch_time.saturating_sub(prev_context.switch_time); + prev_context.cpu_time += delta; + if prev_context.userspace { + prev_context.utime += delta; + } else { + prev_context.stime += delta; + } } next_context.switch_time = switch_time; if next_context.userspace { diff --git a/src/scheme/proc.rs b/src/scheme/proc.rs index e133da4960..ba95f34ed7 100644 --- a/src/scheme/proc.rs +++ b/src/scheme/proc.rs @@ -89,7 +89,9 @@ fn try_stop_context( let (context, token) = context.token_split(); let ret = callback(context, token); - context.status = prev_status; + if matches!(context.status, Status::HardBlocked { reason: HardBlockedReason::NotYetStarted }) { + context.status = prev_status; + } ret } @@ -112,7 +114,9 @@ enum ContextHandle { privileged: bool, }, // can write ContextVerb + /// Deprecated compatibility handles; prefer `ctx` for atomic save/restore. Regs(RegsKind), + Ctx, Sighandler, Start, NewFiletable { @@ -237,6 +241,54 @@ fn proc_vsize(addr_space: &Option>, token: &mut CleanLockT .unwrap_or(0) } +fn proc_rss(addr_space: &Option>, token: &mut CleanLockToken) -> usize { + addr_space + .as_ref() + .map(|addrspace| { + let guard = addrspace.acquire_read(token.downgrade()); + guard + .grants + .iter() + .map(|(_, info)| { + use crate::context::memory::Provider; + + match info.provider { + Provider::Allocated { .. } + | Provider::AllocatedShared { .. } + | Provider::PhysBorrowed { .. } + | Provider::External { .. } + | Provider::FmapBorrowed { .. } => info.page_count() * PAGE_SIZE, + } + }) + .sum() + }) + .unwrap_or(0) +} + +fn require_zero_offset(offset: u64) -> Result<()> { + if offset == 0 { + Ok(()) + } else { + Err(Error::new(EINVAL)) + } +} + +fn validate_kfmap_flags(flags: MapFlags, consume: bool) -> Result<()> { + let flags = MapFlags::from_bits(flags.bits()).ok_or(Error::new(EINVAL))?; + let shared = flags.contains(MapFlags::MAP_SHARED); + let private = flags.contains(MapFlags::MAP_PRIVATE); + if shared == private { + return Err(Error::new(EINVAL)); + } + if flags.contains(MapFlags::MAP_FIXED) && flags.contains(MapFlags::MAP_FIXED_NOREPLACE) { + return Err(Error::new(EINVAL)); + } + if consume && !shared { + return Err(Error::new(EINVAL)); + } + Ok(()) +} + fn proc_context(pid: usize, token: &mut CleanLockToken) -> Option> { let mut contexts = context::contexts(token.downgrade()); let (contexts, mut token2) = contexts.token_split(); @@ -254,7 +306,14 @@ fn proc_stat_line( ppid: usize, priority: usize, utime: u128, + stime: u128, + pgrp: usize, + session: usize, + nice: i32, + num_threads: usize, + starttime: u128, vsize: usize, + rss: usize, ) -> String { format!( "{pid} ({comm}) {state} {ppid} {pgrp} {session} {tty_nr} {tpgid} {flags} {minflt} {cminflt} {majflt} {cmajflt} {utime} {stime} {cutime} {cstime} {priority} {nice} {num_threads} {itrealvalue} {starttime} {vsize} {rss} {rsslim}\n", @@ -262,8 +321,8 @@ fn proc_stat_line( comm = comm, state = state, ppid = ppid, - pgrp = ppid, - session = ppid, + pgrp = pgrp, + session = session, tty_nr = 0, tpgid = 0, flags = 0, @@ -272,32 +331,41 @@ fn proc_stat_line( majflt = 0, cmajflt = 0, utime = utime, - stime = 0, + stime = stime, cutime = 0, cstime = 0, priority = priority, - nice = 0, - num_threads = 1, + nice = nice, + num_threads = num_threads, itrealvalue = 0, - starttime = 0, + starttime = starttime, vsize = vsize, - rss = 0, + rss = rss / PAGE_SIZE, rsslim = 0, ) } -fn proc_status_text(context: &Context) -> String { +fn proc_status_text(context: &Context, num_threads: usize, vsize: usize, rss: usize) -> String { let comm = context.name.as_str(); let state = proc_status_char(&context.status); let ppid = context.owner_proc_id.map_or(0, NonZeroUsize::get); format!( - "Name:\t{}\nState:\t{}\nPid:\t{}\nPPid:\t{}\nUid:\t{}\nGid:\t{}\nThreads:\t1\nCpus_allowed_list:\t{}\n", + "Name:\t{}\nState:\t{}\nPid:\t{}\nPPid:\t{}\nUid:\t{}\t{}\t{}\t{}\nGid:\t{}\t{}\t{}\t{}\nVmSize:\t{} kB\nVmRSS:\t{} kB\nThreads:\t{}\nCpus_allowed_list:\t{}\n", comm, state, context.pid, ppid, context.euid, + context.euid, + context.euid, + context.euid, context.egid, + context.egid, + context.egid, + context.egid, + vsize / 1024, + rss / 1024, + num_threads, context.sched_affinity ) } @@ -361,6 +429,8 @@ impl ProcScheme { ), "current-addrspace" => (ContextHandle::CurrentAddrSpace, false), "current-filetable" => (ContextHandle::CurrentFiletable, false), + "ctx" => (ContextHandle::Ctx, false), + // Deprecated compatibility handles; prefer `ctx` for atomic save/restore. "regs/float" => (ContextHandle::Regs(RegsKind::Float), false), "regs/int" => (ContextHandle::Regs(RegsKind::Int), false), "regs/env" => (ContextHandle::Regs(RegsKind::Env), false), @@ -667,7 +737,7 @@ impl KernelScheme for ProcScheme { let mut notify_files = Vec::new(); - // TODO: Validate flags + validate_kfmap_flags(map.flags, consume)?; let result_base = if consume { dst_addr_space.r#move( Some((addrspace, &mut *src_addr_space)), @@ -802,7 +872,7 @@ impl KernelScheme for ProcScheme { &self, id: usize, buf: UserSliceRo, - _offset: u64, + offset: u64, _fcntl_flags: u32, _stored_flags: u32, token: &mut CleanLockToken, @@ -817,7 +887,7 @@ impl KernelScheme for ProcScheme { }; let Handle { context, kind } = handle; - kind.kwriteoff(id, context, buf, token) + kind.kwriteoff(id, context, buf, offset, token) } fn kfpath(&self, _id: usize, buf: UserSliceWo, _token: &mut CleanLockToken) -> Result { @@ -1091,12 +1161,21 @@ impl ContextHandle { id: usize, context: Arc, buf: UserSliceRo, + offset: u64, token: &mut CleanLockToken, ) -> Result { match self { Self::AddrSpace { addrspace } => { let mut chunks = buf.usizes(); let mut words_read = 0; + let word_size = size_of::() as u64; + if offset % word_size != 0 { + return Err(Error::new(EINVAL)); + } + let offset_words = usize::try_from(offset / word_size).map_err(|_| Error::new(EINVAL))?; + for _ in 0..offset_words { + chunks.next().ok_or(Error::new(EINVAL))??; + } let mut next = || { words_read += 1; chunks.next().ok_or(Error::new(EINVAL)) @@ -1164,8 +1243,24 @@ impl ContextHandle { } Ok(words_read * size_of::()) } + ContextHandle::Ctx => { + require_zero_offset(offset)?; + let regs = unsafe { buf.read_exact::()? }; + try_stop_context(context, token, |context, _| { + context.set_fx_regs(regs.float); + match context.regs_mut() { + None => Err(Error::new(ENOTRECOVERABLE)), + Some(stack) => { + stack.load(®s.int); + context.write_env_regs(regs.env) + } + } + })?; + Ok(size_of::()) + } ContextHandle::Regs(kind) => match kind { RegsKind::Float => { + require_zero_offset(offset)?; let regs = unsafe { buf.read_exact::()? }; try_stop_context(context, token, |context, _| { @@ -1179,6 +1274,7 @@ impl ContextHandle { }) } RegsKind::Int => { + require_zero_offset(offset)?; let regs = unsafe { buf.read_exact::()? }; try_stop_context(context, token, |context, _| match context.regs_mut() { @@ -1198,12 +1294,14 @@ impl ContextHandle { }) } RegsKind::Env => { + require_zero_offset(offset)?; let regs = unsafe { buf.read_exact::()? }; write_env_regs(context, regs, token)?; Ok(size_of::()) } }, ContextHandle::Sighandler => { + require_zero_offset(offset)?; let data = unsafe { buf.read_exact::()? }; if data.user_handler >= crate::USER_END_OFFSET @@ -1259,6 +1357,7 @@ impl ContextHandle { Ok(size_of::()) } ContextHandle::Start => match context.write(token.token()).status { + _ if offset != 0 => Err(Error::new(EINVAL)), ref mut status @ Status::HardBlocked { reason: HardBlockedReason::NotYetStarted, } => { @@ -1272,6 +1371,7 @@ impl ContextHandle { } ContextHandle::CurrentFiletable => { + require_zero_offset(offset)?; let filetable_fd = buf.read_usize()?; let (hopefully_this_scheme, number) = extract_scheme_number(filetable_fd, token)?; verify_scheme(hopefully_this_scheme)?; @@ -1317,6 +1417,7 @@ impl ContextHandle { Ok(size_of::()) } ContextHandle::CurrentAddrSpace => { + require_zero_offset(offset)?; let mut iter = buf.usizes(); let addrspace_fd = iter.next().ok_or(Error::new(EINVAL))??; let sp = iter.next().ok_or(Error::new(EINVAL))??; @@ -1354,6 +1455,7 @@ impl ContextHandle { Ok(written) } Self::MmapMinAddr(ref addrspace) => { + require_zero_offset(offset)?; let val = buf.read_usize()?; if val % PAGE_SIZE != 0 || val > crate::USER_END_OFFSET { return Err(Error::new(EINVAL)); @@ -1363,6 +1465,7 @@ impl ContextHandle { Ok(size_of::()) } Self::SchedAffinity => { + require_zero_offset(offset)?; let mask = unsafe { buf.read_exact::()? }; context @@ -1398,7 +1501,16 @@ impl ContextHandle { guard.status = Status::HardBlocked { reason: HardBlockedReason::Stopped, }; - // TODO: wait for context to be switched away from, and/or IPI? + drop(guard); + + let mut spins = 10_000usize; + while context.read(token.token()).running { + if spins == 0 { + return Err(Error::new(EAGAIN)); + } + spins -= 1; + context::switch(token); + } Ok(size_of::()) } ContextVerb::Unstop => { @@ -1458,6 +1570,7 @@ impl ContextHandle { } } ContextHandle::Attr => { + require_zero_offset(offset)?; let info = unsafe { buf.read_exact::()? }; let mut guard = context.write(token.token()); @@ -1476,9 +1589,16 @@ impl ContextHandle { guard.euid = info.euid; guard.egid = info.egid; guard.prio = (info.prio as usize).min(39); + if guard.pgrp == 0 { + guard.pgrp = guard.pid; + } + if guard.session == 0 { + guard.session = guard.pid; + } Ok(size_of::()) } Self::Groups => { + require_zero_offset(offset)?; const NGROUPS_MAX: usize = 65536; if buf.len() % size_of::() != 0 { return Err(Error::new(EINVAL)); @@ -1512,6 +1632,7 @@ impl ContextHandle { Ok(count * size_of::()) } ContextHandle::OpenViaDup => { + require_zero_offset(offset)?; let mut args = buf.usizes(); let user_data = args.next().ok_or(Error::new(EINVAL))??; @@ -1566,6 +1687,35 @@ impl ContextHandle { token: &mut CleanLockToken, ) -> Result { match self { + ContextHandle::Ctx => { + require_zero_offset(offset)?; + let mut regs = syscall::FullContextRegs::default(); + try_stop_context(context, token, |context, _| { + match context.regs() { + None => { + assert!(!context.running, "try_stop_context is broken, clearly"); + println!( + "{}:{}: Couldn't read registers from stopped process", + file!(), + line!() + ); + Err(Error::new(ENOTRECOVERABLE)) + } + Some(stack) => { + stack.save(&mut regs.int); + regs.float = context.get_fx_regs(); + regs.env = context.read_env_regs()?; + Ok(()) + } + } + })?; + buf.copy_common_bytes_from_slice(unsafe { + slice::from_raw_parts( + ®s as *const syscall::FullContextRegs as *const u8, + size_of::(), + ) + }) + } ContextHandle::Regs(kind) => { union Output { float: FloatRegisters, @@ -1658,7 +1808,7 @@ impl ContextHandle { ContextHandle::Filetable { data, .. } => read_from(buf, data, offset), ContextHandle::ProcStat { pid } => { let context = proc_context(*pid, token).ok_or(Error::new(ESRCH))?; - let (pid, comm, state, ppid, priority, utime, addr_space) = { + let (pid, comm, state, ppid, priority, utime, stime, pgrp, session, starttime, owner_proc_id, addr_space) = { let context = context.read(token.token()); ( context.pid, @@ -1666,14 +1816,45 @@ impl ContextHandle { proc_status_char(&context.status), context.owner_proc_id.map_or(0, NonZeroUsize::get), context.prio, - context.cpu_time / crate::time::NANOS_PER_SEC, + context.utime / crate::time::NANOS_PER_SEC, + context.stime / crate::time::NANOS_PER_SEC, + context.pgrp, + context.session, + context.start_time / (crate::time::NANOS_PER_SEC / 100), + context.owner_proc_id, context.addr_space.clone(), ) }; + let num_threads = { + let mut contexts = context::contexts(token.downgrade()); + let (contexts, mut token2) = contexts.token_split(); + contexts + .iter() + .filter(|ctx| ctx.read(token2.token()).owner_proc_id == owner_proc_id) + .count() + }; let vsize = proc_vsize(&addr_space, token); + let rss = proc_rss(&addr_space, token); + let nice = (priority as i32 / 2) - 20; read_from( buf, - proc_stat_line(pid, &comm, state, ppid, priority, utime, vsize).as_bytes(), + proc_stat_line( + pid, + &comm, + state, + ppid, + priority, + utime, + stime, + pgrp, + session, + nice, + num_threads, + starttime, + vsize, + rss, + ) + .as_bytes(), offset, ) } @@ -1691,8 +1872,26 @@ impl ContextHandle { } ContextHandle::ProcStatus { pid } => { let context = proc_context(*pid, token).ok_or(Error::new(ESRCH))?; + let owner_proc_id = context.read(token.token()).owner_proc_id; + let num_threads = { + let mut contexts = context::contexts(token.downgrade()); + let (contexts, mut token2) = contexts.token_split(); + contexts + .iter() + .filter(|ctx| ctx.read(token2.token()).owner_proc_id == owner_proc_id) + .count() + }; + let (vsize, rss) = { + let addr_space = { + let context = context.read(token.token()); + context.addr_space.clone() + }; + let vsize = proc_vsize(&addr_space, token); + let rss = proc_rss(&addr_space, token); + (vsize, rss) + }; let context = context.read(token.token()); - read_from(buf, proc_status_text(&context).as_bytes(), offset) + read_from(buf, proc_status_text(&context, num_threads, vsize, rss).as_bytes(), offset) } ContextHandle::ProcRoot | ContextHandle::ProcDir { .. } => Err(Error::new(EBADF)), ContextHandle::MmapMinAddr(addrspace) => {