|
我以前是这样实现的:
用自己的外壳程序接管每一个中断,将进中断后保存的现场和系统dispatch所要保存的现场设计成基本一样。进中断后置上一个标志,中断服务程序可以进行wakeup调用,在wakeup程序中,如果唤醒了更高优先级的任务,需要检查当前是否在ISR中,如果不是,则转入正常调度,如果有“中断正在执行”标志,则不立即进行调度,而是置上“延迟调度”的标志,在中断返回前的“外壳程序”中检查“延迟调度”标志决定是返回原来被中断的任务还是进行重新调度。
用伪码说明如下。
ISR_Shell: 保存现场; Int_Flag=1; 执行用户ISR; if (Dispatch_Flag) goto Dispatch; Int_Flag=0; 恢复现场; 中断返回;
Dispatch: if (!Int_Flag) 保存现场; Int_Flag=0; 找出最高优先级任务; 切换堆栈指针; Dispatch_Flag=0; 恢复现场; 返回到最高优先级任务;
Wakeup: 将待唤醒的任务置入Ready队列; if (唤醒的是最高优先级任务) { if (Int_Flag) Dispatch_Flag=1; else goto Dispatch; }
至于要实现函数可重入,只要将所有现场都保存就可以了(因为C语言函数天然可重入)。比如老版本的ethernut用嵌入汇编实现的: void NutThreadSwitch(void) { /* * Save all CPU registers. */ asm volatile ("push r0" "\n\t" "push r1" "\n\t" "push r2" "\n\t" "push r3" "\n\t" "push r4" "\n\t" "push r5" "\n\t" "push r6" "\n\t" "push r7" "\n\t" "push r8" "\n\t" "push r9" "\n\t" "push r10" "\n\t" "push r11" "\n\t" "push r12" "\n\t" "push r13" "\n\t" "push r14" "\n\t" "push r15" "\n\t" "push r16" "\n\t" "push r17" "\n\t" "push r18" "\n\t" "push r19" "\n\t" "push r20" "\n\t" "push r21" "\n\t" "push r22" "\n\t" "push r23" "\n\t" "push r24" "\n\t" "push r25" "\n\t" "push r26" "\n\t" "push r27" "\n\t" "push r28" "\n\t" "push r29" "\n\t" "push r30" "\n\t" "push r31" "\n\t" "in %A0, %1" "\n\t" "in %B0, %2" "\n\t":"=r" (runningThread->td_sp) :"I" _SFR_IO_ADDR(SPL), "I" _SFR_IO_ADDR(SPH) );
/* * This defines a global label, which may be called * as an entry point into this function. */ asm volatile (".global thread_start\n" "thread_start:\n\t"::);
/* * Reload CPU registers from the thread in front * of the run queue. */ runningThread = runQueue; runningThread->td_state = TDS_RUNNING; asm volatile ("out %1, %A0" "\n\t" "out %2, %B0" "\n\t" "pop r31" "\n\t" "pop r30" "\n\t" "pop r29" "\n\t" "pop r28" "\n\t" "pop r27" "\n\t" "pop r26" "\n\t" "pop r25" "\n\t" "pop r24" "\n\t" "pop r23" "\n\t" "pop r22" "\n\t" "pop r21" "\n\t" "pop r20" "\n\t" "pop r19" "\n\t" "pop r18" "\n\t" "pop r17" "\n\t" "pop r16" "\n\t" "pop r15" "\n\t" "pop r14" "\n\t" "pop r13" "\n\t" "pop r12" "\n\t" "pop r11" "\n\t" "pop r10" "\n\t" "pop r9" "\n\t" "pop r8" "\n\t" "pop r7" "\n\t" "pop r6" "\n\t" "pop r5" "\n\t" "pop r4" "\n\t" "pop r3" "\n\t" "pop r2" "\n\t" "pop r1" "\n\t" "pop r0" "\n\t"::"r" (runningThread->td_sp), "I" _SFR_IO_ADDR(SPL), "I" _SFR_IO_ADDR(SPH) ); }
至于比较协作式和抢占式两种类型的RTOS,其实是各有优缺点的。很多人只看到了抢占式的实时性强的优点,却没有看到它有3个缺点:1、任务间公用的数据结构必需考虑完整性问题;2、增加的任务切换频率实际上加大了内务开销,降低了总体的CPU利用率;3、导致内核实现困难,代码增多。而协作式的RTOS,任务间公用的数据结构很容易保证其完整性(因为任务切换的位置是已知的),至于其实时性差的缺点,其实是很容易克服的,即,只要在执行时间长的任务中间人为增加一些taskdelay(0)进行系统调度检查即可。实际上,对大多数的任务而言,执行时间不会超过几毫秒,这一点延时完全能够满足要求的(更急迫的工作当然可以用中断或者硬件来做)。
|