Skip to content

Latest commit

 

History

History
370 lines (329 loc) · 13.2 KB

11-messages.org

File metadata and controls

370 lines (329 loc) · 13.2 KB

Message types

So far messages have just been passing a single 64-bit register, but we’re getting to the point where we might want to pass more information, and different kinds of information, between threads. Like messages in Xous we’re going to have a short (and hopefully fast) message type, and a long type for larger or more complex messages.

Short messages

Short messages just copy registers between threads. We use rax for the syscall number and error return code, rdi for the handle, and we can’t use rcx or r11 because these are used by syscall/sysret. The Linux syscall interface uses rdi, rsi, rdi, r10, r8 and r9 registers, so these are reasonable choices.

There is a penalty to using too many registers: The more registers we clobber with message passing, the more the user program will have to avoid using those registers, and perhaps push and pop from the stack instead. The optimum number of registers will depend on the typical message sizes, so tuning will have to wait until later.

We’ll need a register to store information about the kind of data the message contains. The rax register seems like a good place to do this: We’re using rax for the syscall number, but we don’t need 64 bits for that. We’re not likely to need more than 256 syscalls (8 bits), leaving 56 bits for other things. We can start by putting the rendezvous handle, which is currently in rdi, into the high 32 bits of rax. This will limit each program to “only” 4 billion handles, which is probably enough.

Somewhat arbitrarily we’re going to use three registers for the data (for now): rdi, rsi and rdx. We’ll make the Short message type larger, and move it into a new file kernel/src/message.rs:

pub enum Message {
   Short(u64, u64, u64),
}

We can also define Message in the user library in a new file euralios_std/src/message.rs with the same code, and add pub mod message; to euralios_std/src/lib.rs. We’ll keep the definitions separate because the storage of messages in the kernel and user code will be different for the long messages.

./img/11-01-short-messages.svg

The library code (euralios_std/src/syscalls.rs) is now going to put the syscall number and handle into rax, and the three values into rdi, rsi and rdx.

use crate::message::Message;
pub fn send(
    handle: u32,
    message: Message
) -> Result<(), u64> {
    match message {
        Message::Short(data1, data2, data3) => {
            let err: u64;
            unsafe {
                asm!("syscall",
                     in("rax") 4 + ((handle as u64) << 32),
                     in("rdi") data1,
                     in("rsi") data2,
                     in("rdx") data3,
                     lateout("rax") err,
                     out("rcx") _,
                     out("r11") _);
            }
            if err == 0 {
                return Ok(());
            }
            Err(err)
        },
        _ => return Err(0)
    }
}

Note that we signal to the user code that the handle is only 32 bits, but convert to 64 bits to shift and put into rax.

The syscall instruction then switches to kernel code. To get the data values into sys_send function we first need to add to handle_syscall(). In the C calling convention the first six function arguments are stored in registers rdi, rsi, rdx, rcx, r8 and r9. The fifth argument is therefore r8, which will contain data3:

"mov r8, rdx", // New
"mov rcx, rsi",
"mov rdx, rdi",
"mov rsi, rax",

and dispatch_syscall() we’re now just using 8 bits for the syscall number so we now match on syscall_id & 0xFF:

extern "C" fn dispatch_syscall(context_ptr: *mut Context, syscall_id: u64,
                               arg1: u64, arg2: u64, arg3: u64) {
    ...
    match syscall_id & 0xFF { // New
        ...
        4 => sys_send(context_ptr, syscall_id,
                      arg1, arg2, arg3), // New
        ...
    }
}

and sys_send()

fn sys_send(
    context_ptr: *mut Context,
    syscall_id: u64,
    data1: u64,
    data2: u64,
    data3: u64) {
    let handle = syscall_id >> 32; // New
    ...
        let (thread1, thread2) = rdv.write().send(
            Some(thread),
            Message::Short(data1,
                           data2, // New
                           data3)); // New
}

The other place we send messages is in the keyboard_handler_inner function (interrupts.rs);

let (thread1, thread2) =
    KEYBOARD_RENDEZVOUS.write().send(
        None,
        Message::Short(character as u64,
                       0, 0)); // New

To get the data out of the kernel to the receiving thread, we need to modify return_message() in process.rs which consumes a Message object and puts the values into a receiving thread’s registers:

pub fn return_message(&self, message: Message) {
    let context = self.context_mut();
    context.rax = 0;
    match message {
        Message::Short(data1, data2, data3) => {
            context.rdi = data1 as usize;
            context.rsi = data2 as usize; // New
            context.rdx = data3 as usize; // New
        },
        _ => {}
    }
}

Long messages

A long message has to handle everything that we might want to send between processes. That includes values, as in short messages, but also rendezvous handles, and probably other things later like memory chunk handles, which will refer to a set of pages for transferring large amounts of data. For any kind of handle we might want to either copy or move/assign to the other process.

To keep things simple we’ll use the same three registers as short messages (rdi, rsi and rdx), and just send three things. Each register can contain either a value or a rendezvous handle. rax will contain

  • 8 bits for the syscall number
  • 32 bits for the handle
  • 1 bit to specify if it’s a long or short message. If a short message then the kernel skips any other checks and just copies the values.
  • 1 bit per register (3 total) specifying the type (value or handle)
  • 1 bit per register (3 total) to specify copy or move
  • 17 remaining bits for future expansion

./img/11-02-messages.svg

In rendezvous.rs we can define the long message as it will be stored in the kernel:

use alloc::{boxed::Box, sync::Arc};
use spin::RwLock;

pub enum MessageData {
    Value(u64),
    Rendezvous(Arc<RwLock<Rendezvous>>),
}

pub enum Message {
    Short(u64, u64, u64),
    Long(u64, MessageData, MessageData), // New
}

