Skip to content

Crossing FFI boundaries with raw pointers. #171

@Ben-PH

Description

@Ben-PH

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?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions