Global, mutable pointer at a fixed memory address

This code is meant to safely wrap the built-in text terminal that's provided to an OS.

lazy_static! {
    static ref SCREEN: spin::Mutex<&'static mut [[u16; 80]; 25]> = spin::Mutex::new(unsafe {
        &mut *(0xB8000 as *mut [[u16; 80]; 25])
    });
}
  1. Is this safe, assuming that the text terminal exists?
  2. Is there a way to use a regular static instead of a lazy_static?

Playground Link

A simpler version should work:

static mut SCREEN: *mut [[u16; 80]; 25] = 0xB8000 as _;

Access to this is of course considered unsafe, because it's a raw pointer and you need to ensure there are no data races, but technically that's identical to a global pointer that you'd define in C.

You could wrap that in a struct (AKA newtype) and implement safe methods on that struct.

Okay, but why can't I use &mut in a static? Going through the errors,

  • E0017 doesn't make sense to me since the mutable value is behind a mutex.
  • The other errors go away if I use mem::transmute instead of &mut *.

Because &mut semantically is more than a pointer, it's a guarantee of exclusive access to this memory with no possibility of mutable aliasing.

It's fine to have it behind a mutex that will ensure no mutable aliasing at run time, but it can't be a bare global variable that doesn't ensure that (but the type implies it is guaranteed).

You can set global variables without lazy_static! if the initializer is const. Rust is currently expanding number of things that are const-compatible, so check the latest version or nightly.

1 Like

spin::Mutex::new is a const fn,so you should be able to spin::Mutex it in a static if the data can also be evaluated in const-constexts.

Here is a version that works in a bare static:

use spin::Mutex;

pub struct UnsafeSend<T>(pub T);

unsafe impl<T> Send for UnsafeSend<T>{}

static SCREEN: Mutex<UnsafeSend<*mut [[u16; 80]; 25]>> = {
    let ptr=0xB8000 as *mut [[u16; 80]; 25];
    Mutex::new(UnsafeSend(ptr))
};
3 Likes

unsafe { &mut *ptr } is pretty much zero cost AFAIK.

Avoid the pub when your code breaks safety when not used in intended confine.

You can't dereference raw pointers when initializing statics yet(it requires nightly).

Here is a corrected version that requires an unsafe block when constructing UnsafeSend:

If you feature-gate the following code based on some feature / target that guarantees that reads and writes to 0xB8000 (and to the 80 * 25 - 1 following u16s) are valid, I like doing the following:

  1. Having a singleton type (i.e., accessible only from a single static or with a constructor that enforces it with a runtime check).

  2. This singleton type is a Mutex that owns/wraps a dummy ().

  3. The RAII guard from .lock() is also wrapped so that it Deref[Mut]s to the desired buffer, thus acting as if the Mutex was actually wrapping the buffer.

  • This provides a sound interface thanks to the singleton invariant, as long as the address is valid etc. and not accessed elsewhere.
#![deny(elided_lifetimes_in_paths)]

//! Safety: assumes that 0xB8000 ... yadda yadda

const RAW_SCREEN_ADDRESS: *mut RawScreen = 0xB8000 as _;
const NUM_ROWS: usize = 25;
const NUM_COLS: usize = 80;
type RawScreen = [[u16; NUM_COLS]; NUM_ROWS];

pub use self::screen_hack::SCREEN;
mod screen_hack {
    use super::*;
    use ::core::*;
    use ::spin::{
        Mutex,
        MutexGuard,
    };

    /// Singleton instance
    pub
    static SCREEN: Screen = Screen(
        Mutex::new(()),
    );

    /// Singleton type
    pub
    struct Screen /* = */ (
        Mutex<()>,
    );
    
    impl Screen {
        pub
        fn lock (self: &'_ Self) -> LockedScreen<'_>
        {
            LockedScreen(self.0.lock())
        }

        pub
        fn try_lock (self: &'_ Self) -> Option<LockedScreen<'_>>
        {
            self.0.try_lock().map(LockedScreen)
        }
    }
    
    pub
    struct LockedScreen<'lock> /* = */ (
        MutexGuard<'lock, ()>,
    );
    
    impl ops::Deref for LockedScreen<'_> {
        type Target = RawScreen;
        
        #[inline]
        fn deref (self: &'_ Self) -> &'_ Self::Target
        {
            unsafe {
                &*RAW_SCREEN_ADDRESS
            }
        }
    }
    impl ops::DerefMut for LockedScreen<'_> {
        #[inline]
        fn deref_mut (self: &'_ mut Self) -> &'_ mut Self::Target
        {
            unsafe {
                &mut *RAW_SCREEN_ADDRESS
            }
        }
    }
}

fn main ()
{
    let mut locked_screen = SCREEN.lock();

    assert_eq!(
        locked_screen[0][0],
        0_u16,
    );
    locked_screen[0][0] = 42 + 27;
    assert_eq!(
        locked_screen[0][0] - 27,
        42,
    );
    
    assert!(SCREEN.try_lock().is_none());
    ::core::mem::drop(locked_screen);
    
    assert!(SCREEN.try_lock().is_some());
}
2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.