rustqueue is a Linux kernel module that exposes a bounded FIFO message queue at /dev/rustqueue, written in safe Rust. It demonstrates how Rust's ownership and locking discipline make a small in-kernel IPC primitive easy to write and structurally free of buffer-handling bugs.
Basic enqueue and dequeue:
ubuntu@ln24:~/rustqueue-project$ echo "first message" | sudo tee /dev/rustqueue > /dev/null
ubuntu@ln24:~/rustqueue-project$ echo "second message" | sudo tee /dev/rustqueue > /dev/null
ubuntu@ln24:~/rustqueue-project$ echo "third message" | sudo tee /dev/rustqueue > /dev/null
ubuntu@ln24:~/rustqueue-project$ sudo cat /dev/rustqueue
first message
ubuntu@ln24:~/rustqueue-project$ sudo cat /dev/rustqueue
second message
ubuntu@ln24:~/rustqueue-project$ sudo cat /dev/rustqueue
third message
Kernel log output:
ubuntu@ln24:~/rustqueue-project$ sudo dmesg | tail -10
[ 539.891725] rustqueue: loading out-of-tree module taints kernel.
[ 539.891748] rustqueue: module verification failed: signature and/or required key missing - tainting kernel
[ 539.892234] rustqueue: module loaded (capacity 16 messages)
[ 616.489881] rustqueue: enqueued 14 bytes (1 in queue)
[ 620.920965] rustqueue: enqueued 15 bytes (2 in queue)
[ 628.330564] rustqueue: enqueued 14 bytes (3 in queue)
[ 631.878653] rustqueue: dequeued (2 remaining)
[ 635.882471] rustqueue: dequeued (1 remaining)
[ 639.089140] rustqueue: dequeued (0 remaining)
Full-queue rejection (capacity is 16 messages):
ubuntu@ln24:~/rustqueue-project$ for i in {1..20}; do echo "message $i" | sudo tee /dev/rustqueue > /dev/null || echo "write $i FAILED"; done
/dev/rustqueue: No space left on device (os error 28)
write 17 FAILED
/dev/rustqueue: No space left on device (os error 28)
write 18 FAILED
/dev/rustqueue: No space left on device (os error 28)
write 19 FAILED
/dev/rustqueue: No space left on device (os error 28)
write 20 FAILED
rustqueue implements a simple in-kernel FIFO message queue exposed as a character device. Writing to /dev/rustqueue enqueues a message; reading from it dequeues and returns the oldest message (one message per cat invocation). The queue holds up to 16 messages of up to 4096 bytes each.
The module exists to demonstrate that Rust's ownership model and type-level locking discipline apply cleanly to kernel programming — the mutex protecting the queue can never be forgotten, double-locked, or used after the lock guard is dropped. The compiler enforces it.
Requires a Rust-enabled kernel (
CONFIG_RUST=y). Ubuntu 26.04 LTS ships one out of the box. See Rust in the Linux kernel for background.
Prerequisites:
sudo apt install -y build-essential linux-headers-$(uname -r) kmod
sudo apt install -y rustc-1.93 rust-1.93-src bindgen
sudo update-alternatives --install /usr/bin/rustc rustc /usr/bin/rustc-1.93 100Build:
git clone https://github.com/jiacelyne/rustqueue-project.git
cd rustqueue-project
makeLoad and test:
sudo insmod rustqueue.ko
ls -la /dev/rustqueue
echo "hello" | sudo tee /dev/rustqueue > /dev/null
sudo cat /dev/rustqueue # → "hello"
sudo rmmod rustqueueNote:
/dev/rustqueueis created asroot:root 0600. All reads and writes requiresudo. Usesudo tee ... > /dev/nullinstead ofsudo echo ... >— the redirect is evaluated before sudo runs.
Start reading in this order:
module!macro (line 13) — registers the module with the kernel, declares its name, description, and license.InPlaceModule::init(line 34) — module entry point. Initializes the global queue and registers the/dev/rustqueuemisc device. Usestry_pin_init!becauseMiscDeviceRegistrationmust be pinned in memory.MiscDevice::open(line 55) — called on everyopen()syscall. Allocates a freshRustQueueDevicewith an emptypendingslot for this file descriptor.write_iter(line 67) — enqueue path. Acquires the globalQUEUEmutex, checks capacity, copies the user's bytes into aKVec<u8>, and pushes it onto the queue.read_iter(line 89) — dequeue path. On the firstread()of a given open, pulls one message off the global queue into the per-openpendingslot. Subsequent reads on the same descriptor stream bytes out ofpendinguntil EOF.
Why one message per cat invocation?
The read_iter design lazily dequeues exactly one message on the first read() of each open file descriptor, then streams it out across however many read() syscalls cat issues until it sees EOF. This maps naturally to the cat mental model and avoids the complexity of partial-message buffering.
- Partial reads. When the user's buffer is smaller than the next message, leave the unread tail in the queue instead of discarding it.
- Per-process queues. Use the
open()handler to give each process its own queue, or each open file descriptor its own session. - Configurable capacity. Expose
MAX_MESSAGESas a module parameter (module_param!) so users can tune it without recompiling. - Non-blocking semantics. Implement
poll()so user-spaceselect/epollcan wait for queue activity efficiently. - Persistence. Serialize queue contents to a file on module unload and restore them on load.
GPL-2.0 — required to match the Linux kernel. See LICENSE.