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

Commit a2e3469

Browse files
committed
Add RCU read bindings (#143) and each_process() iterator
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.
1 parent b355d71 commit a2e3469

File tree

9 files changed

+227
-0
lines changed

9 files changed

+227
-0
lines changed

build.rs

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ const INCLUDED_VARS: &[&str] = &[
4545
"SEEK_CUR",
4646
"SEEK_END",
4747
"O_NONBLOCK",
48+
"init_task",
49+
"TASK_COMM_LEN",
4850
];
4951
const OPAQUE_TYPES: &[&str] = &[
5052
// These need to be opaque because they're both packed and aligned, which rustc

src/bindings_helper.h

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include <linux/fs.h>
33
#include <linux/module.h>
44
#include <linux/random.h>
5+
#include <linux/sched/task.h>
56
#include <linux/slab.h>
67
#include <linux/uaccess.h>
78
#include <linux/version.h>

src/helpers.c

+23
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
#include <linux/bug.h>
22
#include <linux/printk.h>
3+
#include <linux/rcupdate.h>
34
#include <linux/uaccess.h>
45
#include <linux/version.h>
6+
#include <linux/sched/signal.h>
7+
#include <linux/sched/task.h>
58

69

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

29+
void rcu_read_lock_helper(void) {
30+
rcu_read_lock();
31+
}
32+
33+
void rcu_read_unlock_helper(void) {
34+
rcu_read_unlock();
35+
}
36+
37+
struct task_struct *next_task_helper(struct task_struct *p) {
38+
return next_task(p);
39+
}
40+
41+
void task_lock_helper(struct task_struct *p) {
42+
return task_lock(p);
43+
}
44+
45+
void task_unlock_helper(struct task_struct *p) {
46+
return task_unlock(p);
47+
}
48+
2649
/* see https://github.com/rust-lang/rust-bindgen/issues/1671 */
2750
_Static_assert(__builtin_types_compatible_p(size_t, uintptr_t),
2851
"size_t must match uintptr_t, what architecture is this??");

src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ pub mod filesystem;
1515
pub mod printk;
1616
#[cfg(kernel_4_13_0_or_greater)]
1717
pub mod random;
18+
pub mod rcu;
19+
pub mod sched;
1820
pub mod sysctl;
1921
mod types;
2022
pub mod user_ptr;

src/rcu.rs

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//! Bindings to RCU (read-copy-update), a high-performance lockless
2+
//! synchronization system used by many kernel data structures. At the
3+
//! moment, only calling functions that perform RCU reads is supported.
4+
5+
extern "C" {
6+
fn rcu_read_lock_helper();
7+
fn rcu_read_unlock_helper();
8+
}
9+
10+
/// A guard representing an RCU read-side critical section. Its
11+
/// constructor calls `rcu_read_lock()` and its destructor calls
12+
/// `rcu_read_unlock()`.
13+
///
14+
/// Within a read-side critical section (i.e., while at least one
15+
/// RcuReadGuard object is instantiated), objects behind RCU-protected
16+
/// pointers are guaranteed not to change, and so reading from them
17+
/// (after gaining a pointer with `rcu_dereference()`) is safe.
18+
///
19+
/// It is an error (risk of deadlock, but not memory unsafety) to block
20+
/// or schedule while holding an RcuReadGuard. It is also an error
21+
/// (guaranteed deadlock) to call `synchronize_rcu()` while holding an
22+
/// RcuReadGuard. Holding multiple guards (i.e., nesting read-side
23+
/// critical sections) is safe.
24+
pub struct RcuReadGuard(());
25+
26+
#[allow(clippy::new_without_default)]
27+
impl RcuReadGuard {
28+
pub fn new() -> Self {
29+
unsafe {
30+
rcu_read_lock_helper();
31+
}
32+
RcuReadGuard(())
33+
}
34+
}
35+
36+
impl Drop for RcuReadGuard {
37+
fn drop(&mut self) {
38+
unsafe {
39+
rcu_read_unlock_helper();
40+
}
41+
}
42+
}

src/sched.rs

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
//! APIs for interacting with the scheduler and with processes,
2+
//! corresponding to <linux/sched.h> and related header files.
3+
#![allow(improper_ctypes)]
4+
5+
use core::ptr;
6+
7+
use crate::bindings;
8+
use crate::rcu;
9+
10+
extern "C" {
11+
fn task_lock_helper(p: *mut bindings::task_struct);
12+
fn task_unlock_helper(p: *mut bindings::task_struct);
13+
fn next_task_helper(p: *mut bindings::task_struct) -> *mut bindings::task_struct;
14+
}
15+
16+
/// Represents a `struct task_struct *`.
17+
pub struct TaskStruct<'a>(&'a mut bindings::task_struct);
18+
19+
impl TaskStruct<'_> {
20+
/// Returns the threadgroup ID (what userspace calls the process ID).
21+
pub fn tgid(&self) -> i32 {
22+
self.0.tgid
23+
}
24+
25+
/// Returns the command name / process title. This is a short name,
26+
/// typically the base name of the command, and does not have the
27+
/// full path or arguments. It's a fixed-sized set of bytes, but by
28+
/// convention it's interpreted as NUL-terminated.
29+
pub fn comm(&mut self) -> [u8; bindings::TASK_COMM_LEN as usize] {
30+
let mut result = [0u8; bindings::TASK_COMM_LEN as usize];
31+
unsafe {
32+
task_lock_helper(self.0);
33+
}
34+
// if only char were unsigned char
35+
let mut it = result.iter_mut();
36+
for byte in &self.0.comm {
37+
if *byte == 0 {
38+
break;
39+
}
40+
match it.next() {
41+
Some(lvalue) => {
42+
*lvalue = *byte as _;
43+
}
44+
None => {
45+
break;
46+
}
47+
}
48+
}
49+
unsafe {
50+
task_unlock_helper(self.0);
51+
}
52+
result
53+
}
54+
}
55+
56+
/// Iterate over every process on the system. Returns only processes,
57+
/// i.e., thread group leaders.
58+
///
59+
/// ```
60+
/// let g = rcu::RcuReadGuard::new();
61+
/// for p in each_process(&g) {
62+
/// println!("{:?}", p.comm());
63+
/// }
64+
/// ```
65+
pub struct EachProcess<'g> {
66+
p: *mut bindings::task_struct,
67+
_g: &'g rcu::RcuReadGuard,
68+
}
69+
70+
pub fn each_process(g: &rcu::RcuReadGuard) -> EachProcess {
71+
// unsafe is bogus here because we don't read it
72+
// https://github.com/rust-lang/rust/issues/74843
73+
EachProcess {
74+
p: unsafe { &mut bindings::init_task },
75+
_g: g,
76+
}
77+
}
78+
79+
impl<'g> Iterator for EachProcess<'g> {
80+
type Item = TaskStruct<'g>;
81+
82+
fn next(&mut self) -> Option<TaskStruct<'g>> {
83+
// Safety:
84+
// - oldp is valid if not null, because it is either &init_task
85+
// (a static location) or updated by this function.
86+
// - next_task calls rcu_dereference internally, which is safe
87+
// because we hold self._g.
88+
// - The returned reference has lifetime 'g, which is valid
89+
// because self._g lives at least that long.
90+
let oldp = unsafe { self.p.as_mut()? };
91+
self.p = unsafe { next_task_helper(self.p) };
92+
if self.p == unsafe { &mut bindings::init_task } {
93+
self.p = ptr::null_mut();
94+
}
95+
Some(TaskStruct(oldp))
96+
}
97+
}