In syscalls.rs the flags which will be used in rax:

pub const MESSAGE_LONG: u64 = 2 << 8;
pub const MESSAGE_DATA2_RDV: u64 = 2 << 9;
const MESSAGE_DATA2_TYPE: u64 = MESSAGE_DATA2_RDV; // Bit mask
const MESSAGE_DATA2_MOVE: u64 = 2 << 10;

pub const MESSAGE_DATA3_RDV: u64 = 2 << 11;
const MESSAGE_DATA3_TYPE: u64 = MESSAGE_DATA3_RDV; // Bit mask
const MESSAGE_DATA3_MOVE: u64 = 2 << 12;
fn sys_send(
  context_ptr: *mut Context,
  syscall_id: u64,
  data1: u64,
  data2: u64,
  data3: u64) {
    ...
        if let Some(rdv) = thread.rendezvous(handle) {

            let message = if syscall_id & MESSAGE_LONG == 0 {
                Message::Short(data1,
                               data2,
                               data3)
            } else {
                // Long message

                let message = Message::Long(
                    data1,
                    if syscall_id & MESSAGE_DATA2_TYPE == MESSAGE_DATA2_RDV {
                        // Moving or copying a handle
                        // First copy, then drop if message is valid
                        if let Some(rdv) = thread.rendezvous(data2) {
                            MessageData::Rendezvous(rdv)
                        } else {
                            // Invalid handle
                            thread.return_error(SYSCALL_ERROR_INVALID_HANDLE);
                            process::set_current_thread(thread);
                            return;
                        }
                    } else {
                        MessageData::Value(data2)
                    },
                    if syscall_id & MESSAGE_DATA3_TYPE == MESSAGE_DATA3_RDV {
                        if let Some(rdv) = thread.rendezvous(data3) {
                            MessageData::Rendezvous(rdv)
                        } else {
                            // Invalid handle.
                            // If we moved data2 we would have to put it back here
                            thread.return_error(SYSCALL_ERROR_INVALID_HANDLE);
                            process::set_current_thread(thread);
                            return;
                        }
                    } else {
                        MessageData::Value(data3)
                    });
                // Message is valid => Remove handles being moved
                if (syscall_id & MESSAGE_DATA2_TYPE == MESSAGE_DATA2_RDV) &&
                    (syscall_id & MESSAGE_DATA2_MOVE != 0) {
                        let _ = thread.take_rendezvous(data2);
                    }
                if (syscall_id & MESSAGE_DATA3_TYPE == MESSAGE_DATA3_RDV) &&
                    (syscall_id & MESSAGE_DATA3_MOVE != 0) {
                        let _ = thread.take_rendezvous(data3);
                    }
                message
            };

            let (thread1, thread2) = rdv.write().send(
                Some(thread),
                message);
            ...
        }

Then in process.rs we need to be able to modify the vector of handles, but Thread.process is an Arc<Process> which doesn’t allow modification. We need to use a mutex such as a spin lock:

struct Thread {
    ...
    process: Arc<RwLock<Process>>,
    ...
}
use crate::rendezvous::{Rendezvous, MessageData};

impl Thread {
    pub fn return_message(&self, message: Message) {
        let context = self.context_mut();

        context.rax = 0; // No error
        match message {
            Message::Short(data1, data2, data3) => {
                context.rdi = data1 as usize;
                context.rsi = data2 as usize;
                context.rdx = data3 as usize;
            },
            Message::Long(data1, data2, data3) => {
                context.rdi = data1 as usize;

                context.rsi = match data2 {
                    MessageData::Value(value) => value,
                    MessageData::Rendezvous(rdv) => {
                        context.rax |= (syscalls::MESSAGE_DATA2_RDV |
                                        syscalls:: MESSAGE_LONG) as usize;
                        self.give_rendezvous(rdv)
                    }
                } as usize;

                context.rdx = match data3 {
                    MessageData::Value(value) => value,
                    MessageData::Rendezvous(rdv) => {
                        context.rax |= (syscalls::MESSAGE_DATA3_RDV |
                                        syscalls::MESSAGE_LONG) as usize;
                        self.give_rendezvous(rdv)
                    }
                } as usize;
            }
        }
    }
}

so in new_kernel_thread and new_user_thread we now need to construct this with process: Arc::new(RwLock::new(Process {...}))

pub fn rendezvous(&self, id: u64)
                  -> Option<Arc<RwLock<Rendezvous>>> {
    self.process.read().handles.get(id as usize) // Option<&Option<Arc<>>>
        .unwrap_or(&None)  // &Option<Arc<>>
        .as_ref() // Option<&Arc<>>
        .map(|rv| rv.clone()) // Option<Arc<>>
}

/// Take the rendezvous, leaving handle empty (None)
pub fn take_rendezvous(&self, id: u64)
                       -> Option<Arc<RwLock<Rendezvous>>> {
    self.process.write().handles.get_mut(id as usize).map_or(None, |elem| elem.take())
}

/// Add a rendezvous to the process, returning the handle
pub fn give_rendezvous(&self, rendezvous: Arc<RwLock<Rendezvous>>) -> u64 {
    // Lock the handles
    let handles = &mut self.process.write().handles;

    // Find empty handle slot
    for (pos, handle) in handles.iter().enumerate() {
        if handle.is_none() {
            // Found empty slot => Store rendezvous
            handles[pos] = Some(rendezvous);
            return pos as u64;
        }
    }
    // All full => Add new handle
    handles.push(Some(rendezvous));
    (handles.len() - 1) as u64
}

Ok, enough messaging for now (hurray!). Next it’s time for the operating system to start doing something useful, so we’ll start work on accessing devices and storage next time.