Initial proc fs (#70)

This commit is contained in:
Ashwin Naren
2025-12-21 01:37:14 -08:00
committed by GitHub
parent 35a6caa541
commit cd6d830ae9
11 changed files with 448 additions and 4 deletions

View File

@@ -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

View File

@@ -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"

View File

@@ -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"

View File

@@ -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()));
}

377
src/drivers/fs/proc.rs Normal file
View File

@@ -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<ProcRootInode>,
next_inode_id: AtomicU64,
}
impl ProcFs {
fn new() -> Arc<Self> {
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<Arc<dyn Inode>> {
Ok(self.root.clone())
}
fn id(&self) -> u64 {
PROCFS_ID
}
}
struct ProcDirStream {
entries: Vec<Dirent>,
idx: usize,
}
#[async_trait]
impl DirStream for ProcDirStream {
async fn next_entry(&mut self) -> Result<Option<Dirent>> {
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<Arc<dyn Inode>> {
// 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::<KernelError>::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<FileAttr> {
Ok(self.attr.clone())
}
async fn readdir(&self, start_offset: u64) -> Result<Box<dyn DirStream>> {
let mut entries: Vec<Dirent> = 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<Arc<dyn Inode>> {
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<FileAttr> {
Ok(self.attr.clone())
}
async fn readdir(&self, start_offset: u64) -> Result<Box<dyn DirStream>> {
let mut entries: Vec<Dirent> = 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<TaskFileType, Self::Error> {
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<Arc<dyn Inode>> {
Err(FsError::NotADirectory.into())
}
async fn getattr(&self) -> Result<FileAttr> {
Ok(self.attr.clone())
}
async fn readdir(&self, _start_offset: u64) -> Result<Box<dyn DirStream>> {
Err(FsError::NotADirectory.into())
}
async fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<usize> {
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<Arc<ProcFs>> = 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> {
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<Self>) -> Option<Arc<dyn FilesystemDriver>> {
Some(self)
}
}
#[async_trait]
impl FilesystemDriver for ProcFsDriver {
async fn construct(
&self,
_fs_id: u64,
device: Option<Box<dyn BlockDevice>>,
) -> Result<Arc<dyn Filesystem>> {
if device.is_some() {
warn!("procfs should not be constructed with a block device");
return Err(KernelError::InvalidValue);
}
Ok(procfs())
}
}

View File

@@ -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)),

View File

@@ -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;

View File

@@ -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()
}
}

View File

@@ -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<<ArchImpl as VirtualMemory>::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<SpinLock<Comm>>,
pub process: Arc<ThreadGroup>,
pub vm: Arc<SpinLock<ProcVM>>,
pub cwd: Arc<SpinLock<(Arc<dyn Inode>, 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()))),

View File

@@ -70,7 +70,7 @@ pub fn insert_task(task: Arc<Task>) {
pub struct SchedState {
running_task: Option<Arc<Task>>,
run_queue: BTreeMap<TaskDescriptor, Arc<Task>>,
pub run_queue: BTreeMap<TaskDescriptor, Arc<Task>>,
}
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<Task> {

View File

@@ -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;