tests/for-each-process/Cargo.toml

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[package]
2+
name = "for-each-process-tests"
3+
version = "0.1.0"
4+
authors = ["Alex Gaynor <[email protected]>", "Geoffrey Thomas <[email protected]>"]
5+
edition = "2018"
6+
7+
[lib]
8+
crate-type = ["staticlib"]
9+
test = false
10+
11+
[features]
12+
default = ["linux-kernel-module"]
13+
14+
[dependencies]
15+
linux-kernel-module = { path = "../..", optional = true }
16+
17+
[dev-dependencies]
18+
kernel-module-testlib = { path = "../../testlib" }
19+
libc = "0.2.58"
20+
tempfile = "3"

tests/for-each-process/src/lib.rs

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#![no_std]
2+
3+
use linux_kernel_module::{self, println, rcu, sched};
4+
5+
struct ForEachProcessTestModule;
6+
7+
impl linux_kernel_module::KernelModule for ForEachProcessTestModule {
8+
fn init() -> linux_kernel_module::KernelResult<Self> {
9+
let g = rcu::RcuReadGuard::new();
10+
for mut p in sched::each_process(&g) {
11+
let comm = p.comm();
12+
let comm_until_nul = comm.split(|c| *c == 0).next().unwrap();
13+
println!(
14+
"for-each-process: {:8} {}",
15+
p.tgid(),
16+
core::str::from_utf8(comm_until_nul).unwrap_or("[invalid UTF-8]")
17+
);
18+
}
19+
Ok(ForEachProcessTestModule)
20+
}
21+
}
22+
23+
linux_kernel_module::kernel_module!(
24+
ForEachProcessTestModule,
25+
author: b"Fish in a Barrel Contributors",
26+
description: b"A module for testing EachProcess",
27+
license: b"GPL"
28+
);

tests/for-each-process/tests/tests.rs

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
use std::fs::File;
2+
use std::io::Write;
3+
4+
use kernel_module_testlib::{assert_dmesg_contains, with_kernel_module};
5+
6+
#[test]
7+
fn test_for_each_process() {
8+
File::create("/proc/self/comm").unwrap().write_all(b"areyouthere").unwrap();
9+
with_kernel_module(|| {
10+
assert_dmesg_contains(&[b"areyouthere"]);
11+
});
12+
}

0 commit comments

Comments
 (0)