RTAI分析

来源:互联网 发布:矩阵归一化处理公式 编辑:程序博客网 时间:2024/06/05 06:40
RTAI(Real-Time Application Interface)是对Linux内核的硬实时扩展,它遵循自由软件规范;它可以提供工业级的RTOS功能,而且其所有的功能都可无缝的通过GNU/Linux环境访问。RTAI项目是由意大利米兰理工学院航天工程系(DIAPM)发起开发的遵循GPL的开源项目。
RTAI目前的稳定版本是3.3(以下的分析基于3.2),支持的CPU类型包括I386,PPC和ARM,在ARM类型处理器上的移植官方软件包仅包含对EP9301和PXA255的支持;
目前,基于RTAI已经有了很多的应用,如RTnet,USB4RT,RTCAN等。
RTnet是基于Xenomai和RTAI的开源的硬实时网络协议栈;它使用标准的以太网硬件,并且已经支持流行的网络芯片;它以确定性的方式实现了UDP/IP,ICMP和ARP,并且给内核模块和实时用户进程提供了POSIX套接字接口。
USB4RT的目标是在Linux/RTAI上实现一个具有硬实时能力的USB协议栈;它当前已经实现了核心栈和一个UHCI主机控制器驱动的功能,其将来的工作包括实现各种高层功能和支持EHCI(2.0)。
RTCAN为基于Linux的CAN节点提供了一个实时通讯的框架,通过RTCAN提供的一组功能,CAN消息可以通过RTAI进行发送和接收;RTCAN的重点在于平台的可移植性,它为控制系统的开发者提供了一个强有力的工具。
RTAI为开发硬实时系统提供了丰富的功能,其主要功能如下:
提供多种对实时任务的调度方式,它的调度器分为UP(Uniprocessor),SMP(Symmetric Multiple Processor和MUP(Multi-Uniprocessor)三类;其中,UP调度器用于单处理器的系统;SMP调度器用于对称的多处理器的系统,所有的CPU使用相同的时钟中断源,也就是说,任务在所有的CPU上都是以相同的模式运行;而MUP调度器也是用于多处理器系统,只不过每个CPU有自己独立的时钟中断源,所以,在不同的CPU上运行的任务可以以不同的时钟模式运行。在这三类调度方式中,时钟运行的模式都有两种,单触发模式(one-shot mode)和周期模式(periodic mode)。
提供了丰富的任务间的通信方法,主要包括邮箱机制(Mailboxe),消息和过程调用机制(Message and RPC),信号量机制(Semaphore),管道机制(FIFO),共享内存机制(Shared memory);其中的管道机制和共享内存机制可用于RTAI的实时任务和Linux进程之间的通信。
此外,RTAI还提供了LXRT机制,使得可以在Linux的用户空间创建软实时进程,满足软实时应用的要求。
RTAI还在不断的发展之中,而且它在实时环境中的应用也越来越多,它将来的主要发展方向包括实现更多硬件平台上的移植、为RTAI提供C++开发环境、实时文件系统、对基于Flash的文件系统的支持、高级的实时内存管理机制等。
RTAI在ARM平台上的移植,从3.2版本开始,是基于Adeos实现的,它的硬件抽象层HAL使用Adeos提供的服务。也就是说,RTAI实现了Adeos中的一个域,通过Adeos实现RTAI域的初始化、中断的申请、中断服务程序的注册等。
RTAI在Adoes系统中的域优先级高于Linux域(也就是根域),每当中断到来之后,Adeos先调度RTAI对该中断进行处理、执行中断相关的实时任务,只有当RTAI没有实时任务和中断需要处理的时候,Adeos才会调度Linux运行,这就保证了RTAI的中断响应速度和实时任务不受Linux的影响,从而提供了实时系统的可确定性。图4-1表示了RTAI,Linux和Adeos这三个软件实体之间的相互关系。

如图4-1所示,我们可以把整个实时Linux系统划分成硬件平台、Adeos,RTAI,Linux kernel和Linux应用程序五大部分,可以把各个部分之间的关系归为9类,下面对这9类关系分别作简短的描述,有助于理解整个系统的架构和相互关系。
关系①表示实时任务如何与RTAI提供的实时任务接口相互作用。实时任务(本文所指的实时任务,如果没有特别说明都是硬实时的)都是以Linux内核模块方式实现的,要实现一个实时任务,在模块初始化的时候要调用RTAI的任务创建函数初始化实时任务相关的数据和环境,指定定时器的运行模式(单触发模式或周期模式),初始化定时器,然后开始执行任务;需要注意的是,当没有加载任何RTAI的实时任务模块的时候,RTAI的任务调度和时钟中断都没有启动。
关系②表示实时任务通过RTAI提供的管道(FIFO)和共享内存与Linux用户空间中的进程进行通信,通过这种方式,实时任务获取的实时数据就可以传递到用户空间让非实时进程对数据进行后续的处理。
关系③表示RTAI本身的实现需要用到Linux内核提供的某些功能;例如,RTAI本身(包括各种提供给实时任务的服务模块)是以Linux内核驱动模块的形式存在的,这就需要用到Linux内核的动态内核模块加载功能;另外,RTAI目前的内存管理模块在初始化时是使用Linux的内存分配接口分配足够的内存。
关系④表示传统的Linux内核和Linux用户程序的关系。
关系⑤表示RTAI和Adeos之间的交互。最新的RTAI版本3.2是基于Adeos实现的,RTAI实现了Adeos内的一个域,这个域的优先级高于Linux内核所在的根域,可以保证所有的RTAI中断和实时任务都不会受Linux本身的影响,从而确保快速的中断响应和实时任务的按时完成。
关系⑥表示RTAI和底层硬件之间的交互,当外部事件触发了实时任务之后,实时任务在处理的过程中一般要对外部设备执行某些操作,例如控制采集卡进行数据采集、控制步进电机等。
关系⑦⑧⑨表示Linux、Adoes和硬件平台之间的关系,详细分析将在后面关于Adeos分析的讨论。
RTAI初始化
RTAI的初始化是由用户动态加载RTAI的硬件抽象层模块rtai_hal.o开始的,涉及到Linux用户空间、Linux域、RTAI域和Adeos,其初始化流程如图4-2。


从图4-2可以看出,RTAI的初始化通过函数__rtai_hal_init完成,这个函数位于rtai-3.2/base/arch/arm/hal/hal.c中,它完成的主要功能包括(注意:RTAI初始化是在根域中完成的,也就是说当前域为Linux):
1.通过调用adeos_alloc_irq分配一个虚拟中断rtai_sysreq_virq,在前面分析Adeos的时候,我们知道虚拟中断可用于域之间的交互,rtai_sysreq_virq就是用于RTAI请求Linux执行某些必须在Linux域内执行的操作。Linux域内处理这个虚拟中断的入口点是rtai_ssrq_trampoline,这是在初始化函数中通过调用adeos_virtualize_irq实现的。
2.用rtai_syscall_trampoline替换Adeos默认的系统调用入口函数adeos_syscall_entry,这是用来处理特殊的RTAI系统调用(与一般的Linux系统调用相比)和实现LXRT(Linux RealTime,一种通过RTAI在Linux用户空间实现软实时的机制)。
3.调用rtai_proc_register注册RTAI相关的proc文件系统项,通过标准Linux下的proc文件系统提供相关的RTAI系统信息。
4.调用rtai_archdep_init进行一些与硬件体系结构相关的初始化操作。
5.初始化RTAI的属性结构变量,然后调用Adeos的域注册接口adeos_register_domain进行RTAI域的注册,RTAI域实体变量名为rtai_domain。
从前面章节对Adeos域注册的分析,我们知道在域注册的最后,如果新注册的域指定了自己的域初始化函数,Adeos会切换到新注册的域,新域开始运行的时候会调用域本身的初始化函数,RTAI域本身的初始化函数是rtai_domain_entry,位于文件rtai-3.2/base/arch/arm/hal/hal.c中,它完成的主要功能如下(注意,此时的初始化操作都是在RTAI域中完成的):
1.通过adeos_virtualize_irq向Adeos注册RTAI的中断处理函数rtai_irq_trampoline,注册时的处理中断模式掩码为IPIPE_DYNAMIC_MASK,这也就是告诉Adeos不要将中断直接传递给中断管道的下一个低优先级域(在这里就是Linux域),而是由RTAI的中断处理程序来决定是否将这个中断传给下一个低优先级的域(可以通过调用adeos_propagate_irq将中断沿着中断管道向下传播)。
2.通过adeos_catch_event向Adeos注册RTAI的事件和异常处理函数rtai_trap_fault。
3.调用adeos_suspend_domain挂起RTAI域。
---------------
RTAI中断处理
RTAI的中断处理入口是rtai_irq_trampoline,这是在RTAI的初始化过程中向Adeos注册的。它完成的功能很简单,就是根据中断号调用相应的RTAI中断处理程序,当RTAI没有注册某个中断的中断服务程序时,它会通过调用Adeos接口函数adeos_propagate将这个中断沿着Adeos中断管道传递到下一个低优先级的域。
RTAI的中断管理信息保存在结构变量rtai_realtime_irq中,其定义如下:
struct {
    rt_irq_handler_t handler;
    void *cookie;
    int retmode;
} rtai_realtime_irq[NR_IRQS]
每一个中断对应这数组rtai_realtime_irq中的一个元素,其中,handler用于保存中断处理函数,cookie用于保存传递给中断处理函数的特殊信息;retmode在基于ARM的实现中没有用到。
另外,RTAI提供三个函数用于中断请求、中断释放和cookie的设置,它们分别是rt_request_irq,rt_release_irq,rt_set_irq_cookie。
--------------------
时钟中断服务程序
RTAI的时钟中断服务程序rt_timer_handler位于文件rtai-3.2/base/sched/sched.c中,每当系统时钟中断发生的时候,它就处理RTAI中与时钟相关的信息,并有可能进行任务的调度,它类似于Linux中的do_timer函数。它完成的主要功能如下:
1.更新系统的时间信息,RTAI将系统的时间信息保存在结构变量rt_times中,它的结构定义如下:
struct rt_times {
        int linux_tick;
        int periodic_tick;
        RTIME tick_time;
        RTIME linux_time;
        RTIME intr_time;
};
其中,linux_tick表示Linux的一个系统周期(Linux每秒的系统周期数为HZ)所需的时钟的嘀嗒数;periodic_tick表示RTAI所设置的时钟(定时器)的的运行周期(也就是定时器每计数periodic_tick次就产生一次时钟中断);tick_time表示上一次时钟中断产生时系统所经过的时钟嘀嗒数;linux_time表示下次Linux时钟中断应该产生的时间(以时钟嘀嗒数为单位);intr_time表示下次RTAI时钟中断应该产生的时间。
2.调用wake_up_timed_tasks唤醒RTAI中的超时任务,并调用宏RR_YIELD和TASK_TO_SCHEDULE找到下一个应该被调度运行的任务。
3.根据时钟的运行模式(单触发模式或周期模式)更新系统时间信息,并对定时器进行相应的设置。
4.根据需要进行任务的重新调度。
-----------------
实时任务的管理
在RTAI中,一个实时任务的生成一般包括任务创建、设置定时器、启动时实任务几个阶段,下面就这几个方面进行分析。
1        实时任务创建
实时任务的创建主要完成对代表实时任务实体的任务结构变量的初始化操作,包括分配任务栈、初始化任务栈、初始化链表指针等。任务的初始化操作最终由函数int rt_task_init_cpuid(RT_TASK *task, void (*rt_thread)(int), int data, int stack_size, int priority, int uses_fpu, void(*signal)(void), unsigned int cpuid)来完成。参数列表中的task是指向任务结构变量的指针,这个结构变量应该在初始化任务之前由任务的创建者分配;rt_thread是一个函数指针,指向代表任务的函数,参数列表中的data则是传递给这个函数的参数;stack_size指明任务所需的任务栈大小,priority指明任务的优先级,uses_fpu指明任务是否使用FPU;signal是一个函数指针,它所指向的函数在任务切换的时候被调度,它的调用方式有点类似于Linux中的信号;而cpuid则指明在多处理器系统中优先考虑将任务放在某个处理器上运行。
函数rt_task_init_cpuid的定义在文件rtai-3.2/base/sched/sched.c中,它完成的主要功能如下:
1.调用sched_malloc分配任务栈;
2.初始化任务结构变量(task所指向的RT_TASK结构)内的字段;
3.调用init_arch_stack为任务的首次运行准备任务栈内容;
4.将任务加入实时任务列表;
一个实时任务的绝大部分属性都保存在实时任务的RT_TASK结构变量中,下面对这个结构进行简要的说明。
typedef struct rt_task_struct {
int *stack __attribute__ ((__aligned__ (L1_CACHE_BYTES)));
int uses_fpu;
int magic;
volatile int state, running;
unsigned long runnable_on_cpus;
int *stack_bottom;
volatile int priority;
int base_priority;
int policy;
int sched_lock_priority;
struct rt_task_struct *prio_passed_to;
RTIME period;
RTIME resume_time;
RTIME yield_time;
int rr_quantum;
int rr_remaining;
int suspdepth;
struct rt_queue queue;
int owndres;
struct rt_queue *blocked_on;
struct rt_queue msg_queue;
int tid;        /* trace ID */
unsigned msg;
struct rt_queue ret_queue;
void (*signal)(void);
FPU_ENV fpu_reg __attribute__ ((__aligned__ (L1_CACHE_BYTES)));
struct rt_task_struct *prev;
struct rt_task_struct *next;
struct rt_task_struct *tprev;
struct rt_task_struct *tnext;
struct rt_task_struct *rprev;
struct rt_task_struct *rnext;
/* Appended for calls from LINUX. */
int *fun_args, *bstack;
struct task_struct *lnxtsk;
long long retval;
char *msg_buf[2];
int max_msg_size[2];
char task_name[16];
void *system_data_ptr;
struct rt_task_struct *nextp;
struct rt_task_struct *prevp;
/* Added to support user specific trap handlers. */
RT_TRAP_HANDLER task_trap_handler[RTAI_NR_TRAPS];
/* Added from rtai-22. */
void (*usp_signal)(void);
volatile unsigned long pstate;
unsigned long usp_flags;
unsigned long usp_flags_mask;
unsigned long force_soft;
volatile int is_hard;
void *trap_handler_data;
struct rt_task_struct *linux_syscall_server;
/* For use by watchdog. */
int resync_frame;
/* For use by exit handler functions. */
XHDL *ExitHook;
RTIME exectime[2];
struct mcb_t mcb;
/* Real time heaps. */
struct rt_heap_t heap[2];
} RT_TASK __attribute__ ((__aligned__ (L1_CACHE_BYTES)));
其中,stack用于保存实时任务的当前堆栈指针;uses_fpu表示任务是否使用FPU;magic用于标识这个结构是否是RT_TASK数据结构;字段state,running用于表示任务所处的状态;字段runnable_on_cpus用于表示任务当前运行的CPU;stack_bottom指向任务对栈的栈底;priority表示任务的当前优先级,由于优先级继承的机制存在,这个字段的指有可能会和base_prority的值不同;base_prority表示任务本身的优先级;字段prio_passed_to表示这个任务在优先级继承机制中将优先级传给了那个任务;字段period,resume_time,yield_time用于任务的调度;rr_quantum,rr_remaining用于同等优先级任务的时间片轮转调度;字段suspdepth用于记录任务被挂起的次数;字段signal保存任务的一个类似于Linux下的信号处理函数,这个函数在任务调度的时候会被调用;字段prev,next,tprev,tnext,rprev,rnext用于任务的队列管理;
2        实时任务定时器设置
在创建实时任务的过程中,主要通过3个函数对定时器进行设置,它们分别是:rt_set_oneshot_mode,rt_set_periodic_mode和start_rt_timer,下面分别对这3个函数进行分析。
rt_set_oneshot_mode用于将定时器设置为单触发模式,所谓单触发模式,就是说,每当定时器产生一次中断后,系统都要根据目前系统任务对时间精度的要求情况对定时器重新进行编程,设定下一次触发的时间。rt_set_oneshot_mode完成的功能如下:
1.调用stop_rt_timer停止定时器的运行;
2.设置全局变量oneshot_timer的值为1;在时钟中断函数中,系统会检查oneshot_timer的值,判断定时器的工作模式,然后根据定时器的模式对定时器进行相应的操作。
rt_set_periodic_mode的操作与rt_set_oneshot_mode的类似,只不过它将oneshot_timer的值设为0,表示定时器应工作在周期模式;当定时器工作在周期模式下时,系统只要对定时器进行一次初始化,指定定时器产生中断的周期,以后就不再需要对定时器进行编程了。
start_rt_timer根据设定的定时器运行模式对定时器进行初始化,它完成的主要功能如下:
1.调用函数rt_request_timer注册时钟中断服务程序rt_timer_handler;
2.初始化系统用于保存时间信息的结构rt_smp_times(就是前面所说的rt_times);
3.调用rt_request_linux_irq注册一个Linux下的中断服务程序recover_jiffies,这个中断程序和Linux的时钟中断服务程序共享时钟中断,recover_jiffies用于补偿Linux所丢失的时钟中断(因为可能由于实时任务的运行,使Linux很长一段时间得不到运行的机会,无法响应时钟中断)。
3        实时任务调度
实时任务的调度是通过函数rt_schedule完成的,这个函数完成的功能与rt_timer_handler完成的功能类似,它的定义在文件rtai-3.2/base/sched/sched.c中,它的主要功能如下:
1.通过调用函数wake_up_timed_tasks唤醒超时的任务;
2.调用宏RR_YIELD()和TASK_TO_SCHEDULE()找到下一个优先级最高的任务;
3.更新系统的时间相关信息rt_times;
4.根据需要,如果定时器工作在单触发模式,则根据系统时间要求对定时器进行编程;
5.如果下一个最高优先级的任务不是当前运行的任务,则进行任务切换;


from:http://bbs.chinaunix.net/thread-1970771-1-1.html

原创粉丝点击