diff --git a/kernel/src/fs/procfs/mod.rs b/kernel/src/fs/procfs/mod.rs index b50f10c1..bdf134b9 100644 --- a/kernel/src/fs/procfs/mod.rs +++ b/kernel/src/fs/procfs/mod.rs @@ -63,6 +63,8 @@ pub enum ProcEntryType { TraceCounters, /// /proc/trace/providers - registered providers TraceProviders, + /// /proc/slabinfo - slab allocator statistics + SlabInfo, } impl ProcEntryType { @@ -79,6 +81,7 @@ impl ProcEntryType { ProcEntryType::TraceBuffer => "buffer", ProcEntryType::TraceCounters => "counters", ProcEntryType::TraceProviders => "providers", + ProcEntryType::SlabInfo => "slabinfo", } } @@ -95,6 +98,7 @@ impl ProcEntryType { ProcEntryType::TraceBuffer => "/proc/trace/buffer", ProcEntryType::TraceCounters => "/proc/trace/counters", ProcEntryType::TraceProviders => "/proc/trace/providers", + ProcEntryType::SlabInfo => "/proc/slabinfo", } } @@ -111,6 +115,7 @@ impl ProcEntryType { ProcEntryType::TraceBuffer => 103, ProcEntryType::TraceCounters => 104, ProcEntryType::TraceProviders => 105, + ProcEntryType::SlabInfo => 5, } } @@ -166,6 +171,7 @@ pub fn init() { procfs.entries.push(ProcEntry::new(ProcEntryType::Version)); procfs.entries.push(ProcEntry::new(ProcEntryType::MemInfo)); procfs.entries.push(ProcEntry::new(ProcEntryType::CpuInfo)); + procfs.entries.push(ProcEntry::new(ProcEntryType::SlabInfo)); // Register /proc/trace directory and entries procfs.entries.push(ProcEntry::new(ProcEntryType::TraceDir)); @@ -288,6 +294,7 @@ pub fn read_entry(entry_type: ProcEntryType) -> Result { ProcEntryType::TraceBuffer => Ok(trace::generate_buffer()), ProcEntryType::TraceCounters => Ok(trace::generate_counters()), ProcEntryType::TraceProviders => Ok(trace::generate_providers()), + ProcEntryType::SlabInfo => Ok(generate_slabinfo()), } } @@ -408,3 +415,20 @@ fn generate_cpuinfo() -> String { ) } } + +/// Generate /proc/slabinfo content +fn generate_slabinfo() -> String { + use alloc::format; + use crate::memory::slab::{FD_TABLE_SLAB, SIGNAL_HANDLERS_SLAB}; + + let mut out = String::from("# name active free total objsize pct\n"); + for slab in &[&FD_TABLE_SLAB, &SIGNAL_HANDLERS_SLAB] { + let s = slab.stats(); + let pct = if s.capacity > 0 { (s.allocated * 100) / s.capacity } else { 0 }; + out.push_str(&format!( + "{:<16} {:>5} {:>5} {:>5} {:>7} {:>2}%\n", + s.name, s.allocated, s.free, s.capacity, s.obj_size, pct, + )); + } + out +} diff --git a/kernel/src/ipc/fd.rs b/kernel/src/ipc/fd.rs index 9f1ce951..83b76efd 100644 --- a/kernel/src/ipc/fd.rs +++ b/kernel/src/ipc/fd.rs @@ -5,6 +5,7 @@ //! to underlying file objects (pipes, stdio, sockets, etc.). use alloc::sync::Arc; +use crate::memory::slab::{SlabBox, FD_TABLE_SLAB}; use spin::Mutex; /// Maximum number of file descriptors per process @@ -128,6 +129,8 @@ pub enum FdKind { FifoRead(alloc::string::String, Arc>), /// FIFO (named pipe) write end - path is stored for cleanup on close FifoWrite(alloc::string::String, Arc>), + /// Procfs virtual file (content generated at open time) + ProcfsFile { content: alloc::string::String, position: usize }, } impl core::fmt::Debug for FdKind { @@ -161,6 +164,7 @@ impl core::fmt::Debug for FdKind { } FdKind::FifoRead(path, _) => write!(f, "FifoRead({})", path), FdKind::FifoWrite(path, _) => write!(f, "FifoWrite({})", path), + FdKind::ProcfsFile { content, position } => write!(f, "ProcfsFile(len={}, pos={})", content.len(), position), } } } @@ -208,11 +212,11 @@ impl FileDescriptor { /// Per-process file descriptor table /// -/// Note: Uses Box to heap-allocate the fd array to avoid stack overflow -/// during process creation (the array is ~6KB which is too large for stack). +/// Note: Uses SlabBox to allocate the fd array from a slab cache (O(1) alloc/free) +/// with fallback to the global heap. The array is ~6KB which is too large for stack. pub struct FdTable { /// The file descriptors (None = unused slot) - fds: alloc::boxed::Box<[Option; MAX_FDS]>, + fds: SlabBox<[Option; MAX_FDS]>, } impl Default for FdTable { @@ -225,7 +229,7 @@ impl Clone for FdTable { fn clone(&self) -> Self { // CRITICAL: No logging here - this runs during fork() with potential timer interrupts // Logging can cause deadlock if timer fires while holding logger lock - let cloned_fds = alloc::boxed::Box::new((*self.fds).clone()); + let cloned_fds = self.fds.clone(); // Increment reference counts for all cloned fds that need it for fd_opt in cloned_fds.iter() { @@ -274,8 +278,17 @@ impl Clone for FdTable { impl FdTable { /// Create a new file descriptor table with standard I/O pre-allocated pub fn new() -> Self { - // Use Box::new to allocate directly on heap, avoiding stack overflow - let mut fds = alloc::boxed::Box::new(core::array::from_fn(|_| None)); + // Try slab allocation first (O(1)), fall back to global heap + let mut fds = if let Some(raw) = FD_TABLE_SLAB.alloc() { + // Slab returns zeroed memory; write None into each slot + let arr = raw as *mut [Option; MAX_FDS]; + for i in 0..MAX_FDS { + unsafe { core::ptr::write(&mut (*arr)[i], None); } + } + unsafe { SlabBox::from_slab(arr, &FD_TABLE_SLAB) } + } else { + SlabBox::from_box(alloc::boxed::Box::new(core::array::from_fn(|_| None))) + }; // Pre-allocate stdin, stdout, stderr fds[STDIN as usize] = Some(FileDescriptor::new(FdKind::StdIo(STDIN))); @@ -609,6 +622,9 @@ impl Drop for FdTable { buffer.lock().close_write(); log::debug!("FdTable::drop() - closed FIFO write fd {} ({})", i, path); } + FdKind::ProcfsFile { .. } => { + // Procfs files are purely in-memory, nothing to clean up + } } } } diff --git a/kernel/src/ipc/poll.rs b/kernel/src/ipc/poll.rs index 0df94e6a..9a0d5464 100644 --- a/kernel/src/ipc/poll.rs +++ b/kernel/src/ipc/poll.rs @@ -284,6 +284,12 @@ pub fn poll_fd(fd_entry: &FileDescriptor, events: i16) -> i16 { } } } + FdKind::ProcfsFile { content, position } => { + // Procfs file is readable if there's remaining content + if (events & events::POLLIN) != 0 && *position < content.len() { + revents |= events::POLLIN; + } + } } revents diff --git a/kernel/src/main.rs b/kernel/src/main.rs index b7ed8b48..786783e3 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -359,6 +359,10 @@ fn kernel_main(boot_info: &'static mut bootloader_api::BootInfo) -> ! { crate::fs::devptsfs::init(); log::info!("devptsfs initialized at /dev/pts"); + // Initialize procfs (/proc virtual filesystem) + crate::fs::procfs::init(); + log::info!("procfs initialized at /proc"); + // Update IST stacks with per-CPU emergency stacks gdt::update_ist_stacks(); log::info!("Updated IST stacks with per-CPU emergency and page fault stacks"); diff --git a/kernel/src/main_aarch64.rs b/kernel/src/main_aarch64.rs index 13453472..a883a1a9 100644 --- a/kernel/src/main_aarch64.rs +++ b/kernel/src/main_aarch64.rs @@ -327,6 +327,10 @@ pub extern "C" fn kernel_main() -> ! { kernel::fs::devptsfs::init(); serial_println!("[boot] devptsfs initialized at /dev/pts"); + // Initialize procfs (/proc virtual filesystem) + kernel::fs::procfs::init(); + serial_println!("[boot] procfs initialized at /proc"); + // Initialize TTY subsystem (console + PTY infrastructure) kernel::tty::init(); serial_println!("[boot] TTY subsystem initialized"); diff --git a/kernel/src/memory/mod.rs b/kernel/src/memory/mod.rs index c40b318f..4d93a026 100644 --- a/kernel/src/memory/mod.rs +++ b/kernel/src/memory/mod.rs @@ -10,6 +10,7 @@ pub mod per_cpu_stack; pub mod process_memory; pub mod stack; pub mod tlb; +pub mod slab; pub mod vma; #[cfg(not(target_arch = "x86_64"))] pub mod arch_stub; @@ -44,6 +45,7 @@ pub fn init_aarch64_heap() { let phys_offset = physical_memory_offset(); let mapper = unsafe { paging::init(phys_offset) }; heap::init(&mapper).expect("ARM64 heap initialization failed"); + slab::init(); } /// Next available MMIO virtual address @@ -125,6 +127,9 @@ pub fn init(physical_memory_offset: VirtAddr, memory_regions: &'static MemoryReg heap::init(&mapper).expect("heap initialization failed"); } + // Initialize slab caches (must be after heap) + slab::init(); + // Initialize stack allocation system log::info!("Initializing stack allocation system..."); stack::init(); diff --git a/kernel/src/memory/slab.rs b/kernel/src/memory/slab.rs new file mode 100644 index 00000000..19ce9635 --- /dev/null +++ b/kernel/src/memory/slab.rs @@ -0,0 +1,355 @@ +//! Slab allocator for fixed-size kernel objects +//! +//! Provides O(1) allocation and deallocation for frequently created/destroyed +//! fixed-size objects. Uses bitmap-based free tracking following the pattern +//! from `kernel_stack.rs`. +//! +//! Two static caches are provided: +//! - `FD_TABLE_SLAB`: for `[Option; 256]` (~6 KiB each, 64 slots) +//! - `SIGNAL_HANDLERS_SLAB`: for `[SignalAction; 64]` (~2 KiB each, 64 slots) +//! +//! Objects that cannot be served from the slab fall back to the global heap +//! allocator transparently via `SlabBox`. + +use alloc::boxed::Box; +use alloc::vec::Vec; +use core::alloc::Layout; +use core::ops::{Deref, DerefMut}; +use core::ptr::{self, NonNull}; +use spin::Mutex; + +// --------------------------------------------------------------------------- +// SlabCache: bitmap-based object pool +// --------------------------------------------------------------------------- + +struct SlabCacheInner { + /// Heap-allocated backing buffer + storage: *mut u8, + /// Per-object size (8-byte aligned) + obj_size: usize, + /// Maximum number of objects + capacity: usize, + /// 1 = allocated, 0 = free + bitmap: Vec, + /// Count of live objects + allocated: usize, + /// Whether the cache has been initialized + initialized: bool, +} + +// SAFETY: The storage pointer is heap-allocated and only accessed under the +// mutex lock. We need Send for the Mutex. +unsafe impl Send for SlabCacheInner {} + +pub struct SlabCache { + inner: Mutex, + name: &'static str, +} + +/// Statistics for a slab cache +pub struct SlabStats { + pub name: &'static str, + pub obj_size: usize, + pub capacity: usize, + pub allocated: usize, + pub free: usize, +} + +impl SlabCache { + /// Create an uninitialized slab cache for static declaration. + /// + /// Both `Vec::new()` and `Mutex::new()` are const, so this works in statics. + pub const fn uninit(name: &'static str) -> Self { + SlabCache { + inner: Mutex::new(SlabCacheInner { + storage: ptr::null_mut(), + obj_size: 0, + capacity: 0, + bitmap: Vec::new(), + allocated: 0, + initialized: false, + }), + name, + } + } + + /// Initialize the slab cache, allocating backing storage from the global heap. + /// + /// `obj_size` is the size of each object (will be rounded up to 8-byte alignment). + /// `capacity` is the maximum number of objects. + pub fn init(&self, obj_size: usize, capacity: usize) { + let obj_size_aligned = (obj_size + 7) & !7; + let total_bytes = obj_size_aligned * capacity; + let bitmap_words = (capacity + 63) / 64; + + // Allocate backing storage from the global heap + let layout = Layout::from_size_align(total_bytes, 8) + .expect("slab: invalid layout"); + let storage = unsafe { alloc::alloc::alloc_zeroed(layout) }; + if storage.is_null() { + panic!("slab: failed to allocate {} bytes for cache '{}'", total_bytes, self.name); + } + + let mut inner = self.inner.lock(); + inner.storage = storage; + inner.obj_size = obj_size_aligned; + inner.capacity = capacity; + inner.bitmap = alloc::vec![0u64; bitmap_words]; + inner.allocated = 0; + inner.initialized = true; + + log::info!( + "Slab cache '{}' initialized: {} slots x {} bytes = {} KiB", + self.name, + capacity, + obj_size_aligned, + total_bytes / 1024 + ); + } + + /// Allocate a slot from this cache. + /// + /// Returns a pointer to zeroed memory, or `None` if the cache is full + /// or not yet initialized. + pub fn alloc(&self) -> Option<*mut u8> { + let mut inner = self.inner.lock(); + if !inner.initialized { + return None; + } + + // Cache fields before mutable borrow of bitmap + let capacity = inner.capacity; + let obj_size = inner.obj_size; + let storage = inner.storage; + + // Scan bitmap for a free slot + for (word_idx, word) in inner.bitmap.iter_mut().enumerate() { + if *word != u64::MAX { + for bit_idx in 0..64 { + let global_idx = word_idx * 64 + bit_idx; + if global_idx >= capacity { + return None; + } + if (*word & (1u64 << bit_idx)) == 0 { + // Mark as allocated + *word |= 1u64 << bit_idx; + inner.allocated += 1; + + let offset = global_idx * obj_size; + let ptr = unsafe { storage.add(offset) }; + // Zero the slot (defense in depth) + unsafe { ptr::write_bytes(ptr, 0, obj_size); } + return Some(ptr); + } + } + } + } + None + } + + /// Deallocate a slot back to this cache. + /// + /// # Safety + /// + /// `ptr` must have been returned by a previous call to `self.alloc()`. + pub unsafe fn dealloc(&self, ptr: *mut u8) { + let mut inner = self.inner.lock(); + debug_assert!(inner.initialized, "slab dealloc on uninitialized cache"); + + let offset = (ptr as usize).wrapping_sub(inner.storage as usize); + let slot = offset / inner.obj_size; + + debug_assert!( + slot < inner.capacity, + "slab dealloc: pointer {:#x} out of range for cache '{}'", + ptr as usize, + self.name + ); + debug_assert_eq!( + offset % inner.obj_size, + 0, + "slab dealloc: misaligned pointer for cache '{}'", + self.name + ); + + let word_idx = slot / 64; + let bit_idx = slot % 64; + debug_assert!( + (inner.bitmap[word_idx] & (1u64 << bit_idx)) != 0, + "slab dealloc: double free in cache '{}'", + self.name + ); + + inner.bitmap[word_idx] &= !(1u64 << bit_idx); + inner.allocated -= 1; + } + + /// Get statistics for this cache. + pub fn stats(&self) -> SlabStats { + let inner = self.inner.lock(); + SlabStats { + name: self.name, + obj_size: inner.obj_size, + capacity: inner.capacity, + allocated: inner.allocated, + free: inner.capacity.saturating_sub(inner.allocated), + } + } +} + +// SAFETY: SlabCache uses a Mutex internally. +unsafe impl Sync for SlabCache {} + +// --------------------------------------------------------------------------- +// SlabBox: smart pointer that returns memory to slab (or global heap) +// --------------------------------------------------------------------------- + +/// A smart pointer that deallocates to a slab cache on drop. +/// +/// If `slab` is `Some`, the memory was allocated from that slab and will be +/// returned there. If `slab` is `None`, the memory was allocated from the +/// global heap via `Box` and will be freed normally. +pub struct SlabBox { + ptr: NonNull, + slab: Option<&'static SlabCache>, +} + +// SAFETY: SlabBox owns its data exclusively, like Box. +unsafe impl Send for SlabBox {} +unsafe impl Sync for SlabBox {} + +impl SlabBox { + /// Wrap a slab-allocated pointer. + /// + /// # Safety + /// + /// `ptr` must point to a valid, initialized `T` that was allocated from `slab`. + pub unsafe fn from_slab(ptr: *mut T, slab: &'static SlabCache) -> Self { + SlabBox { + ptr: NonNull::new_unchecked(ptr), + slab: Some(slab), + } + } +} + +impl SlabBox { + /// Wrap a heap-allocated `Box` (slab = None, so Drop frees via global allocator). + pub fn from_box(b: Box) -> Self { + let raw = Box::into_raw(b); + SlabBox { + // SAFETY: Box::into_raw always returns a non-null pointer. + ptr: unsafe { NonNull::new_unchecked(raw) }, + slab: None, + } + } +} + +impl Deref for SlabBox { + type Target = T; + #[inline] + fn deref(&self) -> &T { + unsafe { self.ptr.as_ref() } + } +} + +impl DerefMut for SlabBox { + #[inline] + fn deref_mut(&mut self) -> &mut T { + unsafe { self.ptr.as_mut() } + } +} + +impl Drop for SlabBox { + fn drop(&mut self) { + unsafe { + // Run the destructor for T + ptr::drop_in_place(self.ptr.as_ptr()); + + if let Some(slab) = self.slab { + // Return to slab + slab.dealloc(self.ptr.as_ptr() as *mut u8); + } else { + // Reconstruct Box and let it free via global allocator + let _ = Box::from_raw(self.ptr.as_ptr()); + } + } + } +} + +impl Clone for SlabBox<[T]> { + fn clone(&self) -> Self { + // Try to allocate from the same slab if we came from one + if let Some(slab) = self.slab { + if let Some(raw) = slab.alloc() { + let src: &[T] = self; + let dst = raw as *mut T; + for i in 0..src.len() { + unsafe { + ptr::write(dst.add(i), src[i].clone()); + } + } + // Reconstruct the fat pointer with the same length + let fat: *mut [T] = ptr::slice_from_raw_parts_mut(dst, src.len()); + return SlabBox { + ptr: unsafe { NonNull::new_unchecked(fat) }, + slab: Some(slab), + }; + } + } + // Fall back to heap + let boxed: Box<[T]> = self.deref().into(); + SlabBox::from_box(boxed) + } +} + +/// Clone for sized types (used by SignalState handlers array) +impl Clone for SlabBox<[T; N]> { + fn clone(&self) -> Self { + if let Some(slab) = self.slab { + if let Some(raw) = slab.alloc() { + let src: &[T; N] = self; + let dst = raw as *mut T; + for i in 0..N { + unsafe { + ptr::write(dst.add(i), src[i].clone()); + } + } + return unsafe { SlabBox::from_slab(raw as *mut [T; N], slab) }; + } + } + // Fall back to heap + SlabBox::from_box(Box::new((**self).clone())) + } +} + +// --------------------------------------------------------------------------- +// Static cache declarations and initialization +// --------------------------------------------------------------------------- + +use crate::ipc::fd::FileDescriptor; +use crate::signal::types::SignalAction; + +/// Slab cache for `[Option; 256]` (~6 KiB each). +/// Used by `FdTable::new()` and `FdTable::clone()` (fork). +pub static FD_TABLE_SLAB: SlabCache = SlabCache::uninit("fd_table"); + +/// Slab cache for `[SignalAction; 64]` (~2 KiB each). +/// Used by `SignalState::default()` and `SignalState::fork()`. +pub static SIGNAL_HANDLERS_SLAB: SlabCache = SlabCache::uninit("signal_handlers"); + +/// Initialize all slab caches. +/// +/// Must be called after the global heap allocator is initialized. +pub fn init() { + use crate::ipc::fd::MAX_FDS; + use core::mem::size_of; + + FD_TABLE_SLAB.init( + size_of::<[Option; MAX_FDS]>(), + 64, + ); + SIGNAL_HANDLERS_SLAB.init( + size_of::<[SignalAction; 64]>(), + 64, + ); +} diff --git a/kernel/src/process/process.rs b/kernel/src/process/process.rs index ae8d7a3f..05d14f0e 100644 --- a/kernel/src/process/process.rs +++ b/kernel/src/process/process.rs @@ -327,6 +327,9 @@ impl Process { buffer.lock().close_write(); log::debug!("Process::close_all_fds() - closed FIFO write fd {} ({})", fd, path); } + FdKind::ProcfsFile { .. } => { + // Procfs files are purely in-memory, nothing to clean up + } } } } @@ -425,6 +428,9 @@ impl Process { let _ = crate::net::tcp::tcp_close(&conn_id); log::debug!("Process::close_all_fds() - closed TCP connection fd {}", fd); } + FdKind::ProcfsFile { .. } => { + // Procfs files are purely in-memory, nothing to clean up + } } } } diff --git a/kernel/src/signal/types.rs b/kernel/src/signal/types.rs index 1e4821f9..3b38f5f6 100644 --- a/kernel/src/signal/types.rs +++ b/kernel/src/signal/types.rs @@ -1,6 +1,7 @@ //! Signal-related data structures use super::constants::*; +use crate::memory::slab::{SlabBox, SIGNAL_HANDLERS_SLAB}; /// Alternate signal stack configuration (matches Linux stack_t) /// @@ -146,8 +147,8 @@ pub struct SignalState { /// Blocked signals bitmap (sigprocmask) pub blocked: u64, /// Signal handlers (one per signal, indices 0-63 for signals 1-64) - /// Boxed to avoid stack overflow - 64 * 32 bytes = 2KB - handlers: alloc::boxed::Box<[SignalAction; 64]>, + /// Slab-allocated for O(1) alloc/free, falls back to heap - 64 * 32 bytes = 2KB + handlers: SlabBox<[SignalAction; 64]>, /// Alternate signal stack configuration pub alt_stack: AltStack, /// Saved signal mask from sigsuspend - restored after signal handler returns via sigreturn @@ -158,10 +159,19 @@ pub struct SignalState { impl Default for SignalState { fn default() -> Self { + let handlers = if let Some(raw) = SIGNAL_HANDLERS_SLAB.alloc() { + let arr = raw as *mut [SignalAction; 64]; + unsafe { + core::ptr::write(arr, [SignalAction::default(); 64]); + SlabBox::from_slab(arr, &SIGNAL_HANDLERS_SLAB) + } + } else { + SlabBox::from_box(alloc::boxed::Box::new([SignalAction::default(); 64])) + }; SignalState { pending: 0, blocked: 0, - handlers: alloc::boxed::Box::new([SignalAction::default(); 64]), + handlers, alt_stack: AltStack::default(), sigsuspend_saved_mask: None, } diff --git a/kernel/src/syscall/fs.rs b/kernel/src/syscall/fs.rs index 0bcc2dac..b2e129bc 100644 --- a/kernel/src/syscall/fs.rs +++ b/kernel/src/syscall/fs.rs @@ -200,6 +200,11 @@ pub fn sys_open(pathname: u64, flags: u32, mode: u32) -> SyscallResult { return handle_fifo_open(&path, flags); } + // Check for /proc paths - route to procfs + if path == "/proc" || path.starts_with("/proc/") { + return handle_procfs_open(&path, flags); + } + // Parse flags let want_creat = (flags & O_CREAT) != 0; let want_excl = (flags & O_EXCL) != 0; @@ -694,6 +699,14 @@ pub fn sys_fstat(fd: i32, statbuf: u64) -> SyscallResult { stat.st_nlink = 1; stat.st_size = 0; // FIFOs don't have a seekable size } + FdKind::ProcfsFile { ref content, .. } => { + // Procfs virtual files + stat.st_dev = 0; + stat.st_ino = 0; + stat.st_mode = S_IFREG | 0o444; // Regular file, read-only + stat.st_nlink = 1; + stat.st_size = content.len() as i64; + } } // Copy stat structure to userspace @@ -1667,6 +1680,43 @@ pub fn sys_access(pathname: u64, mode: u32) -> SyscallResult { /// /// # Arguments /// * `device_name` - Name of the device (without /dev/ prefix) +/// Handle opening a /proc file +/// +/// Generates the file content at open time, stores it in a ProcfsFile fd. +fn handle_procfs_open(path: &str, _flags: u32) -> SyscallResult { + use crate::ipc::fd::{FdKind, FileDescriptor}; + + let content = match crate::fs::procfs::read_file(path) { + Ok(c) => c, + Err(_) => return SyscallResult::Err(super::errno::ENOENT as u64), + }; + + let fd_kind = FdKind::ProcfsFile { content, position: 0 }; + let fd_entry = FileDescriptor::new(fd_kind); + + // Get current process + let thread_id = match crate::task::scheduler::current_thread_id() { + Some(id) => id, + None => return SyscallResult::Err(3), // ESRCH + }; + let mut manager_guard = crate::process::manager(); + let process = match &mut *manager_guard { + Some(manager) => match manager.find_process_by_thread_mut(thread_id) { + Some((_pid, p)) => p, + None => return SyscallResult::Err(3), + }, + None => return SyscallResult::Err(3), + }; + + match process.fd_table.alloc_with_entry(fd_entry) { + Ok(fd) => { + log::debug!("handle_procfs_open: opened {} as fd={}", path, fd); + SyscallResult::Ok(fd as u64) + } + Err(e) => SyscallResult::Err(e as u64), + } +} + /// * `_flags` - Open flags (currently unused for devices) /// /// # Returns diff --git a/kernel/src/syscall/handlers.rs b/kernel/src/syscall/handlers.rs index 9cf6b6dd..14a9de7d 100644 --- a/kernel/src/syscall/handlers.rs +++ b/kernel/src/syscall/handlers.rs @@ -285,6 +285,7 @@ pub fn sys_write(fd: u64, buf_ptr: u64, count: u64) -> SyscallResult { // PTY write not implemented yet WriteOperation::Eopnotsupp } + FdKind::ProcfsFile { .. } => WriteOperation::Ebadf, } // manager_guard dropped here, releasing the lock before I/O }; @@ -1218,6 +1219,34 @@ pub fn sys_read(fd: u64, buf_ptr: u64, count: u64) -> SyscallResult { log::error!("sys_read: Cannot read from unconnected Unix socket"); SyscallResult::Err(super::errno::ENOTCONN as u64) } + FdKind::ProcfsFile { ref content, position } => { + // Read from procfs virtual file + let bytes = content.as_bytes(); + let pos = *position; + if pos >= bytes.len() { + return SyscallResult::Ok(0); + } + let remaining = &bytes[pos..]; + let to_copy = remaining.len().min(count as usize); + if to_copy > 0 { + if copy_to_user(buf_ptr, remaining.as_ptr() as u64, to_copy).is_err() { + return SyscallResult::Err(14); // EFAULT + } + } + // Update position - re-acquire the manager lock + drop(manager_guard); + let mut mg = crate::process::manager(); + if let Some(manager) = &mut *mg { + if let Some((_pid, process)) = manager.find_process_by_thread_mut(thread_id) { + if let Some(fd_entry) = process.fd_table.get_mut(fd as i32) { + if let FdKind::ProcfsFile { position, .. } = &mut fd_entry.kind { + *position += to_copy; + } + } + } + } + SyscallResult::Ok(to_copy as u64) + } } } diff --git a/kernel/src/syscall/io.rs b/kernel/src/syscall/io.rs index c111b304..c8b9e2b5 100644 --- a/kernel/src/syscall/io.rs +++ b/kernel/src/syscall/io.rs @@ -127,6 +127,7 @@ pub fn sys_write(fd: u64, buf_ptr: u64, count: u64) -> SyscallResult { // TCP sockets FdKind::TcpSocket(_) | FdKind::TcpListener(_) => WriteOperation::Enotconn, FdKind::TcpConnection(conn_id) => WriteOperation::TcpConnection { conn_id: *conn_id }, + FdKind::ProcfsFile { .. } => WriteOperation::Ebadf, } }; @@ -630,6 +631,34 @@ pub fn sys_read(fd: u64, buf_ptr: u64, count: u64) -> SyscallResult { crate::net::drain_loopback_queue(); } } + FdKind::ProcfsFile { ref content, position } => { + // Read from procfs virtual file + let bytes = content.as_bytes(); + let pos = *position; + if pos >= bytes.len() { + return SyscallResult::Ok(0); + } + let remaining = &bytes[pos..]; + let to_copy = remaining.len().min(count as usize); + if to_copy > 0 { + if copy_to_user_bytes(buf_ptr, &remaining[..to_copy]).is_err() { + return SyscallResult::Err(14); // EFAULT + } + } + // Update position - need to re-acquire the manager lock + drop(manager_guard); + let mut mg = crate::process::manager(); + if let Some(manager) = &mut *mg { + if let Some((_pid, process)) = manager.find_process_by_thread_mut(thread_id) { + if let Some(fd_entry) = process.fd_table.get_mut(fd as i32) { + if let FdKind::ProcfsFile { position, .. } = &mut fd_entry.kind { + *position += to_copy; + } + } + } + } + SyscallResult::Ok(to_copy as u64) + } } } diff --git a/kernel/src/syscall/pipe.rs b/kernel/src/syscall/pipe.rs index 173917f3..17ce6be3 100644 --- a/kernel/src/syscall/pipe.rs +++ b/kernel/src/syscall/pipe.rs @@ -235,6 +235,9 @@ pub fn sys_close(fd: i32) -> SyscallResult { buffer.lock().close_write(); log::debug!("sys_close: Closed FIFO write end fd={} ({})", fd, path); } + FdKind::ProcfsFile { .. } => { + log::debug!("sys_close: Closed procfs file fd={}", fd); + } } log::debug!("sys_close: returning to userspace fd={}", fd); SyscallResult::Ok(0) diff --git a/tests/boot_post_test.rs b/tests/boot_post_test.rs index 96389903..c85af1fd 100644 --- a/tests/boot_post_test.rs +++ b/tests/boot_post_test.rs @@ -25,6 +25,7 @@ fn test_kernel_post_with_file_output() { ("Frame Allocator", "Frame allocator initialized", "Physical memory management"), ("Paging", "Page table initialized", "Virtual memory management"), ("Heap", "Heap initialized", "Dynamic memory allocation"), + ("Slab Allocator", "Slab cache 'fd_table' initialized", "Fixed-size object allocator"), ("Memory System", "Memory management initialized", "Complete memory subsystem"), ("Timer/RTC", "Timer initialized", "System timer and RTC"), ("Keyboard", "Keyboard queue initialized", "Keyboard controller"), diff --git a/tests/memory_tests.rs b/tests/memory_tests.rs index b77c9173..597901aa 100644 --- a/tests/memory_tests.rs +++ b/tests/memory_tests.rs @@ -45,6 +45,53 @@ fn test_heap_allocation() { println!("✅ Heap allocation test passed"); } +/// Test slab allocator initialization +#[test] +fn test_slab_allocator_initialization() { + println!("Testing slab allocator initialization..."); + + let output = get_kernel_output(); + + // Check that both slab caches initialized + assert!( + output.contains("Slab cache 'fd_table' initialized"), + "fd_table slab cache initialization not found" + ); + assert!( + output.contains("Slab cache 'signal_handlers' initialized"), + "signal_handlers slab cache initialization not found" + ); + + // Verify fd_table slab has 64 slots + assert!( + output.contains("fd_table' initialized: 64 slots"), + "fd_table slab should have 64 slots" + ); + + // Verify signal_handlers slab has 64 slots + assert!( + output.contains("signal_handlers' initialized: 64 slots"), + "signal_handlers slab should have 64 slots" + ); + + println!("✅ Slab allocator initialization test passed"); +} + +/// Test that procfs is initialized (supports /proc/slabinfo) +#[test] +fn test_procfs_initialization() { + println!("Testing procfs initialization..."); + + let output = get_kernel_output(); + + assert!( + output.contains("procfs initialized at /proc") || output.contains("procfs: initialized"), + "procfs initialization not found" + ); + + println!("✅ Procfs initialization test passed"); +} + /// Test memory regions enumeration #[test] fn test_memory_regions() {