Skip to content

Instantly share code, notes, and snippets.

@ihciah
Last active August 5, 2022 03:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ihciah/e6b15de30d0a6d90258af0626b4bc06d to your computer and use it in GitHub Desktop.
Save ihciah/e6b15de30d0a6d90258af0626b4bc06d to your computer and use it in GitHub Desktop.
Try Rust for Linux

My note for trying rust-for-linux.

Prepare environment

Here I take arch linux as an example, and I assume you already installed rust and put ~/.cargo/bin inside your PATH.

cd /some/where

# Install requirements
sudo pacman -Syuu --noconfirm bc bison curl clang diffutils flex git gcc llvm libelf lld ncurses make qemu-system-x86 cpio
export MAKEFLAGS="-j32"
export LLVM="1"

# Clone rust-for-linux
git clone https://github.com/Rust-for-Linux/linux.git rust-for-linux --depth=1
cd rust-for-linux

# Install rust-bindgen
cargo install --locked --version $(scripts/min-tool-version.sh bindgen) bindgen

# Install other rust components
rustup override set $(scripts/min-tool-version.sh rustc)
rustup component add rust-src

# Check if the enviroment is ready
make LLVM=1 rustavailable

Compile the kernel

make allnoconfig qemu-busybox-min.config rust.config

# You can enable some sample code in the following step
# Here enable Kernel hacking > Sample kernel code > Rust samples > Echo server module
make menuconfig

make

Make initramfs

To run the kernel, we need a initramfs(aka initrd.img).

cd /some/where/busybox

# Download busybox and unzip it
wget https://busybox.net/downloads/busybox-snapshot.tar.bz2
tar -jxvf busybox-snapshot.tar.bz2

# Build it
cd busybox
make defconfig
# Settings > Build Options > Build BusyBox as a static binary (no shared libs)
make menuconfig
make
make install

# Prepare files
cd _install
mkdir -p bin dev sbin etc/init.d proc sys var usr/bin usr/sbin usr/share/udhcpc
cp ../examples/udhcp/simple.script usr/share/udhcpc/default.script

Create etc/init.d/rcS and chmod +x etc/init.d/rcS:

#!bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev
mkdir /dev/pts
mount -t devpts none /dev/pts
ifconfig lo up
udhcpc -i eth0

echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n"

Create etc/inittab:

::sysinit:/etc/init.d/rcS
::respawn:-/bin/sh
::restart:/sbin/init
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r

Then we can pack it.

find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../../initramfs.cpio.gz

After this step, we will find initramfs.cpio.gz in /some/where/busybox

Run the VM

qemu-system-x86_64 -nographic -kernel /some/where/rust-for-linux/arch/x86_64/boot/bzImage -initrd /some/where/busybox/initramfs.cpio.gz -nic user,model=rtl8139,hostfwd=tcp::38080-:8080 -append "console=ttyS0" -enable-kvm

Try it:

nc 127.0.0.1 38080

Type something and the words will be sent back. To implement it is easy, you can use async in it.

Code: https://github.com/Rust-for-Linux/linux/blob/rust/samples/rust/rust_echo_server.rs

Develop Environment

To make vscode+RA ready, build rust-project.json for it.

make rust-analyzer

Poem Device

Here we want to add a demo device to print poem.

Create samples/rust/rust_poem.rs:

//! A demo device for print poem.
//! Run: cat /dev/rust_poem

use kernel::prelude::*;
use kernel::{
    file::{self, File},
    io_buffer::IoBufferWriter,
    miscdev,
    sync::{Mutex, Ref, RefBorrow, UniqueRef},
};

module! {
    type: RustPoemDev,
    name: b"rust_poem",
    author: b"ihciah",
    description: b"Poem device in rust",
    license: b"GPL",
}

const POEM: &str = r#"Shall I compare thee to a summer’s day?
Thou art more lovely and more temperate.
Rough winds do shake the darling buds of May,
And summer’s lease hath all too short a date.
Sometime too hot the eye of heaven shines,
And often is his gold complexion dimmed;
And every fair from fair sometime declines,
By chance, or nature’s changing course, untrimmed;
But thy eternal summer shall not fade,
Nor lose possession of that fair thou ow’st,
Nor shall death brag thou wand'rest in his shade,
When in eternal lines to Time thou grow'st.
    So long as men can breathe, or eyes can see,
    So long lives this, and this gives life to thee.
