A request has its data, and its worker has been woken, yet the response is late. Follow three questions: where may the task run, how much CPU service can it receive, and whose turn is it now?
Observation
Start here
Woken but not executing
Section 1: runnable state and rescheduling
Different nice values, different CPU shares
Sections 2–3: weights and EEVDF
Idle host CPUs but a throttled container
Section 5: group weights and quota
Uneven load, but migration may hurt
Sections 6–7: placement and measurement
This article focuses on ordinary fair scheduling. Linux began introducing EEVDF in 6.6; the teaching models are compared with local Linux 7.2 source, rather than presented as a complete kernel simulation.
1. Run queues identify tasks that can execute
Linux primarily maintains run queues per CPU. Both the current task and tasks waiting for CPU time can be TASK_RUNNING. A task sleeping for I/O or a condition cannot consume CPU merely because it has high priority. Wakeup updates state, selects a suitable CPU, and enqueues the task; it may request rescheduling if the new task should preempt the current one.
A rescheduling request is not an unconditional jump at any instruction. The kernel checks it at appropriate scheduling points, subject to interrupt state, disabled preemption, locks, and the preemption model. A scheduler invocation may select the current task again: calling schedule and switching between distinct tasks are different events.
Per-CPU queues reduce global contention, but remote wakeups, load pulling, and migration still coordinate CPUs and queue locks. Affinity, cpusets, and active scheduling policies constrain the candidates before balancing can improve their distribution.
2. Fair accounting: convert runtime to virtual service
Take two continuously runnable tasks A and B on one CPU. With weights 2:1 and 9 ms to distribute, their ideal allocation is 6 ms and 3 ms.
Dividing runtime by weight gives both tasks 3 virtual units. A heavier task's virtual clock advances more slowly, so equal virtual progress requires more actual runtime. With reference weight w0:
Δvi=Δtiwiw0
Nice 0 has base weight 1024; nice 5 has weight 335. If only those two tasks continuously compete on one CPU at the same fair-scheduling level, the former's ideal share is 1024 / (1024 + 335) ≈ 75.35%.
That share changes when a competitor blocks, another task arrives, or a cgroup hierarchy changes the competition. Nice expresses relative weight, not a fixed CPU percentage.
In the interactive figure, width represents weight and area represents actual CPU time. Move the allocation away from its weighted split, then change the weight: the height reveals which task has received more normalized service.
Preparing the visual
How much CPU time gives equal virtual service?
Virtual runtime advances by actual time times reference weight divided by task weight. Width shows weight, height normalized service, and area actual CPU time.
{"id":"scheduler-virtual-service","title":"How much CPU time gives equal virtual service?","summary":"Virtual runtime advances by actual time times reference weight divided by task weight. Width shows weight, height normalized service, and area actual CPU time.","height":1000,"html":"<p class=\"eyebrow\" data-i18n=\"eyebrow\"></p><h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"controls\"><div class=\"control\"><label for=\"weight\" data-i18n=\"weight\"></label><output id=\"weight-value\" for=\"weight\"></output><input id=\"weight\" type=\"range\" min=\"1\" max=\"4\" step=\"1\" value=\"2\"></div><div class=\"control\"><label for=\"service\" data-i18n=\"service\"></label><output id=\"service-value\" for=\"service\"></output><input id=\"service\" type=\"range\" min=\"0\" max=\"9\" step=\"0.05\" value=\"6\"></div></div><div class=\"service-plot\" role=\"img\" data-i18n-label=\"chart\"><span class=\"axis-top\">Δv = 9</span><span class=\"axis-bottom\">0</span><div class=\"service-areas\"><div id=\"area-a\" class=\"service-area\"><b>A</b></div><div id=\"area-b\" class=\"service-area other\"><b>B</b></div><i id=\"level-a\"></i><i id=\"level-b\"></i></div></div><p class=\"caption\" data-i18n=\"geometry\"></p><div class=\"metrics\"><div><span data-i18n=\"clockA\"></span><strong id=\"virtual-a\"></strong></div><div><span data-i18n=\"clockB\"></span><strong id=\"virtual-b\"></strong></div></div><div class=\"formula\"><div id=\"formula-a\"></div><div id=\"formula-b\"></div></div><div class=\"actions\"><button id=\"balance\" data-i18n=\"balance\"></button></div><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"equal\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"aheadA\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"aheadB\"></p></div><details class=\"scope\"><summary data-i18n=\"scope\"></summary><p class=\"note\" data-i18n=\"note\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.note,.caption{font-size:12px;color:var(--muted);margin-top:14px}.controls{display:grid;gap:12px;margin:18px 0}.control{display:grid;grid-template-columns:1fr auto;gap:6px 12px;align-items:center;font-size:13px}.control input{grid-column:1/3;width:100%;margin:0}.control output{color:var(--accent);font-variant-numeric:tabular-nums}.presets{display:flex;flex-wrap:wrap;gap:4px}.presets button{flex:1 1 auto;min-width:min-content}.actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:14px}.actions button{flex:1;min-width:0;min-height:42px;font-size:13px}.copy{display:grid;margin-top:16px;font-size:14px}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.metrics{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin:16px 0}.metrics>div{min-width:0;border-left:2px solid var(--accent);padding-left:12px}.metrics span{display:block;color:var(--muted);font-size:12px;min-height:2.8em}.metrics strong{display:block;font-size:22px;font-weight:500;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.formula{font-size:13px;overflow-wrap:anywhere;border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-variant-numeric:tabular-nums}.formula div+div{margin-top:6px}.two{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-top:18px}.card{min-width:0;border:1px solid var(--rule);border-radius:9px;padding:14px}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card code{font-size:12px;overflow-wrap:anywhere}.card .value{font-size:24px;font-weight:500;font-variant-numeric:tabular-nums}.card small{display:block;color:var(--muted);font-size:12px}.card button{width:100%;min-width:0;font-size:13px;min-height:42px}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{accent-color:var(--accent);width:16px;height:16px;flex-shrink:0}.pill{display:inline-block;padding:3px 8px;border-radius:5px;background:var(--accent-soft);color:var(--accent);font-size:12px}.plot{position:relative;margin:18px 0}.plot svg{width:100%;height:auto;overflow:visible}.plot text{font-size:12px;fill:var(--muted)}.plot .ink{fill:var(--ink)}.plot .blue{fill:var(--accent);stroke:var(--accent)}.plot .gold{fill:var(--second);stroke:var(--second)}.plot .area{fill:var(--accent-soft);stroke:var(--accent);transition:all .25s}.plot .other{fill:var(--second-soft);stroke:var(--second)}.plot line,.plot path,.plot rect,.plot circle{transition:all .25s}.legend{display:flex;flex-wrap:wrap;gap:6px 16px;font-size:12px;color:var(--muted);margin-top:10px}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:5px}.legend .gold:before{background:var(--second)}details{margin-top:14px;font-size:12px;color:var(--muted)}summary{cursor:pointer;padding:8px 0}button:disabled{cursor:default}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.card button{font-size:12px}.metrics{gap:10px}.metrics strong{font-size:20px}.actions button{font-size:12px}.plot text{font-size:13px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n\n.service-plot{height:230px;position:relative;padding:25px 0 25px 28px}.service-areas{height:180px;position:relative;display:flex;gap:16px;align-items:flex-end;border-bottom:1px solid var(--rule)}.service-area{position:relative;background:var(--accent-soft);border:0;box-shadow:inset 0 0 0 1px var(--accent);transition:width .25s,height .25s;min-height:0}.service-area.other{background:var(--second-soft);box-shadow:inset 0 0 0 1px var(--second)}.service-area b{position:absolute;bottom:-26px;left:50%;transform:translateX(-50%);font-size:13px;font-weight:600}.axis-top,.axis-bottom{position:absolute;left:0;font-size:11px;color:var(--muted)}.axis-top{top:0}.axis-bottom{bottom:19px}.service-areas>i{position:absolute;left:0;right:0;border-top:1px dashed var(--accent);pointer-events:none;transition:bottom .25s}#level-b{border-color:var(--second)}","js":"const $=s=>document.querySelector(s),num=(n,d=2)=>viz.number(n,d);\nfunction draw(){const w=+$('#weight').value,t=+$('#service').value,b=9-t,va=t/w,vb=b;$('#weight-value').textContent=w+' : 1';$('#service-value').textContent=num(t,2)+' / 9 ms';$('#area-a').style.width='calc((100% - 16px) * '+w/5+')';$('#area-b').style.width='calc((100% - 16px) / 5)';$('#area-a').style.height=va*20+'px';$('#area-b').style.height=vb*20+'px';$('#level-a').style.bottom=va*20+'px';$('#level-b').style.bottom=vb*20+'px';$('#virtual-a').textContent=num(va);$('#virtual-b').textContent=num(vb);$('#formula-a').textContent='ΔvA = '+num(t)+' / '+w+' = '+num(va);$('#formula-b').textContent='ΔvB = '+num(b)+' / 1 = '+num(vb);$('#explanation').textContent=viz.t(Math.abs(va-vb)<1e-8?'equal':va>vb?'aheadA':'aheadB');}\nfor(const id of ['weight','service'])$('#'+id).addEventListener('input',draw);$('#balance').addEventListener('click',()=>{$('#service').value=9*(+$('#weight').value)/(+$('#weight').value+1);draw();});draw();\n","audio":false,"strings":{"aheadA":"A is taller: it has received more service relative to its weight. More runtime increases height; more weight reduces the height for the same runtime.","aheadB":"B is taller: it has received more service relative to its weight. Adjust the allocation or A’s weight and compare the heights.","balance":"Allocate 9 ms by weight","chart":"Rectangle width is weight, height is virtual service, and area is actual CPU time","clockA":"A: virtual service Δv","clockB":"B: virtual service Δv","equal":"Equal heights mean equal normalized service. When A is wider, it needs more actual runtime to reach the same height.","eyebrow":"01 · Measuring fairness","geometry":"Width uses a fixed unit per weight. Height is Δv; area is actual runtime. Equal heights mean equal normalized service.","heading":"How much CPU time gives equal virtual service?","intro":"Allocate a total of 9 ms. Change A’s weight and actual runtime, then compare the heights. B’s weight stays at 1.","note":"One CPU and two continuously runnable tasks; reference weight is normalized to 1. This illustrates accounting, not every real scheduling decision. Nice values, groups, and wakeups affect actual competition.","scope":"Model scope and assumptions","service":"Actual time allocated to A","weight":"Weight A : B"}}
Classic CFS uses vruntime to identify tasks behind in normalized service. This explains long-run shares but does not fully express how promptly a short request should respond. EEVDF separates eligibility from ordering eligible requests.
3. EEVDF: eligibility and virtual deadlines do different jobs
Eligibility from lag
For a simplified fixed runnable set, let task runtime be vi, weight wi, and system virtual time V their weighted mean:
V=∑iwi∑iwivi,lagi=wi(V−vi)
Positive lag means the task has received less service than the fair reference; zero lag is also eligible. Negative lag means it is ahead and should not obtain still more service merely by asking for a short request.
Order eligible requests by deadline
For a new request for actual service ri, its virtual deadline is:
di=vi,start+riwiw0
This uses the request's starting virtual position. As execution advances vruntime, an already assigned deadline does not continually slide forward with it. A new request establishes a new deadline. The example below takes a snapshot at request starts.
Among eligible entities, earlier virtual deadlines receive preference. These are service coordinates, not wall-clock instants or SCHED_DEADLINE completion guarantees.
Why B wins this snapshot
All three tasks have weight 1, with reference weight normalized to 1:
Task
v
Request r
d = v + r
V − v
Eligible?
A
98
10
108
2
Yes
B
99
5
104
1
Yes
C
103
0.25
103.25
-3
No
V = (98 + 99 + 103) / 3 = 100. Choosing the smallest vruntime alone gives A; choosing the smallest deadline alone gives C. EEVDF selects B here: C's short request does not erase its excess service, and B has the earlier eligible deadline.
Move C's service position to change eligibility, or B's request length to change its deadline. Shortening a request does not itself change the service already received. Each edit constructs a new snapshot.
Preparing the visual
Why can the earliest deadline still lose?
EEVDF first requires lag ≥ 0, then compares virtual deadlines. In the default snapshot, C has the earliest deadline but is ineligible; B wins among eligible tasks.
{"id":"scheduler-eevdf","title":"Why can the earliest deadline still lose?","summary":"EEVDF first requires lag ≥ 0, then compares virtual deadlines. In the default snapshot, C has the earliest deadline but is ineligible; B wins among eligible tasks.","height":1000,"html":"<p class=\"eyebrow\" data-i18n=\"eyebrow\"></p><h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"controls\"><div class=\"control\"><label for=\"position\" data-i18n=\"position\"></label><output id=\"position-value\" for=\"position\"></output><input id=\"position\" type=\"range\" min=\"94\" max=\"106\" step=\"0.25\" value=\"103\"></div><div class=\"control\"><label for=\"request\" data-i18n=\"request\"></label><output id=\"request-value\" for=\"request\"></output><input id=\"request\" type=\"range\" min=\"0.25\" max=\"12\" step=\"0.25\" value=\"5\"></div></div><div class=\"eevdf-chart\"><div id=\"system-v\"><span id=\"system-label\"></span></div><div class=\"request-row\" id=\"task-A\"><header><strong>A</strong><span id=\"status-A\"></span></header><div class=\"request-rail\"><i id=\"request-A\"></i><b class=\"v-point\" id=\"v-A\"></b><b class=\"d-point\" id=\"d-A\"></b></div><div class=\"values\" id=\"values-A\"></div></div><div class=\"request-row\" id=\"task-B\"><header><strong>B</strong><span id=\"status-B\"></span></header><div class=\"request-rail\"><i id=\"request-B\"></i><b class=\"v-point\" id=\"v-B\"></b><b class=\"d-point\" id=\"d-B\"></b></div><div class=\"values\" id=\"values-B\"></div></div><div class=\"request-row\" id=\"task-C\"><header><strong>C</strong><span id=\"status-C\"></span></header><div class=\"request-rail\"><i id=\"request-C\"></i><b class=\"v-point\" id=\"v-C\"></b><b class=\"d-point\" id=\"d-C\"></b></div><div class=\"values\" id=\"values-C\"></div></div><div class=\"scale\"><span>94</span><span>118</span></div></div><p class=\"caption\" data-i18n=\"markers\"></p><div class=\"formula\" id=\"formula\"></div><div class=\"winner\"><span data-i18n=\"winner\"></span><strong id=\"winner\"></strong></div><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"whyB\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"whyA\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"whyC\"></p></div><details class=\"scope\"><summary data-i18n=\"scope\"></summary><p class=\"note\" data-i18n=\"note\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.note,.caption{font-size:12px;color:var(--muted);margin-top:14px}.controls{display:grid;gap:12px;margin:18px 0}.control{display:grid;grid-template-columns:1fr auto;gap:6px 12px;align-items:center;font-size:13px}.control input{grid-column:1/3;width:100%;margin:0}.control output{color:var(--accent);font-variant-numeric:tabular-nums}.presets{display:flex;flex-wrap:wrap;gap:4px}.presets button{flex:1 1 auto;min-width:min-content}.actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:14px}.actions button{flex:1;min-width:0;min-height:42px;font-size:13px}.copy{display:grid;margin-top:16px;font-size:14px}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.metrics{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin:16px 0}.metrics>div{min-width:0;border-left:2px solid var(--accent);padding-left:12px}.metrics span{display:block;color:var(--muted);font-size:12px;min-height:2.8em}.metrics strong{display:block;font-size:22px;font-weight:500;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.formula{font-size:13px;overflow-wrap:anywhere;border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-variant-numeric:tabular-nums}.formula div+div{margin-top:6px}.two{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-top:18px}.card{min-width:0;border:1px solid var(--rule);border-radius:9px;padding:14px}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card code{font-size:12px;overflow-wrap:anywhere}.card .value{font-size:24px;font-weight:500;font-variant-numeric:tabular-nums}.card small{display:block;color:var(--muted);font-size:12px}.card button{width:100%;min-width:0;font-size:13px;min-height:42px}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{accent-color:var(--accent);width:16px;height:16px;flex-shrink:0}.pill{display:inline-block;padding:3px 8px;border-radius:5px;background:var(--accent-soft);color:var(--accent);font-size:12px}.plot{position:relative;margin:18px 0}.plot svg{width:100%;height:auto;overflow:visible}.plot text{font-size:12px;fill:var(--muted)}.plot .ink{fill:var(--ink)}.plot .blue{fill:var(--accent);stroke:var(--accent)}.plot .gold{fill:var(--second);stroke:var(--second)}.plot .area{fill:var(--accent-soft);stroke:var(--accent);transition:all .25s}.plot .other{fill:var(--second-soft);stroke:var(--second)}.plot line,.plot path,.plot rect,.plot circle{transition:all .25s}.legend{display:flex;flex-wrap:wrap;gap:6px 16px;font-size:12px;color:var(--muted);margin-top:10px}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:5px}.legend .gold:before{background:var(--second)}details{margin-top:14px;font-size:12px;color:var(--muted)}summary{cursor:pointer;padding:8px 0}button:disabled{cursor:default}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.card button{font-size:12px}.metrics{gap:10px}.metrics strong{font-size:20px}.actions button{font-size:12px}.plot text{font-size:13px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n\n.eevdf-chart{position:relative;padding-top:30px;margin-top:20px}.request-row{position:relative;padding:10px 0}.request-row header{display:flex;justify-content:space-between;gap:10px;font-size:12px}.request-row header span{color:var(--muted)}.request-rail{height:22px;position:relative;margin:8px 0;border-bottom:1px solid var(--rule)}.request-rail>i{position:absolute;height:6px;bottom:0;transform:translateY(50%);background:var(--accent);transition:left .25s,width .25s}.v-point{position:absolute;width:10px;height:10px;border-radius:50%;bottom:0;transform:translate(-50%,50%)}.d-point{position:absolute;width:3px;height:18px;bottom:0;transform:translate(-50%,50%)}.v-point,.d-point{background:var(--accent);transition:left .25s}.values{font-size:12px;font-variant-numeric:tabular-nums;color:var(--muted);margin-top:12px;min-height:3.4em}.scale{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}#system-v{position:absolute;top:25px;bottom:25px;border-left:1px dashed var(--accent);pointer-events:none;transition:left .25s}#system-label{position:absolute;top:-25px;transform:translateX(-50%);white-space:nowrap;font-size:12px;color:var(--accent);background:var(--paper)}.winner{display:flex;justify-content:space-between;align-items:center;padding:14px;margin-top:16px;background:var(--accent-soft);border-radius:8px}.winner strong{font-size:26px;color:var(--accent)}","js":"const $=s=>document.querySelector(s),f=n=>viz.number(n,2),x=n=>(n-94)/24*100;\nfunction draw(){const v=[98,99,+$('#position').value],r=[10,+$('#request').value,.25],V=v.reduce((a,b)=>a+b)/3,d=v.map((n,i)=>n+r[i]),eligible=v.map(n=>n<=V+1e-9);let winner=-1;for(let i=0;i<3;i++)if(eligible[i]&&(winner<0||d[i]<d[winner]))winner=i;$('#position-value').textContent=f(v[2]);$('#request-value').textContent=f(r[1]);$('#system-v').style.left=x(V)+'%';$('#system-label').textContent='V = '+f(V);for(let i=0;i<3;i++){const k='ABC'[i],color=winner===i?'var(--second)':eligible[i]?'var(--accent)':'var(--muted)';$('#request-'+k).style.left=x(v[i])+'%';$('#request-'+k).style.width=(d[i]-v[i])/24*100+'%';$('#request-'+k).style.background=color;$('#request-'+k).style.opacity=eligible[i]?'1':'.25';$('#v-'+k).style.left=x(v[i])+'%';$('#v-'+k).style.background=color;$('#d-'+k).style.left=x(d[i])+'%';$('#d-'+k).style.background=color;$('#values-'+k).textContent='v '+f(v[i])+' → d '+f(d[i])+' · lag '+f(V-v[i]);$('#status-'+k).textContent=viz.t(eligible[i]?'eligible':'ineligible');$('#status-'+k).style.color=color;}$('#formula').textContent='V = ('+v.map(f).join(' + ')+') / 3 = '+f(V);$('#winner').textContent='ABC'[winner];$('#explanation').textContent=viz.t('why'+'ABC'[winner]);}\n$('#position').addEventListener('input',draw);$('#request').addEventListener('input',draw);draw();\n","audio":false,"strings":{"eligible":"Eligible","eyebrow":"02 · EEVDF","heading":"Why can the earliest deadline still lose?","ineligible":"Ineligible","intro":"Move C’s service position or shorten B’s request. See how eligibility at V and virtual deadlines affect different parts of the decision.","markers":"Dots mark v, vertical ticks mark d, and segments show request lengths. The dashed line is V. Gold marks the winner; gray marks ineligible tasks.","note":"A new-request snapshot of three equal-weight tasks, with reference weight 1. A has v=98 and r=10; B has v=99; C has r=0.25. Edits rebuild the snapshot, not an in-flight deadline. Alphabetical tie-breaking is only a display convention.","position":"C’s virtual runtime vC","request":"B’s requested service rB","scope":"Model scope and assumptions","whyA":"A is eligible and its deadline is no later than the other eligible requests. A task ahead of its fair service cannot bypass eligibility with an earlier deadline.","whyB":"B has the earliest deadline among eligible tasks. A shorter request changes a deadline; it does not directly erase excess service already received.","whyC":"C is at or to the left of V and is now eligible. Its short request also has the earliest deadline, so it wins.","winner":"Snapshot winner"}}
This small executable model checks the arithmetic without modeling wakeups, hierarchy, or migration:
A shorter request can affect response order without increasing long-term weight. The kernel must also prevent a task from running ahead, briefly sleeping, and waking with undeserved fresh credit. Preserved and decaying lag, including deferred-dequeue accounting, maintain this constraint. An entity retained for accounting does not imply that a task still waiting for its event may execute user code.
In local kernel/sched/fair.c, the relevant tree is organized by deadline and uses subtree vruntime information to prune eligibility searches. pick_eevdf() also handles protection of the current request. Group entities, enqueue/dequeue adjustments, and integer precision make maintaining V more involved than averaging a fixed set. Read the eligibility and deadline decisions first, then these implementation details. EEVDF, CFS design
4. Policies and scheduling classes
Ordinary tasks normally use SCHED_OTHER, also called SCHED_NORMAL inside the kernel. EEVDF operates within fair scheduling; applications do not need a generic SCHED_EEVDF policy to enable it.
Policy or mechanism
Allocation rule
Boundary
SCHED_OTHER / BATCH
Fair shares; BATCH expresses a batch-processing preference
Nice cannot override higher scheduling classes
SCHED_IDLE
Very low weight within fair scheduling
Distinct from a CPU's idle task
SCHED_FIFO
FIFO at equal real-time priority; runs until blocking, yielding, or preemption
No ordinary RR slice does not mean no bandwidth constraints
SCHED_RR
Real-time priority with rotation among equal priorities
Rotation alone gives no application completion guarantee
SCHED_DEADLINE
EDF selection and CBS runtime budgeting
Parameters, admission, and actual workload determine feasibility
sched_ext
A loaded BPF scheduler manages applicable tasks
Takeover depends on mode; failures can cause fallback
Deadline normally precedes real-time, which precedes fair scheduling. Internal classes such as stop and the active sched_ext mode also matter; three rows are not the entire implementation.
For runtime=2 ms, deadline=10 ms, period=10 ms, a task declares its CPU demand and scheduling deadline. Constraints include runtime ≤ deadline ≤ period, with admission checking. This does not automatically invoke an application function every 10 ms: work release, execution demand, and blocking still need analysis.
“Deadline”
What it constrains
EEVDF virtual deadline
Request ordering in fair-service coordinates
SCHED_DEADLINE deadline
A real-time scheduling deadline
RPC timeout
End-to-end queueing, execution, and network waiting
5. cgroups: relative shares and an absolute allowance
Allocate to groups, then to tasks
Suppose sibling groups A:B have weights 2:1 on one CPU, without quota. A contains two equal, continuously runnable workers; B contains one. A receives about 2/3, split in half internally, so each worker receives about 1/3. Adding a third worker to A leaves its group share about 2/3: each A worker receives about 2/9, while B's worker keeps about 1/3. Creating threads need not directly steal another group's share.
Multiple CPUs spend one budget
cpu.weight is neither a reservation nor a strict cap. cpu.max limits total CPU time per period. 50000 100000 permits 50 ms of CPU time per 100 ms period. Two threads running concurrently on two CPUs can spend that combined budget in about 25 ms, then be throttled until replenishment. Each CPU does not get its own 50 ms allowance.
Move wall-clock time across a period boundary while changing parallelism. The budget is shared by every active lane:
Preparing the visual
Why can more CPUs throttle sooner?
CPUs share one group budget. Two concurrent CPUs spend 50 ms of CPU time in about 25 ms of wall time, then wait for replenishment.
{"id":"scheduler-quota","title":"Why can more CPUs throttle sooner?","summary":"CPUs share one group budget. Two concurrent CPUs spend 50 ms of CPU time in about 25 ms of wall time, then wait for replenishment.","height":1000,"html":"<p class=\"eyebrow\" data-i18n=\"eyebrow\"></p><h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"controls\"><div class=\"control\"><label for=\"quota\" data-i18n=\"quota\"></label><output id=\"quota-value\" for=\"quota\"></output><input id=\"quota\" type=\"range\" min=\"10\" max=\"200\" step=\"5\" value=\"50\"></div><div class=\"control\"><label for=\"cpus\" data-i18n=\"cpus\"></label><output id=\"cpus-value\" for=\"cpus\"></output><input id=\"cpus\" type=\"range\" min=\"1\" max=\"4\" step=\"1\" value=\"2\"></div><div class=\"control\"><label for=\"time\" data-i18n=\"time\"></label><output id=\"time-value\" for=\"time\"></output><input id=\"time\" type=\"range\" min=\"0\" max=\"200\" step=\"1\" value=\"25\"></div></div><div class=\"budget\"><div><span data-i18n=\"remaining\"></span><strong id=\"remaining\"></strong></div><div class=\"budget-track\"><i id=\"budget-fill\"></i></div><p id=\"period\"></p></div><div class=\"timeline\"><div id=\"probe\"></div><div class=\"lane\" id=\"lane-0\"><code>CPU 0</code><div class=\"lane-track\"><i class=\"run first\"></i><i class=\"run second\"></i><span class=\"inactive\" data-i18n=\"inactive\"></span></div></div><div class=\"lane\" id=\"lane-1\"><code>CPU 1</code><div class=\"lane-track\"><i class=\"run first\"></i><i class=\"run second\"></i><span class=\"inactive\" data-i18n=\"inactive\"></span></div></div><div class=\"lane\" id=\"lane-2\"><code>CPU 2</code><div class=\"lane-track\"><i class=\"run first\"></i><i class=\"run second\"></i><span class=\"inactive\" data-i18n=\"inactive\"></span></div></div><div class=\"lane\" id=\"lane-3\"><code>CPU 3</code><div class=\"lane-track\"><i class=\"run first\"></i><i class=\"run second\"></i><span class=\"inactive\" data-i18n=\"inactive\"></span></div></div><div class=\"ticks\"><span>0</span><span>100</span><span>200 ms</span></div></div><div class=\"legend\"><span data-i18n=\"running\"></span><span class=\"gold\" data-i18n=\"throttled\"></span></div><div class=\"formula\" id=\"formula\"></div><div class=\"actions\"><button id=\"play\" aria-pressed=\"false\"><span id=\"play-label\"></span><span class=\"reserve\" aria-hidden=\"true\" data-i18n=\"play\"></span><span class=\"reserve\" aria-hidden=\"true\" data-i18n=\"pause\"></span></button><button id=\"reset\" data-i18n=\"reset\"></button></div><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"available\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"exhausted\"></p></div><details class=\"scope\"><summary data-i18n=\"scope\"></summary><p class=\"note\" data-i18n=\"note\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.note,.caption{font-size:12px;color:var(--muted);margin-top:14px}.controls{display:grid;gap:12px;margin:18px 0}.control{display:grid;grid-template-columns:1fr auto;gap:6px 12px;align-items:center;font-size:13px}.control input{grid-column:1/3;width:100%;margin:0}.control output{color:var(--accent);font-variant-numeric:tabular-nums}.presets{display:flex;flex-wrap:wrap;gap:4px}.presets button{flex:1 1 auto;min-width:min-content}.actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:14px}.actions button{flex:1;min-width:0;min-height:42px;font-size:13px}.copy{display:grid;margin-top:16px;font-size:14px}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.metrics{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin:16px 0}.metrics>div{min-width:0;border-left:2px solid var(--accent);padding-left:12px}.metrics span{display:block;color:var(--muted);font-size:12px;min-height:2.8em}.metrics strong{display:block;font-size:22px;font-weight:500;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.formula{font-size:13px;overflow-wrap:anywhere;border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-variant-numeric:tabular-nums}.formula div+div{margin-top:6px}.two{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-top:18px}.card{min-width:0;border:1px solid var(--rule);border-radius:9px;padding:14px}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card code{font-size:12px;overflow-wrap:anywhere}.card .value{font-size:24px;font-weight:500;font-variant-numeric:tabular-nums}.card small{display:block;color:var(--muted);font-size:12px}.card button{width:100%;min-width:0;font-size:13px;min-height:42px}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{accent-color:var(--accent);width:16px;height:16px;flex-shrink:0}.pill{display:inline-block;padding:3px 8px;border-radius:5px;background:var(--accent-soft);color:var(--accent);font-size:12px}.plot{position:relative;margin:18px 0}.plot svg{width:100%;height:auto;overflow:visible}.plot text{font-size:12px;fill:var(--muted)}.plot .ink{fill:var(--ink)}.plot .blue{fill:var(--accent);stroke:var(--accent)}.plot .gold{fill:var(--second);stroke:var(--second)}.plot .area{fill:var(--accent-soft);stroke:var(--accent);transition:all .25s}.plot .other{fill:var(--second-soft);stroke:var(--second)}.plot line,.plot path,.plot rect,.plot circle{transition:all .25s}.legend{display:flex;flex-wrap:wrap;gap:6px 16px;font-size:12px;color:var(--muted);margin-top:10px}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:5px}.legend .gold:before{background:var(--second)}details{margin-top:14px;font-size:12px;color:var(--muted)}summary{cursor:pointer;padding:8px 0}button:disabled{cursor:default}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.card button{font-size:12px}.metrics{gap:10px}.metrics strong{font-size:20px}.actions button{font-size:12px}.plot text{font-size:13px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n\n.budget{padding:14px;background:var(--accent-soft);border-radius:8px}.budget>div:first-child{display:flex;justify-content:space-between;gap:12px;font-size:13px}.budget strong{font-size:22px}.budget p{margin:8px 0 0;font-size:12px;color:var(--muted)}.budget-track{height:10px;background:var(--rule);border-radius:5px;overflow:hidden;margin-top:10px}.budget-track i{display:block;height:100%;background:var(--accent);transition:width .2s}.timeline{position:relative;margin:20px 0 0;padding-top:5px}.lane{display:grid;grid-template-columns:52px 1fr;gap:8px;align-items:center;margin:10px 0;font-size:12px}.lane-track{height:26px;position:relative;background:var(--second-soft);border-radius:4px;overflow:hidden}.run{position:absolute;top:0;height:100%;background:var(--accent);transition:width .25s}.run.first{left:0}.run.second{left:50%}.lane.off .lane-track{background:var(--surface)}.lane.off .run{visibility:hidden}.inactive{display:none;font-size:11px;text-align:center;color:var(--muted);line-height:26px}.lane.off .inactive{display:block}#probe{position:absolute;top:0;bottom:28px;width:2px;background:var(--ink);z-index:1;pointer-events:none}.ticks{margin-left:60px;display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}#play{display:grid}#play>span{grid-area:1/1}#quota-value{width:8.4em;white-space:nowrap;text-align:right}#time-value{width:5em;text-align:right}.formula{min-height:calc(3.2em + 13px)}.budget strong{white-space:nowrap}@media(max-width:480px){.budget>div:first-child{display:grid;grid-template-columns:1fr;gap:6px}.budget strong{justify-self:end}}","js":"const $=s=>document.querySelector(s);function playing(on){$('#play').setAttribute('aria-pressed',String(on));$('#play-label').textContent=viz.t(on?'pause':'play');}\nfunction draw(){const q=+$('#quota').value,n=+$('#cpus').value,t=+$('#time').value,local=t%100,used=Math.min(q,n*local),remaining=q-used,run=Math.min(100,q/n),blocked=q<n*100&&local>=q/n;$('#quota-value').textContent=q+' ms / 100 ms';$('#cpus-value').textContent=n;$('#time-value').textContent=t+' ms';$('#remaining').textContent=viz.number(remaining,1)+' ms';$('#budget-fill').style.width=100*remaining/q+'%';$('#period').textContent=viz.t('period')+' '+(Math.floor(t/100)+1)+' · '+local+' / 100 ms';for(let i=0;i<4;i++){const e=$('#lane-'+i);e.classList.toggle('off',i>=n);e.querySelectorAll('.run').forEach(b=>b.style.width=run/2+'%');}$('#probe').style.left='calc(60px + (100% - 60px) * '+t/200+')';$('#formula').textContent=''+q+' ms / '+n+' CPU = '+viz.number(q/n,1)+' ms';$('#explanation').textContent=viz.t(blocked?'exhausted':'available');}\nconst player=viz.playback({delay:100,canAdvance:()=>+$('#time').value<200,advance(){$('#time').value=Math.min(200,+$('#time').value+2);draw();},onChange:playing});for(const id of ['quota','cpus','time'])$('#'+id).addEventListener('input',()=>{player.pause();draw();});$('#play').addEventListener('click',()=>{if(+$('#time').value===200){$('#time').value=0;draw();}player.toggle();});$('#reset').addEventListener('click',()=>{player.pause();$('#time').value=0;draw();});playing(false);draw();\n","audio":false,"strings":{"available":"Budget remains at this instant. Each 100 ms boundary starts a new period; every blue lane spends from the same pool.","cpus":"Concurrent CPUs N","exhausted":"The budget is exhausted. Other idle CPUs do not give this group more allowance. Greater parallelism spends the same CPU-time budget sooner.","eyebrow":"03 · Shared quota","heading":"Why can more CPUs throttle sooner?","inactive":"Not participating","intro":"Concurrent CPUs spend one shared runtime budget. Change the quota and parallelism, then scrub time to see exhaustion and replenishment.","note":"P=100 ms, continuously CPU-demanding tasks, no competing load, ancestor limit, or burst allowance. Continuous accounting omits real bandwidth slices and small carry-over. If Q/N exceeds a period, that period does not exhaust the budget. At 200 ms, period 3 starts.","pause":"Pause","period":"Period","play":"Play time","quota":"CPU-time budget Q per period","remaining":"Budget left in this period","reset":"Back to start","running":"Running","scope":"Model scope and assumptions","throttled":"Throttled after exhaustion","time":"Wall-clock time"}}
An idle CPU elsewhere does not give this group more allowance. Compare cpu.stat throttling increments, cpu.max, and request latency over the same interval; application locks, I/O, and upstream queues still require separate investigation. cgroup v2 CPU controller
6. Placement: even load is not always fastest
Migration can lose a warm cache or move a task farther from its NUMA memory. SMT siblings share execution resources and are not independent full cores. Placement balances spare capacity, locality, topology, and energy rather than merely equalizing task counts.
PELT summarizes recent running and runnable behavior. Utilization, runnable load, and weighted load are different quantities: waiting for CPU can indicate pressure without representing CPU service already consumed. A brief 100% sample neither proves migration will help nor identifies the whole-system bottleneck.
Wakeup placement, periodic balancing, and idle-CPU pulling operate in different situations. First establish the task's allowed CPUs, cpuset, and relevant capacity constraints. Adding CPUs outside a task's allowed set cannot automatically remove its queueing.
7. Repeatable read-only observation
This queries the current Python process without changing policy or configuration. /proc/self/sched fields depend on kernel configuration; do not parse it as a fixed-column format.
Separate the event time, sched_wakeup, and the task's appearance as next in sched_switch. Wakeup-to-run includes CPU waiting and related scheduling paths; executing the request includes application work. Neither isolates register and address-space handoff cost. The next article, context switching, follows that handoff directly.