Crates.io | scx_rustland_core |
lib.rs | scx_rustland_core |
version | 2.2.4 |
source | src |
created_at | 2024-04-04 19:30:21.254525 |
updated_at | 2024-12-06 23:37:43.844294 |
description | Framework to implement sched_ext schedulers running in user space |
homepage | |
repository | https://github.com/sched-ext/scx |
max_upload_size | |
id | 1196657 |
size | 34,983,283 |
sched_ext
schedulers running in user-spacescx_rustland_core
is a Rust framework designed to facilitate the
implementation of user-space schedulers based on the Linux kernel sched_ext
feature.
sched_ext
allows to dynamic load and execute custom schedulers in the kernel,
leveraging BPF to manage scheduling policies.
This crate provides an abstraction layer for sched_ext
, enabling developers
to write schedulers in Rust without dealing with low-level kernel or BPF
details.
BpfScheduler
The BpfScheduler
struct is the core interface for interacting with the BPF
component.
Initialization:
BpfScheduler::init
registers and initializes the BPF component.Task Management:
dequeue_task()
: Retrieve tasks that need to be scheduled.dispatch_task(task: &DispatchedTask)
: Dispatch tasks to specific CPUs.select_cpu(pid: i32, prev_cpu: i32, flags: u64)
: Select an idle CPU for a task.Completion Notification:
notify_complete(nr_pending: u64)
reports the number of pending tasks to the BPF component.scx_rustland_core
to your Cargo.toml
dependencies.[dependencies]
scx_rustland_core = "0.1"
Implementation:
Execution:
Compile and run your scheduler. Ensure that your kernel supports sched_ext
and is configured to load your BPF programs.
Following you can find a simple example of a fully working FIFO scheduler,
implemented using the scx_rustland_core
framework:
// Copyright (c) Andrea Righi <andrea.righi@linux.dev>
// This software may be used and distributed according to the terms of the
// GNU General Public License version 2.
mod bpf_skel;
pub use bpf_skel::*;
pub mod bpf_intf;
mod bpf;
use bpf::*;
use scx_utils::UserExitInfo;
use libbpf_rs::OpenObject;
use std::mem::MaybeUninit;
use std::collections::VecDeque;
use anyhow::Result;
const SLICE_US: u64 = 5000;
struct Scheduler<'a> {
bpf: BpfScheduler<'a>,
task_queue: VecDeque<QueuedTask>,
}
impl<'a> Scheduler<'a> {
fn init(open_object: &'a mut MaybeUninit<OpenObject>) -> Result<Self> {
let bpf = BpfScheduler::init(
open_object,
0, // exit_dump_len (buffer size of exit info, 0 = default)
false, // partial (false = include all tasks)
false, // debug (false = debug mode off)
)?;
Ok(Self { bpf, task_queue: VecDeque::new() })
}
fn consume_all_tasks(&mut self) {
// Consume all tasks that are ready to run.
//
// Each task contains the following details:
//
// pub struct QueuedTask {
// pub pid: i32, // pid that uniquely identifies a task
// pub cpu: i32, // CPU where the task is running
// pub sum_exec_runtime: u64, // Total cpu time
// pub weight: u64, // Task static priority
// pub nvcsw: u64, // Total amount of voluntary context switches
// pub slice: u64, // Remaining time slice budget
// pub vtime: u64, // Current task vruntime / deadline (set by the scheduler)
// }
//
// Although the FIFO scheduler doesn't use these fields, they can provide valuable data for
// implementing more sophisticated scheduling policies.
while let Ok(Some(task)) = self.bpf.dequeue_task() {
self.task_queue.push_back(task);
}
}
fn dispatch_next_task(&mut self) {
if let Some(task) = self.task_queue.pop_front() {
// Create a new task to be dispatched, derived from the received enqueued task.
//
// pub struct DispatchedTask {
// pub pid: i32, // pid that uniquely identifies a task
// pub cpu: i32, // target CPU selected by the scheduler
// pub flags: u64, // special dispatch flags
// pub slice_ns: u64, // time slice assigned to the task (0 = default)
// }
//
// The dispatched task's information are pre-populated from the QueuedTask and they can
// be modified before dispatching it via self.bpf.dispatch_task().
let mut dispatched_task = DispatchedTask::new(&task);
// Decide where the task needs to run (target CPU).
//
// A call to select_cpu() will return the most suitable idle CPU for the task,
// considering its previously used CPU.
let cpu = self.bpf.select_cpu(task.pid, task.cpu, 0);
if cpu >= 0 {
dispatched_task.cpu = cpu;
} else {
dispatched_task.flags |= RL_CPU_ANY;
}
// Decide for how long the task needs to run (time slice); if not specified
// SCX_SLICE_DFL will be used by default.
dispatched_task.slice_ns = SLICE_US;
// Dispatch the task on the target CPU.
self.bpf.dispatch_task(&dispatched_task).unwrap();
// Notify the BPF component of the number of pending tasks and immediately give a
// chance to run to the dispatched task.
self.bpf.notify_complete(self.task_queue.len() as u64);
}
}
fn dispatch_tasks(&mut self) {
loop {
// Consume all tasks before dispatching any.
self.consume_all_tasks();
// Dispatch one task from the queue.
self.dispatch_next_task();
// If no task is ready to run (or in case of error), stop dispatching tasks and notify
// the BPF component that all tasks have been scheduled / dispatched, with no remaining
// pending tasks.
if self.task_queue.is_empty() {
self.bpf.notify_complete(0);
break;
}
}
}
fn run(&mut self) -> Result<UserExitInfo> {
while !self.bpf.exited() {
self.dispatch_tasks();
}
self.bpf.shutdown_and_report()
}
}
fn main() -> Result<()> {
// Initialize and load the FIFO scheduler.
let mut open_object = MaybeUninit::uninit();
loop {
let mut sched = Scheduler::init(&mut open_object)?;
if !sched.run()?.should_restart() {
break;
}
}
Ok(())
}
This software is licensed under the GNU General Public License version 2. See the LICENSE file for details.
Contributions are welcome! Please submit issues or pull requests via GitHub.