Skip to content

Commit 65c013b

Browse files
committed
Implement sched-ext scheduler support and add related tests.
Signed-off-by: Cong Wang <cwang@multikernel.io>
1 parent f9035c3 commit 65c013b

7 files changed

Lines changed: 476 additions & 3 deletions

File tree

SPEC.md

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1802,7 +1802,62 @@ fn main() -> i32 {
18021802
}
18031803
```
18041804

1805-
#### 3.10.3 Registration Function
1805+
#### 3.10.3 Sched-ext Scheduler Implementation
1806+
1807+
KernelScript supports sched-ext (extensible scheduler) through the `sched_ext_ops` struct_ops:
1808+
1809+
```kernelscript
1810+
// Simple FIFO scheduler using sched-ext
1811+
@struct_ops("sched_ext_ops")
1812+
impl simple_fifo_scheduler {
1813+
1814+
// Select CPU for a waking task
1815+
fn select_cpu(p: *u8, prev_cpu: i32, wake_flags: u64) -> i32 {
1816+
// Use default CPU selection with direct dispatch if idle core found
1817+
var direct: bool = false
1818+
var cpu = scx_bpf_select_cpu_dfl(p, prev_cpu, wake_flags, &direct)
1819+
1820+
if (direct) {
1821+
// Insert directly into local DSQ, skipping enqueue
1822+
scx_bpf_dsq_insert(p, SCX_DSQ_LOCAL, SCX_SLICE_DFL, 0)
1823+
}
1824+
1825+
return cpu
1826+
}
1827+
1828+
// Enqueue task into global FIFO queue
1829+
fn enqueue(p: *u8, enq_flags: u64) -> void {
1830+
// Simple FIFO: insert all tasks into global DSQ
1831+
scx_bpf_dsq_insert(p, SCX_DSQ_GLOBAL, SCX_SLICE_DFL, enq_flags)
1832+
}
1833+
1834+
// Dispatch tasks from global queue to local CPU
1835+
fn dispatch(cpu: i32, prev: *u8) -> void {
1836+
// Try to consume a task from the global DSQ
1837+
if (!scx_bpf_consume(SCX_DSQ_GLOBAL)) {
1838+
// No tasks available, CPU will go idle
1839+
}
1840+
}
1841+
1842+
// Initialize scheduler
1843+
fn init() -> i32 {
1844+
return 0 // Success
1845+
}
1846+
1847+
// Scheduler configuration
1848+
name: "simple_fifo",
1849+
timeout_ms: 0, // No timeout
1850+
flags: 0, // Default flags
1851+
}
1852+
1853+
// Register the scheduler
1854+
fn main() -> i32 {
1855+
var result = register(simple_fifo_scheduler)
1856+
return result
1857+
}
1858+
```
1859+
1860+
#### 3.10.4 Registration Function
18061861

18071862
The `register()` function is type-aware and generates the appropriate registration code:
18081863

examples/sched_ext_simple.ks

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
// Simple sched-ext scheduler implementation
2+
// This demonstrates a basic FIFO scheduler using sched_ext_ops
3+
4+
// kfuncs declarations (extracted from BTF)
5+
extern scx_bpf_select_cpu_dfl(p: *u8, prev_cpu: i32, wake_flags: u64, direct: *bool) -> i32
6+
extern scx_bpf_dsq_insert(p: *u8, dsq_id: u64, slice: u64, enq_flags: u64) -> void
7+
extern scx_bpf_consume(dsq_id: u64, cpu: i32, flags: u64) -> i32
8+
9+
// Kernel enums (extracted from BTF)
10+
enum scx_public_consts {
11+
SCX_OPS_NAME_LEN = 128,
12+
SCX_SLICE_DFL = 20000000,
13+
SCX_SLICE_INF = 18446744073709551615,
14+
}
15+
16+
enum scx_dsq_id_flags {
17+
SCX_DSQ_FLAG_BUILTIN = 9223372036854775808,
18+
SCX_DSQ_FLAG_LOCAL_ON = 4611686018427387904,
19+
SCX_DSQ_INVALID = 9223372036854775808,
20+
SCX_DSQ_GLOBAL = 9223372036854775809,
21+
SCX_DSQ_LOCAL = 9223372036854775810,
22+
SCX_DSQ_LOCAL_ON = 13835058055282163712,
23+
SCX_DSQ_LOCAL_CPU_MASK = 4294967295,
24+
}
25+
26+
// Define the sched_ext_ops structure (extracted from BTF)
27+
struct sched_ext_ops {
28+
select_cpu: fn(p: *u8, prev_cpu: i32, wake_flags: u64) -> i32,
29+
enqueue: fn(p: *u8, enq_flags: u64) -> void,
30+
dispatch: fn(cpu: i32, prev: *u8) -> void,
31+
runnable: fn(p: *u8, enq_flags: u64) -> void,
32+
running: fn(p: *u8) -> void,
33+
stopping: fn(p: *u8, runnable: bool) -> void,
34+
quiescent: fn(p: *u8, deq_flags: u64) -> void,
35+
init_task: fn(p: *u8, args: *u8) -> i32,
36+
exit_task: fn(p: *u8, args: *u8) -> void,
37+
enable: fn(p: *u8) -> void,
38+
init: fn() -> i32,
39+
exit: fn(info: *u8) -> void,
40+
name: *u8,
41+
timeout_ms: u64,
42+
flags: u64,
43+
}
44+
45+
// Simple FIFO scheduler implementation
46+
@struct_ops("sched_ext_ops")
47+
impl simple_fifo_scheduler {
48+
49+
// Select CPU for a waking task
50+
fn select_cpu(p: *u8, prev_cpu: i32, wake_flags: u64) -> i32 {
51+
// Use default CPU selection with direct dispatch if idle core found
52+
var direct: bool = false
53+
var cpu = scx_bpf_select_cpu_dfl(p, prev_cpu, wake_flags, &direct)
54+
55+
if (direct) {
56+
// Insert directly into local DSQ, skipping enqueue
57+
scx_bpf_dsq_insert(p, SCX_DSQ_LOCAL, SCX_SLICE_DFL, 0)
58+
}
59+
60+
return cpu
61+
}
62+
63+
// Enqueue task into global FIFO queue
64+
fn enqueue(p: *u8, enq_flags: u64) -> void {
65+
// Simple FIFO: insert all tasks into global DSQ
66+
scx_bpf_dsq_insert(p, SCX_DSQ_GLOBAL, SCX_SLICE_DFL, enq_flags)
67+
}
68+
69+
// Dispatch tasks from global queue to local CPU
70+
fn dispatch(cpu: i32, prev: *u8) -> void {
71+
// Try to consume a task from the global DSQ
72+
if (scx_bpf_consume(SCX_DSQ_GLOBAL, cpu, 0) == 0) {
73+
// No tasks available, CPU will go idle
74+
}
75+
}
76+
77+
// Task becomes runnable
78+
fn runnable(p: *u8, enq_flags: u64) -> void {
79+
// Optional: track runnable tasks
80+
// For simple FIFO, we don't need special handling
81+
}
82+
83+
// Task starts running
84+
fn running(p: *u8) -> void {
85+
// Optional: track running tasks
86+
// For simple FIFO, we don't need special handling
87+
}
88+
89+
// Task stops running
90+
fn stopping(p: *u8, runnable: bool) -> void {
91+
// Optional: handle task stopping
92+
// For simple FIFO, we don't need special handling
93+
}
94+
95+
// Task becomes quiescent
96+
fn quiescent(p: *u8, deq_flags: u64) -> void {
97+
// Optional: handle quiescent tasks
98+
// For simple FIFO, we don't need special handling
99+
}
100+
101+
// Initialize new task
102+
fn init_task(p: *u8, args: *u8) -> i32 {
103+
// Return 0 for success
104+
return 0
105+
}
106+
107+
// Clean up exiting task
108+
fn exit_task(p: *u8, args: *u8) -> void {
109+
// Optional cleanup for exiting tasks
110+
}
111+
112+
// Enable scheduler
113+
fn enable(p: *u8) -> void {
114+
// Optional: scheduler enable logic
115+
}
116+
117+
// Initialize scheduler
118+
fn init() -> i32 {
119+
// Return 0 for successful initialization
120+
return 0
121+
}
122+
123+
// Exit scheduler
124+
fn exit(info: *u8) -> void {
125+
// Optional cleanup on scheduler exit
126+
}
127+
128+
// Scheduler name
129+
name: "simple_fifo",
130+
131+
// Timeout in milliseconds (0 = no timeout)
132+
timeout_ms: 0,
133+
134+
// Scheduler flags
135+
flags: 0,
136+
}
137+
138+
// Userspace main function
139+
fn main() -> i32 {
140+
// Register the sched-ext scheduler
141+
var result = register(simple_fifo_scheduler)
142+
143+
if (result == 0) {
144+
print("Simple FIFO scheduler registered successfully")
145+
} else {
146+
print("Failed to register Simple FIFO scheduler")
147+
}
148+
149+
return result
150+
}

src/ir_generator.ml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3246,7 +3246,12 @@ let lower_multi_program ast symbol_table source_name =
32463246
(match literal with
32473247
| Ast.StringLit s -> make_ir_value (IRLiteral (StringLit s)) (IRStr (String.length s + 1)) field_expr.expr_pos
32483248
| Ast.NullLit -> make_ir_value (IRLiteral NullLit) (IRPointer (IRU8, make_bounds_info ~nullable:true ())) field_expr.expr_pos
3249-
| _ -> failwith "Unsupported literal type in static field")
3249+
| Ast.IntLit (value, _) ->
3250+
let ir_type = if value < 0 then IRI32 else IRU32 in
3251+
make_ir_value (IRLiteral (IntLit (value, None))) ir_type field_expr.expr_pos
3252+
| Ast.BoolLit b -> make_ir_value (IRLiteral (BoolLit b)) IRBool field_expr.expr_pos
3253+
| Ast.CharLit c -> make_ir_value (IRLiteral (CharLit c)) IRChar field_expr.expr_pos
3254+
| _ -> failwith ("Unsupported literal type in static field: " ^ (Ast.string_of_literal literal)))
32503255
| _ -> failwith "Static fields must be literals"
32513256
in
32523257
Some (field_name, field_val)

src/main.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ let rec parse_args () =
3737
printf " prog_type: xdp | tc/direction | probe/target_function | tracepoint/category/event\n";
3838
printf " Examples: tc/ingress, tc/egress, probe/sys_read, probe/vfs_write, probe/tcp_sendmsg\n";
3939
printf " tracepoint: tracepoint/syscalls/sys_enter_read, tracepoint/sched/sched_switch\n";
40-
printf " struct_ops: tcp_congestion_ops\n";
40+
printf " struct_ops: tcp_congestion_ops, sched_ext_ops\n";
4141
printf " project_name: Name of the project directory to create\n";
4242
printf " --btf-vmlinux-path: Path to BTF vmlinux file (default: /sys/kernel/btf/vmlinux)\n";
4343
printf " --kfuncs: Extract available kfuncs from BTF and generate .kh header file\n\n";

src/struct_ops_registry.ml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ let known_struct_ops = [
4646
kernel_version = Some "5.6+";
4747
common_usage = ["Testing and development"];
4848
};
49+
{
50+
name = "sched_ext_ops";
51+
description = "Extensible scheduler operations";
52+
kernel_version = Some "6.12+";
53+
common_usage = ["Custom scheduling policies"; "BPF-based schedulers"; "Scheduler experimentation"];
54+
};
4955
]
5056

5157
(** Check if a struct_ops type is known *)

0 commit comments

Comments
 (0)