rust: introduce scx_rustland_core crate

Introduce a separate crate (scx_rustland_core) that can be used to
implement sched-ext schedulers in Rust that run in user-space.

This commit only provides the basic layout for the new crate and the
abstraction to the custom allocator.

In general, any scheduler that has a user-space component needs to use
the custom allocator to prevent potential deadlock conditions, caused by
page faults (a kthread needs to run to resolve the page fault, but the
scheduler is blocked waiting for the user-space page fault to be
resolved => deadlock).

However, we don't want to necessarily enforce this constraint to all the
existing Rust schedulers, some of them may do all user-space allocations
in safe paths, hence the separate scx_rustland_core crate.

Merging this code in scx_utils would force all the Rust schedulers to
use the custom allocator.

In a future commit the scx_rustland backend will be moved to
scx_rustland_core, making it a totally generic BPF scheduler framework
that can be used to implement user-space schedulers in Rust.

Signed-off-by: Andrea Righi <andrea.righi@canonical.com>
This commit is contained in:
Andrea Righi 2024-02-24 15:56:34 +01:00
parent 4dfb898a08
commit 416d6a940f
15 changed files with 127 additions and 12 deletions

View File

@ -1 +1,2 @@
subdir('scx_utils') subdir('scx_utils')
subdir('scx_rustland_core')

1
rust/scx_rustland_core/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
Cargo.lock

View File

@ -0,0 +1,32 @@
[package]
name = "scx_rustland_core"
version = "0.1.0"
edition = "2021"
authors = ["Andrea Righi <andrea.righi@canonical.com>"]
license = "GPL-2.0-only"
repository = "https://github.com/sched-ext/scx"
description = "Framework to implement sched_ext schedulers running in user space"
[dependencies]
anyhow = "1.0"
bitvec = { version = "1.0", features = ["serde"] }
# FIXME - We need to allow both 0.68 and 0.69 to accommodate fedora. See the
# comment in BpfBuilder::bindgen_bpf_intf() for details.
bindgen = ">=0.68, <0.70"
glob = "0.3"
hex = "0.4.3"
lazy_static = "1.4"
libbpf-cargo = "0.22"
libbpf-rs = "0.22.0"
libc = "0.2.137"
buddy-alloc = "0.5.1"
log = "0.4.17"
regex = "1.10"
sscanf = "0.4"
tar = "0.4"
version-compare = "0.1"
[build-dependencies]
bindgen = ">=0.68, <0.70"
tar = "0.4"
walkdir = "2.4"

View File

@ -0,0 +1 @@
../../LICENSE

View File

