Skip to content

Latest commit

 

History

History
273 lines (242 loc) · 8.39 KB

09-message-sending.org

File metadata and controls

273 lines (242 loc) · 8.39 KB

Sending messages

VGA listener

First we will create a task which will keep receiving messages, printing them when they arrive. In vga_buffer.rs we can write a function similar to the hello user code, calling the sys_receive syscall:

use core::arch::asm;

fn listener() {
    loop {
        // Receive
        let err: u64;
        let value: u64;
        unsafe {
            asm!("mov rax, 3", // sys_receive
                 "mov rdi, 0", // handle
                 "syscall",
                 lateout("rax") err,
                 lateout("rdi") value);
        }
        let ch = char::from_u32(value as u32).unwrap();
        println!("VGA: {} , {} => {}", err, value, ch);
    }
}

and a function to start the listener and return a Rendezvous handle to send messages to:

use alloc::sync::Arc;
use spin::RwLock;
use crate::process;
use crate::rendezvous::Rendezvous;

pub fn start_listener() -> Arc<RwLock<Rendezvous>> {
    let rz = Arc::new(RwLock::new(Rendezvous::Empty));
    process::new_kernel_thread(listener); // Here pass rz
    rz
}

(we’ll fill in the pieces soon…). We need a way to create a kernel thread with a Rendezvous handle, so for that we can modify process.rs, passing a vector of handles as a second argument:

pub fn new_kernel_thread(
    function: fn()->(),
    handles: Vec<Arc<RwLock<Rendezvous>>>
) -> u64 {
    ...
        Box::new(Thread {
          tid: unique_id(),
          process: Arc::new(Process {
              page_table_physaddr: 0,
              handles, // New
          }),
          ...
}

then the start_listener function can become:

extern crate alloc;
use alloc::vec::Vec;

pub fn start_listener() -> Arc<RwLock<Rendezvous>> {
    let rz = Arc::new(RwLock::new(Rendezvous::Empty));
    process::new_kernel_thread(listener,
                               Vec::from([rz.clone()]));
    rz
}

In main.rs (kernel_entry()) we need to modify the call to new_kernel_thread:

extern crate alloc;
use alloc::vec::Vec;

fn kernel_entry(boot_info: &'static BootInfo) -> ! {
    ...
    process::new_kernel_thread(kernel_thread_main,
                               Vec::new()); // New
    kernel::hlt_loop();
}

sys_send syscall

Now that we have a way to receive messages, we need to add a way to send them. In syscalls.rs:

use crate::rendezvous::Message;

fn sys_send(context_ptr: *mut Context, handle: u64, data: u64) {
    // Extract the current thread
    if let Some(mut thread) = process::take_current_thread() {
        let current_tid = thread.tid();
        thread.set_context(context_ptr);

        // Get the Rendezvous and call
        if let Some(rdv) = thread.rendezvous(handle) {
            let (thread1, thread2) = rdv.write().send(Some(thread),
                                                      Message::Short(data as usize));
            // thread1 should be started asap
            // thread2 should be scheduled

            let mut returning = false;
            for maybe_thread in [thread2, thread1] {
                if let Some(t) = maybe_thread {
                    if t.tid() == current_tid {
                        // Same thread -> return
                        process::set_current_thread(t);
                        returning = true;
                    } else {
                        process::schedule_thread(t);
                    }
                }
            }

            if !returning {
                // Original thread is waiting.
                // Switch to a different thread
                let new_context_addr = process::schedule_next(context_ptr as usize);
                interrupts::launch_thread(new_context_addr);
            }
        } else {
            // Missing handle
            thread.return_error(SYSCALL_ERROR_INVALID_HANDLE);
            process::set_current_thread(thread);
        }
    }
}

and then add this function to dispatch_syscall():

extern "C" fn dispatch_syscall(
    context_ptr: *mut Context,
    syscall_id: u64,
    arg1: u64, arg2: u64) {
    ...
    match syscall_id {
      0 => process::fork_current_thread(context),
      1 => process::exit_current_thread(context),
      2 => sys_write(arg1 as *const u8, arg2 as usize),
      3 => sys_receive(context_ptr, arg1),
      4 => sys_send(context_ptr, arg1, arg2), // New
      _ => println!("Unknown syscall {:?} {} {} {}",
                     context_ptr, syscall_id, arg1, arg2)
    }
}

To enable the user code to use the VGA listener handle, we can modify new_user_thread() to add an argument:

pub fn new_user_thread(
    bin: &[u8],
    handles: Vec<Arc<RwLock<Rendezvous>>>  // New
) -> Result<u64, &'static str> {
    ...
        // Create a new process
        process: Arc::new(Process {
            page_table_physaddr: user_page_table_physaddr,
            handles, // New
        }),
}

Now the kernel_thread_main function in main.rs needs to be modified:

use kernel::vga_buffer;
use kernel::interrupts;

fn kernel_thread_main() {
    let kbd_rz = interrupts::keyboard_rendezvous();
    let vga_rz = vga_buffer::start_listener();

    process::new_user_thread(
      include_bytes!("../../user/hello"),
      Vec::from([kbd_rz, vga_rz]));

    kernel::hlt_loop();
}

so now the user program will run with handle 0 linked to the keyboard input, and handle 1 linked to the vga output.

In the user code _start() function we can now try sending messages to the vga listener:

loop{
    println!("Calling sys_read");
    let err: u64;
    let value: u64;
    asm!("mov rax, 3", // sys_receive
         "mov rdi, 0", // handle
         "syscall",
         lateout("rax") err,
         lateout("rdi") value);
    let ch = char::from_u32(value as u32).unwrap();
    println!("Received: {} => {}", value, ch);

    let err: u64;
    unsafe {
        asm!("mov rax, 4", // sys_send
             "mov rdi, 1", // handle
             "syscall",
             in("rsi") value,
             lateout("rax") err);
    }
}

Pressing a key should send a message to the user code, print a “Received: ” message, then send a message to the VGA listener and… panic!

./img/09-01-page-fault.png

Syscalls from kernel code

The error code says that this is a protection error in user mode, which occurred on an instruction fetch. The instruction pointer address 0x4091f1 is below the user start address (0x500000) so is in the kernel code, but it’s in Ring 3.

The reason this is happening is the way syscalls are handled: When the syscall handler is entered, it creates a context but does not have the CS and SS values, which specify the permissions. Those values are set in dispatch_syscall() to the user segments. When the message is received and the listener thread is started, those user segment values are used, so the thread is in Ring 3.

let (code_selector, data_selector) =
    if context.rip < process::USER_CODE_START as usize {
        // Called from kernel code
        gdt::get_kernel_segments()
    } else {
        gdt::get_user_segments()
    };

This should now run, until you type too quickly when it crashes again. When keyboard events are sent quickly a message is already waiting when the VGA listener calls sys_receive, and so returns via a sysret rather than iret. sysret sets the segments to user mode, and unfortunately this is difficult to modify. A workaround is to detect when we’re returning to kernel code, and avoid calling sysret:

...
"pop rsp", // Restore user stack

"cmp rcx, {user_code_start}",
"jl 2f", // rip < USER_CODE_START
"sysretq", // back to userland

"2:", // kernel code return
"push r11",
"popf", // Set RFLAGS
"jmp rcx", // Jump to kernel code
user_code_start = const(process::USER_CODE_START),
...

(note that USER_CODE_START in process.rs needs to be made public)

./img/09-02-vga-listener.png