feature: simple select()

This commit is contained in:
Mark Poliakov 2021-11-12 16:00:13 +02:00
parent 34dbd9d902
commit 496f14d391
13 changed files with 283 additions and 21 deletions

View File

@ -87,6 +87,13 @@ fn impl_inode_fn<T: ToTokens>(name: &str, behavior: T) -> ImplItem {
#behavior
}
},
"is_ready" => quote! {
fn is_ready(&mut self, _node: VnodeRef, _write: bool) ->
Result<bool, libsys::error::Errno>
{
#behavior
}
},
_ => panic!("TODO implement {:?}", name),
})
}
@ -118,6 +125,7 @@ pub fn auto_inode(attr: TokenStream, input: TokenStream) -> TokenStream {
missing.insert("stat".to_string());
missing.insert("size".to_string());
missing.insert("ioctl".to_string());
missing.insert("is_ready".to_string());
for item in &impl_item.items {
match item {

View File

@ -18,6 +18,8 @@ pub trait CharDevice {
/// Performs a TTY control request
fn ioctl(&self, cmd: IoctlCmd, ptr: usize, lim: usize) -> Result<usize, Errno>;
fn is_ready(&self, write: bool) -> Result<bool, Errno>;
}
/// Wrapper struct to attach [VnodeImpl] implementation
@ -44,6 +46,10 @@ impl VnodeImpl for CharDeviceWrapper {
self.device.write(true, data)
}
fn is_ready(&mut self, _node: VnodeRef, write: bool) -> Result<bool, Errno> {
self.device.is_ready(write)
}
fn ioctl(
&mut self,
_node: VnodeRef,

View File

@ -118,6 +118,13 @@ impl File {
pub fn is_cloexec(&self) -> bool {
self.flags & Self::CLOEXEC != 0
}
pub fn is_ready(&self, write: bool) -> Result<bool, Errno> {
match &self.inner {
FileInner::Normal(inner) => inner.vnode.is_ready(write),
_ => todo!(),
}
}
}
impl Drop for File {

View File

@ -80,6 +80,8 @@ pub trait VnodeImpl {
/// Reports the size of this filesystem object in bytes
fn size(&mut self, node: VnodeRef) -> Result<usize, Errno>;
fn is_ready(&mut self, node: VnodeRef, write: bool) -> Result<bool, Errno>;
/// Performs filetype-specific request
fn ioctl(
&mut self,
@ -398,6 +400,14 @@ impl Vnode {
Err(Errno::NotImplemented)
}
}
pub fn is_ready(self: &VnodeRef, write: bool) -> Result<bool, Errno> {
if let Some(ref mut data) = *self.data() {
data.is_ready(self.clone(), write)
} else {
Err(Errno::NotImplemented)
}
}
}
impl fmt::Debug for Vnode {

View File

@ -33,6 +33,10 @@ pub fn derive_tty_char_device(input: TokenStream) -> TokenStream {
{
crate::dev::tty::TtyDevice::tty_ioctl(self, cmd, ptr, len)
}
fn is_ready(&self, write: bool) -> Result<bool, libsys::error::Errno> {
crate::dev::tty::TtyDevice::is_ready(self, write)
}
}
}.into()
}
.into()
}

View File

@ -1,6 +1,6 @@
//! Teletype (TTY) device facilities
use crate::dev::serial::SerialDevice;
use crate::proc::wait::Wait;
use crate::proc::wait::{Wait, WAIT_SELECT};
use crate::sync::IrqSafeSpinLock;
use libsys::error::Errno;
use libsys::{
@ -31,6 +31,17 @@ pub trait TtyDevice<const N: usize>: SerialDevice {
/// Returns a reference to character device's ring buffer
fn ring(&self) -> &CharRing<N>;
fn is_ready(&self, write: bool) -> Result<bool, Errno> {
let ring = self.ring();
let config = ring.config.lock();
if write {
todo!()
} else {
Ok(ring.is_readable(config.lflag.contains(TermiosLflag::ICANON)))
}
}
/// Performs a TTY control request
fn tty_ioctl(&self, cmd: IoctlCmd, ptr: usize, _len: usize) -> Result<usize, Errno> {
match cmd {
@ -234,6 +245,36 @@ impl<const N: usize> CharRing<N> {
}
}
pub fn is_readable(&self, canonical: bool) -> bool {
let inner = self.inner.lock();
if canonical {
// TODO optimize this somehow?
let mut rd = inner.rd;
let mut count = 0usize;
loop {
let readable = if rd <= inner.wr {
(inner.wr - rd) > 0
} else {
(inner.wr + (N - rd)) > 0
};
if !readable {
break;
}
if inner.data[rd] == b'\n' {
count += 1;
}
rd = (rd + 1) % N;
}
count != 0 || inner.flags != 0
} else {
inner.is_readable() || inner.flags != 0
}
}
/// Performs a blocking read of a single byte from the buffer
pub fn getc(&self) -> Result<u8, Errno> {
let mut lock = self.inner.lock();
@ -246,15 +287,10 @@ impl<const N: usize> CharRing<N> {
break;
}
}
if lock.flags != 0 {
if lock.flags & (1 << 0) != 0 {
lock.flags &= !(1 << 0);
return Err(Errno::EndOfFile);
}
todo!();
}
let byte = lock.read_unchecked();
drop(lock);
self.wait_write.wakeup_one();
WAIT_SELECT.wakeup_all();
Ok(byte)
}
@ -265,7 +301,9 @@ impl<const N: usize> CharRing<N> {
todo!()
}
lock.write_unchecked(ch);
drop(lock);
self.wait_read.wakeup_one();
WAIT_SELECT.wakeup_all();
Ok(())
}
}

View File

@ -2,11 +2,11 @@
use crate::arch::machine;
use crate::dev::timer::TimestampSource;
use crate::proc::{self, sched::SCHED, Pid, Process};
use crate::proc::{self, sched::SCHED, Pid, Process, ProcessRef};
use crate::sync::IrqSafeSpinLock;
use alloc::collections::LinkedList;
use core::time::Duration;
use libsys::error::Errno;
use libsys::{error::Errno, stat::FdSet};
/// Wait channel structure. Contains a queue of processes
/// waiting for some event to happen.
@ -20,6 +20,7 @@ struct Timeout {
}
static TICK_LIST: IrqSafeSpinLock<LinkedList<Timeout>> = IrqSafeSpinLock::new(LinkedList::new());
pub static WAIT_SELECT: Wait = Wait::new();
/// Checks for any timed out wait channels and interrupts them
pub fn tick() {
@ -54,6 +55,54 @@ pub fn sleep(timeout: Duration, remaining: &mut Duration) -> Result<(), Errno> {
}
}
pub fn select(
proc: ProcessRef,
n: u32,
mut rfds: Option<&mut FdSet>,
mut wfds: Option<&mut FdSet>,
timeout: Option<Duration>,
) -> Result<u32, Errno> {
// TODO support wfds
if wfds.is_some() || rfds.is_none() {
todo!();
}
let read = rfds.as_deref().map(FdSet::clone);
let write = wfds.as_deref().map(FdSet::clone);
rfds.as_deref_mut().map(FdSet::reset);
wfds.as_deref_mut().map(FdSet::reset);
let deadline = timeout.map(|v| v + machine::local_timer().timestamp().unwrap());
let mut io = proc.io.lock();
loop {
if let Some(read) = &read {
for fd in read.iter() {
let file = io.file(fd as usize)?;
if file.borrow().is_ready(false)? {
rfds.as_mut().unwrap().set(fd);
return Ok(1);
}
}
}
if let Some(write) = &write {
for fd in write.iter() {
let file = io.file(fd as usize)?;
if file.borrow().is_ready(true)? {
wfds.as_mut().unwrap().set(fd);
return Ok(1);
}
}
}
// Suspend
match WAIT_SELECT.wait(deadline) {
Err(Errno::TimedOut) => return Ok(0),
Err(e) => return Err(e),
Ok(_) => {}
}
}
}
impl Wait {
/// Constructs a new wait channel
pub const fn new() -> Self {

View File

@ -18,8 +18,16 @@ fn translate(virt: usize) -> Option<usize> {
/// Unwraps a slim structure pointer
pub fn validate_user_ptr_struct<'a, T>(base: usize) -> Result<&'a mut T, Errno> {
let bytes = validate_user_ptr(base, size_of::<T>())?;
Ok(unsafe { &mut *(bytes.as_mut_ptr() as *mut T) })
validate_user_ptr_struct_option(base).and_then(|e| e.ok_or(Errno::InvalidArgument))
}
pub fn validate_user_ptr_struct_option<'a, T>(base: usize) -> Result<Option<&'a mut T>, Errno> {
if base == 0 {
Ok(None)
} else {
let bytes = validate_user_ptr(base, size_of::<T>())?;
Ok(Some(unsafe { &mut *(bytes.as_mut_ptr() as *mut T) }))
}
}
/// Unwraps an user buffer reference

View File

@ -3,16 +3,16 @@
use crate::arch::platform::exception::ExceptionFrame;
use crate::debug::Level;
use crate::proc::{elf, wait, Pid, Process, ProcessIo};
use core::cmp::Ordering;
use core::mem::size_of;
use core::ops::DerefMut;
use core::time::Duration;
use core::cmp::Ordering;
use libsys::{
abi,
error::Errno,
ioctl::IoctlCmd,
signal::Signal,
stat::{FileMode, OpenFlags, Stat, AT_EMPTY_PATH, AT_FDCWD},
stat::{FdSet, FileMode, OpenFlags, Stat, AT_EMPTY_PATH, AT_FDCWD},
traits::{Read, Write},
};
use vfs::VnodeRef;
@ -194,6 +194,22 @@ pub fn syscall(num: usize, args: &[usize]) -> Result<usize, Errno> {
proc.set_signal(signal);
Ok(0)
}
abi::SYS_SELECT => {
let n = args[0] as u32;
let rfds = validate_user_ptr_struct_option::<FdSet>(args[1])?;
let wfds = validate_user_ptr_struct_option::<FdSet>(args[2])?;
let timeout = if args[3] == 0 {
None
} else {
Some(Duration::from_nanos(args[3] as u64))
};
let proc = Process::current();
let fd = wait::select(proc, n, rfds, wfds, timeout)?;
Ok(fd as usize)
}
_ => {
let proc = Process::current();
errorln!("Undefined system call: {}", num);

View File

@ -15,3 +15,4 @@ pub const SYS_FORK: usize = 7;
pub const SYS_EXECVE: usize = 8;
pub const SYS_WAITPID: usize = 9;
pub const SYS_IOCTL: usize = 10;
pub const SYS_SELECT: usize = 11;

View File

@ -2,7 +2,7 @@ use crate::abi;
use crate::{
ioctl::IoctlCmd,
signal::Signal,
stat::{FileMode, OpenFlags, Stat},
stat::{FdSet, FileMode, OpenFlags, Stat},
};
// TODO document the syscall ABI
@ -217,3 +217,19 @@ pub unsafe fn sys_ex_sigreturn() -> ! {
pub unsafe fn sys_ex_kill(pid: u32, signum: Signal) -> i32 {
syscall!(abi::SYS_EX_KILL, argn!(pid), argn!(signum as u32)) as i32
}
#[inline(always)]
pub unsafe fn sys_select(
n: u32,
read_fds: Option<&mut FdSet>,
write_fds: Option<&mut FdSet>,
timeout: u64,
) -> i32 {
syscall!(
abi::SYS_SELECT,
argn!(n),
argp!(read_fds.map(|e| e as *mut _).unwrap_or(core::ptr::null_mut())),
argp!(write_fds.map(|e| e as *mut _).unwrap_or(core::ptr::null_mut())),
argn!(timeout)
) as i32
}

View File

@ -1,3 +1,5 @@
use core::fmt;
pub const AT_FDCWD: i32 = -2;
pub const AT_EMPTY_PATH: u32 = 1 << 16;
@ -32,6 +34,16 @@ bitflags! {
}
}
#[derive(Clone, Default)]
pub struct FdSet {
bits: [u64; 2]
}
struct FdSetIter<'a> {
idx: u32,
set: &'a FdSet
}
#[derive(Clone, Copy, Debug, Default)]
#[repr(C)]
pub struct Stat {
@ -40,6 +52,75 @@ pub struct Stat {
pub blksize: u32,
}
impl FdSet {
pub const fn empty() -> Self {
Self { bits: [0; 2] }
}
#[inline]
pub fn reset(&mut self) {
self.bits.fill(0);
}
#[inline]
pub fn is_empty(&self) -> bool {
self.bits.iter().find(|&&x| x != 0).is_some()
}
#[inline]
pub fn set(&mut self, fd: u32) {
self.bits[(fd as usize) / 64] |= 1 << (fd % 64);
}
#[inline]
pub fn clear(&mut self, fd: u32) {
self.bits[(fd as usize) / 64] &= !(1 << (fd % 64));
}
#[inline]
pub fn is_set(&self, fd: u32) -> bool {
self.bits[(fd as usize) / 64] & (1 << (fd % 64)) != 0
}
pub fn iter(&self) -> impl Iterator<Item = u32> + '_ {
FdSetIter {
idx: 0,
set: self
}
}
}
impl Iterator for FdSetIter<'_> {
type Item = u32;
fn next(&mut self) -> Option<u32> {
while self.idx < 128 {
if self.set.is_set(self.idx) {
let res = self.idx;
self.idx += 1;
return Some(res);
}
self.idx += 1;
}
None
}
}
impl fmt::Debug for FdSet {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "FdSet {{ ")?;
let mut count = 0;
for fd in self.iter() {
if count != 0 {
write!(f, ", ")?;
}
write!(f, "{}", fd)?;
count += 1;
}
write!(f, " }}")
}
}
impl FileMode {
/// Returns default permission set for directories
pub const fn default_dir() -> Self {

View File

@ -5,19 +5,37 @@
extern crate libusr;
use libusr::sys::Signal;
use libusr::sys::stat::FdSet;
fn readline(fd: i32, buf: &mut [u8]) -> Result<&str, ()> {
let count = unsafe { libusr::sys::sys_read(fd, buf) };
if count >= 0 {
core::str::from_utf8(&buf[..count as usize]).map_err(|_| ())
} else {
Err(())
// select() just for test
loop {
let mut rfds = FdSet::empty();
rfds.set(fd as u32);
let res = unsafe { libusr::sys::sys_select(fd as u32 + 1, Some(&mut rfds), None, 1_000_000_000) };
if res < 0 {
return Err(());
}
if res == 0 {
continue;
}
if !rfds.is_set(fd as u32) {
panic!();
}
let count = unsafe { libusr::sys::sys_read(fd, buf) };
if count >= 0 {
return core::str::from_utf8(&buf[..count as usize]).map_err(|_| ());
} else {
return Err(());
}
}
}
#[no_mangle]
fn main() -> i32 {
let mut buf = [0; 512];
loop {
print!("> ");
let line = readline(libusr::sys::STDIN_FILENO, &mut buf).unwrap();