时钟管理
在RTOS中,时钟具有非常重要的作用,通过时钟可实现延时任务、周期性触发任务执行、任务有限等待的计时。
大多数嵌入式系统有两种时钟源,分别为实时时钟RTC(Real-Time Clock)和定时器/计数器。
实时时钟一般是靠电池供电,即使系统断电,也可以维持日期和时间。由于实时时钟独立于操作系统,因此也被称为硬件时钟,它为整个系统提供一个时间标准。
嵌入式处理器通常集成了多个定时器或计数器,实时内核需要一个定时器作为系统时钟,并由内核控制系统时钟工作。
一般而言,实时时钟是系统时钟的基准,实时内核通过读取实时时钟来初始化系统时钟,此后,二者保持同步运行,共同维持系统时钟。
因此,系统时钟只有当系统运行起来以后才有效,并且由实时内核完全控制。
定时器一般由晶体振荡器提供周期信号源,并通过程序对其计数寄存器进行设置,让其产生固定周期的脉冲,每次脉冲的产生都触发一个时钟中断,时钟中断的频率既是系统的心跳,也称为时基或Tick,Tick的大小决定了整个系统的时间粒度。
晶体振荡器提供周期信号源,它通过Bus总线连接到CPU核上,开发人员设定计数寄存器的初始值,随后,每一个晶体振荡器输入信号都会导致该值增加,当计数器溢出时,就产生一个输出脉冲(Pulse),输出脉冲可以用来触发CPU核上的一个中断。
输出脉冲是RTOS的硬件基础,因为它将送到中断控制器上,产生中断信号,触发时钟中断,由时钟中断服务程序维持系统时钟的正常工作。
实时内核的时间管理以系统时钟为基础,通过Tick处理程序来实现。
定时器产生中断后,RTOS将响应并执行器中断服务程序,并在中断服务程序中调用Tick处理函数,它作为实时内核的一部分,与具体的定时器/计数器无关,由系统时钟中断服务程序调用,使内核具有对不同定时器/计数器的适应性。
在内核层初始化中,重要的一步就是通过acoral_intr_attach()将时钟中断服务程序与Ticks处理程序进行绑定。
这样,每当定时器产生一个输出脉冲(Pulse),输出脉冲就向CPU发出一个时钟中断,找到内核层对应的时钟中断号,最终将执行该中断号对应的服务程序,即Ticks处理函数aCoral_Ticks_entry()。
内核时钟管理的绝大部分工作都在aCoral_Ticks_entry()进行的,如线程延迟操作time_delay_deal()、超时处理timeout_delay_deal()、与调度策略相关的操作(如时间片轮转调度)等。
如果任务采用时间片轮转调度,则需要再Ticks处理程序中对当前正在运行的任务已执行的时间进行“+1”操作。执行完该操作后,如果任务的已执行时间同任务时间片相等,则表示任务使用完一个时间片的执行时间,需要通过acoral_sched()触发重调度。
如果开发人员在线程中调用acoral_delay_thread()对线程进行延迟操作,则会让当前运行线程从运行(Running)状态切换到挂起(Suspend)状态,并将其挂载到一个等待队列“acoral_list_t waiting”,这里的等待队列也称为时间等待链,用来存放需要延迟处理的任务。
接下来每当定时器产生一个Tick,Tick处理函数aCoral_Ticks_entry()的time_delay_deal()需要对时间等待链中线程的剩余等待时间进行“减1”操作,如果某个线程的剩余等待时间被减到了0,则将该线程从等待队列中移出,挂载到就绪队列,通过acoral_sched()触发重调度。
如,开发人员用acoral_delay_thread()将线程A、B、C、D分别延迟。
通常情况下,每当定时器产生一个Tick,time_delay_deal()会对时间等待链中的每一个结点进行“减1”操作。
若时间等待链的结点数越多,时钟中断的Tick处理函数的计算开销就比较大,从而降低系统性能。
为了提高系统性能,减小计算开销,可采用差分时间等待链来描述延迟队列。
队列中某个结点的值是相对于前一个结点的时间差。
当采用查分时间等待链后,每当时钟中断产生一个Tick,只需对队列头部结点进行“减1”操作,当减到0时,就将其从等待链中取下,后续结点将成为新的头部,并且被激活。
等待链其它结点的值保持不变,无须对每一个结点进行“减1”操作,这样可减小计算开销。
如果有新线程要进行延迟操作,需要往差分时间等待链中插入新的结点,如线程E要延迟7Ticks。
只需要在线程B(3+2)和C(3+2+5)中插入线程E即可。再修改结点C的值即可(10-7)。
acoral_list_t time_delay_queue; //线程延时队列,调用线程delay相关函数的线程都会被加到这个队列上,等待一段具体时间后重新被唤醒。
acoral_list_t timeout_queue; //aCoral获取资源(互斥量)超时等待队列,即在timeout时间内获取即成功,否则超时失败。
void time_delay_deal()
{
acoral_list_t *tmp,*tmp1,*head;
acoral_thread_t *thread;
head = &time_delay_queue; //获取时间等待链的头部
if(acoral_list_empty(head))
return;
thread = list_entry(head->next,acoral_thread_t,waiting); //获取时间等待链头结点对应的TCB地址
thread->delay--; //对时间等待链头结点对应线程delay成员的剩余等待时间
for(tmp=head->next;tmp!=head;)
{
if(thread->delay > 0) //如果头结点线程delay成员大于0,则退出
break;
//delay=0,线程延迟结束
tmp1 = tmp>next;
acoral_list_del(&thread->waiting); //将该线程从时间等待链中删除
tmp = tmp1;
thread->state &= ~ACORAL_THREAD_STATE_DELAY; //将线程切换为就绪态
acoral_rdy_thread(thread); //将其挂到就绪队列上
}
}
waiting成员主要用来将线程结构挂到相应链表队列,是一个双向链表结构。
struct acoral_list{
struct acoral_list *next,*prev;
};
以就绪队列ready为例,当用户调用了acoral_rdy_thread或acoral_resume_thread接口时,就会将线程挂到就绪队列acoral_ready_queue上。
acoral_list_t policy_list;
void acoral_policy_delay_deal()
{
acoral_list_t *tmp,*head;
acoral_sched_policy_t *policy_ctrl;
head = &policy_list;
tmp = head;
for(tmp=head->next;tmp!=head;tmp=tmp->next)
{
policy_ctrl = list_entry(tmp,acoral_sched_policy_list,list);
if(policy_ctrl->delay_deal!=NULL)
policy_ctrl->delay_deal();
}
}
void period_policy_init(void)
{
acoral_init_list(&period_delay_queue);
period_policy.type=ACORAL_SCHED_POLICY_PERIOD;
period_policy.policy_thread_init=period_policy_thread_init;
period_policy.policy_thread_release=period_policy_thread_release;
period_policy.delay_deal=period_delay_deal;
period_policy.name="period";
acoral_register_sched_policy(&period_policy);
}
typedef struct{
unsigned int time; //线程周期,单位为ms
void (*route)(void *args); //线程函数
void *args; //线程函数的参数
}period_private_data_t; //周期线程私有数据块,用于保存自己的周期、函数体,以便在新周期重新挂载线程时使用。
acoral_list_t period_delay_queue; //周期线程专用延时队列,只要是周期线程,就会被挂载到这个队列上,
void period_delay_deal()
{
acoral_list_t *tmp,*tmp1,*head;
acoral_thread_t *thread;
period_private_data_t * private_data;
head = &period_delay_queue;
if(acoral_list_empty(head))
return;
thread = list_entry(head->next,acoral_thread_t,waiting);
thread->delay--;
for(tmp=head->next;tmp!=head;){
thread = list_entry(tmp,acoral_thread_t,waiting);
if(thread->delay > 0)
break;
private->data = thread->private_data;
tmp1 = tmp->next; //保存下一个周期延时队列上的结点
acoral_list_del(&thread->waiting);
tmp = tmp1;
if(thread->state & ACORAL_THREAD_STATE_SUSPEND){
thread->stack = (unsigned int *)((char *)thread->stack_buttom+thread->stack_size - 4);
HAL_STACK_INIT(&thread->stack,private_data->route,period_thread_exit,private_data->args);
acoral_rdy_thread(thread);
}
period_thread_delay(thread,private_data->time);
}
}