点击查看《Xenomai/IPIPE源代码情景解析》
原创不易,需要大家多多鼓励!您的关注、点赞、收藏就是我的创作动力!
6.4.2 Xenomai xnthread_harden原理
xnthread_harden 函数主要用于把线程从root domain迁移到head domain,以此满足实时任务对低延迟响应的需求。
xnthread_harden 函数最典型的调用场景:
(1)创建xnthread线程后,会执行xnthread_harden切换线程到head domain,参考《6.1 Xenomai进程的创建流程》和《6.2 Xenomai线程的创建流程》。
(2)就是当执行xenomai调用,必须执行xnthread_harden确保线程线切换到head domain,参考《5.4 IPIPE: Xenomai/Linux双核系统调用》。
xnthread_harden中调用的最核心的函数是第1938行的pipeline_leave_inband()函数。
抽取其中最主要的调用框架,得到如下函数调用堆栈,接下来分3大部分逐步解释其中的玄机。图中用3种颜色来区分3大步骤。
1. set_current_state(TASK_INTERRUPTIBLE | TASK_HARDENING)
__ipipe_migrate_head调用 set_current_state 函数,将当前任务task_stuct的state状态更新为 TASK_INTERRUPTIBLE 和 TASK_HARDENING 的组合状态。
- TASK_INTERRUPTIBLE:这是一个任务状态标志,表示任务处于可中断的睡眠状态。
- TASK_HARDENING:是Xenomai为task_struct引入的自定义的任务状态标志,用于标记任务正处于从普通状态向实时状态转换的过程。
2. __schedule(false)
__schedule()是 Linux 内核中核心的调度函数,负责进行任务的切换和调度。它会调用pick_next_task,根据调度算法从运行队列中选择一个合适的任务,执行context_switch将 CPU 控制权交给该任务。
现在的场景是要把一个任务从Linux切换到Xenomai,为什么__ipipe_migrate_head调用__schedule()来切换Linux的任务呢?
这是有道理的。
当前任务”小沙”占用着CPU,处于执行态。想把它的执行上下文从Linux直接变成Xenoami,非常困难。就好像你不能边开飞机边修飞机吧。
所以,先在root domain,用__schedule函数把当前任务调度出去,重新在Linux的任务队列中拿到一个新的任务“小漏“并执行。这样方便对任务“小沙“继续操作。
具体是怎么操作的呢?接着往下看一下context_switch函数的细节。
context_switch函数非常复杂,这里就不逐行去解释了,而是抓住最核心两个函数:switch_to和__ipipe_switch_tail。
-
switch_to
switch_to 是一个关键的上下文切换函数,在 Linux 内核中用于实现任务上下文的切换。prev 是当前正在执行的任务(即将被切换出去的任务),我们还是用“小沙”作为代称。next 是即将要执行的新任务,我们还是用“小漏”作为代称。
从switch_to函数返回时,已经完成从当前任务 prev 到新任务 next 的上下文切换,包括寄存器状态、栈指针等的切换,使得 CPU 开始执行新任务 next 的代码。这段描述可能有点让人费解,稍微解释一下。
先想一下,next任务“小漏”曾经是处于执行态的。当“小漏”被调度出去时,也是走到了第2838行的switch_to函数,所以“小漏”的任务上下文中,它就停留在switch_to函数返回的位置。
然后,一旦当前任务“小沙”被调度出去,把“小漏”调度进来,那么“小漏”还是从switch_to函数返回的位置之后开始向下执行。
最后,需要注意,任务“小沙”是一体两面的,在Linux有自己的调度实体task_struct,同时在Xenomai有自己的影子线程xnthread。
switch_to函数只是完成了对task_struct的操作,而对xnthread的操作,就依赖第2841行的Xenomai插入的__ipipe_switch_tail函数。 -
__ipipe_switch_tail
__ipipe_switch_tail这个函数已经处于任务“小漏”的上下文中了,而任务“小沙”已经处于TASK_INTERRUPTIBLE可中断睡眠状态,此时就时对任务“小沙”进行精准的外科手术。任务“小沙”是一体两面的,在Linux有自己的调度实体task_struct,同时在Xenomai有自己的影子线程xnthread。__ipipe_switch_tail操作的对象必然是xnthread!
__ipipe_switch_tail具体应该怎么操作?
它必须调用IPIPE定义的接口函数complete_domain_migration!
3. complete_domain_migration函数
1)t->state &= ~TASK_HARDENING;
将task_stuct的state状态中的TASK_HARDENING标记清除。这个标记是__ipipe_migrate_head调用 set_current_state 函数设置的。这也说明TASK_HARDENING标记表达的就是一个过程状态。
2)ipipe_set_ti_thread_flag(task_thread_info(t), TIP_HEAD);
参考《5.3.2 ipipe_flags之TIP_HEAD》,IPIPE在struct thread_info中新增了ipipe_flags,用于Xenomai/Linux双内核之间进行交互。其中TIP_HEAD代表进程当前运行在Head域。
3)ipipe_migration_hook(t);
-
第177行,xnthread_resume(thread, XNRELAX)
xnthread_resume 函数用于恢复之前通过调用 xnthread_suspend() 而被挂起的线程。此函数通过移除影响目标线程的一个或多个挂起条件来实现这一点。当所有挂起条件都被移除后,线程将进入就绪(READY)状态,并重新具备调度资格。
传入的第一个参数thread,就是一个xnthread指针:struct xnthread *thread = xnthread_from_task§。它就指向任务“小沙“在Xenomai的影子线程xnthread。
传入的第二参数XNRELAX,就表示要清除struct xnthread 中的XNRELAX阻塞状态,最终设置XNREADY就绪状态。 -
第194行,xnsched_run
xnsched_run会执行调度操作。
调用xnsched_pick_next(sched)从调度队列中选中一个处于XNREADY的xnthread,假设此时“小沙“xnthread排在了第一,被选中了。
调用pipeline_switch_to,把选中的“小沙“xnthread调度到CPU上执行,把屁股还没坐热的”小漏“给挤下去了。
有趣的事情是,xnsched_run最终是复用了Linux的switch_to函数完成任务切换。
xnsched_run
__xnsched_run
pipeline_schedule
___xnsched_run
next = xnsched_pick_next(sched);
thread = xnsched_rt_pick(sched);//从Multi-level priority queue取出一个thread
set_thread_running(sched, thread); //把XNREADY清除掉
pipeline_switch_to(prev, next, leaving_inband)
xnarch_switch_to(prev, next);
ipipe_switch_to(prev, next)
switch_to(prev, next, last); //完成切换
小结一下
为了把任务“小沙“从Linux送到Xenomai,xnthread_harden首先在Linux中,把任务”小沙“的tast_struct调度出去,换成了任务”小漏“。
在任务“小漏”屁股还没坐热的时候,把任务“小沙”在Xenomai的影子线程xnthread调到到CPU运行了,把任务“小漏”从CPU上挤下去了。
而且,是在任务“小漏“执行过程中,自己把自己给挤下去了,有点不讲武德哈!
点击查看《Xenomai/IPIPE源代码情景解析》
原创不易,需要大家多多鼓励!您的关注、点赞、收藏就是我的创作动力!