线程退出
当线程代码执行完后,系统会隐式地调用acoral_thread_exit()函数进行线程退出相关的操作。
acoral_thread_exit()本质上是要执行acoral_kill_thread()。
void acoral_thread_exit(){
acoral_kill_thread(acoral_cur_thread);
}
void acoral_kill_thread(acoral_thread_t *thread){
acoral_sr CPU_sr;
acoral_8 CPU;
acoral_evt_t *evt;
acoral_pool_t *pool;
acoral_enter_critical();
if(thread->state&ACORAL_THREAD_STATE_SUSPEND){
evt = thread->evt;
if(thread->state&ACORAL_THREAD_STATE_DELAY){
acoral_list_del(&thread->waiting);
}
else{
if(evt!=NULL){
acoral_evt_queue_del(thread);
}
}
}
acoral_unrdy(thread);
acoral_release_thread1(thread);
acoral_exit_critical();
acoral_sched();
}
释放线程就是回收当时创建线程时所分配的空间。
void acoral_release_thread1(acoral_thread_t *thread){
acoral_list_t *head,*tmp;
acoral_thread_t *daem;
thread->state = ACORAL_THREAD_STATE_EXIT; // 将线程置为退出状态,如果是当前线程,只能是ACORAL_THREAD_STATE_EXIT状态,表明还不能释放该线程的资源,如TCB、堆栈,因为该线程尽管要退出了,但是还没完成退出使命,要继续走到线程切换HAL_SWITCH_TO函数,在该过程中,还有函数调用,故还需要堆栈,切换线程前还需要线程的TCB。
tmp=head->prev;
acoral_list_add2_tail(&thread->waiting,head);
daem=(acoral_thread_t *)acoral_get_res_by_id(daemon_id);
acoral_rdy_thread(daem);
}
挂起线程
操作系统在运行时,有时需要挂起某个线程。
例如,当某一线程运行时需要请求某一资源,而该资源正被其它线程所占用,此时用户线程需要挂起自己。
有两种线程挂起方式,对应的接口分别是acoral_suspend_thread()、acoral_unrdy_thread()。acoral_suspend_thread()比acoral_unrdy_thread()多一个acoral_sched(),也就是acoral_suspend_thread()会立刻将指定线程挂起(将其从就绪队列中取出),然后重新调度。而比acoral_unrdy_thread()只改变指定线程的状态,将其从就绪队列中取出,并不会马上触发重新调度。
void acoral_suspend_thread(acoral_thread_t *thread){
acoral_sr cpu_sr;
acoral_8 cpu;
if(!(ACORAL_THREAD_STATE_READY&thread->state))
return;
acoral_enter_critical();
acoral_rdyqueue_del(thread);
acoral_exit_critical();
acoral_sched();
}
/*将线程从就绪队列取下*/
void acoral_rdyqueue_del(acoral_thread_t *thread){
acoral_rdy_queue_t *rdy_queue;
rdy_queue = &acoral_ready_queues;
acoral_prio_queue_del(rdy_queue, thread->prio, &thread->ready);
thread->state &= ~ACORAL_THREAD_STATE_READY;
thread->state &= ~ACORAL_THREAD_STATE_RUNNING;
thread->state |= ~ACORAL_THREAD_STATE_SUSPEND;
//设置线程所在的核可调度
acoral_set_need_sched(true);
}
void acoral_prio_queue_del(acoral_rdy_queue_t *array, acoral_u8 prio, acoral_list_t *list){
acoral_queue_t *queue;
acoral_list_t *head;
queue = array->queue + prio; //根据线程的优先级找到线程所在的优先级链表
head = &queue->head;
array->num--;
acoral_list_del(list); //从链表上删除该线程
if(acoral_list_empty(head))
acoral_clear_bit(prio,array->bitmap);//如果该优先级不存在就绪线程,则清除该优先级就绪标志位。
}
任务挂起接口用到的地方很多,只要牵涉任务的等待都会调用该函数。
看线程TCB的waiting成员是否为空,如果因为等待时间或资源导致suspend,则waiting肯定挂在一个队列上,则是调用acoral_delay_self()导致线程suspend;否则是直接调用acoral_suspend_thread()导致suspend。
线程恢复
线程恢复是线程挂起的逆过程,和线程挂起类似,线程恢复也有两个接口acoral_resume_thread()、acoral_rdy_thread()。
前者比后者多了一个acoral_sched(),还多了一个判断,并且acoral_resume_thread()是用户能够调用的,而acoral_rdy_thread()由内核内部调用。
void acoral_resume_thread(acoral_thrad_t *thread){
if(!(thread->state&ACORAL_THREAD_STATE_SUSPEND))
return;
acoral_enter_critical();
acoral_rdyqueue_add(thread);
acoral_exit_critical();
acoral_sched();
}
改变线程优先级
当多个线程互斥地访问某一共享资源时,可能导致优先级反转,优先级反转将造成实时调度算法的不确定性。
解决优先级反转的方法是优先级继承和优先级天花板,使用这两种方式时,需要动态改变线程优先级。
aCoral描述线程优先级时,采用的是优先级队列,每个优先级是一个链表,因此改变线程的优先级,不是简单地将线程的TCB的prio变量更改,最终要通过acoral_thread_change_prio()实现,将线程挂到要设置的优先级的链表上去。
void thread_change_prio(acoral_thread_t *thread, acoral_u32 prio){
acoral_enter_critical();
if(thread->state&ACORAL_THREAD_STATE_READY){
acoral_rdyqueue_del(thread);
thread->prio = prio;
acoral_rdyqueue_add(thread);
}else{
thread->prio = prio; //线程恢复时,会自动挂载到新的优先级队列上。
}
acoral_exit_critical();
}
调度策略时间处理函数
系统启动后,晶体振荡器源源不断地产生周期性信号,通过设置,晶体振荡器可以为系统产生稳定的Ticks,也称为心跳,Tick是系统的时基,也是系统中最小的时间单位,Tick的大小可以根据晶体振荡器的精度和用户的需求进行设置。
每当产生一个Tick,都对应着一个时钟中断服务程序ISR,时钟中断服务程序的具体实现是acoral_Ticks_entry()。其中包括了几项重要工作:延迟队列的处理time_delay_deal(),将挂到延迟队列中的线程的 TCB的delay成员值依次减一,如果某一线程的TCB的delay成员值减到了0,将触发aCoral重新调度acoral_sched();与调度策略相关的处理函数acoral_plicy_delay_deal(),如果采用周期性调度策略的线程或者采用时间片轮转调度策略的线程,acoral_plicy_delay_deal()将维护每个线程的周期和时间片,每当某一线程新的周期到达,或者时间片到达,都重新调度acoral_sched();超时处理函数timeout_delay_deal();用户也可以在acoral_Tciks_entry()扩展自己所需的函数。
void acoral_ticks_entry(acoral_vector vector){
ticks++;
if(acoral_start_sched == true){
time_delay_deal();
acoral_policy_delay_deal();
timeout_delay_deal();
}
}
对于acoral_policy_delay_deal(),不同的调度策略有不同的时间处理函数delay_deal(),这是在调度策略注册并初始化时绑定的,这充分体现了调度策略与调度机制分离的设计原则。
void acoral_policy_delay_deal(){
acoral_list_t *tmp,*head;
acoral_sched_policy_t *policy_ctrl;
head = &policy_list.head;
tmp = head;
for(tmp=head->next;tmp!=head;tmp=tmp->next){
policy_ctrl = list_entry(tmp,acoral_sched_policy_t,list);
if(policy_ctrl->delay_deal!=NULL)
policy_ctrl->delay_deal();
}
}
slice_policy_init(){
slice_policy.type = ACORAL_SCHED_POLICY_SLICE;
slice_policy.policy_thread_release = slice_policy_thread_release;
slice_policy.policy_thread_init = slice_policy_thread_init;
slice_policy.delay_deal = slice_delay_deal;
slice_policy.name = "slice";
acoral_register_sched_policy(&slice_policy);
}
void slice_delay_deal(){
acoral_thread_t *cur;
slice_policy_data_t *data;
if(cur->policy==ACORAL_SCHED_POLICY_SLICE){
cur->slice--;
if(cur->slice <= 0){
data = (slice_policy_data_t *)cur->private_data;
cur->slice = data->slice_ld;
acoral_thread_move2_tail(cur);//把当前线程置为队列尾部
}
}
}
void acoral_thread_move2_tail(acoral_thread_t *thread){
acoral_enter_critical();
acoral_unrdy_thread(thread);// 将当前线程从就绪队列中移出
acoral_rdy_thread(thread); // 将线程队列中的下一线程置为就绪线程
acoral_exit_critical();
acoral_sched();
}
事务处理机制
内核启动后,用户可以默认地创建几个线程。
在没有用户干预的情况下,哪个线程先执行?哪个线程后执行?哪个线程执行多长时间,完全由acoral_sched()根据用户指定的调度策略(如周期性策略、时间片轮转策略等)来决定,而周期性策略、时间片轮转策略等需要时钟来触发和维护。
如果用户线程执行完毕,内核将安排idle线程执行。
如果用户需要干预系统运行,并通过按钮、键盘等输入设备提出新的事务请求,内核将通过中断机制接收并进行相关处理,若事务过于复杂造成中断服务程序ISR难以处理,ISR的最后部分将创建新的线程来接收用户的请求,再acoral_sched()安排处理。
如果多个线程并发执行过程中因资源暂时无法获取、异常等内部原因造成当前线程无法继续执行,内核也将通过中断来挂起当前线程,再由acoral_sched()安排其它线程执行。
以上便是内核提供的事务处理机制,事务处理机制可分为事件触发机制(Event-triggered)和时间触发机制(Time-triggered)。
- 通过中断响应、处理来触发内核重调度的机制属于事件触发机制。
- 通过时钟维护、管理来触发内核重调度的机制属于时间触发机制。
中断管理
中断是一种硬件机制,其优先级高于系统中所有任务,它的产生将会中断某个线程或者正在执行程序的运行。
中断发生及响应
硬件抽象层HAL响应。中断请求IRQ被中断控制器汇集成中断向量(Interrupt Vector),每个中断向量对应一个中断服务程序ISR,中断向量存放了ISRs的入口地址或ISRs的第一条指令。
系统中通常包含多个中断向量,存放这些中断向量对应的ISRs入口地址的内存区域被称为中断向量表。
在Interl80x86处理器中,中断向量表包含256个入口地址,每个中断向量需要四字节存放ISR的首地址。
ARM处理器的中断请求IRQ将被中断控制器汇集到异常向量(每个异常向量四个字节),该异常向量位于ARM异常向量表的第7条记录,由于ARM异常向量表存放在内存0x00000000开始处,中断请求IRQ将被中断控制器汇集到0x00000018(第7条记录的起始地址)的内存地址,也就是说,当IRQ发生时,PC将通过硬件机制跳转到0x00000018开始执行。
根据ARM的中断机制,0x00到0x1C存放的是8条跳转指令。
当系统复位时,pc跳转到0x00(该地方是一条四字节的跳转指令),此时pc的值将变成“VECTOR_TABLE+0”。
VECTOR_TABLE是ARM处理器中断向量表的起始地址(用户可自己定义)。
当处理器发生IRQ时,pc将跳转到0x18,该地方是另一四字节的跳转指令LDR pc,VECTORE_TABLE+0x18,此时pc的值变为VECTORE_TABLE+0x18。
VECTORE_TABLE:
...
.long HAL_INTR_ENTRY
...
VECTORE_TABLE+0x18存放的是HAL_INTR_ENTRY,它是指向所有IRQ的一个公共入口。当各种IRQ发生时,都将汇拢到HAL_INTR_ENTRY,进行与硬件相关的处理,也称为HAL层中断处理。
HAL_INTR_ENTRY:
stmfd sp!, {r0-r12,lr} @保护通用寄存器及PC
mrs r1, spsr
stmfd sp!, {r1} @保护spsr,以支持中断嵌套
msr cpsr_c, #SVC_MODE|NOIRQ @进入SVC_MODE,以便允许中断嵌套
stmfd sp!, {lr} @保存SVc模式的专用寄存器lr
ldr r0, =INTOFFSET @读取中断向量号
ldr r0, [r0] @将INTOFFSET的值赋给r0
mov lr, pc @求得lr的值
ldr pc, =hal_all_entry @跳转到用C语言编写的中断公共入口函数hal_all_entry(),将RO作为参数传给hal_all_entry()
ldmfd sp!, {lr} @恢复svc模式下的lr,
msr cpsr_c,#IRQ_MODE|NOINT @更新cpsr,进入IRQ模式并禁止中断
ldmfd sp!,{r0} @spsr->r0
msr spsr_cxsf,r0 @恢复spsr
ldmfd sp!,{r0-r12,lr}
subs pc,lr,#4 @此后,中断被重新打开
首先将寄存器R0~R12的值保存在中断模式下堆栈指针指向的内存地址,然后保存中断返回地址LR(LR存放的是中断发生时,被中断程序的下一条指令)到堆栈中,然后保存SPSR,以支持中断嵌套。上述压栈的顺序为:R0,R1,…,R12,LR,CPSR。
将ARM处理器切换到SVC模式,并且保存SVC模式连接寄存器LR到堆栈中,到此完成了中断现场保护的工作。
读取中断控制器中INTOFFSET寄存器的值,对INTOFFSET的读写操作与其它内存地址一致。当某个IRQ发生时,INTOFFSET会为该中断源分配一个整数(如时钟中断,对应的整数是0),这个整数也唯一地对应于该中断源。
这样可通过读取该寄存器分辨不同的中断源,进而执行不同的ISR。
/*
中断入口,对中断复用进行展开
vector 中断向量号,来自INTOFFSET寄存器
*/
void hal_all_entry(acoral_vector vector){
unsigned long eint;
unsigned long irq=4;
if(vector==4||vector==5){
eint=rEINTPND;
for(;irq<24;irq++){
if(eint & (1<<irq)){
acoral_intr_entry(irq);
return;
}
}
}
if(vector>5){
vector+=18;
}
if(vector==4)
acoral_prints("DErr\n");
acoral_intr_entry(vector);
}
HAL层处理完成后,通过hal_all_entry进入内核层响应,hal_all_entry是与ARMS3C2440中断控制器密切相关的设置,经过相关处理后,会调用真正的中断公共服务入口函数acoral_intr_entry(),开始内核层的中断处理。
ARMS3C2440的中断机制中,第4号中断到第7号中断复用了中断号4,第8号中断到第23号中断复用了中断号5。
因此,当从INTOFFSET中读取的值为4或者5时,要根据寄存器EINTPND的值进一步区分中断源。
四号到七号之间包括四个中断,八号到二十三号之间包括十六个中断,再去除4号和5号本身,共有18个中断。
当从INTOFFSET中读取的值R0大于5时,实际对应在内核层中的中断号应该是R0+18。
/*中断公共服务入口函数*/
void acoral_intr_entry(acoral_vector vector){
acoral_vector index;
HAL_TRANSLATE_VECTOR(vector,index);
acoral_intr_nesting_inc();
//根据转换后的中断号index,从中断向量表中找到相应的中断向量,如果中断的类型属于专家模式,则根据内核中断标号找到对应的中断结构体,然后调用这个中断的ISR,并且通过acoral_intr_disable()除能中断关中断。
if(intr_table[index].type == ACORAL_EXPERT_INTR){
intr_table[index].isr(vector);
acoral_intr_disable();
}else{
if(intr_table[vector].enter!=NULL){
intr_table[vector].enter(vector);
/*开中断*/
acoral_intr_enable();// 在进入hal_all_entry()之前是要临时关中断的,但是中断处理程序往往较长,如果整个过程都关中断,必然影响中断的性能,因此为了保证中断性能和实时性,此时要开启中断。
intr_table[vector].isr(vector);//ISR是在中断初始化时进行注册和绑定的
/*关中断*/
acoral_intr_disable();
if(intr_table[vector].exit!=NULL){
intr_table[vector].exit(vector);
}
}
}
acoral_intr_nesting_dec();
acoral_intr_exit();
}
由于ARMS3C2440的中断复用机制,内核层的中断号比HAL层的中断号多。
另一方面,内核层的中断号也可少于HAL层的中断号,因为有些特殊中断或异常时不需要交给内核层处理,所以可能造成内核层的中断数减少,故HAL层中断与内核层中断的对应关系不一样。
因此,需要一个转换,将HAL层的中断号转换为内核层的中断号,该转换是通过HAL_TRANSLATE_VECTOR()实现的。
还要将中断嵌套数加1,因为aCoral只有在最后一层中断退出时才执行调度函数,因此需要一个变量来记录中断嵌套数。
aCoral通过acoral_intr_ctr_t表示内核层的中断向量表。
typedef struct{
acoral_u32 index; // 内核层中断号
acoral_u8 state; //中断状态
acoral_u8 type;
void (*isr)(acoral_vector); //ISR
void (*enter)(acoral_vector); //中断服务程序执行之前执行的操作,aCoral中为hal_intr_ack函数。
void (*exit)(acoral_vector); //中断服务程序执行后完成的操作
void (*mask)(acoral_vector); //除能中断
void (*unmask)(acoral_vector); //使能中断
}acoral_intr_ctr_t;
中断类型
#define ACORAL_COMM_INTR 1 //普通中断,会自动调用acoral_intr_ctr_t中的enter成员清楚中断,用户只需要关心isr即可。
#define ACORAL_EXPERT_INTR 2 //专家中断,不会调用enter成员,需要在isr中手动清除中断,如DM9000网卡。
#define ACORAL_RT_INTR 3 //实时中断
acoral_intr_ctr_t表示内核层的中断向量表,显然,它比HAL层的异常向量表丰富,如中断进入时处理(清除中断Pending标志位),中断退出时处理(置中断结束位)、中断屏蔽、中断开启等。
中断状态是内核层状态,其实中断在HAL层也有状态,如挂起、正在处理、处理完毕等,内核层的state除了包含这状态外,还可以增加一些状态以满足特殊需求。这样设计的目的是为了增加中断的灵活性和可扩展性。
acoral的三种中断模式
- 实时模式:中断处理程序直接调用,不经过HAL_INTR_ENTRY->acoral_intr_entry->中断处理程序。这种方式明显减少了中断响应时间,增加了中断的实时性。
- 专家模式:这种模式需要用户在中断处理程序中自己处理中断响应相关的操作,如清中断位等操作,这种模式主要应用于特殊中断,如aCoral的网卡中断,就需要在中断里关闭中断,这种方法没有办法调用统一中断模型。
- 普通模式:中断处理程序往往需要使用汇编,并对处理相关寄存器进行操作。aCoral的普通模式的中断处理程序不需要任何中断硬件相关的操作,只需要编写中断要做什么事情即可。
acoral_intr_enable()、acoral_intr_disable()是与硬件相关的操作。
HAL_INTR_ENABLE:
mrs r0,cpsr
bic r0,r0,#NOINT
msr cpsr_cxsf,r0
mov pc,lr
HAL_INTR_DISABLE:
mrs r0,cpsr
mov r1,r0
orr r1,r1,#NOINT
msr cpsr_cxsf,r1
mov pc ,lr
/*中断退出函数*/
void acoral_intr_exit()
{
if(!acoral_need_sched){// 中断退出时不需要调度,则直接退出
return;
}
if(acoral_intr_nesting){// 中断如果属于嵌套中断,则直接退出
return;
}
if(acoral_sched_is_lock){// 调度标志未开启,则直接退出
return;
}
if(!acoral_start_sched){
return;
}
// 如果有任务需要调度
HAL_INTR_EXIT_BRIDGE();
}
#define HAL_INTR_EXIT_BRIDGE() hal_intr_exit_bridge_comm()
void hal_intr_exit_bridge_comm(){
HAL_ENTER_CRITICAL();
acoral_real_intr_sched();
HAL_EXIT_CRITICAL();
}
Save ri表示保存ARM S3C2440寄存器的值(保存现场)