中断系统结构
在RTOS中,中断是与具体硬件平台关联度最大的部分,为了实现高可移植性、可配置性,中断子系统依照aCoral的整体结构来设计,划分为HAL(硬件抽象层)和内核层。
在HAL层先将各种中断汇拢,对于硬件相关的公共部分进行前序处理,然后在内核层根据中断源进行后序处理。
HAL层的处理将复位、未定义指令、软中断、预取指终止、数据终止、快速中断等异常的异常号交给special_entry进行相应的专门处理。
而将普通中断IRQ的异常都赋值给HAL_INTR_ENTRY,HAL_INTR_ENTRY根据中断控制器产生的中断号做一些必要的公共中断,如压栈、临时关中断(如果硬件没有自动关中断)、读取中断向量号等,然后跳转到hal_all_entry执行,交内核层处理,当内核层处理完毕后,中断处理程序调用acoral_intr_exit进行中断退出操作。
中断初始化
在中断能够正确响应并处理以前,必须做相关初始化工作,中断初始化是aCoral内核各模块初始化(acoral_module_init())过程中的首要工作,由acoral_intr_sys_init()完成。
/*中断初始化函数*/
void acoral_intr_sys_init()
{
acoral_u32 i;
/*关中断*/
acoral_intr_disable();
/*中断嵌套标志初始化*/
HAL_INTR_NESTING_INIT();
/*中断底层初始化函数*/
HAL_INTR_INIT();
/*对于每个中断,设置默认的服务处理程序,然后屏蔽该中断*/
for(i = HAL_INTR_MIN; i <= HAL_INTR_MAX; i++){
acoral_set_intr_enter(i,hal_intr_ack);
acoral_set_intr_exit(i,NULL);
acoral_set_intr_mask(i,hal_intr_mask);
acoral_set_intr_unmask(i,hal_intr_unmask);
intr_table[i].isr = acoral_default_isr;
intr_table[i].type = ACORAL_COMM_INTR'
acoral_intr_mask(i);
}
/*特殊中断初始化*/
HAL_INTR_SPECIAL();
}
在中断初始化时,先要将中断关闭,否则可能会出现异常,然后是中断嵌套标志初始化。
然后进行中断底层硬件初始化,这是针对ARM S3C2440所做的初始化。
根据以上代码,中断的初始化过程也分为HAL层初始化和内核层初始化,其中,acoral_intr_disable()和以“HAL”打头的是HAL层初始化,剩下的是内核层初始化。
- HAL层初始化是针对具体硬件平台的中断特性做相关设置。acoral_intr_disable()属于HAL层初始化,因为中断关闭是与具体硬件平台相关的,且最终实现是HAL_INTR_DISABLE()。接下来的HAL_INTR_NESTING_INIT()是通过定义hal_intr_nesting_init_com()来实现的。
/*中断嵌套初始化*/
void hal_intr_nesting_init_comm(){
acoral_u32 i;
intr_nesting=0;
}
然后通过HAL_INTR_INIT()完成中断优先级、屏蔽寄存器及中断向量表私有函数等初始化。
void hal_intr_init(){
acoral_u32 i;
rPRIOORITY = 0x00000000; // 使用默认的固定的优先级
rINTMOD = 0x00000000; //所有中断均为IRQ中断
rEINTMSK = 0xffffffff; // 屏蔽所有的外部中断
rINTMSK = 0xffffffff; //屏蔽所有中断
}
通过设置中断控制器的相关寄存器来确定优先级、类型,屏蔽所有中断等。
关中断是将ARM处理器程序当前状态寄存器的第6为置为1,此时ARM成为关中断模式,不会对任何IRQ做出响应。而屏蔽所有中断是设置中断控制器的屏幕寄存器每一位都置为1,用于屏蔽所有中断,用户可以通过改变屏幕寄存器的某些位来屏蔽某些中断,不至于关闭所有中断。
- 内核层初始化
内核层的中断初始化设置内核层的中断向量表。
中断进入时的操作由hal_intr_ack实现,这里主要是清除中断Pending位。由于第4号中断到第7号中断复用了中断号4,第8号中断到第23号中断复用了中断号5,所以中断进入时操作除了设置寄存器INTPND外,还需设置寄存器EINTPND。
void hal_intr_ack(acoral_u32 vector){
if((vector>3) && (vector<8)){
rEINTPND &= ~(1<<vector);
vector = 4;
}
else if((vector>7) && (vector<24)){
rEINTPND &= ~(1<<vector);
vector = 5;
}
else if(vector > 23)
vector -= 18;
rSRCPND = 1<<vector; //清除SRCPND寄存器
rINTPND = 1<<vector; //清除INTPND寄存器
}
中断屏蔽时的操作由hal_intr_mask实现
oid hal_intr_mask(acoral_vector vector){
if((vector>3) && (vector<8)){
rEINTMSK |=(1<<vector);
vector = 4;
}
else if((vector>7) && (vector<24)){
rEINTMSK |=(1<<vector);
vector = 5;
}
else if(vector > 23)
vector -= 18;
rINTMSK |= (1<<vector); //屏蔽中断
}
通过向中断屏蔽寄存器(INTMSK)某位写入1来屏蔽相应中断。
通过向中断屏蔽寄存器(INTMSK)某位写入0来打开相应中断。
void hal_intr_unmask(acoral_vector vector){
if((vector>3) && (vector<8)){
rEINTMSK &=~(1<<vector);
vector = 4;
}
else if((vector>7) && (vector<24)){
rEINTMSK &=~(1<<vector);
vector = 5;
}
else if(vector > 23)
vector -= 18;
rINTMSK &=~(1<<vector); /*开启中断*/
}
时钟中断实例
Ticks由中断触发产生,每隔一定时间就会触发一次时钟中断,用来计时,线程的延时函数就要利用Ticks时钟。
当开发板完成启动和aCoral的加载后,系统会通过代码进入acoral_start(),时钟初始化是acoral_start()的重点工作之一。
- 时钟HAL层初始化主要是设置Ticks时钟中断相关的寄存器,主要涉及时钟模式、时钟计数值、时钟开启等寄存器操作。
void hal_ticks_init(){
rTCON = rTCON & (~0xf) ; /* clear manual update bit, stop Timer0*/
rTCFG0 &= 0xFFFF00;
rTCFG0 |= 0xF9; /* prescaler等于249*/
rTCFG1 &= ~0x0000F;
rTCFG1 |= 0x2; /*divider等于8,则设置定时器4的时钟频率为25kHz*/
rTCNTB0 = PCLK /(8*(249+1)*ACORAL_TICKS_PER_SEC);
rTCON = rTCON & (~0xf) |0x02; /* updata*/
rTCON = rTCON & (~0xf) |0x09; /* 启动定时器0*/
}
- 时钟内核层初始化。内核层初始化是给时钟中断重新设定ISRs。在之前初始化中,为每个中断设定了默认的中断服务程序acoral_default_isr()。
时钟的内核层初始化由acoral_ticks_init()完成,首先将Ticks的初始值设为0,然后重新设置Ticks的ISRs。
void acoral_ticks_init(){
ticks = 0;
acoral_intr_attach(HAL_TICKS_INTR,acoral_ticks_entry);// 注册Ticks的处理函数
HAL_TICKS_INIT();
acoral_intr_unmask(HAL_TICKS_INTR);
return;
}
#define HAL_TICKS_INTR 28 //时钟中断号,对于定时器0的中断
#define HAL_TICKS_INIT() hal_ticks_init()
/*将服务函数isr绑定到中断向量Vector,返回0表示成功*/
acoral_32 acoral_intr_attach(acoral_vector vector, void (*isr)(acoral_vector)){
if(intr_table[vector].type != ACORAL_RT_INTR){
intr_table[vector].isr = isr;
}else{
HAL_INTR_ATTACH(vector,isr);
}
return 0;
}
时钟管理
在RTOS中,时钟具有非常重要的作用,通过时钟可实现延时任务、周期性触发任务执行、任务有限等待的计时、软定时器的定时管理、确认超时以及与时间相关的调度操作。
大多数嵌入式系统有两种时钟源,分别为实时时钟RTC(Real-Time Clock)和定时器/计数器。
实时时钟一般靠电池供电,即使系统断电,也可以维持日期和时间。由于实时时钟独立于操作系统,因此也被称为硬件时钟,它为整个系统提供一个时间标准。
嵌入式处理器还集成了多个定时器和计数器,实时内核需要一个定时器作为系统时钟,并由内核控制系统时钟工作,系统时钟的最小粒度是由应用和操作系统的特点决定的。
在不同RTOS中,实时时钟和系统时钟之间的关系是不一样的,实时时钟和系统时钟之间的关系也决定了RTOS的时钟运行机制。一般而言,实时时钟是系统时钟的基准,实时内核通过读取实时时钟来初始化系统时钟,此后,两者保持同步运行,共同维持系统时间。
因此,系统时钟并不是真正意义上的时钟,只有当系统运行起来以后才有效,并且由实时内核完全控制。
嵌入式系统的时钟源的选择
根据硬件的不同,可以是专门的硬件定时器,也可以是来自AC交流电的50/60Hz信号频率。
定时器一般是由晶体振荡器提供周期信号源,并通过程序对其计数寄存器进行设置,让其产生固定周期的脉冲,而每次脉冲的产生都将触发一个时钟中断,时钟中断的频率既是系统的心跳,也称为时基或Tick,Tick的大小决定了整个系统的时间粒度。
对于RTOS,时钟心跳频率一般为10~100次/秒,甚至更高。
这是一个简单的定时器/计数器示意图,晶体振荡器提供周期信号源,它通过总线连接到CPU核上,开发人员编程设计计数寄存器Counter的初始值,随后,每一个晶体振荡器的输入信号都会导致该值增加,当计数寄存器溢出时,就产生一个输出脉冲(Pulse),输出脉冲可以用来触发CPU核上的一个中断,输出脉冲是RTOS时钟的硬件基础,因为它将送到中断控制器上,产生中断信号,触发时钟中断,由时钟中断服务程序维持系统时钟的正常工作。
实时内核的时间管理以系统时钟为基础,通过Tick处理程序来实现,定时器产生中断后,RTOS将响应并执行其中断服务程序,在中断服务程序中调用Tick处理函数。
在内核层初始化中,重要的一步就是通过acoral_intr_attach()将时钟中断服务程序与Ticks处理程序进行绑定。
每当定时器产生一个输出脉冲,输出脉冲就向CPU核发出一个时钟中断。经过HAL层的中断处理后,就进入内核层的中断响应,找到内核层对应的时钟中断号,最终将执行该中断号对应的服务程序,即acoral_ticks_entry()。
RTOS内核时钟管理的绝大部分都是在acoral_ticks_entry()进行的,如线程延迟操作time_delay_deal()、超时处理timeout_delay_deal()、与调度策略相关的操作等。
如果任务采用时间片轮转调度,则需要在Ticks处理程序中对当前正在运行的任务已执行时间进行“加一”操作。执行完该操作后,如果任务的已执行时间同任务时间片相等,则表示任务使用完一个时间片的执行时间,需要通过acoral_sched()触发重调度。
如果开发人员在线程中调用acoral_delay_thread()对线程进行延迟操作,则acoral_delay_thread()会将当前运行线程从运行状态(Running)切换到挂起状态(Suspend),并将其挂载到一个等待队列“acoral_list_t waiting”,这里的等待队列也被称为时间等待链,用它来存放需要延迟处理的任务。
接下来,每当定时器产生一个Tick,Tick处理函数acoral_ticks_entry()的time_delay_deal()对时间等待链中线程的剩余等待时间进行“减一”操作,如果某个线程的剩余等待时间被减到0,则将该线程从等待队列中移出,挂载到就绪队列,并通过acoral_sched()触发重调度。
例如,开发人员用acoral_delay_thread()将线程A、线程B、线程C、线程D分别延时3、5、10和14个Ticks。
通常情况下,每当定时器产生一个Tick,time_delay_deal()会对时间等待链中的每一个结点进行“减一”操作。若时间等待链的结点数越多,时钟中断的Tick处理函数的计算开销就比较大,从而降低系统性能。
所以采用差分时间队列来描述延迟队列,队列中某个结点的值是相对于前一个结点的时间差,因此线程B所在的结点为2,该值是相对于线程A的相对值。
采用差分时间等待链后,每当时钟中断产生一个Tick,只需对队列头部结点进行“减一”操作,当减到0时,就从等待链中取下,后续结点将成为新的头部,并且被激活。
该过程中,等待链其它结点的值保持不变,无须对每一个结点进行“减一”操作,这样可减小计算开销。
差分时间等待队列时RTOS采用的一种性能优化技术,实现比较简单。
void time_delay_deal(){
acoral_list_t *tmp,*tmp1,*head;
acoral_thread_t *thread;
head = &time_delay_queue.head;
if(acoral_list_empty(head)){
return;
}
thread = list_entry(head->next,acoral_thread_t,waiting);
thread->delay--;
for(tmp=head->next;rmp!=head;){
thread=list_entry(tmp,acoral_thread_t,waiting);
if(thread->delay>0){
break;
}
tmp1=tmp->next;
acoral_list_del(&thread->waiting);
tmp=tmp1;
thread->state&=~ACORAL_THREAD_STATE_DELAY; //将1位清零
acoral_rdy_thread(thread);
}
}