Adeos在Xenomai体系结构中的位置
你将会注意到Adeos接口直接暴露在硬件抽象层构成了Xenomai核的基础。因此,大多数对Adeos服务的请求从HAL层开始解决,它的实现能在相关arch/<archname>/hal目录中找到,通用的位可以在arch/generic/hal下获得。看一看后者是最好的方法去理解Xenomai怎样为了自己的目的利用Adeos的。
2.1. Xenomai的首要和次要域
Xenomai允许运行实时线程在严格内核空间或者在Linux线程地址空间。在文章剩下的部分,我们把后者称作Xenomai线程,不会与常规Linux任务产生混淆(即使他们属于 SCHED_FIFO 类)。Xenomai管理的所有线程
可以从实时nucleus中获知。
支持实时线程排他的运行在内核空间仅仅是一个共同核时代的一个回忆,在用户空间真实时支持来临之前,当实时应用仅能在嵌入到内核模块运行;这些特点Xenomai还保留着主要的目的是支持延迟应用,这里不做讨论。
更有趣的是Xenomai对Linux有一个共生的方法;例如,这个使它不同于RTAI/LXRT的实现。以此为目的,Xenomai线程不仅能像基于内核的Xenomai线程运行在管线里最高优先级域(即,主要域)的上下文中,而且可以运行在常规Linux空间(即,次要域),即使经历更高的调度延迟,仍被Xenomai当做实时的。在Xenomai的术语中,前者被称为运行在主要执行模式,而后者处于次要执行模式。
为了对运行在次要域的线程提供完全的实时支持,Xenomai需要实现以下几点:
- 公共的优先级模型。只能调度,我们需要一种方法使实时核和Linux内核共享相同的优先级模型相对于线程共享控制。换句话说,一个Xenomai线程需要是自己的优先级在任何时候严格生效,不管它当前的域,在所有的Xenomai线程中。Xenomai采用称为根线程可变优先级的技术,通过这个Linux内核自动继承被实时核控制的Xenomai线程的优先级,这恰好发生在进入次要域的时候。实际上,这意味着当前运行在主要域的Xenomai线程没有必要抢占运行在次要域中的线程,除非它们的有效优先级确实更高。例如,上面的行为跟RTAI/LXRT相反,在RTAI/LXRT中线程迁移到Linux空间事实上会丢失它们实时有限级,通过继承RTAI调度器定义的最低优先级实现。也就是说,常规Linux任务队Xenomai一无所知,这仅仅发生在属于SCHED_FIFO的类中,当与来自主要域的Xenomai线程竞争CPU时总会被抢占,即使他们仍然会与运行在次要域中的Xenomai线程竞争优先级明智。
- 程序执行时间的可预测性。当一个Xenomai线程运行在Linux(即,次要)域,不管执行内核还是应用代码,它的时序不应该被非实时的Linux中断活动搅动,通常来说被任何低优先级、发生在内核层的异步异步活动。一个简单的方法来阻止后者发生的几率是当Xenomai线程运行在Linux域时使Linux内核保持中断饥饿,因此没有推迟处理会在这段时间内触发上半部中断处理。使Linux内核遭受中断饥饿的方法是当内部需要中间Adeos域的时候阻塞他们,坐在被实时核和Linux内核占用的中断之间,在Xenomai术语中叫做中断屏蔽。无论什么时候Xenomai线程在Linux内核空间被调用这个屏蔽都会被占用,在其它的情况下不同。需要注意的是屏蔽支持能被启用/禁用在每个线程基础上,或者在Xenomai构建时系统范围的基础上;默认情况下对于Xenomai是被禁用的并且不是内建的。
- 细粒度的Linux内核。为了从次要执行模式中获得最好,我们需要Linux内核表现出最短的可能非抢占部分,因此在Xenomai线程运行在次要域变成准备运行之后重调度的机会会尽快的被抓住。另外,这确保了Xenomai线程能在一个短的和有时间界的一段时间内从主要域迁移到次要域,因为这个操作包含了到达一个内核重调度点。因为这个,
2.2. 系统调用窃取
由于Xenomai实时API(即,skins)可以堆积在Xenomai的nucleus上,能将它们自己的一组服务导出给用户空间Xenomai线程,必须有一种方法对适当的处理程序的分离对应的系统调用,而常规的系统调用都是在一起的。Xenomai拦截每一个Xenomai线程需要处理的Xenomai或Linux域的系统调用陷阱/异常。这通使用适当的Adeos订阅事件处理程序实现。Xenomai使用这种能力来:
(1)将来自应用程序请求的实时服务分发给恰当的系统调用处理程序;
(2)确保每一个系统调用都会在其相应的域中执行,无论是Xenomai还是Linux。
2.3. 中断传播
因为实时nucleus在管线的最前面,当有感兴趣的中断来临时在Xenomai域中的实时nucleus会第一个被通知,中断被处理之后,nucleus会将该中断标记并传递到管线,如果必要最终会传递到Linux内核域。当被产生的中断唤醒时,实时nucleus会在外部中断处理程序返回后(以防中断堆积)重新调度,并会转换成它控制的可运行线程的最高优先级。
当没有实时活动被阻塞时,Xenomai域会将CPU的控制权交给中断屏蔽域。也就是说当Xenomai域空闲时让出CPU,当Xenomai有事件处理时,抢占CPU。对于中断通过管线,Adeos有两种传播模式:隐式模式和显示模式。隐式模式由系统自动传播中断,显示模式需要手动传播中断。
3. 技巧和窍门
3.1 启用/禁用中断源
除了能完全延迟一个域使不再有中断能经过它,直到它被明确的撤销延迟,Adeos允许在硬件层选择性的禁用/重启用中断的实际源。
接管了这个盒子后,Adeos处理所有域的中断禁止的请求,包括Linux内核的和实时核的。这意味着在硬件PIC层禁用中断请求源,并锁住从这个中断源到当前域在管线层的任何中断的分发。相反的,启用中断意味着重新激活PIC层的中断源,并允许从这个源到当前域的进一步分发。因此,一个想启用一个中断源的域必须是与禁止这个中断源的域是同一个,因为这样的操作是域独立的。
实际上,这意味着,成对使用时,rthal_irq_disable()和rthal_irq_enable()服务集成了构成了Xenomai基础的实时HAL内相关Adeos的调用,必须由同一Adeos域解决。例如,如果一个实时中断处理程序用rthal_irq_request()服务于某个中断源连在一起,禁用中断源使用rthal_irq_disable(),那么这个源将会被Xenomai域阻塞直到同一中断源的rthal_irq_enable()被同一域调用。处理这个请求失败将会导致受影响的中断通道永久性丢失。
3.2. 域间共享中断
一个在域间共享硬件中断时误用Adeos管线的典型例子如下:
void realtime_eth_handler (unsigned irq, void *cookie)
{
/*
* This interrupt handler has been installed using
* rthal_irq_request(), so it will always be invoked on behalf of
* the Xenomai (primary) domain.
*/
rthal_irq_disable(irq);
/* The Xenomai domain won't receive this irq anymore */
rthal_irq_host_pend(irq);
/* This irq has been marked as pending for Linux */
}
void linux_eth_handler (int irq, void *dev_id, struct pt_regs *regs)
{
/*
* This interrupt handler has been installed using
* rthal_irq_host_request(), so it will always be invoked on
* behalf of the Linux (secondary) domain, as a shared interrupt
* handler (Linux-wise).
*/
rthal_irq_enable(irq);
/*
* BUG: This won't work as expected: we are only unlocking the
* interrupt source for the Linux domain which is current here,
* not for the Xenomai domain!
*/
在上面的这个不工作的例子中,因为Xenomai对所有截获的中断总是使用显示的传播模式,接下来的以太网将会在Xenomai日志中被标记为阻塞,等待Xenomai处理程序可能手动的将它传播给Linux。但是由于中断仍然被Xenomai锁在管线层(别忘了实际上没有人从Xenomai域解决了期望的rthal_irq_enable()),这样的是不会发生的,因为Xenomai处理程序直到锁被移除才会运行的。因此,我们庆祝吧。
幸运的是,对于适当的共享中断有个方法,域间需要保持中断源禁用直到最终处理结束(例如,处理电平出发的中断是其中的一个问题):实际上,你不需要做任何事情,因为在把它传递给管线之前Adeos已经屏蔽了来自PIC层的进来任何中断。因此,你仅仅需要处理你看到的适合相关域处理程序的中断,并确定在最后一个的时候使用rthal_irq_enable()来重启用中断源。无论何时Linux内核是那些接收者之一,常规的内核处理程序将会自动重启用,所以基本上你只需要担心在处理程序中调用rthal_irq_enable(),这个函数不会传播传进来的中断到Linux内核。
特别的在x86体系结构上,由于性能原因,Adeos接收到的时钟中断将不会被屏蔽。这就是说,中断源将不会是你想以任何方式禁用的,所以这不是个问题。
3.3. 中断共享和延迟
然而,当传播通过整个管线保持屏蔽一个中断源可能会增加延迟。
由于Adeos保证没有由于中断在任何域上堆积引起栈溢出的发生,并且因为它在出发中断处理程序之前会延迟当前阶段,在Xenomai处理程序中没有必要禁用中断源。相反你甚至会想重新启用它,这样即将发生的中断可以被立刻记录下来,并且在当前处理程序调用返回后回立即被处理。
所以,解决方法是以这种方法重写先前的例子,如下:
void realtime_eth_handler (unsigned irq, void *cookie)
{
rthal_irq_enable(irq);
rthal_irq_host_pend(irq);
/* This irq has been marked as pending for Linux */
}
void linux_eth_handler (int irq, void *dev_id, struct pt_regs *regs)
{
/* process the IRQ normally. */
}
4. 结论
Adeos是相当简单的一段代码,如果使用得当包含了许多有趣的属性。Adeos模型的主干是时间管线,正因如此,它提供了所有我们在Xenomai中需要的重要特点:
- 可预测的中断延迟;
- 精确中断虚拟化控制(每个域和每个中断处理程序注册,每个域和每个CPU的中断屏蔽);
- 统一、优先级和面向域的事件传播模型;
- 一个通用和简单的API来简化客户代码的移植。
Xenomai使用这些特点来寻求Linux内核带来的实时服务的最大可能的集成。Xenomai的主要模式在最低的微秒即的延迟提供真的实时性能。另外,Xenomai在Linux未来演化中赌注,来改善内核的总体粒度,例如PREEMPT_RT,因此次要模式仍在在确定意义上的实时,即使是最坏情况下的延迟也是可测的。这就是为什么Xenomai从第一天开始就努力工作来达到与Linux内核高度集成的层次。考虑共生,寻求共利。
5. 链接