diff --git a/libkernel/src/fs/mod.rs b/libkernel/src/fs/mod.rs index a6ac6d0..327a64f 100644 --- a/libkernel/src/fs/mod.rs +++ b/libkernel/src/fs/mod.rs @@ -46,6 +46,7 @@ bitflags::bitflags! { // Reserved psuedo filesystem instances created internally in the kernel. pub const DEVFS_ID: u64 = 1; +pub const PROCFS_ID: u64 = 2; pub const FS_ID_START: u64 = 10; /// Trait for a mounted filesystem instance. Its main role is to act as a diff --git a/scripts/create-image.sh b/scripts/create-image.sh index cc8e817..c1ad9c2 100755 --- a/scripts/create-image.sh +++ b/scripts/create-image.sh @@ -11,6 +11,7 @@ mkfs.vfat -F 32 "$img" mmd -i "$img" ::/bin mmd -i "$img" ::/dev +mmd -i "$img" ::/proc mmd -i "$img" ::/tmp mcopy -i "$img" "$base/build/bin"/* "::/bin" diff --git a/scripts/qemu-runner.sh b/scripts/qemu-runner.sh index 6af2cfa..e864bd4 100755 --- a/scripts/qemu-runner.sh +++ b/scripts/qemu-runner.sh @@ -8,4 +8,4 @@ bin="${elf%.elf}.bin" # Convert to binary format aarch64-none-elf-objcopy -O binary "$elf" "$bin" -qemu-system-aarch64 -M virt,gic-version=3 -initrd moss.img -cpu cortex-a72 -m 2G -smp 4 -nographic -s -kernel "$bin" -append "--init=/bin/bash --rootfs=fat32fs --automount=/dev,devfs --automount=/tmp,tmpfs" +qemu-system-aarch64 -M virt,gic-version=3 -initrd moss.img -cpu cortex-a72 -m 2G -smp 4 -nographic -s -kernel "$bin" -append "--init=/bin/bash --rootfs=fat32fs --automount=/dev,devfs --automount=/tmp,tmpfs --automount=/proc,procfs" diff --git a/src/drivers/fs/mod.rs b/src/drivers/fs/mod.rs index 42b9e86..a5688b6 100644 --- a/src/drivers/fs/mod.rs +++ b/src/drivers/fs/mod.rs @@ -1,12 +1,14 @@ use alloc::sync::Arc; use dev::DevFsDriver; use fat32::Fat32FsDriver; +use proc::ProcFsDriver; use tmpfs::TmpFsDriver; use super::DM; pub mod dev; pub mod fat32; +pub mod proc; pub mod tmpfs; pub fn register_fs_drivers() { @@ -14,5 +16,6 @@ pub fn register_fs_drivers() { dm.insert_driver(Arc::new(Fat32FsDriver::new())); dm.insert_driver(Arc::new(DevFsDriver::new())); + dm.insert_driver(Arc::new(ProcFsDriver::new())); dm.insert_driver(Arc::new(TmpFsDriver::new())); } diff --git a/src/drivers/fs/proc.rs b/src/drivers/fs/proc.rs new file mode 100644 index 0000000..d7ff231 --- /dev/null +++ b/src/drivers/fs/proc.rs @@ -0,0 +1,377 @@ +#![allow(clippy::module_name_repetitions)] + +use crate::process::thread_group::Tgid; +use crate::sched::{SCHED_STATE, current_task}; +use crate::sync::OnceLock; +use crate::{ + drivers::{Driver, FilesystemDriver}, + process::TASK_LIST, +}; +use alloc::{boxed::Box, format, string::ToString, sync::Arc, vec::Vec}; +use async_trait::async_trait; +use core::sync::atomic::{AtomicU64, Ordering}; +use libkernel::{ + error::{FsError, KernelError, Result}, + fs::{ + BlockDevice, DirStream, Dirent, FileType, Filesystem, Inode, InodeId, PROCFS_ID, + attr::{FileAttr, FilePermissions}, + }, +}; +use log::warn; + +pub struct ProcFs { + root: Arc, + next_inode_id: AtomicU64, +} + +impl ProcFs { + fn new() -> Arc { + let root_inode = Arc::new(ProcRootInode::new()); + Arc::new(Self { + root: root_inode, + next_inode_id: AtomicU64::new(1), + }) + } + + /// Convenience helper to allocate a unique inode ID inside this filesystem. + fn alloc_inode_id(&self) -> InodeId { + let id = self.next_inode_id.fetch_add(1, Ordering::Relaxed); + InodeId::from_fsid_and_inodeid(PROCFS_ID, id) + } +} + +#[async_trait] +impl Filesystem for ProcFs { + async fn root_inode(&self) -> Result> { + Ok(self.root.clone()) + } + + fn id(&self) -> u64 { + PROCFS_ID + } +} + +struct ProcDirStream { + entries: Vec, + idx: usize, +} + +#[async_trait] +impl DirStream for ProcDirStream { + async fn next_entry(&mut self) -> Result> { + Ok(if let Some(entry) = self.entries.get(self.idx).cloned() { + self.idx += 1; + Some(entry) + } else { + None + }) + } +} + +struct ProcRootInode { + id: InodeId, + attr: FileAttr, +} + +impl ProcRootInode { + fn new() -> Self { + Self { + id: InodeId::from_fsid_and_inodeid(PROCFS_ID, 0), + attr: FileAttr { + file_type: FileType::Directory, + mode: FilePermissions::from_bits_retain(0o555), + ..FileAttr::default() + }, + } + } +} + +#[async_trait] +impl Inode for ProcRootInode { + fn id(&self) -> InodeId { + self.id + } + + async fn lookup(&self, name: &str) -> Result> { + // Lookup a PID directory. + let pid = if name == "self" { + let current_task = current_task(); + current_task.descriptor().tgid() + } else { + Tgid( + name.parse() + .map_err(|_| FsError::NotFound) + .map_err(Into::::into)?, + ) + }; + + // Validate that the process actually exists. + if !TASK_LIST.lock_save_irq().keys().any(|d| d.tgid() == pid) { + return Err(FsError::NotFound.into()); + } + + let fs = procfs(); + + let inode_id = fs.alloc_inode_id(); + Ok(Arc::new(ProcTaskInode::new(pid, inode_id))) + } + + async fn getattr(&self) -> Result { + Ok(self.attr.clone()) + } + + async fn readdir(&self, start_offset: u64) -> Result> { + let mut entries: Vec = Vec::new(); + // Gather task list under interrupt-safe lock. + let task_list = TASK_LIST.lock_save_irq(); + for (idx, desc) in task_list.keys().enumerate() { + // Use offset index as dirent offset. + let name = desc.tgid().value().to_string(); + let inode_id = + InodeId::from_fsid_and_inodeid(PROCFS_ID, ((desc.tgid().0 + 1) * 100) as u64); + entries.push(Dirent::new( + name, + inode_id, + FileType::Directory, + (idx + 1) as u64, + )); + } + let current_task = current_task(); + entries.push(Dirent::new( + "self".to_string(), + InodeId::from_fsid_and_inodeid( + PROCFS_ID, + ((current_task.process.tgid.0 + 1) * 100) as u64, + ), + FileType::Directory, + (entries.len() + 1) as u64, + )); + + // honour start_offset + let entries = entries.into_iter().skip(start_offset as usize).collect(); + + Ok(Box::new(ProcDirStream { entries, idx: 0 })) + } +} + +struct ProcTaskInode { + id: InodeId, + attr: FileAttr, + pid: Tgid, +} + +impl ProcTaskInode { + fn new(pid: Tgid, inode_id: InodeId) -> Self { + Self { + id: inode_id, + attr: FileAttr { + file_type: FileType::Directory, + mode: FilePermissions::from_bits_retain(0o555), + ..FileAttr::default() + }, + pid, + } + } +} + +#[async_trait] +impl Inode for ProcTaskInode { + fn id(&self) -> InodeId { + self.id + } + + async fn lookup(&self, name: &str) -> Result> { + if let Ok(file_type) = TaskFileType::try_from(name) { + let fs = procfs(); + let inode_id = fs.alloc_inode_id(); + Ok(Arc::new(ProcTaskFileInode::new( + self.pid, file_type, inode_id, + ))) + } else { + Err(FsError::NotFound.into()) + } + } + + async fn getattr(&self) -> Result { + Ok(self.attr.clone()) + } + + async fn readdir(&self, start_offset: u64) -> Result> { + let mut entries: Vec = Vec::new(); + let inode_offset = ((self.pid.value() + 1) * 100) as u64; + entries.push(Dirent::new( + "status".to_string(), + InodeId::from_fsid_and_inodeid(PROCFS_ID, inode_offset + 1), + FileType::File, + 1, + )); + entries.push(Dirent::new( + "comm".to_string(), + InodeId::from_fsid_and_inodeid(PROCFS_ID, inode_offset + 2), + FileType::File, + 2, + )); + entries.push(Dirent::new( + "state".to_string(), + InodeId::from_fsid_and_inodeid(PROCFS_ID, inode_offset + 3), + FileType::File, + 3, + )); + + // honour start_offset + let entries = entries.into_iter().skip(start_offset as usize).collect(); + + Ok(Box::new(ProcDirStream { entries, idx: 0 })) + } +} + +enum TaskFileType { + Status, + Comm, + State, +} + +impl TryFrom<&str> for TaskFileType { + type Error = (); + + fn try_from(value: &str) -> core::result::Result { + match value { + "status" => Ok(TaskFileType::Status), + "comm" => Ok(TaskFileType::Comm), + "state" => Ok(TaskFileType::State), + _ => Err(()), + } + } +} + +struct ProcTaskFileInode { + id: InodeId, + file_type: TaskFileType, + attr: FileAttr, + pid: Tgid, +} + +impl ProcTaskFileInode { + fn new(pid: Tgid, file_type: TaskFileType, inode_id: InodeId) -> Self { + Self { + id: inode_id, + attr: FileAttr { + file_type: FileType::File, + mode: FilePermissions::from_bits_retain(0o444), + ..FileAttr::default() + }, + pid, + file_type, + } + } +} + +#[async_trait] +impl Inode for ProcTaskFileInode { + fn id(&self) -> InodeId { + self.id + } + + async fn lookup(&self, _name: &str) -> Result> { + Err(FsError::NotADirectory.into()) + } + + async fn getattr(&self) -> Result { + Ok(self.attr.clone()) + } + + async fn readdir(&self, _start_offset: u64) -> Result> { + Err(FsError::NotADirectory.into()) + } + + async fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result { + let pid = self.pid; + let task_list = TASK_LIST.lock_save_irq(); + // TODO: Does not obtain details for tasks that are on other CPUs + let id = task_list.iter().find(|(desc, _)| desc.tgid() == pid); + let task_details = if let Some((desc, _)) = id { + SCHED_STATE.borrow().run_queue.get(desc).cloned() + } else { + None + }; + + let status_string = if let Some(task) = task_details { + let state = *task.state.lock_save_irq(); + let name = task.comm.lock_save_irq(); + match self.file_type { + TaskFileType::Status => format!( + "Name:\t{name} +State:\t{state} +Tgid:\t{tgid} +FDSize:\t{fd_size} +Pid:\t{pid} +Threads:\t{threads}\n", + name = name.as_str(), + tgid = task.process.tgid, + fd_size = task.fd_table.lock_save_irq().len(), + threads = task.process.threads.lock_save_irq().len(), + ), + TaskFileType::Comm => format!("{name}\n", name = name.as_str()), + TaskFileType::State => format!("{state}\n"), + } + } else { + "State:\tGone\n".to_string() + }; + + let bytes = status_string.as_bytes(); + let end = usize::min(bytes.len().saturating_sub(offset as usize), buf.len()); + if end == 0 { + return Ok(0); + } + let slice = &bytes[offset as usize..offset as usize + end]; + buf[..end].copy_from_slice(slice); + Ok(end) + } +} + +static PROCFS_INSTANCE: OnceLock> = OnceLock::new(); + +/// Initializes and/or returns the global singleton [`ProcFs`] instance. +/// This is the main entry point for the rest of the kernel to interact with procfs. +pub fn procfs() -> Arc { + PROCFS_INSTANCE + .get_or_init(|| { + log::info!("devfs initialized"); + ProcFs::new() + }) + .clone() +} + +pub struct ProcFsDriver; + +impl ProcFsDriver { + #[must_use] + pub fn new() -> Self { + Self + } +} + +impl Driver for ProcFsDriver { + fn name(&self) -> &'static str { + "procfs" + } + + fn as_filesystem_driver(self: Arc) -> Option> { + Some(self) + } +} + +#[async_trait] +impl FilesystemDriver for ProcFsDriver { + async fn construct( + &self, + _fs_id: u64, + device: Option>, + ) -> Result> { + if device.is_some() { + warn!("procfs should not be constructed with a block device"); + return Err(KernelError::InvalidValue); + } + Ok(procfs()) + } +} diff --git a/src/process/clone.rs b/src/process/clone.rs index 6af0cfd..8dced7c 100644 --- a/src/process/clone.rs +++ b/src/process/clone.rs @@ -115,6 +115,7 @@ pub async fn sys_clone( Task { tid, + comm: Arc::new(SpinLock::new(*current_task.comm.lock_save_irq())), process: tg, vm, fd_table: files, @@ -124,7 +125,7 @@ pub async fn sys_clone( priority: current_task.priority, sig_mask: SpinLock::new(new_sigmask), pending_signals: SpinLock::new(SigSet::empty()), - vruntime: SpinLock::new(*current_task.vruntime.lock_save_irq()), + vruntime: SpinLock::new(0), exec_start: SpinLock::new(None), deadline: SpinLock::new(*current_task.deadline.lock_save_irq()), state: Arc::new(SpinLock::new(TaskState::Runnable)), diff --git a/src/process/exec.rs b/src/process/exec.rs index 9656e49..97fb9a3 100644 --- a/src/process/exec.rs +++ b/src/process/exec.rs @@ -1,3 +1,4 @@ +use crate::process::Comm; use crate::{ arch::{Arch, ArchImpl}, fs::VFS, @@ -116,8 +117,13 @@ pub async fn kernel_exec( // state. Simply activate the new process's address space. vm.mm_mut().address_space_mut().activate(); + let new_comm = argv.first().map(|s| Comm::new(s.as_str())); + let current_task = current_task(); + if let Some(new_comm) = new_comm { + *current_task.comm.lock_save_irq() = new_comm; + } *current_task.ctx.lock_save_irq() = Context::from_user_ctx(user_ctx); *current_task.state.lock_save_irq() = TaskState::Runnable; *current_task.vm.lock_save_irq() = vm; diff --git a/src/process/fd_table.rs b/src/process/fd_table.rs index 413354a..2f11172 100644 --- a/src/process/fd_table.rs +++ b/src/process/fd_table.rs @@ -161,4 +161,9 @@ impl FileDescriptorTable { Ok(Fd(next as i32)) } } + + /// Number of file descriptors in use. + pub fn len(&self) -> usize { + self.entries.iter().filter(|e| e.is_some()).count() + } } diff --git a/src/process/mod.rs b/src/process/mod.rs index 712edd3..fcb00f3 100644 --- a/src/process/mod.rs +++ b/src/process/mod.rs @@ -9,6 +9,7 @@ use alloc::{ collections::btree_map::BTreeMap, sync::{Arc, Weak}, }; +use core::fmt::Display; use creds::Credentials; use ctx::{Context, UserCtx}; use fd_table::FileDescriptorTable; @@ -100,6 +101,16 @@ impl TaskDescriptor { pub fn is_idle(&self) -> bool { self.tgid.is_idle() } + + /// Returns the task-group ID (i.e. the PID) associated with this descriptor. + pub fn tgid(&self) -> Tgid { + self.tgid + } + + /// Returns the thread ID associated with this descriptor. + pub fn tid(&self) -> Tid { + self.tid + } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -111,6 +122,19 @@ pub enum TaskState { Finished, } +impl Display for TaskState { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let state_str = match self { + TaskState::Running => "R", + TaskState::Runnable => "R", + TaskState::Stopped => "T", + TaskState::Sleeping => "S", + TaskState::Finished => "Z", + }; + write!(f, "{}", state_str) + } +} + impl TaskState { pub fn is_finished(self) -> bool { matches!(self, Self::Finished) @@ -118,8 +142,29 @@ impl TaskState { } pub type ProcVM = ProcessVM<::ProcessAddressSpace>; +#[derive(Copy, Clone)] +pub struct Comm([u8; 16]); + +impl Comm { + /// Create a new command name from the given string. + /// Truncates to 15 characters if necessary, and null-terminates. + pub fn new(name: &str) -> Self { + let mut comm = [0u8; 16]; + let bytes = name.as_bytes(); + let len = core::cmp::min(bytes.len(), 15); + comm[..len].copy_from_slice(&bytes[..len]); + Self(comm) + } + + pub fn as_str(&self) -> &str { + let len = self.0.iter().position(|&c| c == 0).unwrap_or(16); + core::str::from_utf8(&self.0[..len]).unwrap_or("") + } +} + pub struct Task { pub tid: Tid, + pub comm: Arc>, pub process: Arc, pub vm: Arc>, pub cwd: Arc, PathBuf)>>, @@ -151,6 +196,7 @@ impl Task { Self { tid: Tid(0), + comm: Arc::new(SpinLock::new(Comm::new("idle"))), process: thread_group_builder.build(), state: Arc::new(SpinLock::new(TaskState::Runnable)), priority: i8::MIN, @@ -172,6 +218,7 @@ impl Task { pub fn create_init_task() -> Self { Self { tid: Tid(1), + comm: Arc::new(SpinLock::new(Comm::new("init"))), process: ThreadGroupBuilder::new(Tgid::init()).build(), state: Arc::new(SpinLock::new(TaskState::Runnable)), cwd: Arc::new(SpinLock::new((Arc::new(DummyInode {}), PathBuf::new()))), diff --git a/src/sched/mod.rs b/src/sched/mod.rs index 70b301d..2f6ddb4 100644 --- a/src/sched/mod.rs +++ b/src/sched/mod.rs @@ -70,7 +70,7 @@ pub fn insert_task(task: Arc) { pub struct SchedState { running_task: Option>, - run_queue: BTreeMap>, + pub run_queue: BTreeMap>, } unsafe impl Send for SchedState {} @@ -165,7 +165,7 @@ impl SchedState { } per_cpu! { - static SCHED_STATE: SchedState = SchedState::new; + pub static SCHED_STATE: SchedState = SchedState::new; } pub fn current_task() -> Arc { diff --git a/src/sched/uspc_ret.rs b/src/sched/uspc_ret.rs index e2f2c86..8d601d9 100644 --- a/src/sched/uspc_ret.rs +++ b/src/sched/uspc_ret.rs @@ -1,4 +1,5 @@ use super::{SCHED_STATE, current_task, schedule, waker::create_waker}; +use crate::process::TASK_LIST; use crate::{ arch::{Arch, ArchImpl}, process::{ @@ -144,6 +145,8 @@ pub fn dispatch_userspace_task(ctx: *mut UserCtx) { .borrow_mut() .run_queue .remove(&task.descriptor()); + let mut task_list = TASK_LIST.lock_save_irq(); + task_list.remove(&task.descriptor()); state = State::PickNewTask; continue;