This repository has been archived by the owner on Mar 7, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 120
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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.
- Loading branch information
Showing
9 changed files
with
227 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"]); | ||
}); | ||
} |