char/pipe: implement pipes

This commit is contained in:
Mark Poliakov 2023-12-14 18:45:56 +02:00
parent 2efa5b8ff4
commit 6848f6c56d
6 changed files with 288 additions and 8 deletions

View File

@ -13,6 +13,7 @@ kernel-util = { path = "../kernel-util" }
ygg_driver_block = { path = "../../driver/block/core" }
log = "0.4.20"
futures-util = { version = "0.3.28", default-features = false, features = ["alloc", "async-await"] }
[dev-dependencies]
hosted-tests = { path = "../hosted-tests" }

View File

@ -16,11 +16,13 @@ use crate::{
use self::{
device::{BlockFile, CharFile},
directory::DirectoryFile,
pipe::PipeEnd,
regular::RegularFile,
};
mod device;
mod directory;
mod pipe;
mod regular;
/// Per-file optional instance data created when a regular file is opened
@ -45,9 +47,19 @@ pub enum File {
Regular(RegularFile),
Block(BlockFile),
Char(CharFile),
AnonymousPipe(PipeEnd),
}
impl File {
/// Constructs a pipe pair, returning its `(read, write)` ends
pub fn new_pipe_pair(capacity: usize) -> (Arc<Self>, Arc<Self>) {
let (read, write) = PipeEnd::new_pair(capacity);
(
Arc::new(Self::AnonymousPipe(read)),
Arc::new(Self::AnonymousPipe(write)),
)
}
pub(crate) fn directory(node: NodeRef, position: DirectoryOpenPosition) -> Arc<Self> {
let position = IrqSafeSpinlock::new(position.into());
Arc::new(Self::Directory(DirectoryFile { node, position }))
@ -133,6 +145,7 @@ impl File {
Self::Regular(file) => Some(&file.node),
Self::Block(file) => Some(&file.node),
Self::Char(file) => Some(&file.node),
Self::AnonymousPipe(_) => None,
}
}
}
@ -143,6 +156,7 @@ impl Read for File {
Self::Regular(file) => file.read(buf),
Self::Block(file) => file.read(buf),
Self::Char(file) => file.read(buf),
Self::AnonymousPipe(pipe) => pipe.read(buf),
Self::Directory(_) => Err(Error::IsADirectory),
}
}
@ -154,6 +168,7 @@ impl Write for File {
Self::Regular(file) => file.write(buf),
Self::Block(file) => file.write(buf),
Self::Char(file) => file.write(buf),
Self::AnonymousPipe(pipe) => pipe.write(buf),
Self::Directory(_) => Err(Error::IsADirectory),
}
}
@ -164,7 +179,7 @@ impl Seek for File {
match self {
Self::Regular(file) => Ok(*file.position.lock()),
Self::Block(file) => Ok(*file.position.lock()),
Self::Char(_) => Err(Error::InvalidOperation),
Self::Char(_) | Self::AnonymousPipe(_) => Err(Error::InvalidOperation),
Self::Directory(_) => Err(Error::IsADirectory),
}
}
@ -173,7 +188,7 @@ impl Seek for File {
match self {
Self::Regular(file) => file.seek(from),
Self::Block(file) => file.seek(from),
Self::Char(_) => Err(Error::InvalidOperation),
Self::Char(_) | Self::AnonymousPipe(_) => Err(Error::InvalidOperation),
Self::Directory(_) => Err(Error::IsADirectory),
}
}
@ -200,6 +215,7 @@ impl fmt::Debug for File {
.field("write", &file.write)
.finish_non_exhaustive(),
Self::Directory(_) => f.debug_struct("DirectoryFile").finish_non_exhaustive(),
Self::AnonymousPipe(_) => f.debug_struct("AnonymousPipe").finish_non_exhaustive(),
}
}
}
@ -210,6 +226,7 @@ mod tests {
use std::sync::{Arc, Mutex};
use kernel_util::sync::IrqSafeSpinlock;
use ygg_driver_block::BlockDevice;
use yggdrasil_abi::{
error::Error,
io::{DirectoryEntry, FileType, OpenOptions, SeekFrom},
@ -217,7 +234,7 @@ mod tests {
};
use crate::{
device::{BlockDevice, CharDevice},
device::CharDevice,
file::DirectoryOpenPosition,
impls::const_value_node,
node::{AccessToken, CommonImpl, DirectoryImpl, Node, NodeFlags, NodeRef, RegularImpl},

226
lib/vfs/src/file/pipe.rs Normal file
View File

@ -0,0 +1,226 @@
use core::{
pin::Pin,
sync::atomic::{AtomicBool, Ordering},
task::{Context, Poll},
};
use alloc::{sync::Arc, vec, vec::Vec};
use futures_util::{task::AtomicWaker, Future};
use kernel_util::{block, sync::IrqSafeSpinlock};
use yggdrasil_abi::error::Error;
struct PipeInner {
data: Vec<u8>,
capacity: usize,
rd: usize,
wr: usize,
}
pub struct Pipe {
inner: IrqSafeSpinlock<PipeInner>,
shutdown: AtomicBool,
read_notify: AtomicWaker,
write_notify: AtomicWaker,
}
pub enum PipeEnd {
Read(Arc<Pipe>),
Write(Arc<Pipe>),
}
impl PipeInner {
pub fn new(capacity: usize) -> Self {
Self {
data: vec![0; capacity],
capacity,
rd: 0,
wr: 0,
}
}
pub fn can_write(&self) -> bool {
(self.wr + 1) % self.capacity != self.rd
}
pub fn can_read(&self) -> bool {
self.rd != self.wr
}
pub unsafe fn write(&mut self, val: u8) {
self.data[self.wr] = val;
self.wr = (self.wr + 1) % self.capacity;
}
pub unsafe fn read(&mut self) -> u8 {
let val = self.data[self.rd];
self.rd = (self.rd + 1) % self.capacity;
val
}
fn try_write(&mut self, val: u8) -> bool {
if self.can_write() {
unsafe {
self.write(val);
}
true
} else {
false
}
}
fn try_read(&mut self) -> Option<u8> {
if self.can_read() {
Some(unsafe { self.read() })
} else {
None
}
}
}
impl Pipe {
pub fn new(capacity: usize) -> Self {
Self {
inner: IrqSafeSpinlock::new(PipeInner::new(capacity)),
shutdown: AtomicBool::new(false),
read_notify: AtomicWaker::new(),
write_notify: AtomicWaker::new(),
}
}
pub fn shutdown(&self) {
self.shutdown.store(true, Ordering::Release);
self.read_notify.wake();
self.write_notify.wake();
}
pub fn blocking_write(&self, val: u8) -> impl Future<Output = Result<(), Error>> + '_ {
struct F<'a> {
pipe: &'a Pipe,
val: u8,
}
impl<'a> Future for F<'a> {
type Output = Result<(), Error>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut lock = self.pipe.inner.lock();
// Try fast path before acquiring write notify to avoid unnecessary contention
if self.pipe.shutdown.load(Ordering::Acquire) {
// TODO BrokenPipe
return Poll::Ready(Err(Error::ReadOnly));
} else if lock.try_write(self.val) {
self.pipe.read_notify.wake();
return Poll::Ready(Ok(()));
}
self.pipe.write_notify.register(cx.waker());
if self.pipe.shutdown.load(Ordering::Acquire) {
Poll::Ready(Err(Error::ReadOnly))
} else if lock.try_write(self.val) {
self.pipe.read_notify.wake();
Poll::Ready(Ok(()))
} else {
Poll::Pending
}
}
}
F { pipe: self, val }
}
pub fn blocking_read(&self) -> impl Future<Output = Option<u8>> + '_ {
struct F<'a> {
pipe: &'a Pipe,
}
impl<'a> Future for F<'a> {
type Output = Option<u8>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut lock = self.pipe.inner.lock();
if let Some(val) = lock.try_read() {
self.pipe.write_notify.wake();
return Poll::Ready(Some(val));
} else if self.pipe.shutdown.load(Ordering::Acquire) {
return Poll::Ready(None);
}
self.pipe.read_notify.register(cx.waker());
if let Some(val) = lock.try_read() {
Poll::Ready(Some(val))
} else if self.pipe.shutdown.load(Ordering::Acquire) {
Poll::Ready(None)
} else {
Poll::Pending
}
}
}
F { pipe: self }
}
}
impl PipeEnd {
pub fn new_pair(capacity: usize) -> (PipeEnd, PipeEnd) {
let pipe = Arc::new(Pipe::new(capacity));
let read = PipeEnd::Read(pipe.clone());
let write = PipeEnd::Write(pipe);
(read, write)
}
pub fn read(&self, buf: &mut [u8]) -> Result<usize, Error> {
let PipeEnd::Read(read) = self else {
return Err(Error::InvalidOperation);
};
block! {
let mut pos = 0;
let mut rem = buf.len();
while rem != 0 {
if let Some(val) = read.blocking_read().await {
buf[pos] = val;
pos += 1;
rem -= 1;
} else {
break;
}
}
Ok(pos)
}?
}
pub fn write(&self, buf: &[u8]) -> Result<usize, Error> {
let PipeEnd::Write(write) = self else {
return Err(Error::InvalidOperation);
};
block! {
let mut pos = 0;
let mut rem = buf.len();
while rem != 0 {
write.blocking_write(buf[pos]).await?;
pos += 1;
rem -= 1;
}
Ok(pos)
}?
}
}
impl Drop for PipeEnd {
fn drop(&mut self) {
match self {
Self::Read(read) => read.shutdown(),
Self::Write(write) => write.shutdown(),
}
}
}

