Why does the iocost cgroup cost model file only switch the built-in model between auto and user coefficient sets, and how does a BPF struct ops cost model take over a device while that file stops selecting a model?
Because the cgroup file is the interface of the built-in model, and a BPF program is attached out of band. The blk-iocost cgroup cost model file's write path parses the ctrl= token (auto or user), the model= token (always linear), and the seven linear coefficient tokens, and flips the user_cost_model bit; it has no program to run. The public patch series binds a BPF struct ops implementation to one device's iocost instance through a struct ops link; while the link is attached, the device prices its IO with that program, and the cgroup file stops selecting a model. It keeps accepting ctrl=auto and ctrl=user, but the built-in coefficients take effect only after the link is detached.
The mechanism
- The built-in side (mainline). The blk-iocost cgroup file is
cost.model, registeredCFTYPE_ONLY_ON_ROOT. Its write handler,ioc_cost_model_write(), parses only thectrl=token (autooruser), themodel=token (accepted only aslinear), and the seven linear coefficient tokens (rbps,rseqiops,rrandiops,wbps,wseqiops,wrandiops,flushiops). Writing any coefficient orctrl=usersets theuser_cost_modelbit andioc_refresh_paramsapplies the user set;ctrl=autoclears it and the automatic profile stepped from the device class applies again. The show path printsctrl=userorctrl=autotogether withmodel=linearand the current coefficients. There is no value that names a program. - The takeover (public patch). The series adds a struct ops cost model interface: a BPF program implements the model ops, and the attach path, gated on a BPF cost model config option, binds it to one device's iocost instance. The attach and detach paths match the attachment under the queue lock by comparing the program ops and the link against the ioc attached model (
ioc_attached_model_locked()). The model is deliverediocg_init()andiocg_free()for the cgroups already online; both callbacks run with the ioc lock held and must not sleep. Device removal ejects the attached model, while the struct ops link remains until userspace destroys it. The same series adds aniocost_ioc_ticktracepoint that reports the per-period device summary. - The cgroup file while attached. The cgroup-v2 wording the series adds says that while a BPF model is attached the file's model value is
bpf; the built-in coefficient tokens remain readable and writable, but they take effect only after switching back tolinear, and the automatic profile stepping does not switch profiles while the BPF model is in use — as with user-set parameters, the profile selected from the device class still applies. Only detaching the link (or removing the device) restores built-in pricing.
How to verify and debug
- Read the file. Read the iocost cost model file for the cgroup of the device. With a BPF model attached the model value shows
bpfwhile the built-in coefficient line still lists the linear coefficients; those values are inert until the link is detached. - Detach out of band. Destroy the struct ops link from userspace. The device prices with the built-in model again and the cgroup file's
ctrland coefficient writes take effect again. Device removal instead ejects the attached model while the link stays alive until userspace destroys it. - Watch the per-period pricing. Enable the
iocost_ioc_ticktracepoint added by the same series. It reports the final per-period summary — busy level, cur period, nr active, and usage time — so the BPF model's effect is visible per period. Note that a mid-period write toio.cost.qoschanges the period time and vrate only after the period has already run, so the summary reflects the values the period ran with.
The limitation
- Mainline has no attach path: master
block/blk-iocost.cknows only the built-in linear model and theuser_cost_modelbit; the struct ops binding is the public patch series, gated on a BPF cost model config option. - The binding is per device: one model per device iocost instance, not a global override.
- Coefficient writes while a BPF model is attached are accepted but inert; they take effect only after switching back to the built-in model.
- Detach is asymmetric: device removal ejects the attached model, but the struct ops link remains until userspace destroys it.
References
- kernel
block/blk-iocost.c— thecost.modelcgroup file registeredCFTYPE_ONLY_ON_ROOT, its write handler that accepts onlyctrlauto/user andmodel=linear, and theuser_cost_modelbit. - kernel Documentation/block/cgroup-v2.rst — the iocost block and the automatic profile stepping the doc patch extends.
- Public bpf mailing-list patch series: blk-iocost BPF struct ops cost model support — the attach/detach paths, the
iocg_init()/iocg_free()callbacks, theiocost_ioc_ticktracepoint, and the cgroup-v2 doc wording.
Community discussion today
A public bpf-list patch series adds BPF struct ops cost model support to blk-iocost: a BPF program bound to one device's iocost instance takes over its IO pricing while the cgroup cost model file stops selecting a model. The series is a five-part post: the shared-definition move (later folded back into the main file), the struct ops attach/detach core, a selftest, the per-period iocost_ioc_tick tracepoint, and the cgroup-v2 doc wording. The review thread concentrates on the lifetime edges: the attachment match must compare both the program ops and the link, the iocg init/free callbacks run with the ioc lock held and must not sleep, device removal ejects the model while the link stays alive, and the doc must say the built-in coefficients remain writable while a BPF model is attached but take effect only after switching back.
This run's archive-backed sources produced 0 messages in the 7-day window (the two opt-in archive sources were empty at snapshot time), so the question above was sourced from the permitted public bpf mailing list, reviewed through its public atom feed, and the patch thread was used as the primary reference. The Discord channels for eunomia-bpf and sched-ext and r/eBPF were not covered this run (no visible-browser session was available), so they are marked not-covered rather than quiet.
Continue exploring
Back to index
eBPF Q&A
Daily, source-grounded answers to recurring questions about eBPF, Linux observability, profiling, runtime extension, and security.
Previous
Why does the BPF redirect neighbor helper trip a kernel BUG on a packet whose Ethernet header is short, and how does the fix read the true header length differently on the receive path than on the transmit path?
Because skb-maclen is only populated on the receive path. A bpfredirectneigh() fix that guards the pull with maclen under ETHHLEN is safe on ingress, but on the transmit path — locally generated packets through tc
Next
Why does libbpf skip manual strategy programs during the initial object load, and how does the struct ops map init pass clobber a manual load strategy set via the API?
Because the bulk load pass loads only the programs whose load strategy is AUTO, and it deliberately leaves a MANUAL program unloaded so that program can be loaded or unloaded on its own later.
- Last updated
- Oct 29, 2026
- First published
- Oct 29, 2026
- Contributors
- Yunwei
Was this page helpful?