1use super::*;
4pub(super) struct OwnerBalanceSelection {
10 candidate: QueuedThreadSnapshot,
11 target: CpuId,
12 reason: BalanceReason,
13}
14
15#[derive(Clone, Copy)]
18pub(super) struct OwnerBalanceOutcome {
19 run_queue_changed: bool,
20}
21
22impl OwnerBalanceOutcome {
23 pub(super) const fn run_queue_changed(self) -> bool {
24 self.run_queue_changed
25 }
26}
27
28impl OwnerBalanceSelection {
29 pub(super) const fn target(&self) -> CpuId {
30 self.target
31 }
32}
33
34fn fair_migration_imbalance(
35 source_demand: u64,
36 target_demand: u64,
37 candidate_demand: u64,
38) -> Option<u64> {
39 if candidate_demand == 0 || source_demand <= target_demand {
40 return None;
41 }
42 let imbalance_before = source_demand - target_demand;
43 let source_after = source_demand.saturating_sub(candidate_demand);
44 let target_after = target_demand.saturating_add(candidate_demand);
45 let imbalance_after = source_after.abs_diff(target_after);
46 (imbalance_after < imbalance_before).then_some(imbalance_after)
47}
48
49impl TaskSystem {
50 pub const fn cpu_topology_len(&self) -> usize {
52 self.config.cpu_count()
53 }
54
55 pub fn snapshot(&self, cpu: Pin<&CpuLocal>) -> Result<CpuSnapshot, TaskError> {
57 self.ensure_owner_cpu_context(&cpu)?;
58 Ok(CpuSnapshot::capture(&cpu))
59 }
60
61 pub fn online_cpu_count(&self) -> usize {
63 self.root_domain.lock().online.count()
64 }
65
66 pub fn active_cpu_set(&self) -> CpuSet {
72 self.root_domain.lock().online.clone()
73 }
74
75 pub(crate) fn publish_run_queue_summary(
76 &self,
77 remote: &CpuRemote,
78 run_queue: &mut CpuRunQueueState,
79 ) {
80 let online = remote.accepts_placement();
81 if !run_queue.take_summary_dirty(online) {
82 return;
83 }
84 let _ = remote.publish_run_queue_load_summary(run_queue);
85 if let Some((previous, publication)) = run_queue.take_domain_publication(online) {
86 self.root_domain
87 .publish_run_queue(remote.owner(), previous, publication);
88 }
89 }
90
91 pub(super) fn notify_overloaded_owners_after_priority_drop(
96 &self,
97 owner: CpuId,
98 previous: Option<SchedulingUrgency>,
99 next: SchedulingUrgency,
100 ) {
101 let Some(previous) = previous else {
102 return;
103 };
104 if !matches!(
105 previous.class_rank(),
106 DEADLINE_CLASS_RANK | REALTIME_CLASS_RANK
107 ) || next <= previous
108 {
109 return;
110 }
111 let class = match previous.class_rank() {
112 DEADLINE_CLASS_RANK => RootDomainPushClass::Deadline,
113 REALTIME_CLASS_RANK => RootDomainPushClass::Realtime,
114 _ => return,
115 };
116 self.root_domain.request_rt_deadline_push(class, owner);
117 }
118
119 fn select_owner_balance_transfer_by(
120 &self,
121 cpu: &CpuLocal,
122 reason: BalanceReason,
123 class_filter: Option<SchedulingClass>,
124 mut select_target: impl FnMut(&QueuedThreadSnapshot, &ThreadSchedState) -> Option<CpuId>,
125 ) -> Option<OwnerBalanceSelection> {
126 let source = cpu.owner();
127 let (current_policy, mut scan) = {
128 let mut transaction = OwnerRqTxn::begin(self, cpu.remote());
129 let current_policy = transaction.current().map(CurrentDispatch::schedule_policy);
130 let scan = transaction.begin_balance_scan(class_filter);
131 transaction.commit();
132 (current_policy, scan)
133 };
134 loop {
135 let candidate = {
136 let mut transaction = OwnerRqTxn::begin(self, cpu.remote());
137 let queued_top_rt = transaction.highest_rt_priority();
138 let top_rt_count =
139 queued_top_rt.map_or(0, |priority| transaction.rt_count_at_priority(priority));
140 let candidate = transaction.next_balance_candidate(&mut scan, |candidate| {
141 let class_allowed = match reason {
142 BalanceReason::IdlePull | BalanceReason::FairPeriodic => {
145 matches!(candidate.policy(), SchedulePolicy::Fair { .. })
146 }
147 BalanceReason::RtDeadlinePush => matches!(
148 candidate.policy(),
149 SchedulePolicy::Deadline(_)
150 | SchedulePolicy::Fifo { .. }
151 | SchedulePolicy::RoundRobin { .. }
152 ),
153 };
154 let matches_filter = class_filter.is_none_or(|class| match class {
155 SchedulingClass::Deadline => {
156 matches!(candidate.policy(), SchedulePolicy::Deadline(_))
157 }
158 SchedulingClass::Realtime => matches!(
159 candidate.policy(),
160 SchedulePolicy::Fifo { .. } | SchedulePolicy::RoundRobin { .. }
161 ),
162 SchedulingClass::Fair => {
163 matches!(candidate.policy(), SchedulePolicy::Fair { .. })
164 }
165 SchedulingClass::Stop => {
166 matches!(candidate.policy(), SchedulePolicy::KernelStop)
167 }
168 });
169 if !class_allowed || !matches_filter {
170 return false;
171 }
172 let candidate_priority = match candidate.policy() {
173 SchedulePolicy::Fifo { priority }
174 | SchedulePolicy::RoundRobin { priority, .. } => priority.get(),
175 _ => return true,
176 };
177 match current_policy {
178 Some(SchedulePolicy::Deadline(_)) => true,
179 Some(SchedulePolicy::Fifo { priority })
180 | Some(SchedulePolicy::RoundRobin { priority, .. }) => {
181 candidate_priority <= priority.get()
182 }
183 _ => queued_top_rt.is_some_and(|top| {
184 candidate_priority < top
185 || (candidate_priority == top && top_rt_count > 1)
186 }),
187 }
188 });
189 transaction.commit();
190 candidate
191 }?;
192 let sched = candidate.core.sched().lock();
193 let Some(target) = select_target(&candidate, &sched) else {
194 continue;
195 };
196 let target_is_allowed = |target: CpuId| {
197 self.cpu_remotes
198 .get(target.as_usize())
199 .is_some_and(|remote| {
200 remote.accepts_placement()
201 && remote.is_scheduler_ready()
202 && sched.affinity.affinity.contains(target)
203 })
204 };
205 let deadline_covers_online = !matches!(sched.policy.base, SchedulePolicy::Deadline(_))
206 || self.cpu_remotes.iter().enumerate().all(|(index, remote)| {
207 !remote.accepts_placement()
208 || sched.affinity.affinity.contains(CpuId::new(index as u32))
209 });
210 if target == source
211 || !target_is_allowed(target)
212 || sched.placement.queued_cpu() != Some(source)
213 || sched.placement.has_pending_migration()
214 || sched.placement.on_cpu().is_some()
215 || candidate.core.sleep_timer_cpu().is_some()
216 || !deadline_covers_online
217 {
218 continue;
219 }
220 let queued = {
221 let transaction = OwnerRqTxn::begin(self, cpu.remote());
222 let queued = transaction.queued_thread(candidate.id);
223 transaction.commit();
224 queued
225 };
226 if let Some(queued) = queued {
227 return Some(OwnerBalanceSelection {
228 candidate: queued,
229 target,
230 reason,
231 });
232 }
233 }
234 }
235
236 pub(super) fn select_owner_balance_transfer(
237 &self,
238 cpu: &CpuLocal,
239 target: CpuId,
240 reason: BalanceReason,
241 class_filter: Option<SchedulingClass>,
242 ) -> Option<OwnerBalanceSelection> {
243 self.select_owner_balance_transfer_by(cpu, reason, class_filter, |_, _| Some(target))
244 }
245
246 pub(super) fn select_rt_deadline_balance_transfer(
247 &self,
248 cpu: &CpuLocal,
249 class: Option<SchedulingClass>,
250 ) -> Option<OwnerBalanceSelection> {
251 let source = cpu.owner();
252 self.select_owner_balance_transfer_by(
253 cpu,
254 BalanceReason::RtDeadlinePush,
255 class,
256 |candidate, sched| {
257 self.select_rt_deadline_push_cpu(
258 candidate.policy,
259 candidate.entity.clone(),
260 &sched.affinity.affinity,
261 source,
262 )
263 },
264 )
265 }
266
267 pub(super) fn commit_owner_balance_transfer(
268 &self,
269 mut cpu: Pin<&mut CpuLocal>,
270 selection: OwnerBalanceSelection,
271 ) -> Result<BalanceTransferOutcome, TaskError> {
272 self.ensure_owner_cpu_online(&cpu)?;
273 let _irq = IrqScope::enter();
274 let OwnerBalanceSelection {
275 candidate,
276 target,
277 reason,
278 } = selection;
279 if self
280 .cpu_remote(target)
281 .is_none_or(|remote| !remote.is_scheduler_ready())
282 {
283 return Ok(BalanceTransferOutcome::Retry);
284 }
285 let source = cpu.owner();
286 if source == target {
287 return Ok(BalanceTransferOutcome::NoCandidate);
288 }
289 let migrated_fair = matches!(candidate.policy(), SchedulePolicy::Fair { .. });
290 let core = candidate.core;
291 let mut sched = core.sched().lock();
292 let deadline_covers_online = !matches!(sched.policy.base, SchedulePolicy::Deadline(_))
293 || self.cpu_remotes.iter().enumerate().all(|(index, remote)| {
294 !remote.accepts_placement()
295 || sched.affinity.affinity.contains(CpuId::new(index as u32))
296 });
297 if sched.lifecycle.state() != ThreadState::Running
298 || sched.placement.queued_cpu() != Some(source)
299 || sched.placement.has_pending_migration()
300 || sched.placement.on_cpu().is_some()
301 || !sched.affinity.affinity.contains(target)
302 || core.sleep_timer_cpu().is_some()
303 || !deadline_covers_online
304 {
305 return Ok(BalanceTransferOutcome::Retry);
306 }
307
308 let carrier = match self.prepare_owner_migration(&core, source, target) {
309 Ok(carrier) => carrier,
310 Err(_) => {
311 return Ok(BalanceTransferOutcome::Retry);
312 }
313 };
314 let remote = Arc::clone(cpu.remote());
315 let mut transaction = OwnerRqTxn::begin(self, &remote);
316 let detached = {
317 let current_fair = transaction.current_fair_contender();
318 let Some(detached) = transaction.detach_for_transfer(
319 core.id(),
320 current_fair,
321 self.config.timing_granularity_ns(),
322 ) else {
323 transaction.commit();
324 return Ok(BalanceTransferOutcome::Retry);
325 };
326 detached
327 };
328 Self::detach_owner_deadline_bandwidth_in_rq(
329 &core,
330 &mut sched,
331 cpu.remote(),
332 &mut transaction,
333 );
334 core.sched()
335 .install_active(&mut sched, detached.into_active());
336 sched.placement.begin_migration(source, target);
337 core.set_wake_cpu_hint(target);
338 transaction.commit();
339 drop(sched);
340 carrier.commit();
341 if migrated_fair && reason != BalanceReason::FairPeriodic {
342 cpu.as_mut().reset_fair_balance(
343 task_runtime::monotonic_now(),
344 self.config.balance_interval_ns(),
345 );
346 }
347
348 Ok(BalanceTransferOutcome::Migrated(core.id()))
349 }
350
351 pub(super) fn transfer_owner_balance_candidate(
352 &self,
353 cpu: Pin<&mut CpuLocal>,
354 target: CpuId,
355 reason: BalanceReason,
356 class_filter: Option<SchedulingClass>,
357 ) -> Result<BalanceTransferOutcome, TaskError> {
358 self.ensure_owner_cpu_online(&cpu)?;
359 let Some(selection) = self.select_owner_balance_transfer(
360 cpu.as_ref().get_ref(),
361 target,
362 reason,
363 class_filter,
364 ) else {
365 return Ok(BalanceTransferOutcome::NoCandidate);
366 };
367 self.commit_owner_balance_transfer(cpu, selection)
368 }
369
370 pub(super) fn owner_balance_work_pending(&self, cpu: &CpuLocal, next: ThreadId) -> bool {
377 let idle = cpu.remote().idle_thread() == Some(next);
382 let idle_pull_pending = idle
383 && cpu.idle_pull_pending()
384 && self.root_domain.has_idle_pull_source();
388 if idle_pull_pending
389 || cpu.fair_balance_pending()
390 || self.root_domain.fair_nohz_balancer_pending(cpu.owner())
391 {
392 return true;
393 }
394 rt_deadline_balance_work_pending(self.root_domain.push_target_pending(cpu.owner()))
395 }
396
397 pub(super) fn service_owner_balance(
398 &self,
399 mut cpu: Pin<&mut CpuLocal>,
400 next: ThreadId,
401 ) -> Result<OwnerBalanceOutcome, TaskError> {
402 let idle = cpu.remote().idle_thread() == Some(next);
403 let class_pull_required = idle
404 && (self.root_domain.cpu_has_rt_deadline_overload(cpu.owner())
405 || self.root_domain.push_target_pending(cpu.owner()));
406 let idle_pull_required =
407 idle && (cpu.as_mut().take_idle_pull_pending() || class_pull_required);
408 let fair_nohz_claim = self.root_domain.claim_fair_nohz_balancer(cpu.owner());
409 let push_claim = self.root_domain.claim_rt_deadline_push(cpu.owner());
410 let mut fair_nohz_serviced = false;
411 let balance = (|| -> Result<(Option<ThreadId>, Option<ThreadId>), TaskError> {
412 if idle {
413 if idle_pull_required {
414 let _requested = self.request_idle_pull(cpu.as_mut())?;
415 }
416 if fair_nohz_claim.is_some() {
417 fair_nohz_serviced = self.request_fair_nohz_idle_pulls();
418 }
419 let fair = self.balance_fair(cpu.as_mut())?;
420 Ok((None, fair))
421 } else {
422 let class = push_claim
423 .as_ref()
424 .map(|claim| claim.class().scheduling_class());
425 let pushed = self.push_rt_deadline_from_root_domain(cpu.as_mut(), class)?;
426 let fair = self.balance_fair(cpu.as_mut())?;
427 Ok((pushed, fair))
428 }
429 })();
430 let (pushed, fair) = match balance {
431 Ok(outcome) => outcome,
432 Err(error) => {
433 if let Some(claim) = fair_nohz_claim {
434 self.root_domain.finish_fair_nohz_balancer(claim, false);
435 }
436 if let Some(claim) = push_claim {
437 self.root_domain.finish_rt_deadline_push(claim, false);
438 }
439 return Err(error);
440 }
441 };
442 if let Some(claim) = fair_nohz_claim {
443 self.root_domain
444 .finish_fair_nohz_balancer(claim, fair_nohz_serviced);
445 }
446 if let Some(claim) = push_claim {
447 self.root_domain
448 .finish_rt_deadline_push(claim, pushed.is_some());
449 } else if pushed.is_some() && self.root_domain.cpu_has_rt_deadline_overload(cpu.owner()) {
450 cpu.request_scheduler_work();
455 }
456 Ok(OwnerBalanceOutcome {
457 run_queue_changed: pushed.is_some() || fair.is_some(),
458 })
459 }
460
461 pub(super) fn balance_fair(
462 &self,
463 mut cpu: Pin<&mut CpuLocal>,
464 ) -> Result<Option<ThreadId>, TaskError> {
465 if task_runtime::in_hard_irq() || !cpu.fair_balance_pending() {
466 return Ok(None);
467 }
468 self.ensure_owner_cpu_online(&cpu)?;
469 let source = cpu.owner();
470 self.root_domain.kick_fair_nohz_balance_if_source(source);
471 let source_demand = cpu.remote().placement_demand();
472 let result = {
473 let lower_load_target_seen =
474 self.cpu_remotes.iter().enumerate().any(|(index, remote)| {
475 let target = CpuId::new(index as u32);
476 remote.accepts_placement()
477 && target != source
478 && remote.placement_demand() < source_demand
479 });
480 let selection = self.select_owner_balance_transfer_by(
481 cpu.as_ref().get_ref(),
482 BalanceReason::FairPeriodic,
483 Some(SchedulingClass::Fair),
484 |candidate, sched| {
485 let candidate_demand = candidate.placement_demand();
486 self.cpu_remotes
487 .iter()
488 .enumerate()
489 .filter_map(|(index, remote)| {
490 let target = CpuId::new(index as u32);
491 if target == source
492 || !remote.accepts_placement()
493 || !remote.is_scheduler_ready()
494 || !sched.affinity.affinity.contains(target)
495 {
496 return None;
497 }
498 let target_demand = remote.placement_demand();
499 fair_migration_imbalance(source_demand, target_demand, candidate_demand)
500 .map(|imbalance| (imbalance, target_demand, target))
501 })
502 .min_by_key(|(imbalance, demand, target)| {
503 (*imbalance, *demand, target.as_u32())
504 })
505 .map(|(_, _, target)| target)
506 },
507 );
508 if let Some(selection) = selection {
509 match self.commit_owner_balance_transfer(cpu.as_mut(), selection)? {
510 BalanceTransferOutcome::Migrated(thread) => FairBalanceResult::Migrated(thread),
511 BalanceTransferOutcome::NoCandidate | BalanceTransferOutcome::Retry => {
512 FairBalanceResult::Constrained
513 }
514 }
515 } else if lower_load_target_seen {
516 FairBalanceResult::Constrained
517 } else {
518 FairBalanceResult::Balanced
519 }
520 };
521 let completion_now = task_runtime::monotonic_now();
526 let minimum_interval_ns = self.config.balance_interval_ns();
527 match result {
528 FairBalanceResult::Migrated(_) => {
529 cpu.as_mut()
530 .reset_fair_balance(completion_now, minimum_interval_ns);
531 }
532 FairBalanceResult::Balanced => {
533 cpu.as_mut().backoff_fair_balance(
534 completion_now,
535 minimum_interval_ns,
536 minimum_interval_ns.saturating_mul(FAIR_BALANCE_BALANCED_BACKOFF_FACTOR),
537 );
538 }
539 FairBalanceResult::Constrained => {
540 cpu.as_mut().backoff_fair_balance(
541 completion_now,
542 minimum_interval_ns,
543 minimum_interval_ns.saturating_mul(FAIR_BALANCE_CONSTRAINED_BACKOFF_FACTOR),
544 );
545 }
546 }
547 Ok(result.migrated())
548 }
549}
550
551const fn rt_deadline_balance_work_pending(push_target_pending: bool) -> bool {
552 push_target_pending
553}
554
555pub(in crate::sched::system::task_system) const fn push_class_for_policy(
557 policy: SchedulePolicy,
558) -> Option<RootDomainPushClass> {
559 match policy {
560 SchedulePolicy::Fifo { .. } | SchedulePolicy::RoundRobin { .. } => {
561 Some(RootDomainPushClass::Realtime)
562 }
563 SchedulePolicy::Deadline(_) => Some(RootDomainPushClass::Deadline),
564 SchedulePolicy::Fair { .. } | SchedulePolicy::KernelStop => None,
565 }
566}