View File

@ -62,12 +62,20 @@ impl ProcessIo {
/// Removes and closes a [FileRef] from the struct
pub fn close_file(&mut self, fd: RawFd) -> Result<(), Error> {
// Do nothing, file will be dropped and closed
self.files.remove(&fd).ok_or(Error::InvalidFile)?;
Ok(())
if self.files.remove(&fd).is_some() {
Ok(())
} else {
Err(Error::InvalidFile)
}
}
/// Removes all [FileRef]s from the struct which do not pass the `predicate` check
pub fn retain<F: Fn(&RawFd, &mut FileRef) -> bool>(&mut self, predicate: F) {
self.files.retain(predicate);
}
/// Handles process exit by closing all of its files
pub fn handle_exit(&mut self) {
self.files.clear();
}
}

View File

@ -9,7 +9,7 @@ use abi::{
};
use alloc::sync::Arc;
use kernel_util::{block, runtime, sync::IrqSafeSpinlockGuard};
use vfs::{IoContext, NodeRef, Read, Seek, Write};
use vfs::{File, IoContext, NodeRef, Read, Seek, Write};
use yggdrasil_abi::{error::SyscallResult, io::MountOptions};
use crate::{
@ -188,7 +188,17 @@ fn syscall_handler(func: SyscallFunction, args: &[u64]) -> Result<usize, Error>
let fd = RawFd(args[0] as u32);
run_with_io(process, |mut io| {
io.close_file(fd)?;
let res = io.close_file(fd);
match res {
Err(Error::InvalidFile) => {
warnln!("Double close of fd {:?} in process {}", fd, process.id());
}
_ => (),
}
res?;
Ok(0)
})
}
@ -262,6 +272,24 @@ fn syscall_handler(func: SyscallFunction, args: &[u64]) -> Result<usize, Error>
SyscallFunction::RemoveDirectory => {
todo!()
}
SyscallFunction::CreatePipe => {
let ends: &mut [MaybeUninit<RawFd>; 2] = arg_user_mut(args[0] as usize)?;
run_with_io(process, |mut io| {
let (read, write) = File::new_pipe_pair(256);
let read_fd = io.place_file(read)?;
let write_fd = io.place_file(write)?;
infoln!("Read end: {:?}", read_fd);
infoln!("Write end: {:?}", write_fd);
ends[0].write(read_fd);
ends[1].write(write_fd);
Ok(0)
})
}
// Process management
SyscallFunction::SpawnProcess => {
let options = arg_user_ref::<SpawnOptions>(args[0] as usize)?;

View File

@ -346,7 +346,7 @@ impl Process {
inner.state = ProcessState::Terminated(code);
// XXX
// self.io.lock().handle_exit();
self.io.lock().handle_exit();
drop(inner);