Skip to main content

ax_task/sched/system/task_system/
thread_creation.rs

1//! Transactional thread creation and initial CPU binding.
2
3use super::*;
4
5/// Owns an unpublished identity and its admission charge until registry commit.
6struct ThreadSlotReservation<'system> {
7    system: &'system TaskSystem,
8    slot: u32,
9    generation: u32,
10    bandwidth: u64,
11    committed: bool,
12}
13
14impl Drop for ThreadSlotReservation<'_> {
15    fn drop(&mut self) {
16        if self.committed {
17            return;
18        }
19        let mut state = self.system.state.lock();
20        let mut root_domain = self.system.root_domain.lock();
21        let slot = &mut state.slots[self.slot as usize];
22        assert_eq!(slot.generation, self.generation);
23        assert!(slot.record.is_none());
24        assert_eq!(slot.pending_deadline_reservation, self.bandwidth);
25        slot.pending_deadline_reservation = 0;
26        if advance_thread_slot_generation(slot) {
27            state.free_slots.push(self.slot);
28        }
29        root_domain.release_deadline(self.bandwidth);
30    }
31}
32
33impl TaskSystem {
34    /// Creates a thread in the [`ThreadState::New`] state.
35    ///
36    /// Deadline threads are admitted immediately and therefore must cover the
37    /// complete online root domain.
38    pub fn create_thread(&self, spec: ThreadSpec) -> Result<ThreadHandle, TaskError> {
39        // SAFETY: the runtime publishes the calling CPU identity before task
40        // creation is enabled. Like Linux fork, this establishes task_cpu()
41        // before the new task can participate in PI or become runnable.
42        let initial_cpu = CpuId::new(unsafe { task_runtime::current_cpu_id() }.as_u32());
43        self.create_thread_on_cpu(spec, initial_cpu)
44    }
45
46    /// Builds an unpublished task with an explicit initial `task_cpu`.
47    ///
48    /// Ordinary fork uses the calling CPU. Per-CPU bootstrap and idle tasks
49    /// instead mirror Linux `init_idle()` and bind the target rq before the
50    /// task can be observed by PI, policy, or hotplug code.
51    fn create_thread_on_cpu(
52        &self,
53        spec: ThreadSpec,
54        initial_cpu: CpuId,
55    ) -> Result<ThreadHandle, TaskError> {
56        use crate::thread::allocation::try_arc;
57        // Install the resource owner before any validation or fallible allocation.
58        let mut unpublished = UnpublishedThreadGuard::new(self, spec);
59        if initial_cpu.as_usize() >= self.config.cpu_count() {
60            return Err(TaskError::InvalidCpu(initial_cpu.as_u32()));
61        }
62        let policy = unpublished.spec().policy();
63        policy.validate()?;
64        let spec = unpublished
65            .spec
66            .as_mut()
67            .expect("unpublished specification");
68        let affinity = match spec.take_affinity() {
69            Some(affinity) => affinity,
70            None => CpuSet::try_all(self.config.cpu_count())?,
71        };
72        validate_affinity(&affinity, self.config.cpu_count())?;
73        let affinity = try_arc(affinity)?;
74        let execution = spec.execution.take();
75        let mut reservation = {
76            let mut state = self.state.lock();
77            let mut root_domain = self.root_domain.lock();
78            let bandwidth = root_domain.reserve_deadline(policy, &affinity)?;
79            let (slot, generation) = match state.allocate_thread_slot(self.config.thread_capacity())
80            {
81                Ok(identity) => identity,
82                Err(error) => {
83                    root_domain.release_deadline(bandwidth);
84                    return Err(error);
85                }
86            };
87            state.slots[slot as usize].pending_deadline_reservation = bandwidth;
88            ThreadSlotReservation {
89                system: self,
90                slot,
91                generation,
92                bandwidth,
93                committed: false,
94            }
95        };
96        let id = ThreadId::from_parts(reservation.slot, reservation.generation);
97        // rq indexes and exit candidates are fixed-capacity, initialized before
98        // their locks exist. Fork allocates only private task-owned objects here.
99        let deadline_server = DeadlineServer::unbound()?;
100        let entity = SchedulingEntity::new_with_deadline_server(
101            policy,
102            self.config.fair_slice_ns(),
103            0,
104            deadline_server.clone(),
105        );
106        let extension = unpublished.spec().extension();
107        let switch_extension = extension.map(ThreadExtension::as_view);
108        let scheduler_tick_cpu_time = extension.and_then(ThreadExtension::scheduler_tick_cpu_time);
109        let scheduler_tick_work = extension.and_then(ThreadExtension::scheduler_tick_work);
110        let resources = unpublished.spec().resources();
111        let address_space = resources.address_space();
112        let membarrier_identity = if address_space.is_none() {
113            crate::runtime::resource::AddressSpaceMembarrierId::NONE
114        } else {
115            task_runtime::address_space_membarrier_state(address_space).identity()
116        };
117        let sched = try_arc(ThreadSchedCell::new(
118            id,
119            ThreadSchedInit {
120                policy: ThreadPolicyInit { policy, entity },
121                placement: ThreadPlacementInit {
122                    initial_cpu,
123                    affinity: Arc::clone(&affinity),
124                },
125                deadline: ThreadDeadlineInit {
126                    server: deadline_server,
127                    reservation_scaled: reservation.bandwidth,
128                },
129                runtime: ThreadRuntimeInit {
130                    context: resources.context(),
131                    address_space,
132                },
133            },
134        )?)?;
135        let core = try_arc(ThreadCore::new(ThreadCoreInit {
136            id,
137            policy,
138            sched: Arc::clone(&sched),
139            extension: switch_extension,
140            execution,
141            scheduler_tick_cpu_time,
142            scheduler_tick_work,
143            membarrier_identity,
144            task_work: Some(Arc::clone(&self.task_work)),
145        })?)?;
146        let (extension, resources) = unpublished.into_owned_parts();
147        let record = ThreadRecord {
148            core: Arc::clone(&core),
149            sched,
150            resources,
151            extension,
152            callbacks: ThreadCallbackState::new(),
153            activation: None,
154        };
155        let context = record.resources.context();
156        if !context.is_none() {
157            let status = task_runtime::bind_context_thread(ContextThreadBinding {
158                context,
159                publication: CurrentThreadPublication::from_core(id, &core),
160            });
161            if status != RuntimeStatus::Success {
162                drop(reservation);
163                drop(core);
164                self.release_thread_record(record);
165                return Err(TaskError::RuntimeFailure(status as u32));
166            }
167        }
168        let mut record = Some(record);
169        let commit_error = {
170            let mut state = self.state.lock();
171            let root_domain = self.root_domain.lock();
172            let is_deadline = matches!(policy, SchedulePolicy::Deadline(_));
173            if is_deadline && !affinity.covers(&root_domain.online) {
174                Some(TaskError::DeadlineAffinity)
175            } else if is_deadline && root_domain.admission_overcommitted() {
176                Some(TaskError::DeadlineAdmission)
177            } else {
178                let slot = &mut state.slots[reservation.slot as usize];
179                assert_eq!(slot.generation, reservation.generation);
180                assert!(slot.record.is_none());
181                assert_eq!(slot.pending_deadline_reservation, reservation.bandwidth);
182                slot.pending_deadline_reservation = 0;
183                slot.record = record.take();
184                reservation.committed = true;
185                None
186            }
187        };
188        if let Some(error) = commit_error {
189            drop(reservation);
190            drop(core);
191            self.release_thread_record(record.expect("rejected commit owns its record"));
192            return Err(error);
193        }
194        Ok(ThreadHandle::from_core(core))
195    }
196
197    /// Performs the initial runnable transition before the owner CPU is online.
198    ///
199    /// # Safety
200    ///
201    /// The caller must retain the boot CPU's raw IRQ exclusion and
202    /// `PREEMPT_DISABLED` ownership.
203    unsafe fn make_ready_bootstrap(&self, thread: ThreadId) -> Result<(), TaskError> {
204        let state = self.state.lock();
205        let record = state.thread_record(thread)?;
206        // SAFETY: forwarded from this method's offline boot-owner contract.
207        let mut sched = unsafe { record.sched.lock_bootstrap() };
208        sched.transition(&record.core, ThreadState::Running)
209    }
210
211    /// Installs the CPU's already-running bootstrap execution context.
212    ///
213    /// This operation is used before a CPU is published online and performs no
214    /// context switch. The runtime must call it exactly once with an empty
215    /// `CpuLocal` current slot.
216    pub fn install_bootstrap_thread(
217        &self,
218        mut cpu: Pin<&mut CpuLocal>,
219        spec: ThreadSpec,
220    ) -> Result<ThreadHandle, TaskError> {
221        let unpublished = UnpublishedThreadGuard::new(self, spec);
222        self.ensure_owner_cpu_context(&cpu)?;
223        if !matches!(
224            unpublished.spec().policy(),
225            SchedulePolicy::Fair {
226                mode: FairMode::Normal | FairMode::Batch,
227                ..
228            }
229        ) {
230            return Err(TaskError::InvalidConfiguration);
231        }
232        {
233            let state = self.state.lock();
234            let registration = state.cpu_registration(cpu.owner())?;
235            if !Arc::ptr_eq(&registration.remote, cpu.remote()) {
236                return Err(TaskError::InvalidRuntimeHandle);
237            }
238            // SAFETY: install_bootstrap_thread is an offline owner operation;
239            // its caller retains the boot CPU's raw IRQ exclusion.
240            if unsafe { cpu.remote().lock_run_queue_irq_disabled() }
241                .current_thread()
242                .is_some()
243            {
244                return Err(TaskError::InvalidConfiguration);
245            }
246        }
247
248        let thread = self.create_thread_on_cpu(unpublished.into_spec(), cpu.owner())?;
249        let setup = (|| {
250            let core = {
251                let state = self.state.lock();
252                Arc::clone(&state.thread_record(thread.id())?.core)
253            };
254            // SAFETY: the CPU is still offline under the boot owner's raw IRQ
255            // exclusion.
256            let mut sched = unsafe { core.sched().lock_bootstrap() };
257            sched.transition(&core, ThreadState::Running)?;
258            let remote = Arc::clone(cpu.remote());
259            // SAFETY: the CPU is still offline under the boot owner's raw IRQ
260            // exclusion and cannot enter the runtime IRQ-exit service.
261            let mut transaction = unsafe { OwnerRqTxn::begin_bootstrap(self, &remote) };
262            let _enqueue_consumed_by_immediate_bootstrap_pick = self
263                .link_owner_ready_thread_locked(
264                    cpu.owner(),
265                    &mut transaction,
266                    &core,
267                    &mut sched,
268                    EnqueueReason::Wake,
269                );
270            let next = self.pick_owner_bootstrap_in_rq(cpu.as_mut(), &mut transaction);
271            if !core::ptr::eq(next.core.as_ref(), Arc::as_ref(&core)) {
272                task_runtime::fatal_invariant(0x4254_0001, core.id().as_u64() as usize);
273            }
274            transaction.commit_bootstrap();
275            Ok(())
276        })();
277        if let Err(error) = setup {
278            return match self.discard_unpublished_thread(thread) {
279                Ok(()) => Err(error),
280                Err(cleanup_error) => Err(cleanup_error),
281            };
282        }
283        Ok(thread)
284    }
285
286    /// Creates and registers a dedicated CPU idle thread before online publish.
287    pub fn register_idle_thread(
288        &self,
289        mut cpu: Pin<&mut CpuLocal>,
290        spec: ThreadSpec,
291    ) -> Result<ThreadHandle, TaskError> {
292        let unpublished = UnpublishedThreadGuard::new(self, spec);
293        self.ensure_owner_cpu_context(&cpu)?;
294        if !matches!(
295            unpublished.spec().policy(),
296            SchedulePolicy::Fair {
297                mode: crate::sched::FairMode::Idle,
298                ..
299            }
300        ) {
301            return Err(TaskError::InvalidConfiguration);
302        }
303        {
304            let state = self.state.lock();
305            let registration = state.cpu_registration(cpu.owner())?;
306            if !Arc::ptr_eq(&registration.remote, cpu.remote()) {
307                return Err(TaskError::InvalidRuntimeHandle);
308            }
309            // SAFETY: register_idle_thread runs in the same offline bootstrap
310            // owner transaction as install_bootstrap_thread.
311            if unsafe { cpu.remote().lock_run_queue_irq_disabled() }
312                .idle()
313                .is_some()
314            {
315                return Err(TaskError::InvalidConfiguration);
316            }
317        }
318
319        let thread = self.create_thread_on_cpu(unpublished.into_spec(), cpu.owner())?;
320        // SAFETY: the target CPU remains offline and boot-owned until idle is
321        // installed and the complete runtime endpoint is published.
322        let setup = unsafe { self.make_ready_bootstrap(thread.id()) }.and_then(|()| {
323            let state = self.state.lock();
324            let core = Arc::clone(&state.thread_record(thread.id())?.core);
325            drop(state);
326            self.install_idle_core(cpu.as_mut(), core)
327        });
328        if let Err(error) = setup {
329            return match self.discard_unpublished_thread(thread) {
330                Ok(()) => Err(error),
331                Err(cleanup_error) => Err(cleanup_error),
332            };
333        }
334        Ok(thread)
335    }
336
337    /// Installs the dedicated idle task directly into its owner rq, matching
338    /// Linux `init_idle()` rather than passing idle through a scheduling-class
339    /// enqueue/dequeue cycle.
340    pub(super) fn install_idle_core(
341        &self,
342        mut cpu: Pin<&mut CpuLocal>,
343        core: Arc<ThreadCore>,
344    ) -> Result<(), TaskError> {
345        let owner = cpu.owner();
346        // SAFETY: idle installation precedes CPU online publication and the
347        // boot owner retains local IRQ exclusion.
348        if unsafe { cpu.remote().lock_run_queue_irq_disabled() }
349            .idle()
350            .is_some()
351        {
352            return Err(TaskError::InvalidConfiguration);
353        }
354        // SAFETY: install_idle_core is reached only from the offline bootstrap
355        // transaction above.
356        let mut sched = unsafe { core.sched().lock_bootstrap() };
357        let policy = core.sched().active(&sched).policy();
358        if sched.lifecycle.state() != ThreadState::Running
359            || !matches!(
360                policy,
361                SchedulePolicy::Fair {
362                    mode: crate::sched::FairMode::Idle,
363                    ..
364                }
365            )
366            || !sched.affinity.affinity.contains(owner)
367            || sched.placement.assigned_cpu() != Some(owner)
368            || sched.placement.on_cpu().is_some()
369            || sched.placement.requested_migration().is_some()
370        {
371            return Err(TaskError::InvalidConfiguration);
372        }
373        let metadata = sched.rq_task_metadata()?;
374        let rt_quota_exempt = sched.is_pi_boosted_rt_owner_for(policy);
375        let active = core.sched().take_active(&mut sched);
376        // SAFETY: the CPU remains offline and boot-owned through this direct
377        // init_idle-style rq transaction.
378        unsafe {
379            cpu.as_mut().install_idle_bootstrap(
380                self,
381                core.id(),
382                Arc::clone(&core),
383                active,
384                metadata,
385                rt_quota_exempt,
386            )
387        };
388        core.set_wake_cpu_hint(owner);
389        Ok(())
390    }
391
392    fn discard_unpublished_thread(&self, handle: ThreadHandle) -> Result<(), TaskError> {
393        let record = {
394            let mut state = self.state.lock();
395            let mut root_domain = self.root_domain.lock();
396            let (record, released) = state.remove_unpublished_thread_with_handle(&handle)?;
397            root_domain.release_deadline(released);
398            record
399        };
400        drop(handle);
401        self.release_thread_record(record);
402        Ok(())
403    }
404}