@ -0,0 +1,8 @@
# Framework to implement sched_ext schedulers running in user-space
[sched_ext](https://github.com/sched-ext/scx) is a Linux kernel feature
which enables implementing kernel thread schedulers in BPF and dynamically
loading them.
Thie crate provides a generic framework that allows to implement sched_ext
schedulers that are running in user-space.

View File

@ -0,0 +1 @@
#include "bpf_h/vmlinux/vmlinux.h"

View File

@ -0,0 +1 @@
../../scheds/include

View File

@ -0,0 +1,53 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// This software may be used and distributed according to the terms of the
// GNU General Public License version 2.
use std::env;
use std::fs::File;
use std::path::PathBuf;
const BPF_H: &str = "bpf_h";
fn gen_bpf_h() {
let file =
File::create(PathBuf::from(env::var("OUT_DIR").unwrap()).join(format!("{}.tar", BPF_H)))
.unwrap();
let mut ar = tar::Builder::new(file);
ar.follow_symlinks(false);
ar.append_dir_all(".", BPF_H).unwrap();
ar.finish().unwrap();
for ent in walkdir::WalkDir::new(BPF_H) {
let ent = ent.unwrap();
if !ent.file_type().is_dir() {
println!("cargo:rerun-if-changed={}", ent.path().to_string_lossy());
}
}
}
fn gen_bindings() {
// FIXME - bindgen's API changed between 0.68 and 0.69 so that
// `bindgen::CargoCallbacks::new()` should be used instead of
// `bindgen::CargoCallbacks`. Unfortunately, as of Dec 2023, fedora is
// shipping 0.68. To accommodate fedora, allow both 0.68 and 0.69 of
// bindgen and suppress deprecation warning. Remove the following once
// fedora can be updated to bindgen >= 0.69.
#[allow(deprecated)]
let bindings = bindgen::Builder::default()
.header("bindings.h")
.allowlist_type("scx_exit_kind")
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
.generate()
.expect("Unable to generate bindings");
bindings
.write_to_file(PathBuf::from(env::var("OUT_DIR").unwrap()).join("bindings.rs"))
.expect("Couldn't write bindings");
}
fn main() {
gen_bpf_h();
gen_bindings();
}

View File

@ -0,0 +1,7 @@
custom_target('scx_rustland_core',
output: '@PLAINNAME@.__PHONY__',
input: 'Cargo.toml',
command: [cargo, 'build', '--manifest-path=@INPUT@', '--target-dir=@OUTDIR@',
cargo_build_args],
env: cargo_env,
build_by_default: true)

View File

@ -15,7 +15,7 @@ const HEAP_SIZE: usize = 64 * 1024 * 1024; // 64M
const LEAF_SIZE: usize = 64; const LEAF_SIZE: usize = 64;
#[repr(align(4096))] #[repr(align(4096))]
pub struct AlignedHeap<const N: usize>([u8; N]); struct AlignedHeap<const N: usize>([u8; N]);
// Statically pre-allocated memory arena. // Statically pre-allocated memory arena.
static mut FAST_HEAP: AlignedHeap<FAST_HEAP_SIZE> = AlignedHeap([0u8; FAST_HEAP_SIZE]); static mut FAST_HEAP: AlignedHeap<FAST_HEAP_SIZE> = AlignedHeap([0u8; FAST_HEAP_SIZE]);
@ -26,25 +26,25 @@ static mut HEAP: AlignedHeap<HEAP_SIZE> = AlignedHeap([0u8; HEAP_SIZE]);
// To prevent potential deadlock conditions under heavy loads, any scheduler that delegates // To prevent potential deadlock conditions under heavy loads, any scheduler that delegates
// scheduling decisions to user-space should avoid triggering page faults. // scheduling decisions to user-space should avoid triggering page faults.
// //
// To address this issue, replace the global allocator with a custom one (RustLandAllocator), // To address this issue, replace the global allocator with a custom one (UserAllocator),
// designed to operate on a pre-allocated buffer. This, coupled with the memory locking achieved // designed to operate on a pre-allocated buffer. This, coupled with the memory locking achieved
// through mlockall(), prevents page faults from occurring during the execution of the user-space // through mlockall(), prevents page faults from occurring during the execution of the user-space
// scheduler. // scheduler.
#[cfg_attr(not(test), global_allocator)] #[cfg_attr(not(test), global_allocator)]
pub static ALLOCATOR: RustLandAllocator = unsafe { pub static ALLOCATOR: UserAllocator = unsafe {
let fast_param = FastAllocParam::new(FAST_HEAP.0.as_ptr(), FAST_HEAP_SIZE); let fast_param = FastAllocParam::new(FAST_HEAP.0.as_ptr(), FAST_HEAP_SIZE);
let buddy_param = BuddyAllocParam::new(HEAP.0.as_ptr(), HEAP_SIZE, LEAF_SIZE); let buddy_param = BuddyAllocParam::new(HEAP.0.as_ptr(), HEAP_SIZE, LEAF_SIZE);
RustLandAllocator { UserAllocator {
arena: NonThreadsafeAlloc::new(fast_param, buddy_param), arena: NonThreadsafeAlloc::new(fast_param, buddy_param),
} }
}; };
// Main allocator class. // Main allocator class.
pub struct RustLandAllocator { pub struct UserAllocator {
pub arena: NonThreadsafeAlloc, arena: NonThreadsafeAlloc,
} }
impl RustLandAllocator { impl UserAllocator {
pub fn lock_memory(&self) { pub fn lock_memory(&self) {
unsafe { unsafe {
VM.save(); VM.save();
@ -75,7 +75,7 @@ impl RustLandAllocator {
} }
// Override global allocator methods. // Override global allocator methods.
unsafe impl GlobalAlloc for RustLandAllocator { unsafe impl GlobalAlloc for UserAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 { unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
self.arena.alloc(layout) self.arena.alloc(layout)
} }

View File

@ -0,0 +1,6 @@
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(dead_code)]
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

View File

@ -0,0 +1,4 @@
mod bindings;
mod alloc;
pub use alloc::ALLOCATOR;

View File

@ -18,6 +18,7 @@ hex = "0.4.3"
lazy_static = "1.4" lazy_static = "1.4"
libbpf-cargo = "0.22" libbpf-cargo = "0.22"
libbpf-rs = "0.22.0" libbpf-rs = "0.22.0"
buddy-alloc = "0.5.1"
log = "0.4.17" log = "0.4.17"
regex = "1.10" regex = "1.10"
sscanf = "0.4" sscanf = "0.4"

View File

@ -15,10 +15,10 @@ fb_procfs = "0.7.0"
hex = "0.4.3" hex = "0.4.3"
libbpf-rs = "0.22.0" libbpf-rs = "0.22.0"
libc = "0.2.137" libc = "0.2.137"
buddy-alloc = "0.5.1"
log = "0.4.17" log = "0.4.17"
ordered-float = "3.4.0" ordered-float = "3.4.0"
scx_utils = { path = "../../../rust/scx_utils", version = "0.6" } scx_utils = { path = "../../../rust/scx_utils", version = "0.6" }
scx_rustland_core = { path = "../../../rust/scx_rustland_core", version = "0.1" }
simplelog = "0.12.0" simplelog = "0.12.0"
[build-dependencies] [build-dependencies]

View File

@ -15,13 +15,12 @@ use libbpf_rs::skel::SkelBuilder as _;
use libc::{sched_param, sched_setscheduler}; use libc::{sched_param, sched_setscheduler};
mod alloc;
use alloc::*;
use scx_utils::init_libbpf_logging; use scx_utils::init_libbpf_logging;
use scx_utils::uei_exited; use scx_utils::uei_exited;
use scx_utils::uei_report; use scx_utils::uei_report;
use scx_rustland_core::ALLOCATOR;
// Defined in UAPI // Defined in UAPI
const SCHED_EXT: i32 = 7; const SCHED_EXT: i32 = 7;