"#;
const POEM_LEN: usize = POEM.as_bytes().len();

struct Poem;

type Shared = Mutex<usize>;

#[vtable]
impl file::Operations for Poem {
    type Data = Ref<Shared>;
    type OpenData = Ref<Shared>;

    fn open(shared: &Ref<Shared>, _file: &File) -> Result<Self::Data> {
        Ok(shared.clone())
    }

    fn read(
        shared: RefBorrow<'_, Shared>,
        _: &File,
        data: &mut impl IoBufferWriter,
        _offset: u64,
    ) -> Result<usize> {
        if data.is_empty() {
            return Ok(0);
        }

        let mut locked = shared.lock();
        let copy_len = data.len().min(POEM_LEN - *locked);
        data.write_slice(&POEM.as_bytes()[*locked..*locked + copy_len])?;
        *locked += copy_len;
        if copy_len == 0 {
            *locked = 0;
        }
        Ok(copy_len)
    }
}

struct RustPoemDev {
    _dev: Pin<Box<miscdev::Registration<Poem>>>,
}

impl kernel::Module for RustPoemDev {
    fn init(name: &'static CStr, _module: &'static ThisModule) -> Result<Self> {
        pr_info!("Poem device in rust (init)\n");
        let mut state = Pin::from(UniqueRef::try_new(unsafe { Mutex::new(0) })?);
        kernel::mutex_init!(state.as_mut(), "poem counter");
        let dev_reg = miscdev::Registration::new_pinned(fmt!("{name}"), state.into())?;
        Ok(RustPoemDev { _dev: dev_reg })
    }
}

impl Drop for RustPoemDev {
    fn drop(&mut self) {
        pr_info!("Poem device in rust (exit)\n");
    }
}

Add into samples/rust/Kconfig:

config SAMPLE_RUST_POEM
	tristate "Poem Device"
	help
	  This option builds the Rust module poem.

	  To compile this as a module, choose M here:
	  the module will be called rust_poem.

	  If unsure, say N.

Add into samples/rust/Makefile:

obj-$(CONFIG_SAMPLE_RUST_POEM)		+= rust_poem.o

Then run it and you can see:

[    0.945646] Write protecting the kernel read-only data: 12288k
[    0.963170] Freeing unused kernel image (text/rodata gap) memory: 2044K
[    0.966805] Freeing unused kernel image (rodata/data gap) memory: 792K
[    0.970233] Run /sbin/init as init process
[    0.973995] process '/bin/busybox' started with executable stack
udhcpc: started, v1.36.0.git
Clearing IP addresses on eth0, upping it
[    1.013829] 8139cp 0000:00:03.0 eth0: link up, 100Mbps, full-duplex, lpa 0x05E1
udhcpc: broadcasting discover
udhcpc: broadcasting select for 10.0.2.15, server 10.0.2.2
udhcpc: lease of 10.0.2.15 obtained from 10.0.2.2, lease time 86400
Setting IP address 10.0.2.15 on eth0
Deleting routers
route: SIOCDELRT: No such process
Adding router 10.0.2.2
Recreating /etc/resolv.conf
 Adding DNS server 10.0.2.3

Boot took 1.09 seconds

~ #
~ # cat /dev/rust_poem
Shall I compare thee to a summer’s day?
Thou art more lovely and more temperate.
Rough winds do shake the darling buds of May,
And summer’s lease hath all too short a date.
Sometime too hot the eye of heaven shines,
And often is his gold complexion dimmed;
And every fair from fair sometime declines,
By chance, or nature’s changing course, untrimmed;
But thy eternal summer shall not fade,
Nor lose possession of that fair thou ow’st,
Nor shall death brag thou wand'rest in his shade,
When in eternal lines to Time thou grow'st.
    So long as men can breathe, or eyes can see,
    So long lives this, and this gives life to thee.
~ #

Standalone kernel module?

It will be good if we can compile it into a .ko and load it in a stable kernel. I enabled ko support and compiled the former poem device. Then I tried to insmod -f into my arch linux(5.18.14-arch1-1 SMP), and it failed due to Unknown symbol for example Unknown symbol __rust_alloc. It seems the kernel module depends on some symbols related to rust. So maybe it is available until the rust-for-linux is merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment