hujun260 commented on PR #19398:
URL: https://github.com/apache/nuttx/pull/19398#issuecomment-4953320919
> @hujun260 Good question — I re-verified this against master rather than
just repeating the issue text. The divergence happens in
`nxsched_add_readytorun()` (`sched/sched/sched_addreadytorun.c`, non-SMP path):
>
> ```c
> else if (nxsched_add_prioritized(btcb, list_readytorun()))
> {
> /* The new btcb was added at the head of the ready-to-run list. It
> * is now the new active task!
> */
>
> btcb->task_state = TSTATE_TASK_RUNNING;
> btcb->flink->task_state = TSTATE_TASK_READYTORUN;
> up_update_task(btcb);
> ret = true;
> }
> ```
>
> When a higher-priority task `btcb` becomes ready and preemption is _not_
locked, this immediately (synchronously, in whatever context called
`nxsched_add_readytorun()`) makes `btcb` the new ready-to-run head — so
`this_task()` returns `btcb` from this point on. `up_update_task(btcb)` only
_signals_ that a context switch is needed; the actual CPU-level register switch
(and the point where `g_running_tasks[cpu]` gets updated to `btcb`, e.g. in
`arm_doirq.c`/`riscv_doirq.c`) happens later, on whatever deferred mechanism
the architecture uses (e.g. PendSV on ARMv7-M). Until that later switch
actually executes, `g_running_tasks[cpu]` still correctly points at the _old_
task, which is still the one whose registers are loaded and which keeps
executing.
>
> So the two diverge whenever a task's own currently-executing code creates
or wakes a higher-priority task partway through a call chain, without
immediately yielding the CPU. `nxtask_exit()` is reached through exactly such a
chain: `exit()` -> `_exit()` -> `nxtask_exithook()` -> `up_exit()` ->
`nxtask_exit()`. For a `CONFIG_BINFMT_LOADABLE` app this is a comparatively
long, preemptible window (`binfmt_exit()` -> `unload_module()` ->
`elf_unloadbinary()` -> `libelf_uninit()` -> `up_textheap_free()`, etc.) - if a
higher-priority task is promoted to the ready-to-run head anywhere in that
window, `this_task()` in the subsequent `nxtask_exit()` call resolves to that
new task while the exiting task (still `g_running_tasks[cpu]`) is the one
that's actually finishing up.
>
> This matches the original issue reporter's on-target finding (#19308):
they captured this exact divergence with an SRAM breadcrumb during the teardown
of a loadable ELF app - `this_task()` resolved to a live, higher-priority Wi-Fi
thread while `g_running_tasks[cpu]` correctly still pointed at the exiting app
- and reproduced the resulting hard fault deterministically with
`CONFIG_MM_FILL_ALLOCATIONS`.
@94xhn
The case you just described, where this_task and g_running_tasks can be
unequal, only occurs in an interrupt context. However:
1 nxtask_exit runs in a thread context. A thread can never preempt an
interrupt; once the interrupt exits and thread execution resumes, this_task and
g_running_tasks will be equal again.
2 nxtask_exit runs with interrupts masked.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]