diff --git a/examples/scx/bpf_bpfeb.go b/examples/scx/bpf_bpfeb.go new file mode 100644 index 000000000..be26adb3b --- /dev/null +++ b/examples/scx/bpf_bpfeb.go @@ -0,0 +1,144 @@ +// Code generated by bpf2go; DO NOT EDIT. +//go:build (mips || mips64 || ppc64 || s390x) && linux + +package main + +import ( + "bytes" + _ "embed" + "fmt" + "io" + + "github.com/cilium/ebpf" +) + +// loadBpf returns the embedded CollectionSpec for bpf. +func loadBpf() (*ebpf.CollectionSpec, error) { + reader := bytes.NewReader(_BpfBytes) + spec, err := ebpf.LoadCollectionSpecFromReader(reader) + if err != nil { + return nil, fmt.Errorf("can't load bpf: %w", err) + } + + return spec, err +} + +// loadBpfObjects loads bpf and converts it into a struct. +// +// The following types are suitable as obj argument: +// +// *bpfObjects +// *bpfPrograms +// *bpfMaps +// +// See ebpf.CollectionSpec.LoadAndAssign documentation for details. +func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error { + spec, err := loadBpf() + if err != nil { + return err + } + + return spec.LoadAndAssign(obj, opts) +} + +// bpfSpecs contains maps and programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type bpfSpecs struct { + bpfProgramSpecs + bpfMapSpecs + bpfVariableSpecs +} + +// bpfProgramSpecs contains programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type bpfProgramSpecs struct { + ScxInit *ebpf.ProgramSpec `ebpf:"scx_init"` + ScxQuiescent *ebpf.ProgramSpec `ebpf:"scx_quiescent"` + ScxRunnable *ebpf.ProgramSpec `ebpf:"scx_runnable"` + ScxRunning *ebpf.ProgramSpec `ebpf:"scx_running"` + ScxStopping *ebpf.ProgramSpec `ebpf:"scx_stopping"` + ScxYield *ebpf.ProgramSpec `ebpf:"scx_yield"` +} + +// bpfMapSpecs contains maps before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type bpfMapSpecs struct { +} + +// bpfVariableSpecs contains global variables before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type bpfVariableSpecs struct { +} + +// bpfObjects contains all objects after they have been loaded into the kernel. +// +// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. +type bpfObjects struct { + bpfPrograms + bpfMaps + bpfVariables +} + +func (o *bpfObjects) Close() error { + return _BpfClose( + &o.bpfPrograms, + &o.bpfMaps, + ) +} + +// bpfMaps contains all maps after they have been loaded into the kernel. +// +// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. +type bpfMaps struct { +} + +func (m *bpfMaps) Close() error { + return _BpfClose() +} + +// bpfVariables contains all global variables after they have been loaded into the kernel. +// +// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. +type bpfVariables struct { +} + +// bpfPrograms contains all programs after they have been loaded into the kernel. +// +// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. +type bpfPrograms struct { + ScxInit *ebpf.Program `ebpf:"scx_init"` + ScxQuiescent *ebpf.Program `ebpf:"scx_quiescent"` + ScxRunnable *ebpf.Program `ebpf:"scx_runnable"` + ScxRunning *ebpf.Program `ebpf:"scx_running"` + ScxStopping *ebpf.Program `ebpf:"scx_stopping"` + ScxYield *ebpf.Program `ebpf:"scx_yield"` +} + +func (p *bpfPrograms) Close() error { + return _BpfClose( + p.ScxInit, + p.ScxQuiescent, + p.ScxRunnable, + p.ScxRunning, + p.ScxStopping, + p.ScxYield, + ) +} + +func _BpfClose(closers ...io.Closer) error { + for _, closer := range closers { + if err := closer.Close(); err != nil { + return err + } + } + return nil +} + +// Do not access this directly. +// +//go:embed bpf_bpfeb.o +var _BpfBytes []byte diff --git a/examples/scx/bpf_bpfeb.o b/examples/scx/bpf_bpfeb.o new file mode 100644 index 000000000..99fbfc592 Binary files /dev/null and b/examples/scx/bpf_bpfeb.o differ diff --git a/examples/scx/bpf_bpfel.go b/examples/scx/bpf_bpfel.go new file mode 100644 index 000000000..1d3e9a45d --- /dev/null +++ b/examples/scx/bpf_bpfel.go @@ -0,0 +1,144 @@ +// Code generated by bpf2go; DO NOT EDIT. +//go:build (386 || amd64 || arm || arm64 || loong64 || mips64le || mipsle || ppc64le || riscv64 || wasm) && linux + +package main + +import ( + "bytes" + _ "embed" + "fmt" + "io" + + "github.com/cilium/ebpf" +) + +// loadBpf returns the embedded CollectionSpec for bpf. +func loadBpf() (*ebpf.CollectionSpec, error) { + reader := bytes.NewReader(_BpfBytes) + spec, err := ebpf.LoadCollectionSpecFromReader(reader) + if err != nil { + return nil, fmt.Errorf("can't load bpf: %w", err) + } + + return spec, err +} + +// loadBpfObjects loads bpf and converts it into a struct. +// +// The following types are suitable as obj argument: +// +// *bpfObjects +// *bpfPrograms +// *bpfMaps +// +// See ebpf.CollectionSpec.LoadAndAssign documentation for details. +func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error { + spec, err := loadBpf() + if err != nil { + return err + } + + return spec.LoadAndAssign(obj, opts) +} + +// bpfSpecs contains maps and programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type bpfSpecs struct { + bpfProgramSpecs + bpfMapSpecs + bpfVariableSpecs +} + +// bpfProgramSpecs contains programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type bpfProgramSpecs struct { + ScxInit *ebpf.ProgramSpec `ebpf:"scx_init"` + ScxQuiescent *ebpf.ProgramSpec `ebpf:"scx_quiescent"` + ScxRunnable *ebpf.ProgramSpec `ebpf:"scx_runnable"` + ScxRunning *ebpf.ProgramSpec `ebpf:"scx_running"` + ScxStopping *ebpf.ProgramSpec `ebpf:"scx_stopping"` + ScxYield *ebpf.ProgramSpec `ebpf:"scx_yield"` +} + +// bpfMapSpecs contains maps before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type bpfMapSpecs struct { +} + +// bpfVariableSpecs contains global variables before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type bpfVariableSpecs struct { +} + +// bpfObjects contains all objects after they have been loaded into the kernel. +// +// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. +type bpfObjects struct { + bpfPrograms + bpfMaps + bpfVariables +} + +func (o *bpfObjects) Close() error { + return _BpfClose( + &o.bpfPrograms, + &o.bpfMaps, + ) +} + +// bpfMaps contains all maps after they have been loaded into the kernel. +// +// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. +type bpfMaps struct { +} + +func (m *bpfMaps) Close() error { + return _BpfClose() +} + +// bpfVariables contains all global variables after they have been loaded into the kernel. +// +// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. +type bpfVariables struct { +} + +// bpfPrograms contains all programs after they have been loaded into the kernel. +// +// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. +type bpfPrograms struct { + ScxInit *ebpf.Program `ebpf:"scx_init"` + ScxQuiescent *ebpf.Program `ebpf:"scx_quiescent"` + ScxRunnable *ebpf.Program `ebpf:"scx_runnable"` + ScxRunning *ebpf.Program `ebpf:"scx_running"` + ScxStopping *ebpf.Program `ebpf:"scx_stopping"` + ScxYield *ebpf.Program `ebpf:"scx_yield"` +} + +func (p *bpfPrograms) Close() error { + return _BpfClose( + p.ScxInit, + p.ScxQuiescent, + p.ScxRunnable, + p.ScxRunning, + p.ScxStopping, + p.ScxYield, + ) +} + +func _BpfClose(closers ...io.Closer) error { + for _, closer := range closers { + if err := closer.Close(); err != nil { + return err + } + } + return nil +} + +// Do not access this directly. +// +//go:embed bpf_bpfel.o +var _BpfBytes []byte diff --git a/examples/scx/bpf_bpfel.o b/examples/scx/bpf_bpfel.o new file mode 100644 index 000000000..82f373a08 Binary files /dev/null and b/examples/scx/bpf_bpfel.o differ diff --git a/examples/scx/main.go b/examples/scx/main.go new file mode 100644 index 000000000..5388a5563 --- /dev/null +++ b/examples/scx/main.go @@ -0,0 +1,42 @@ +package main + +// This program demonstrates attaching an eBPF program to sched_ext + +import ( + "log" + "os" + "os/signal" + "syscall" + + "github.com/cilium/ebpf/link" + "github.com/cilium/ebpf/rlimit" +) + +//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -no-global-types -tags linux bpf sched.c -- -I../headers + +func main() { + if err := rlimit.RemoveMemlock(); err != nil { + log.Fatal(err) + } + + objs := bpfObjects{} + if err := loadBpfObjects(&objs, nil); err != nil { + log.Fatalf("loading objects: %v", err) + } + defer objs.Close() + + m := objs.Scx + l, err := link.AttachStructOps(m) + if err != nil { + log.Fatalf("Failed to attach sched_ext: %s", err) + } + defer l.DetachStructOps() + + log.Println("Successfully attached sched_ext struct operations") + + stopper := make(chan os.Signal, 1) + signal.Notify(stopper, os.Interrupt, syscall.SIGTERM) + log.Print("Press Ctrl+C to stop...") + + <-stopper +} diff --git a/examples/scx/sched.c b/examples/scx/sched.c new file mode 100644 index 000000000..131568529 --- /dev/null +++ b/examples/scx/sched.c @@ -0,0 +1,59 @@ +// go:build ignore + +#include "common.h" + +char __license[] SEC("license") = "Dual MIT/GPL"; + +struct sched_ext_ops { + __s32 (*init)(void); + void (*runnable)(void *, __u64); + void (*running)(void *); + void (*stopping)(void *, __u8); + void (*quiescent)(void *, __u64); + __u8 (*yield)(void *, void *); + __u32 timeout_ms; + char name[128]; +}; + +SEC("struct_ops/scx_runnable") +void scx_runnable(void *p, __u64 enq_flags) { + bpf_printk("scheduler runnable\n"); +}; + +SEC("struct_ops/scx_running") +void scx_running(void *p) { + bpf_printk("scheduler running\n"); +}; + +SEC("struct_ops/scx_stopping") +void scx_stopping(void *p, __u8 runnable) { + bpf_printk("scheduler stopping\n"); +}; + +SEC("struct_ops/scx_quiescent") +void scx_quiescent(void *p, __u64 deq_flags) { + bpf_printk("scheduler quiescent\n"); +}; + +SEC("struct_ops/scx_yield") +void scx_yield(void *from, void *to) { + bpf_printk("scheduler yield\n"); +}; + +SEC("struct_ops.s/scx_init") +int scx_init(void) { + bpf_printk("scheduler init\n"); + return 0; +}; + +SEC(".struct_ops.link") +struct sched_ext_ops scx = { + .init = (void *)scx_init, + .running = (void *)scx_running, + .runnable = (void *)scx_runnable, + .stopping = (void *)scx_stopping, + .quiescent = (void *)scx_quiescent, + .yield = (void *)scx_yield, + .timeout_ms = 10000U, + .name = "scx", +}; diff --git a/link/struct_ops.go b/link/struct_ops.go new file mode 100644 index 000000000..9528f1965 --- /dev/null +++ b/link/struct_ops.go @@ -0,0 +1,54 @@ +package link + +import ( + "fmt" + + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/internal/sys" +) + +type structOpsLink struct { + *RawLink + m *ebpf.Map +} + +// AttachStructOps links a StructOps map +func AttachStructOps(m *ebpf.Map) (*structOpsLink, error) { + if m == nil { + return nil, fmt.Errorf("attach StructOps: map cannot be nil: %w", errInvalidInput) + } + + if t := m.Type(); t != ebpf.StructOpsMap { + return nil, fmt.Errorf("attach StrcutOps: invalid map type %s, expected struct_ops: %w", t, errInvalidInput) + } + + mapFD, err := sys.NewFD(m.FD()) + if err != nil { + return nil, fmt.Errorf("attach StructOps: %w", err) + } + + if (int(m.Flags()) & sys.BPF_F_LINK) != sys.BPF_F_LINK { + return &structOpsLink{&RawLink{mapFD, ""}, m}, nil + } + + fd, err := sys.LinkCreate(&sys.LinkCreateAttr{ + ProgFd: uint32(m.FD()), + AttachType: sys.AttachType(ebpf.AttachStructOps), + TargetFd: 0, + }) + if err != nil { + return nil, fmt.Errorf("attach StructOps: create link: %w", err) + } + + rawLink := &RawLink{fd, ""} + return &structOpsLink{rawLink, nil}, nil +} + +// DetachStructOps detaches a StructOps +func (l *structOpsLink) DetachStructOps() error { + if l.m != nil { + // delete kern_vdata directly when it's without real link + return l.m.Delete(uint32(0)) + } + return l.Close() +}