Skip to content
This repository has been archived by the owner on Mar 7, 2021. It is now read-only.

Commit

Permalink
Add RCU read bindings (#143) and each_process() iterator
Browse files Browse the repository at this point in the history
Add an RcuReadGuard object for calling functions that expect to be
RCU-protected, and add a Rust equivalent of for_each_process and a very
light task_struct bindings sufficient to write a simple test/demo.
  • Loading branch information
geofft committed Aug 19, 2020
1 parent b355d71 commit a2e3469
Show file tree
Hide file tree
Showing 9 changed files with 227 additions and 0 deletions.
2 changes: 2 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ const INCLUDED_VARS: &[&str] = &[
"SEEK_CUR",
"SEEK_END",
"O_NONBLOCK",
"init_task",
"TASK_COMM_LEN",
];
const OPAQUE_TYPES: &[&str] = &[
// These need to be opaque because they're both packed and aligned, which rustc
Expand Down
1 change: 1 addition & 0 deletions src/bindings_helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/random.h>
#include <linux/sched/task.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/version.h>
Expand Down
23 changes: 23 additions & 0 deletions src/helpers.c
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
#include <linux/bug.h>
#include <linux/printk.h>
#include <linux/rcupdate.h>
#include <linux/uaccess.h>
#include <linux/version.h>
#include <linux/sched/signal.h>
#include <linux/sched/task.h>


int printk_helper(const unsigned char *s, int len)
Expand All @@ -23,6 +26,26 @@ int access_ok_helper(const void __user *addr, unsigned long n)
#endif
}

void rcu_read_lock_helper(void) {
rcu_read_lock();
}

void rcu_read_unlock_helper(void) {
rcu_read_unlock();
}

struct task_struct *next_task_helper(struct task_struct *p) {
return next_task(p);
}

void task_lock_helper(struct task_struct *p) {
return task_lock(p);
}

void task_unlock_helper(struct task_struct *p) {
return task_unlock(p);
}

/* see https://github.com/rust-lang/rust-bindgen/issues/1671 */
_Static_assert(__builtin_types_compatible_p(size_t, uintptr_t),
"size_t must match uintptr_t, what architecture is this??");
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ pub mod filesystem;
pub mod printk;
#[cfg(kernel_4_13_0_or_greater)]
pub mod random;
pub mod rcu;
pub mod sched;
pub mod sysctl;
mod types;
pub mod user_ptr;
Expand Down
42 changes: 42 additions & 0 deletions src/rcu.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//! Bindings to RCU (read-copy-update), a high-performance lockless
//! synchronization system used by many kernel data structures. At the
//! moment, only calling functions that perform RCU reads is supported.
extern "C" {
fn rcu_read_lock_helper();
fn rcu_read_unlock_helper();
}

/// A guard representing an RCU read-side critical section. Its
/// constructor calls `rcu_read_lock()` and its destructor calls
/// `rcu_read_unlock()`.
///
/// Within a read-side critical section (i.e., while at least one
/// RcuReadGuard object is instantiated), objects behind RCU-protected
/// pointers are guaranteed not to change, and so reading from them
/// (after gaining a pointer with `rcu_dereference()`) is safe.
///
/// It is an error (risk of deadlock, but not memory unsafety) to block
/// or schedule while holding an RcuReadGuard. It is also an error
/// (guaranteed deadlock) to call `synchronize_rcu()` while holding an
/// RcuReadGuard. Holding multiple guards (i.e., nesting read-side
/// critical sections) is safe.
pub struct RcuReadGuard(());

#[allow(clippy::new_without_default)]
impl RcuReadGuard {
pub fn new() -> Self {
unsafe {
rcu_read_lock_helper();
}
RcuReadGuard(())
}
}

impl Drop for RcuReadGuard {
fn drop(&mut self) {
unsafe {
rcu_read_unlock_helper();
}
}
}
97 changes: 97 additions & 0 deletions src/sched.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
//! APIs for interacting with the scheduler and with processes,
//! corresponding to <linux/sched.h> and related header files.
#![allow(improper_ctypes)]

use core::ptr;

use crate::bindings;
use crate::rcu;

extern "C" {
fn task_lock_helper(p: *mut bindings::task_struct);
fn task_unlock_helper(p: *mut bindings::task_struct);
fn next_task_helper(p: *mut bindings::task_struct) -> *mut bindings::task_struct;
}

/// Represents a `struct task_struct *`.
pub struct TaskStruct<'a>(&'a mut bindings::task_struct);

