WIP: proc: fork()/execve() implementation for c compat

This commit is contained in:
Mark Poliakov 2024-01-08 18:44:55 +02:00
parent 0e8860c719
commit e7a6243cb3
15 changed files with 559 additions and 148 deletions

View File

@ -186,11 +186,18 @@ impl File {
}
/// Clones an open file for sending it to another process
pub fn send(&self) -> Result<Arc<Self>, Error> {
match self {
pub fn send(self: &Arc<Self>) -> Result<Arc<Self>, Error> {
match self.as_ref() {
Self::Char(_) => Ok(self.clone()),
Self::Block(_) => todo!(),
Self::Regular(file) => Ok(Arc::new(Self::Regular(file.clone()))),
Self::SharedMemory(shm) => Ok(Arc::new(Self::SharedMemory(shm.clone()))),
_ => Err(Error::InvalidOperation),
Self::PtySlave(pt) => Ok(Arc::new(Self::PtySlave(pt.clone()))),
Self::PtyMaster(pt) => Ok(Arc::new(Self::PtyMaster(pt.clone()))),
_ => {
log::info!("Invalid file send(): {:?}", self);
Err(Error::InvalidOperation)
}
}
}
@ -416,6 +423,11 @@ impl FileSet {
self.map.retain(predicate);
}
/// XXX
pub fn iter(&self) -> impl Iterator<Item = (&RawFd, &FileRef)> {
self.map.iter()
}
/// Closes all of the files
pub fn close_all(&mut self) {
self.map.clear();

View File

@ -40,13 +40,14 @@ pub struct PseudoTerminal {
slave_to_master: PtyHalf,
master_to_slave: PtyHalf,
size: IrqSafeRwLock<TerminalSize>,
eof: AtomicBool,
}
/// Slave part of a PTY device
#[derive(Clone)]
pub struct PseudoTerminalSlave {
pty: Arc<PseudoTerminal>,
}
/// Master part of a PTY device
#[derive(Clone)]
pub struct PseudoTerminalMaster {
pty: Arc<PseudoTerminal>,
}
@ -54,21 +55,23 @@ pub struct PseudoTerminalMaster {
fn input_discipline(
mut lock: IrqSafeSpinlockGuard<RingBuffer<u8, CAPACITY>>,
config: &TerminalOptions,
eof: &AtomicBool,
buffer: &mut [u8],
) -> Result<usize, Error> {
let mut pos = 0;
if !config.is_canonical() {
while !eof.load(Ordering::Acquire) && pos < buffer.len() && lock.is_readable() {
while pos < buffer.len() && lock.is_readable() {
buffer[pos] = unsafe { lock.read_single_unchecked() };
pos += 1;
}
Ok(pos)
} else {
while !eof.load(Ordering::Acquire) && pos < buffer.len() && lock.is_readable() {
while pos < buffer.len() && lock.is_readable() {
let ch = unsafe { lock.read_single_unchecked() };
if ch == config.chars.eof {
break;
}
if ch == config.chars.erase {
pos = pos.saturating_sub(1);
continue;
@ -112,32 +115,25 @@ impl PtyHalf {
}
}
fn read(
&self,
config: &TerminalOptions,
eof: &AtomicBool,
buffer: &mut [u8],
) -> Result<usize, Error> {
if buffer.is_empty() || eof.load(Ordering::Acquire) {
fn read(&self, config: &TerminalOptions, buffer: &mut [u8]) -> Result<usize, Error> {
if buffer.is_empty() {
return Ok(0);
}
if let Some(lock) = self.try_begin_read(config) {
input_discipline(lock, config, eof, buffer)
input_discipline(lock, config, buffer)
} else {
block!(self.read_async(config, eof, buffer).await)?
block!(self.read_async(config, buffer).await)?
}
}
fn read_async<'a>(
&'a self,
config: &'a TerminalOptions,
eof: &'a AtomicBool,
buffer: &'a mut [u8],
) -> impl Future<Output = Result<usize, Error>> + 'a {
struct F<'f> {
config: &'f TerminalOptions,
eof: &'f AtomicBool,
half: &'f PtyHalf,
buffer: &'f mut [u8],
}
@ -148,12 +144,9 @@ impl PtyHalf {
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.half.notify.register(cx.waker());
if self.eof.load(Ordering::Acquire) {
if let Some(lock) = self.half.try_begin_read(self.config) {
self.half.notify.remove(cx.waker());
Poll::Ready(Ok(0))
} else if let Some(lock) = self.half.try_begin_read(self.config) {
self.half.notify.remove(cx.waker());
Poll::Ready(input_discipline(lock, self.config, self.eof, self.buffer))
Poll::Ready(input_discipline(lock, self.config, self.buffer))
} else {
Poll::Pending
}
@ -162,21 +155,15 @@ impl PtyHalf {
F {
half: self,
eof,
buffer,
config,
}
}
fn poll_read(
&self,
config: &TerminalOptions,
eof: &AtomicBool,
cx: &mut Context<'_>,
) -> Poll<Result<(), Error>> {
fn poll_read(&self, config: &TerminalOptions, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
self.notify.register(cx.waker());
if eof.load(Ordering::Acquire) || self.try_begin_read(config).is_some() {
if self.try_begin_read(config).is_some() {
self.notify.remove(cx.waker());
Poll::Ready(Ok(()))
} else {
@ -184,15 +171,15 @@ impl PtyHalf {
}
}
fn read_raw(&self, eof: &AtomicBool, buffer: &mut [u8]) -> Result<usize, Error> {
if buffer.is_empty() || eof.load(Ordering::Acquire) {
fn read_raw(&self, buffer: &mut [u8]) -> Result<usize, Error> {
if buffer.is_empty() {
return Ok(0);
}
let mut lock = self.ring.lock();
if lock.is_readable() {
let mut pos = 0;
while !eof.load(Ordering::Acquire) && lock.is_readable() {
while lock.is_readable() {
buffer[pos] = unsafe { lock.read_single_unchecked() };
pos += 1;
}
@ -203,10 +190,10 @@ impl PtyHalf {
}
}
fn poll_raw(&self, eof: &AtomicBool, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
fn poll_raw(&self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
self.notify.register(cx.waker());
if eof.load(Ordering::Acquire) || self.ring.lock().is_readable() {
if self.ring.lock().is_readable() {
self.notify.remove(cx.waker());
Poll::Ready(Ok(()))
} else {
@ -234,7 +221,6 @@ impl PseudoTerminal {
master_to_slave: PtyHalf::new(),
slave_to_master: PtyHalf::new(),
size: IrqSafeRwLock::new(size),
eof: AtomicBool::new(false),
});
let master = PseudoTerminalMaster { pty: pty.clone() };
@ -288,10 +274,7 @@ impl PseudoTerminal {
}
if byte == config.chars.eof {
self.slave_to_master.notify.wake_all();
self.master_to_slave.notify.wake_all();
self.eof.store(true, Ordering::Release);
self.master_to_slave.putc(byte, true);
return;
}
@ -316,21 +299,21 @@ impl PseudoTerminal {
}
fn read_from_slave(&self, buf: &mut [u8]) -> Result<usize, Error> {
self.slave_to_master.read_raw(&self.eof, buf)
self.slave_to_master.read_raw(buf)
}
fn read_from_master(&self, buf: &mut [u8]) -> Result<usize, Error> {
let config = self.config.read();
self.master_to_slave.read(&config, &self.eof, buf)
self.master_to_slave.read(&config, buf)
}
fn poll_from_slave(&self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
self.slave_to_master.poll_raw(&self.eof, cx)
self.slave_to_master.poll_raw(cx)
}
fn poll_from_master(&self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
let config = self.config.read();
self.master_to_slave.poll_read(&config, &self.eof, cx)
self.master_to_slave.poll_read(&config, cx)
}
fn device_request(&self, req: &mut DeviceRequest) -> Result<(), Error> {
@ -402,16 +385,14 @@ impl PseudoTerminalMaster {
impl Drop for PseudoTerminalMaster {
fn drop(&mut self) {
self.pty.eof.store(true, Ordering::Release);
self.pty.master_to_slave.notify.wake_all();
self.pty.slave_to_master.notify.wake_all();
self.pty
.master_to_slave
.putc(self.pty.config.read().chars.eof, true);
}
}
impl Drop for PseudoTerminalSlave {
fn drop(&mut self) {
self.pty.eof.store(true, Ordering::Release);
self.pty.master_to_slave.notify.wake_all();
self.pty.slave_to_master.notify.wake_all();
// TODO
}
}

View File

@ -20,13 +20,14 @@ type PfnIndex = u64;
/// Metadata associated with an allocated memory region. The [Eq] trait is used to coalesce "equal"
/// regions if they "touch".
pub trait RangeData: Eq {}
pub trait RangeData: Eq + Clone {}
fn ie(from: PfnIndex, to: PfnIndex) -> InclusiveInterval<PfnIndex> {
InclusiveInterval::from(from..to)
}
/// Main virtual memory allocator
#[derive(Clone)]
pub struct VirtualMemoryAllocator<D: RangeData> {
map: DiscreteRangeMap<PfnIndex, InclusiveInterval<PfnIndex>, D>,
outer_range: InclusiveInterval<PfnIndex>,
@ -41,6 +42,14 @@ impl<D: RangeData> VirtualMemoryAllocator<D> {
}
}
/// XXX
pub fn regions(&self) -> impl Iterator<Item = (Range<usize>, &D)> {
self.map.iter().map(|(range, data)| {
let range = range.start() as usize..range.end() as usize + 1;
(range, data)
})
}
/// Allocates a contiguous range of virtual address space and associates metadata with it
pub fn allocate(&mut self, page_count: usize, data: D) -> Result<usize, Error> {
let start_pfn = self
@ -80,10 +89,7 @@ impl<D: RangeData> VirtualMemoryAllocator<D> {
start_pfn: usize,
page_count: usize,
mut release: F,
) -> Result<(), Error>
where
D: Clone,
{
) -> Result<(), Error> {
let end_pfn = (start_pfn + page_count) as PfnIndex;
let start_pfn = start_pfn as PfnIndex;
@ -94,4 +100,15 @@ impl<D: RangeData> VirtualMemoryAllocator<D> {
release(origin.start() as _, range, data)
})
}
/// XXX
pub fn clear<F: FnMut(Range<usize>, D) -> Result<(), Error>>(
&mut self,
mut release: F,
) -> Result<(), Error> {
self.map.drain().try_for_each(|(range, data)| {
let range = range.start() as usize..range.end() as usize + 1;
release(range, data)
})
}
}

View File

@ -54,11 +54,29 @@
.global __x86_64_task_enter_user
.global __x86_64_task_enter_kernel
.global __x86_64_task_enter_from_fork
.global __x86_64_enter_task
.global __x86_64_switch_task
.section .text
__x86_64_task_enter_from_fork:
xorq %rax, %rax
xorq %rcx, %rcx
xorq %r11, %r11
popq %rdi
popq %rsi
popq %rdx
popq %r10
popq %r8
popq %r9
swapgs
iretq
__x86_64_task_enter_user:
// User stack pointer
popq %rcx

View File

@ -2,9 +2,16 @@
use core::{arch::global_asm, cell::UnsafeCell};
use abi::error::Error;
use kernel_util::mem::address::{AsPhysicalAddress, IntoRaw};
use kernel_util::mem::address::{AsPhysicalAddress, IntoRaw, PhysicalAddress};
use tock_registers::interfaces::Readable;
use crate::{arch::x86_64::mem::KERNEL_TABLES, mem::phys, task::context::TaskContextImpl};
use crate::{
arch::x86_64::{mem::KERNEL_TABLES, registers::CR3},
mem::phys,
task::context::TaskContextImpl,
};
use super::syscall::SyscallFrame;
struct StackBuilder {
base: usize,
@ -77,6 +84,54 @@ impl StackBuilder {
unsafe impl Sync for TaskContext {}
impl TaskContext {
/// XXX
pub(super) unsafe fn from_syscall_frame(frame: &SyscallFrame, cr3: u64) -> Result<Self, Error> {
const USER_TASK_PAGES: usize = 8;
let stack_base = phys::alloc_pages_contiguous(USER_TASK_PAGES)?.virtualize_raw();
let mut stack = StackBuilder::new(stack_base, USER_TASK_PAGES * 0x1000);
// iretq frame
stack.push(0x1B);
stack.push(frame.user_sp as _);
stack.push(0x200);
stack.push(0x23);
stack.push(frame.user_ip as _);
stack.push(frame.args[5] as _); // r9
stack.push(frame.args[4] as _); // r8
stack.push(frame.args[3] as _); // r10
stack.push(frame.args[2] as _); // rdx
stack.push(frame.args[1] as _); // rsi
stack.push(frame.args[0] as _); // rdi
// callee-saved registers
stack.push(__x86_64_task_enter_from_fork as _);
stack.push(cr3 as _);
stack.push(frame.rbp as _);
stack.push(0x12345678); // XXX TODO: fs_base from SyscallFrame
stack.push(frame.r15 as _);
stack.push(frame.r14 as _);
stack.push(frame.r13 as _);
stack.push(frame.r12 as _);
stack.push(frame.rbx as _);
let sp = stack.build();
let rsp0 = stack_base + USER_TASK_PAGES * 0x1000;
debugln!("Fork TSS.RSP0 = {:#x}, cr3 = {:#x}", rsp0, cr3);
Ok(Self {
inner: UnsafeCell::new(Inner { sp, tss_rsp0: rsp0 }),
stack_base,
stack_size: USER_TASK_PAGES * 0x1000,
})
}
}
impl TaskContextImpl for TaskContext {
const SIGNAL_STACK_EXTRA_ALIGN: usize = 8;
const USER_STACK_EXTRA_ALIGN: usize = 8;
@ -157,6 +212,7 @@ impl TaskContextImpl for TaskContext {
extern "C" {
fn __x86_64_task_enter_kernel();
fn __x86_64_task_enter_user();
fn __x86_64_task_enter_from_fork();
fn __x86_64_enter_task(to: *mut Inner) -> !;
fn __x86_64_switch_task(to: *mut Inner, from: *mut Inner);
}

View File

@ -2,9 +2,13 @@
use core::{arch::global_asm, mem::size_of_val};
use abi::{arch::SavedFrame, primitive_enum, process::Signal};
use tock_registers::interfaces::Readable;
use crate::{
arch::{x86_64::apic, CpuAccess},
arch::{
x86_64::{apic, registers::CR3},
CpuAccess,
},
task::{context::TaskFrame, thread::Thread, Cpu},
};
@ -285,10 +289,12 @@ static mut IDT: [Entry; SIZE] = [Entry::NULL; SIZE];
fn user_exception_inner(kind: ExceptionKind, frame: &ExceptionFrame) {
let thread = Thread::current();
let cr3 = CR3.get();
warnln!("{:?} in {} {:?}", kind, thread.id, thread.name);
warnln!("CS:RIP = {:#x}:{:#x}", frame.cs, frame.rip);
warnln!("SS:RSP = {:#x}:{:#x}", frame.ss, frame.rsp);
warnln!("CR3 = {:#x}", cr3);
match kind {
ExceptionKind::PageFault => {

View File

@ -2,35 +2,51 @@
use core::arch::global_asm;
use abi::{arch::SavedFrame, process::SignalEntryData, syscall::SyscallFunction};
use abi::{arch::SavedFrame, error::Error, process::SignalEntryData, syscall::SyscallFunction};
use kernel_util::mem::address::PhysicalAddress;
use tock_registers::interfaces::{ReadWriteable, Writeable};
use crate::{
arch::x86_64::registers::{MSR_IA32_EFER, MSR_IA32_LSTAR, MSR_IA32_SFMASK, MSR_IA32_STAR},
syscall::raw_syscall_handler,
task::{context::TaskFrame, thread::Thread},
task::{
context::{ForkFrame, TaskFrame},
process::Process,
thread::Thread,
TaskContext,
},
};
/// Set of registers saved when taking a syscall instruction
#[derive(Debug)]
#[repr(C)]
struct SyscallFrame {
rax: u64,
args: [u64; 6],
pub(super) struct SyscallFrame {
pub rax: u64,
pub args: [u64; 6],
rcx: u64,
r11: u64,
pub rcx: u64,
pub r11: u64,
user_ip: u64,
user_sp: u64,
user_flags: u64,
pub user_ip: u64,
pub user_sp: u64,
pub user_flags: u64,
rbx: u64,
rbp: u64,
r12: u64,
r13: u64,
r14: u64,
r15: u64,
pub rbx: u64,
pub rbp: u64,
pub r12: u64,
pub r13: u64,
pub r14: u64,
pub r15: u64,
}
impl ForkFrame for SyscallFrame {
unsafe fn fork(&self, address_space: u64) -> Result<TaskContext, Error> {
TaskContext::from_syscall_frame(self, address_space)
}
fn set_return_value(&mut self, value: u64) {
self.rax = value;
}
}
impl TaskFrame for SyscallFrame {
@ -117,6 +133,12 @@ fn syscall_inner(frame: &mut SyscallFrame) {
return;
}
}
if frame.rax == usize::from(SyscallFunction::Fork) as u64 {
unsafe {
Process::raw_fork(frame);
return;
}
}
let result = raw_syscall_handler(frame.rax, &frame.args);

View File

@ -24,7 +24,8 @@
exact_size_is_empty,
inline_const,
maybe_uninit_uninit_array,
const_maybe_uninit_uninit_array
const_maybe_uninit_uninit_array,
never_type
)]
#![allow(
clippy::new_without_default,

View File

@ -1,15 +1,21 @@
//! Process address space structures and management functions
use core::ops::Range;
use abi::error::Error;
use cfg_if::cfg_if;
use kernel_util::{
mem::{table::EntryLevelExt, PageProvider},
mem::{
pointer::{PhysicalRef, PhysicalRefMut},
table::EntryLevelExt,
PageProvider,
},
sync::IrqSafeSpinlock,
};
use vfs::FileRef;
use vmalloc::{RangeData, VirtualMemoryAllocator};
use crate::{arch::L3, mem::phys};
use crate::{arch::L3, debug, mem::phys};
use super::{table::MapAttributes, PhysicalAddress};
@ -252,6 +258,70 @@ impl Inner {
Ok(())
}
unsafe fn clear(&mut self) -> Result<(), Error> {
self.allocator.clear(|pfn_range, backing| {
let origin_pfn = pfn_range.start;
for pfn in pfn_range {
let offset = ((pfn - origin_pfn) * ProcessAddressSpaceImpl::PAGE_SIZE) as u64;
let virt = pfn * ProcessAddressSpaceImpl::PAGE_SIZE;
let phys = unsafe { self.table.unmap_page(virt)? };
backing.release_page(offset, phys)?;
}
Ok(())
})
}
fn clone_range(
&mut self,
source: &Self,
pfn_range: Range<usize>,
backing: &VirtualRangeBacking,
) -> Result<(), Error> {
self.allocator
.insert(pfn_range.start, pfn_range.len(), backing.clone())
.unwrap();
match backing {
VirtualRangeBacking::Anonymous => {
let start = pfn_range.start * ProcessAddressSpaceImpl::PAGE_SIZE;
let end = pfn_range.end * ProcessAddressSpaceImpl::PAGE_SIZE;
debugln!("fork: clone_range({:#x?})", start..end);
// Clone the data
for i in pfn_range {
let address = i * ProcessAddressSpaceImpl::PAGE_SIZE;
let (src_page, attrs) = source.table.translate(address).unwrap();
let dst_page = clone_page(address, src_page)?;
unsafe {
self.table
.map_page(
address,
dst_page,
MapAttributes::USER_READ
| MapAttributes::USER_WRITE
| MapAttributes::NON_GLOBAL,
)
.unwrap();
}
}
Ok(())
}
VirtualRangeBacking::File(f) => {
todo!();
}
}
}
}
fn clone_page(src_virt: usize, src_phys: PhysicalAddress) -> Result<PhysicalAddress, Error> {
let dst_page = phys::alloc_page()?;
let src_map = unsafe { PhysicalRef::<[u8; 4096]>::map(src_phys) };
let mut dst_map = unsafe { PhysicalRefMut::<[u8; 4096]>::map(dst_page) };
dst_map.copy_from_slice(src_map.as_ref());
Ok(dst_page)
}
impl ProcessAddressSpace {
@ -267,6 +337,37 @@ impl ProcessAddressSpace {
})
}
/// XXX
pub fn fork(&self) -> Result<Self, Error> {
let src_inner = self.inner.lock();
let new_table = ProcessAddressSpaceImpl::new()?;
let mut new_inner = Inner {
allocator: VirtualMemoryAllocator::new(
ProcessAddressSpaceImpl::LOWER_LIMIT_PFN,
ProcessAddressSpaceImpl::UPPER_LIMIT_PFN,
),
table: new_table,
};
debugln!("fork address space!");
for (range, backing) in src_inner.allocator.regions() {
// If they are present in existing allocator, there should be no
// problem adding them to a new one
new_inner.clone_range(&src_inner, range, backing)?;
}
for (range, _) in new_inner.allocator.regions() {
let start = range.start * ProcessAddressSpaceImpl::PAGE_SIZE;
let end = range.end * ProcessAddressSpaceImpl::PAGE_SIZE;
debugln!("forked region: {:#x?}", start..end);
}
Ok(Self {
inner: IrqSafeSpinlock::new(new_inner),
})
}
/// Allocates a region of virtual memory within the address space and maps the pages to the
/// ones returned from `get_page` function
pub fn allocate(
@ -350,4 +451,10 @@ impl ProcessAddressSpace {
pub fn as_address_with_asid(&self) -> u64 {
self.inner.lock().table.as_address_with_asid()
}
/// XXX
pub fn clear(&self) -> Result<(), Error> {
let mut inner = self.inner.lock();
unsafe { inner.clear() }
}
}

View File

@ -8,7 +8,7 @@ use abi::{
path::Path,
process::ProgramArgumentInner,
};
use alloc::{string::String, sync::Arc, vec::Vec};
use alloc::{borrow::ToOwned, string::String, sync::Arc, vec::Vec};
use kernel_util::mem::pointer::PhysicalRefMut;
use vfs::{FileRef, IoContext, Read, Seek};
@ -86,8 +86,8 @@ unsafe impl<'a> Placer for BufferPlacer<'a> {
fn setup_program_env(
space: &ProcessAddressSpace,
virt: usize,
args: &[&str],
env: &[&str],
args: &Vec<String>,
envs: &Vec<String>,
) -> Result<usize, Error> {
// TODO growing buffer
let phys_page = space.map_single(
@ -98,19 +98,24 @@ fn setup_program_env(
let mut buffer = unsafe { PhysicalRefMut::map_slice(phys_page, 4096) };
let mut placer = BufferPlacer::new(virt, &mut buffer);
let in_kernel = ProgramArgumentInner { args, env };
let args = args.iter().map(String::as_ref).collect::<Vec<_>>();
let envs = envs.iter().map(String::as_ref).collect::<Vec<_>>();
let in_kernel = ProgramArgumentInner {
args: &args,
env: &envs,
};
let in_user = in_kernel.place_ref(&mut placer)?;
Ok(in_user as *const _ as usize)
}
fn setup_binary<S: Into<String>>(
name: S,
space: ProcessAddressSpace,
image: ProcessImage,
args: &[&str],
envs: &[&str],
) -> Result<(Arc<Process>, Arc<Thread>), Error> {
fn setup_context(
space: &ProcessAddressSpace,
image: &ProcessImage,
args: &Vec<String>,
envs: &Vec<String>,
) -> Result<TaskContext, Error> {
const USER_STACK_PAGES: usize = 16;
let virt_stack_base = 0x3000000;
@ -148,18 +153,25 @@ fn setup_binary<S: Into<String>>(
let tls_address = proc::elf::clone_tls(&space, &image)?;
let context = TaskContext::user(
TaskContext::user(
image.entry,
arg,
space.as_address_with_asid(),
user_sp,
tls_address,
)?;
)
}
fn setup_binary<S: Into<String>>(
name: S,
space: ProcessAddressSpace,
image: ProcessImage,
args: &Vec<String>,
envs: &Vec<String>,
) -> Result<(Arc<Process>, Arc<Thread>), Error> {
let context = setup_context(&space, &image, args, envs)?;
let (process, main) = Process::new_with_main(name, Arc::new(space), context, Some(image));
infoln!("exec::setup_binary -> {:?}", process.id());
Ok((process, main))
}
@ -175,13 +187,13 @@ fn load_binary(
}
}
/// Loads a program from given `path`
pub fn load<P: AsRef<Path>>(
fn xxx_load_program<P: AsRef<Path>>(
space: &ProcessAddressSpace,
ioctx: &mut IoContext,
path: P,
args: &[&str],
envs: &[&str],
) -> Result<(Arc<Process>, Arc<Thread>), Error> {
args: Vec<String>,
envs: Vec<String>,
) -> Result<(ProcessImage, Vec<String>, Vec<String>), Error> {
let mut head = [0; 256];
let path = path.as_ref();
let file = ioctx.open_exec(None, path)?;
@ -196,19 +208,52 @@ pub fn load<P: AsRef<Path>>(
&& let Some((shebang, _)) = shebang.split_once(|&ch| ch == b'\n')
{
let shebang = core::str::from_utf8(shebang).map_err(|_| Error::InvalidFile)?;
let mut shebang_args = shebang.split(' ').collect::<Vec<_>>();
let mut shebang_args = shebang.split(' ').map(|s| s.to_owned()).collect::<Vec<_>>();
if shebang_args.is_empty() || shebang_args.len() >= 8 {
return Err(Error::UnrecognizedExecutable);
}
shebang_args.extend_from_slice(args);
shebang_args.extend_from_slice(&args);
return load(ioctx, shebang_args[0], &shebang_args, envs);
return xxx_load_program(space, ioctx, shebang_args[0].clone(), shebang_args, envs);
}
file.seek(SeekFrom::Start(0))?;
let space = ProcessAddressSpace::new()?;
let image = load_binary(head, file, &space)?;
setup_binary(path.display(), space, image, args, envs)
Ok((image, args, envs))
}
/// Loads a program from given `path`
pub fn load<P: AsRef<Path>>(
ioctx: &mut IoContext,
path: P,
args: &[&str],
envs: &[&str],
) -> Result<(Arc<Process>, Arc<Thread>), Error> {
let path = path.as_ref();
let args = args.into_iter().map(|&s| s.to_owned()).collect();
let envs = envs.into_iter().map(|&s| s.to_owned()).collect();
let space = ProcessAddressSpace::new()?;
let (image, args, envs) = xxx_load_program(&space, ioctx, path, args, envs)?;
setup_binary(path.display(), space, image, &args, &envs)
}
/// XXX
pub fn load_into<P: AsRef<Path>>(
process: &Process,
path: P,
args: Vec<String>,
envs: Vec<String>,
) -> Result<(TaskContext, ProcessImage), Error> {
let mut io = process.io.lock();
// Have to make the Path owned, going to drop the address space from which it came
let path = path.as_ref().to_owned();
let space = process.space();
space.clear()?;
let (image, args, envs) = xxx_load_program(&space, io.ioctx(), &path, args, envs)?;
let context = setup_context(&space, &image, &args, &envs)?;
Ok((context, image))
}

View File

@ -9,10 +9,13 @@ use abi::{
TerminalSize,
},
mem::MappingSource,
process::{ExitCode, MutexOperation, Signal, SpawnOption, SpawnOptions, ThreadSpawnOptions},
process::{
ExecveOptions, ExitCode, MutexOperation, Signal, SpawnOption, SpawnOptions,
ThreadSpawnOptions,
},
syscall::SyscallFunction,
};
use alloc::{boxed::Box, sync::Arc};
use alloc::{borrow::ToOwned, boxed::Box, sync::Arc, vec::Vec};
use kernel_util::{block, mem::table::EntryLevelExt, runtime, sync::IrqSafeSpinlockGuard};
use vfs::{File, IoContext, MessagePayload, NodeRef, Read, Seek, Write};
use yggdrasil_abi::{error::SyscallResult, io::MountOptions};
@ -25,7 +28,7 @@ use crate::{
proc::{self, io::ProcessIo, random},
task::{
process::{Process, ProcessId},
thread::Thread,
thread::{CurrentThread, Thread},
},
};
@ -57,6 +60,23 @@ fn run_with_io_at<
f(at, io)
}
fn sys_execve(thread: CurrentThread, options: &ExecveOptions) -> Result<!, Error> {
let process = thread.process();
// Clone the options into the kernel
let mut argv = Vec::new();
let mut envp = Vec::new();
for &arg in options.arguments {
argv.push(arg.to_owned());
}
for &env in options.environment {
envp.push(env.to_owned());
}
process.exec(options.program, argv, envp)
}
fn syscall_handler(func: SyscallFunction, args: &[u64]) -> Result<usize, Error> {
let thread = Thread::current();
let process = thread.process();
@ -448,6 +468,23 @@ fn syscall_handler(func: SyscallFunction, args: &[u64]) -> Result<usize, Error>
Ok(0)
})
}
SyscallFunction::CloneFd => {
let source_fd = RawFd::from(args[0] as u32);
let target_fd = RawFd::from(args[1] as u32);
run_with_io(process, |mut io| {
let file = io.files.file(source_fd)?.clone();
let fd = if target_fd != RawFd::NONE {
io.files.set_file(target_fd, file)?;
target_fd
} else {
io.files.place_file(file, true)?
};
Ok(fd.0 as usize)
})
}
// Process management
SyscallFunction::SpawnProcess => {
let options = arg_user_ref::<SpawnOptions>(args[0] as usize)?;
@ -507,6 +544,10 @@ fn syscall_handler(func: SyscallFunction, args: &[u64]) -> Result<usize, Error>
Ok(pid as _)
})
}
SyscallFunction::Exec => {
let options = arg_user_ref::<ExecveOptions>(args[0] as usize)?;
sys_execve(thread, options)?;
}
SyscallFunction::SpawnThread => {
let options = arg_user_ref::<ThreadSpawnOptions>(args[0] as usize)?;
let id = process.spawn_thread(options)?;
@ -602,6 +643,8 @@ fn syscall_handler(func: SyscallFunction, args: &[u64]) -> Result<usize, Error>
Ok(0)
}
_ => unreachable!(),
}
}
@ -619,5 +662,11 @@ pub fn raw_syscall_handler(func: u64, args: &[u64]) -> u64 {
// func,
// args
// );
syscall_handler(func, args).into_syscall_result() as u64
let result = syscall_handler(func, args);
if let &Err(error) = &result {
warnln!("{:?}: {:?}", func, error);
}
result.into_syscall_result() as u64
}

View File

@ -3,7 +3,7 @@
use abi::{arch::SavedFrame, error::Error};
use alloc::boxed::Box;
use cfg_if::cfg_if;
use kernel_util::thread::Termination;
use kernel_util::{mem::address::PhysicalAddress, thread::Termination};
use crate::task::thread::Thread;
@ -47,6 +47,15 @@ pub trait TaskFrame {
fn user_ip(&self) -> usize;
}
/// XXX
pub trait ForkFrame: Sized {
/// XXX
unsafe fn fork(&self, address_space: u64) -> Result<TaskContext, Error>;
/// XXX
fn set_return_value(&mut self, value: u64);
}
/// Platform-specific task context implementation
pub trait TaskContextImpl: Sized {
/// Number of bytes to offset the signal stack pointer by

View File

@ -9,21 +9,25 @@ use core::{
};
use abi::{
error::Error,
error::{Error, SyscallResult},
process::{ExitCode, Signal, ThreadSpawnOptions},
};
use alloc::{collections::BTreeMap, string::String, sync::Arc, vec::Vec};
use futures_util::Future;
use kernel_util::{runtime::QueueWaker, sync::IrqSafeSpinlock};
use vfs::NodeRef;
use kernel_util::{
runtime::QueueWaker,
sync::{spin_rwlock::IrqSafeRwLock, IrqSafeSpinlock},
};
use vfs::{IoContext, NodeRef};
use crate::{
mem::process::ProcessAddressSpace,
proc::{self, io::ProcessIo},
task::context::TaskContextImpl,
task::{context::TaskContextImpl, sched::CpuQueue},
};
use super::{
context::ForkFrame,
sync::UserspaceMutex,
thread::{Thread, ThreadId},
TaskContext,
@ -52,7 +56,7 @@ pub struct ProcessId(u64);
// | ??? | Data .....|
/// Describes Thread-Local Storage of a process
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct ProcessTlsInfo {
/// Location of the TLS master copy within the process's memory
pub master_copy_base: usize,
@ -61,7 +65,7 @@ pub struct ProcessTlsInfo {
}
/// Describes TLS layout for a program image
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct ProcessTlsLayout {
/// Data offset from the TLS base
pub data_offset: usize,
@ -126,6 +130,7 @@ impl ProcessTlsLayout {
}
/// Describes information about a program's image in memory
#[derive(Clone)]
pub struct ProcessImage {
/// Entry point address
pub entry: usize,
@ -142,6 +147,9 @@ struct ProcessInner {
session_terminal: Option<NodeRef>,
threads: Vec<Arc<Thread>>,
mutexes: BTreeMap<usize, Arc<UserspaceMutex>>,
space: Arc<ProcessAddressSpace>,
image: Option<ProcessImage>,
}
/// Describes a process within the system
@ -149,9 +157,7 @@ pub struct Process {
name: String,
id: ProcessId,
space: Arc<ProcessAddressSpace>,
inner: IrqSafeSpinlock<ProcessInner>,
image: Option<ProcessImage>,
inner: IrqSafeRwLock<ProcessInner>,
exit_waker: QueueWaker,
/// Process I/O information
@ -176,16 +182,16 @@ impl Process {
name,
id,
image,
space: space.clone(),
inner: IrqSafeSpinlock::new(ProcessInner {
inner: IrqSafeRwLock::new(ProcessInner {
state: ProcessState::Running,
session_id: id,
group_id: id,
session_terminal: None,
threads: Vec::new(),
mutexes: BTreeMap::new(),
image,
space: space.clone(),
}),
exit_waker: QueueWaker::new(),
@ -194,7 +200,7 @@ impl Process {
// Create "main" thread
let thread = Thread::new_uthread(process.clone(), space, context);
process.inner.lock().threads.push(thread.clone());
process.inner.write().threads.push(thread.clone());
PROCESSES.lock().insert(id, process.clone());
@ -208,14 +214,15 @@ impl Process {
self.id(),
options
);
let mut inner = self.inner.write();
let tls_address = if let Some(image) = self.image.as_ref() {
proc::elf::clone_tls(&self.space, image)?
let tls_address = if let Some(image) = inner.image.as_ref() {
proc::elf::clone_tls(&inner.space, image)?
} else {
0
};
let space = self.space.clone();
let space = inner.space.clone();
let context = TaskContext::user(
options.entry as _,
options.argument as _,
@ -226,13 +233,81 @@ impl Process {
let thread = Thread::new_uthread(self.clone(), space, context);
let id = thread.id;
self.inner.lock().threads.push(thread.clone());
inner.threads.push(thread.clone());
thread.enqueue();
Ok(id)
}
unsafe fn fork_inner<F: ForkFrame>(&self, frame: &F) -> Result<ProcessId, Error> {
let src_inner = self.inner.read();
let new_space = src_inner.space.fork()?;
let new_context = frame.fork(new_space.as_address_with_asid())?;
let (new_process, new_main) =
Self::new_with_main(self.name(), Arc::new(new_space), new_context, None);
{
let mut src_io = self.io.lock();
let new_ioctx = IoContext::inherit(src_io.ioctx());
let mut new_io = new_process.io.lock();
new_io.set_ioctx(new_ioctx);
for (old_fd, old_file) in src_io.files.iter() {
let new_file = old_file.send()?;
new_io.files.set_file(*old_fd, new_file)?;
}
}
new_process.inherit(self)?;
infoln!("Process::fork -> {:?}", new_process.id());
new_main.enqueue_to(CpuQueue::for_cpu(1));
Ok(new_process.id())
}
/// XXX
pub unsafe fn raw_fork<F: ForkFrame>(frame: &mut F) {
let src_thread = Thread::current();
let src_process = src_thread.process();
let rax = src_process
.fork_inner(frame)
.map(|ProcessId(p)| p as u32)
.into_syscall_result();
frame.set_return_value(rax as _);
}
/// XXX
pub fn exec(&self, program: &str, argv: Vec<String>, envp: Vec<String>) -> Result<!, Error> {
if self.inner.read().threads.len() != 1 {
todo!();
}
let (context, image) = proc::exec::load_into(self, program, argv, envp)?;
let mut inner = self.inner.write();
let main = &inner.threads[0];
let old_context = unsafe { main.replace_context(context) };
let new_context = main.context.as_ptr();
inner.image = Some(image);
drop(inner);
// TODO old context is leaked
unsafe { (&*new_context).switch(&old_context) }
unreachable!()
}
/// XXX
pub fn space(&self) -> Arc<ProcessAddressSpace> {
self.inner.read().space.clone()
}
/// Returns the [ProcessId] of this process
pub fn id(&self) -> ProcessId {
self.id
@ -240,12 +315,12 @@ impl Process {
/// Returns the process group ID of the process
pub fn group_id(&self) -> ProcessId {
self.inner.lock().group_id
self.inner.read().group_id
}
/// Returns the process session ID of the process
pub fn session_id(&self) -> ProcessId {
self.inner.lock().session_id
self.inner.read().session_id
}
/// Returns the process name
@ -255,35 +330,35 @@ impl Process {
/// Changes the process's group ID
pub fn set_group_id(&self, id: ProcessId) {
self.inner.lock().group_id = id;
self.inner.write().group_id = id;
}
/// Changes the process's session ID
pub fn set_session_id(&self, id: ProcessId) {
self.inner.lock().session_id = id;
self.inner.write().session_id = id;
}
// Resources
/// Returns the current session terminal of the process, if set
pub fn session_terminal(&self) -> Option<NodeRef> {
self.inner.lock().session_terminal.clone()
self.inner.read().session_terminal.clone()
}
/// Changes the current session terminal of the process
pub fn set_session_terminal(&self, node: NodeRef) {
self.inner.lock().session_terminal.replace(node);
self.inner.write().session_terminal.replace(node);
}
/// Resets the current session terminal of the process
pub fn clear_session_terminal(&self) -> Option<NodeRef> {
self.inner.lock().session_terminal.take()
self.inner.write().session_terminal.take()
}
/// Inherits the process information from the `parent`
pub fn inherit(&self, parent: &Process) -> Result<(), Error> {
let mut our_inner = self.inner.lock();
let their_inner = parent.inner.lock();
let mut our_inner = self.inner.write();
let their_inner = parent.inner.read();
our_inner.session_id = their_inner.session_id;
our_inner.group_id = their_inner.group_id;
@ -296,7 +371,7 @@ impl Process {
/// Returns the [ExitCode] of the process, if it has exited
pub fn get_exit_status(&self) -> Option<ExitCode> {
match self.inner.lock().state {
match self.inner.read().state {
ProcessState::Running => None,
ProcessState::Terminated(x) => Some(x),
}
@ -331,7 +406,7 @@ impl Process {
/// Handles exit of a single child thread
pub fn handle_thread_exit(&self, thread: ThreadId, code: ExitCode) {
debugln!("Thread {} of process {}: {:?}", thread, self.id, code);
let mut inner = self.inner.lock();
let mut inner = self.inner.write();
// TODO make this cleaner
let old_len = inner.threads.len();
@ -356,7 +431,7 @@ impl Process {
/// Raises a signal for the specified process
pub fn raise_signal(self: &Arc<Self>, signal: Signal) {
let thread = self.inner.lock().threads[0].clone();
let thread = self.inner.read().threads[0].clone();
thread.raise_signal(signal);
}
@ -364,7 +439,7 @@ impl Process {
pub fn signal_group(group_id: ProcessId, signal: Signal) {
let processes = PROCESSES.lock();
for (_, proc) in processes.iter() {
let inner = proc.inner.lock();
let inner = proc.inner.read();
if !matches!(inner.state, ProcessState::Terminated(_)) && inner.group_id == group_id {
debugln!("Deliver group signal to {}: {:?}", proc.id(), signal);
drop(inner);
@ -376,7 +451,7 @@ impl Process {
/// Returns a [UserspaceMutex] associated with the `address`. If one does not exist, will
/// create it.
pub fn get_or_insert_mutex(&self, address: usize) -> Arc<UserspaceMutex> {
let mut inner = self.inner.lock();
let mut inner = self.inner.write();
inner
.mutexes
.entry(address)
@ -393,7 +468,7 @@ impl Process {
/// Terminates all children of the process, `except` one
pub async fn terminate_others(&self, except: ThreadId) {
let mut inner = self.inner.lock();
let mut inner = self.inner.write();
for thread in inner.threads.iter() {
if thread.id == except {

View File

@ -1,5 +1,7 @@
//! Per-CPU queue implementation
use core::cell::Cell;
use alloc::{sync::Arc, vec::Vec};
use cfg_if::cfg_if;
use crossbeam_queue::SegQueue;
@ -20,7 +22,7 @@ use super::{
pub struct CpuQueue {
queue: SegQueue<ThreadId>,
index: usize,
idle: TaskContext,
idle: Cell<TaskContext>,
}
impl CpuQueue {
@ -32,7 +34,7 @@ impl CpuQueue {
Self {
queue: SegQueue::new(),
index,
idle,
idle: Cell::new(idle),
}
}
@ -50,7 +52,7 @@ impl CpuQueue {
pub unsafe fn enter(&self) -> ! {
let _guard = IrqGuard::acquire();
self.idle.enter()
(&*self.idle.as_ptr()).enter()
}
/// "Pushes" a thread to the queue for execution
@ -151,9 +153,12 @@ impl CpuQueue {
cpu.set_current_thread_id(next_id);
let current_ctx = current_ctx.as_ptr();
let next_ctx = next_ctx.as_ptr();
if !core::ptr::eq(current_ctx, next_ctx) {
// Perform the switch
next_ctx.switch(current_ctx);
(&*next_ctx).switch(&*current_ctx);
true
} else {

View File

@ -3,6 +3,7 @@
//! Thread data structures and management
use core::{
cell::Cell,
fmt,
mem::size_of,
ops::Deref,
@ -91,7 +92,7 @@ pub struct Thread {
pub id: ThreadId,
pub name: Option<String>,
pub sched: IrqSafeSpinlock<ThreadSchedulingInfo>,
pub context: TaskContext,
pub context: Cell<TaskContext>,
process: Option<Arc<Process>>,
space: Option<Arc<ProcessAddressSpace>>,
@ -103,6 +104,8 @@ pub struct Thread {
pub affinity: ThreadAffinity,
}
unsafe impl Sync for Thread {}
impl GlobalThreadList {
pub const fn new() -> Self {
Self {
@ -161,7 +164,7 @@ impl Thread {
in_queue: false,
queue: None,
}),
context,
context: Cell::new(context),
process,
space,
@ -203,6 +206,10 @@ impl Thread {
)
}
pub unsafe fn replace_context(&self, context: TaskContext) -> TaskContext {
self.context.replace(context)
}
// Get/Set
/// Updates the thread affinity to run on a specific set of CPUs
@ -432,7 +439,8 @@ impl CurrentThread {
let inner = self.inner.lock();
let Some(entry) = inner.signal_entry.as_ref() else {
todo!();
drop(inner);
self.exit_process(ExitCode::BySignal(signal));
};
// TODO check if really in a syscall, lol