Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rethink scheduling using Futures #787

Open
mkroening opened this issue Jul 7, 2023 · 4 comments
Open

Rethink scheduling using Futures #787

mkroening opened this issue Jul 7, 2023 · 4 comments

Comments

@mkroening
Copy link
Member

It might be interesting to revision scheduling by making all tasks a Future.

@joboet
Copy link
Contributor

joboet commented Aug 1, 2023

This would need a bit of planning beforehand, as there are multiple ways/levels of implementing this:

Thread wraps userspace or syscall

For my (unreleased) kernel, I'm using a kernel-thread-per-core design where the user threads are handles with an execute method that returns a result indicating the trap reason (syscall number and parameters or interrupt number). The kernel main loop then matches on this enum and performs the required actions. This has the benefit of working nicely with the ownership and borrowing rules but requires something akin to syscall numbers.

struct UserThread { .. }

impl UserThread {
   fn execute(&mut self, retval: isize) -> Return { .. }
}

enum Return {
   Syscall(u8 or fn(..) -> usize, Parameters),
   Interrupt(u8),
}

// Execute these futures in the scheduler
async fn thread(mut user: UserThread) {
    let mut retval = 0;
    loop {
        match user.execute(retval) {
            // Handle syscall/interrupt
        }
    }
}

Thread wraps userspace wraps syscall

Another way of doing this would be to make all blocking syscalls async and wrap them in handlers that, on receiving Poll::Pending, restore the execution state before userspace was entered, where a Poll::Pending result is passed back to the scheduler. This eliminates any jump tables but requires allocating the syscall futures on the heap or on the user stack.

extern "C" fn syscall_wrapper(..params) -> isize {
    let mut future = pin!(syscall(..params));
    loop {
        match future.as_mut().poll(scheduler.waker()) {
            Poll::Ready(retval) => return retval,
            Poll::Pending => {
                exchange_context_with_kernel_context(terminate: false/true);
            }
        }
    }
}

struct Thread { .. }

// Could be a separate method instead of a `Future` implementation.
impl Future for Thread {
    fn poll(..) -> Poll<()> {
        let terminate = execute_userspace();
        if terminate {
            Poll::Ready(())
        } else {
            Poll::Pending
        }
    }
}

@mkroening
Copy link
Member Author

The first option sounds really interesting. I had thought of something similar to the second option, I think, but I have not looked deeper into this topic. I also did not discuss with @stlankes if this was something that he would be interested in adopting.

What level of interest do you have in this, @joboet? Would you be interested in contributing something like this? If not, I will look into this myself, eventually, but that will probably take a while. :)

@joboet
Copy link
Contributor

joboet commented Aug 3, 2023

My last exam for this semester is on Saturday, and I'll have plenty of time over the summer break, so sure, I can do it 😄!

@mkroening
Copy link
Member Author

Great! 🥳
You are also welcome to join our Zulip for coordination, if you like. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants