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

Commit

Permalink
WIP: RCU
Browse files Browse the repository at this point in the history
  • Loading branch information
geofft committed Aug 17, 2020
1 parent b355d71 commit 9e7eba3
Show file tree
Hide file tree
Showing 9 changed files with 294 additions and 0 deletions.
1 change: 1 addition & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const INCLUDED_VARS: &[&str] = &[
"SEEK_CUR",
"SEEK_END",
"O_NONBLOCK",
"init_task",
];
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();
}
}
}
79 changes: 79 additions & 0 deletions src/sched.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//! APIs for interacting with the scheduler and with processes,
//! corresponding to <linux/sched.h> and related header files.
#![allow(improper_ctypes)]

use alloc::vec::Vec;
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 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) -> Vec<u8> {
unsafe {
task_lock_helper(self.0);
// if only char were unsigned char
let v = (&*(&(*self.0).comm[..] as *const _ as *const [u8])).to_vec();
task_unlock_helper(self.0);
v
}
}
}

/// Iterate over every process on the system. Returns only processes,
/// i.e., thread group leaders.
///
/// ```
/// for p in each_process() {
/// 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.
// - Casting the returned pointer to &'g is safe because _g lives
// for at least 'g.
let oldp = self.p;
if oldp.is_null() {
return None;
}
self.p = unsafe { next_task_helper(self.p) };
if self.p == unsafe { &mut bindings::init_task } {
self.p = ptr::null_mut();
}
Some(TaskStruct(unsafe { &mut *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"
54 changes: 54 additions & 0 deletions tests/for-each-process/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#![no_std]

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

struct PsAuxFile;

impl linux_kernel_module::file_operations::FileOperations for PsAuxFile {
const VTABLE: linux_kernel_module::file_operations::FileOperationsVtable =
linux_kernel_module::file_operations::FileOperationsVtable::builder::<Self>()
.read()
.build();

fn open() -> linux_kernel_module::KernelResult<Self> {
Ok(PsAuxFile)
}
}

impl linux_kernel_module::file_operations::Read for PsAuxFile {
fn read(
&self,
_file: &linux_kernel_module::file_operations::File,
buf: &mut linux_kernel_module::user_ptr::UserSlicePtrWriter,
_offset: u64,
) -> linux_kernel_module::KernelResult<()> {
let g = rcu::RcuReadGuard::new();
for mut p in sched::each_process(&g) {
buf.write(&p.comm())?;
buf.write(b"\n")?;
}
Ok(())
}
}
struct ForEachProcessTestModule {
_chrdev_registration: linux_kernel_module::chrdev::Registration,
}

impl linux_kernel_module::KernelModule for ForEachProcessTestModule {
fn init() -> linux_kernel_module::KernelResult<Self> {
let chrdev_registration =
linux_kernel_module::chrdev::builder(cstr!("foreachprocess-tests"), 0..1)?
.register_device::<PsAuxFile>()
.build()?;
Ok(ForEachProcessTestModule {
_chrdev_registration: chrdev_registration,
})
}
}

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

use libc;

use tempfile::TempDir;

use kernel_module_testlib::with_kernel_module;

fn get_device_major_number() -> libc::dev_t {
let devices = fs::read_to_string("/proc/devices").unwrap();
let dev_no_line = devices
.lines()
.find(|l| l.ends_with("foreachprocess-tests"))
.unwrap();
let elements = dev_no_line.rsplitn(2, " ").collect::<Vec<_>>();
assert_eq!(elements.len(), 2);
assert_eq!(elements[0], "foreachprocess-tests");
return elements[1].trim().parse().unwrap();
}

fn temporary_file_path() -> PathBuf {
let mut p = TempDir::new().unwrap().into_path();
p.push("device");
return p;
}

struct UnlinkOnDrop<'a> {
path: &'a PathBuf,
}

impl Drop for UnlinkOnDrop<'_> {
fn drop(&mut self) {
Command::new("sudo")
.arg("rm")
.arg(self.path.to_str().unwrap())
.status()
.unwrap();
}
}

fn mknod(path: &PathBuf, major: libc::dev_t, minor: libc::dev_t) -> UnlinkOnDrop {
Command::new("sudo")
.arg("mknod")
.arg("--mode=a=rw")
.arg(path.to_str().unwrap())
.arg("c")
.arg(major.to_string())
.arg(minor.to_string())
.status()
.unwrap();
return UnlinkOnDrop { path };
}

#[test]
fn test_running_systemd() {
with_kernel_module(|| {
let device_number = get_device_major_number();
let p = temporary_file_path();
let _u = mknod(&p, device_number, 0);

let f = fs::File::open(&p).unwrap();
for line in io::BufReader::new(f).lines().take(5) {
if dbg!(line).unwrap().starts_with("systemd\0") {
return;
}
}
panic!("what are you running, sysvinit?");
});
}

0 comments on commit 9e7eba3

Please sign in to comment.