impl TaskStruct<'_> {
/// Returns the threadgroup ID (what userspace calls the process ID).
pub fn tgid(&self) -> i32 {
self.0.tgid
}

/// Returns the command name / process title. This is a short name,
/// typically the base name of the command, and does not have the
/// full path or arguments. It's a fixed-sized set of bytes, but by
/// convention it's interpreted as NUL-terminated.
pub fn comm(&mut self) -> [u8; bindings::TASK_COMM_LEN as usize] {
let mut result = [0u8; bindings::TASK_COMM_LEN as usize];
unsafe {
task_lock_helper(self.0);
}
// if only char were unsigned char
let mut it = result.iter_mut();
for byte in &self.0.comm {
if *byte == 0 {
break;
}
match it.next() {
Some(lvalue) => {
*lvalue = *byte as _;
}
None => {
break;
}
}
}
unsafe {
task_unlock_helper(self.0);
}
result
}
}

/// Iterate over every process on the system. Returns only processes,
/// i.e., thread group leaders.
///
/// ```
/// let g = rcu::RcuReadGuard::new();
/// for p in each_process(&g) {
/// println!("{:?}", p.comm());
/// }
/// ```
pub struct EachProcess<'g> {
p: *mut bindings::task_struct,
_g: &'g rcu::RcuReadGuard,
}

pub fn each_process(g: &rcu::RcuReadGuard) -> EachProcess {
// unsafe is bogus here because we don't read it
// https://github.com/rust-lang/rust/issues/74843
EachProcess {
p: unsafe { &mut bindings::init_task },
_g: g,
}
}

impl<'g> Iterator for EachProcess<'g> {
type Item = TaskStruct<'g>;

fn next(&mut self) -> Option<TaskStruct<'g>> {
// Safety:
// - oldp is valid if not null, because it is either &init_task
// (a static location) or updated by this function.
// - next_task calls rcu_dereference internally, which is safe
// because we hold self._g.
// - The returned reference has lifetime 'g, which is valid
// because self._g lives at least that long.
let oldp = unsafe { self.p.as_mut()? };
self.p = unsafe { next_task_helper(self.p) };
if self.p == unsafe { &mut bindings::init_task } {
self.p = ptr::null_mut();
}
Some(TaskStruct(oldp))
}
}
20 changes: 20 additions & 0 deletions tests/for-each-process/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "for-each-process-tests"
version = "0.1.0"
authors = ["Alex Gaynor <[email protected]>", "Geoffrey Thomas <[email protected]>"]
edition = "2018"

[lib]
crate-type = ["staticlib"]
test = false

[features]
default = ["linux-kernel-module"]

[dependencies]
linux-kernel-module = { path = "../..", optional = true }

[dev-dependencies]
kernel-module-testlib = { path = "../../testlib" }
libc = "0.2.58"
tempfile = "3"
28 changes: 28 additions & 0 deletions tests/for-each-process/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#![no_std]

use linux_kernel_module::{self, println, rcu, sched};

struct ForEachProcessTestModule;

impl linux_kernel_module::KernelModule for ForEachProcessTestModule {
fn init() -> linux_kernel_module::KernelResult<Self> {
let g = rcu::RcuReadGuard::new();
for mut p in sched::each_process(&g) {
let comm = p.comm();
let comm_until_nul = comm.split(|c| *c == 0).next().unwrap();
println!(
"for-each-process: {:8} {}",
p.tgid(),
core::str::from_utf8(comm_until_nul).unwrap_or("[invalid UTF-8]")
);
}
Ok(ForEachProcessTestModule)
}
}

linux_kernel_module::kernel_module!(
ForEachProcessTestModule,
author: b"Fish in a Barrel Contributors",
description: b"A module for testing EachProcess",
license: b"GPL"
);
12 changes: 12 additions & 0 deletions tests/for-each-process/tests/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use std::fs::File;
use std::io::Write;

use kernel_module_testlib::{assert_dmesg_contains, with_kernel_module};

#[test]
fn test_for_each_process() {
File::create("/proc/self/comm").unwrap().write_all(b"areyouthere").unwrap();
with_kernel_module(|| {
assert_dmesg_contains(&[b"areyouthere"]);
});
}

0 comments on commit a2e3469

Please sign in to comment.