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

Crossing FFI boundaries with raw pointers. #171

Open
Ben-PH opened this issue Apr 12, 2020 · 0 comments
Open

Crossing FFI boundaries with raw pointers. #171

Ben-PH opened this issue Apr 12, 2020 · 0 comments

Comments

@Ben-PH
Copy link

Ben-PH commented Apr 12, 2020

I'm building a system that calls into C libraries. One of the libraries handles device initialisation. It returns a pointer, and cannot be called more than once. Hence the use of lazy_static.

I have two questions, one is about assuring how to constrain construction across the lifetime of a system, and another about the contracts that come with unsafe impl Send/Sync for $raw_ptr

Here is a code snippet of what I'm dealing with

// ../rustlibs/serial/src/lib.rs
#![no_std]

#[derive(Copy, Clone)]
#[repr(C)]
struct SerialPtr(*const core::ffi::c_void);

extern "C" {
    fn serial_send(c_library_struct: *const core::ffi::c_void, ...) -> usize;
    fn serial_init() -> SerialPtr;
    fn serial_register_handler(serial: SerialPtr,  ...);
}

unsafe impl core::marker::Send for Serial {}
unsafe impl core::marker::Sync for Serial {}
pub struct Serial {
    ptr: SerialPtr
}

// essentially, SERIAL must be a singleton for the entire system due to serial_init call count.
lazy_static! {
    static ref SERIAL: Serial = unsafe {
        let res = Serial {
            ptr: serial_init(), // CRITICAL: must be called once, and once ONLY for entire system uptime
        };
        serial_register_handler(res.ptr, Some(default_handler));
        res
    };
}

impl Serial {
    pub fn send(msg: &[u8]) -> usize {
        unsafe { serial_send(SERIAL.ptr, ...) }
    }
    ...
}

Here is the process flow of my system:

  1. root-process start in C code-base (is always the first process after power-up, and last before power-down)
  2. system initialisation from C code-base (not including serial_init)
  3. spin up child process from C code-base(immediately sends request to root-process use of serial device. Blocks until root-process replys to request)
  4. root-process calls into Rust code-base
  5. root-proc recieves request from child
  6. part of processing this uses Serial::send

For context of how I currently use it from the static library that C calls:

// ../rustlibs/root_service/src/lib.rs
#![no_std]
#[crate_type = "static_lib"]
use serial::*;

#[no_mangle]
fn entry_point() -> ! {
    loop {
        match blocking_recv(ipc_source, msg) {
            // Derefs into SERIAL, thus init'ing the device on first match, replying with the result
            SERIAL_SEND => reply(ipc_source, Serial::send(msg)),
            ...
        }
    }
}

Regarding my question of limiting serial_init() invocation to once-only system wide: If I make serial a static lib, will that mean there will be a fresh serial_init() invoked for each executable that links it? Does the same apply if I make it an rlib? If I make it dylib, what would that do to duplicate SERIALs within system memory?

Regarding my question of marking Serial with Send + Sync, what can I do with respect to ensuring that it's safe?

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

No branches or pull requests

1 participant