⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 移植参考txt版.txt

📁 ucos在ARM7上的移植包括源代码说明文字
💻 TXT
字号:
移植参考
任务切换中的部分代码是和CPU相关的,该部分包括:

OS_ENTER_CRITICAL(其原身为void ARMDisableInt(void))
进入临界区,关闭中断
OS_EXIT_CRITICAL(其原身为void ARMEnableInt(void))
退出临界区,打开中断
OSStartHighRdy;
从系统中切换到第一个任务
OS_TASK_SW;
从任务中切换到任务
OSIntCtxSw;
任务被中断,从中断中切换到任务
OSTaskStkInit;
初始化任务中断,在构造任务时调用。




首先,我们先来申明一些公共的内容:
一、任务系统(除了中断)运行在SYS模式下,该模式没有SPSR
二、所有希望在中断中实现任务调度的处理程序必须使用IRQ中断同时使用OSIntEnter和OSIntExit,这是因为
OSIntExit中执行切换的OSIntCtxSw直接操作IRQ的堆栈从而实现了被中断现场的数据保护(详情见下)。
	系统开始时不反对Fiq中断打开,但是,不可以在其中使用OSIntCtxSw(即不可调用OSIntEnter和OSIntExit),简单的不涉及到任务状态的代码可以使用。
	IRQ中断不支持IRQ嵌套,但支持FIQ的嵌套。
	
	
	
三、ARM的c编译器对子函数调用的汇编处理也是我们值得注意的部分。调用函数一般使用bl xxx,但也有时用b xxx。请看下面的图例,自己去理解。
图一的函数关系
int a;
void fun1(void);
void fun2(void);
main()
{
a++;
fun1();
a++;
}
void fun1(void)
{
a++;
fun2();
a++;
}
void fun2(void)
{
a++;
}


图二的函数关系
int a;
void fun1(void);
void fun2(void);
main()
{
a++;
fun1();
}
void fun1(void)
{
a++;
fun2();
}
void fun2(void)
{
a++;
}

插入,图一、二

区别在于调用函数的语句之后是否还有代码。



四、标准堆栈格式
为任务切换函数所承认的堆栈格式如下:

插入图三

按标准格式调整好的栈内容,都可以被顺利的弹出现场。




其次,我们来对代码进行分析:
一、OSTaskStkInit,代码如下:

OS_STK * OSTaskStkInit (void (*task)(void *pd), 
			void *pdata, 
			OS_STK *ptos, 
			INT16U opt)
{
    unsigned int *stk;
    opt    = opt; 			/*'opt' is not used, prevent warning*/
    stk    = (unsigned int *)ptos;      /* Load stack pointer*/
    /* build a context for the new task */
    *--stk = (unsigned int) task;       /* pc */
    *--stk = (unsigned int) task;       /* lr */

    *--stk = 0;                         /* r12 */
    *--stk = 0;                         /* r11 */
    *--stk = 0;                         /* r10 */
    *--stk = 0;                         /* r9 */
    *--stk = 0;                         /* r8 */
    *--stk = 0;                         /* r7 */
    *--stk = 0;                         /* r6 */
    *--stk = 0;                         /* r5 */
    *--stk = 0;                         /* r4 */
    *--stk = 0;                         /* r3 */
    *--stk = 0;                         /* r2 */
    *--stk = 0;                         /* r1 */
    *--stk = (unsigned int) pdata;      /* r0 */
    *--stk = (0x1f|0x00);		/* cpsr  Sys mode FIQ IRQ Enable*/
    *--stk = (0x1f|0x00);		/* spsr  Sys mode FIQ IRQ Enable */
//这是标准的栈结构
    return ((void *)stk);
}

用于任务初始化时堆栈的建立,为OSTaskCreate()调用;
结构清晰,参见标准栈结构。其来源于:OS_CPU_C.c


二、OSStartHighRdy,代码如下:


	EXPORT	OSStartHighRdy
;功能示例
;SP=OSTCBHighRdy->OSTCBStkPtr
;OSTCBCur=OSTCBHighRdy
;弹出现场
	
OSStartHighRdy
	LDR	r4, addr_OSTCBCur	; Get current task TCB address
	LDR	r5, addr_OSTCBHighRdy	; Get highest priority task TCB address

	LDR	r5, [r5]		; get stack pointer
	LDR	sp, [r5]		; switch to the new stack
	;SP=OSTCBHighRdy->OSTCBStkPtr
	STR	r5, [r4]		; set new current task TCB address
	;OSTCBCur=OSTCBHighRdy

	LDMFD	sp!, {r4}		; YYY
	MSR	CPSR_cxsf, r4		
	LDMFD	sp!, {r4}		; get new state from top of the stack
	MSR	CPSR_cxsf, r4		; CPSR should be SYS32Mode
	LDMFD	sp!, {r0-r12, lr, pc }	; start the new task

用于系统开始时,从系统切换到第一个任务,为OSStart()调用。
其来源于:OS_CPU_a.s



三、OS_ENTER/EXIT_CRITICAL,这是两个宏,定义见代码,注意对压栈方式论证的分析:

下面的代码来源于 OS_CPU.h
#define  OS_CRITICAL_METHOD    0

#if      OS_CRITICAL_METHOD == 0
#define	OS_ENTER_CRITICAL()	ARMDisableInt()	//disable int
#define	OS_EXIT_CRITICAL()	ARMEnableInt()	//enable int
#endif


下面的代码来源于 OS_CPU_a.s

;下面是两个开关中断的函数,其只用来操作CPSR中的I位,
;不要担心进入切换前的任务被屏蔽了中断:主动放弃CPU的
;任务,其CPSR一定是禁止中断的(注意理解),但是其返回
;一定是返回到OSCtxSw之后,“复活”时,自然会有enable来恢
;复中断,凡是中断放弃CPU的任务,CPSR一定是允许中断的。
;不论怎样返回也一定返回到被中断的现场。

;倪磊带来的版本是利用用户的堆栈来盛放CPSR,随后pop出来。
;在这里是不可以的:使用栈方式保存CPSR要保证Enable在切换后
;可以被运行到,在OSSched中(主动放弃CPU)OS_TASK_SW被Disable与Enable
;前后包围,进入OS_TASK_SW也可以从中返回,进出时栈结构是相同的
;这是可以使用的。
;但是,在中断切换的情况下便不同了,在OSIntCtxSw前的Disable
;使用的栈内容不能被保存起来,我们只将任务运行时的现场保护起来,
;不会保留中断标志的压栈信息,因此,这种方式也可使用,但是在没有
;增加效率的前提下反而会将堆栈结构复杂化,不建议使用。

	EXPORT	ARMDisableInt
ARMDisableInt
	STMDB	sp!, {r0}	;r0要保存,不可以破坏调用函数的现场
	MRS	r0, CPSR
	ORR	r0, r0, #0x80
	MSR	CPSR_c, r0
	LDMIA	sp!, {r0}
	MOV	pc, lr

	EXPORT	ARMEnableInt
ARMEnableInt
	STMDB	sp!, {r0}
	MRS	r0, CPSR
	BIC r0, r0, #0x80
	MSR	CPSR_c, r0
	LDMIA	sp!, {r0}
	MOV	pc, lr
其来源于:OS_CPU_a.s



四、OS_TASK_SW,见代码:



	EXPORT	OS_TASK_SW
;功能示例
;现场压栈
;OSPrioCur = OSPrioHighRdy
;OSTCBCur->OSTCBStkPtr=SP	保存SP到当前任务控制块
;SP=OSTCBHighRdy->OSTCBStkPtr
;OSTCBCur=OSTCBHighRdy
;弹出现场

OS_TASK_SW
	STMFD	sp!, {lr}		; save pc
	STMFD	sp!, {lr}		; save lr
	;分析:OS_TASK_SW是个函数,在任务间接的调用它时会在最后失去CPU,再次
	;获得CPU时,其恰好在OS_TASK_SW调用完毕后的那个位置,因此,lr被放入pc
	;在栈中的位置;此外,lr放入lr在栈中的位置也是无关紧要的,因为,子函数
	;返回时的lr是无意义的。
	STMFD	sp!, {r0-r12}	; save register file and ret address
	MRS	r4, CPSR
	STMFD	sp!, {r4}		; save current PSR
	;MRS	r4, SPSR		; YYY+ 这里屏蔽掉是因为任务运行的
					;模式是SYS,没有SPSR,栈中的CPSR与SPSR相同
	STMFD	sp!, {r4}		; YYY+ save SPSR


	LDR	r4, addr_OSPrioCur
	LDR	r5, addr_OSPrioHighRdy
	LDRB	r6, [r5]
	STRB	r6, [r4]
	; OSPrioCur = OSPrioHighRdy
		
	; Get current task TCB address
	LDR	r4, addr_OSTCBCur
	LDR	r5, [r4]
	STR	sp, [r5]		; store sp in preempted tasks's TCB
	;OSTCBCur->OSTCBStkPtr=SP	保存SP到当前任务控制块
	
	; Get highest priority task TCB address
	LDR	r6, addr_OSTCBHighRdy
	LDR	r6, [r6]
	LDR	sp, [r6]		; get new task's stack pointer
	;SP=OSTCBHighRdy->OSTCBStkPtr	更新SP为即将运行的任务
	
	STR	r6, [r4]		; set new current task TCB address
	; OSTCBCur = OSTCBHighRdy
	
	LDMFD	sp!, {r4}		; YYY+
	;MSR	SPSR_cxsf, r4		; YYY+运行在sys模式下,无需SPSR的更新
	LDMFD	sp!, {r4}		; YYY+
	MSR	CPSR_cxsf, r4		; YYY+
	LDMFD	sp!, {r0-r12, lr, pc}	; YYY+
	;现场恢复完毕
其来源于:OS_CPU_a.s

这个函数是任务主动放弃CPU时最终调用的的函数,其为OSSched()调用,前后为OS_ENTER/EXIT_CRITICAL所包围。因此进入OS_TASK_SW时,IRQ是被屏蔽的,保存到堆栈中的CPSR I位也是为1,但是放心,OS_TASK_SW保存的现场恰好是从OSSched()中OS_TASK_SW返回的地方,任务复活时紧接着是后面解除屏蔽的代码。



四、OSIntCtxSw,
首先,见调用示意图:

插入图四

可见,中断中的调用是较复杂,嵌套较深,这也是我们在OSIntCtxSw中从底向上发掘数据的原因。

其次,见代码,根据上图的IRQ栈结构,可以分析如何将其中的有用数据搬运到被中断任务的堆栈中:

        IMPORT  IRQ_STACK
	EXPORT  OSIntCtxSw
OSIntCtxSw
	;该函数运行在IRQ状态,他返回SYS模式,并将压入SP_IRQ的
	;现场数据搬运到SP_SYS中构造出标准栈,保存该栈指针;再
	;切换到其它的任务中(后半部分与OSCtxSw相同)
	;
 	;该函数是不返回的,IRQ处理函数在调用到该函数时,不知道
 	;已经压了多少的栈,而我们不需要从栈顶向下挖掘出当时压
 	;入的现场,我们只要知道进入IRQ状态时的SP是多少,便可以
 	;由栈底来发掘出r0-r12和pc+4(其实就是lr_IRQ),见图
 	;这种方法建立的前提是:在IRQ的中断处理代码之前对其SP赋值
 	;这样我们便不可以实现IRQ的嵌套了。
 	;IRQ处理代码的最前有一句LDR	SP,=IRQ_STACK(见init.s)
	LDR	r0,=IRQ_STACK	;我们要找到这个地方
	sub	SP,r0,#14*4	;压入的内容从r0-r12 pc+4(lr_irq)14个
				;字,现在SP指向最低位置的r0,可见 图四
	mrs	r0,SPSR		;在IRQ状态下读SPSR其实就是被中断现场的CPSR
	stmfd	sp!,{r0}	;现在,irq栈中的最低位置为CPSR
	
	orr	r0,r0,#0x80	;屏蔽spsr中的I位
	msr	CPSR_cxsf,r0	;返回现场状态,注意,这时lr_sys还没变
				;要不是sys模式,SPSR也是没变的
	
	ldr	r0,=IRQ_STACK
	ldr	r1,[r0,#-1*4]!	;现在r0指向PC+4,且取出放入r1
	sub	r1,r1,#4	;现在r1中是返回的PC
	
	stmfd	sp!,{r1}	;压入pc	我们现在开始构造标准的任务栈
	stmfd	sp!,{lr}	;压入lr		
	
	ldmdb	r0!,{r4-r11}	;r0指向PC+4,先减后读取出8个,参见 移植参考中的栈示意
	stmfd	sp!,{r4-r11}
	ldmdb	r0!,{r4-r9}	
	stmfd	sp!,{r4-r9}	;两次批量搬运从irq栈到sys栈转移14个数据,有低到高cpsr,r0-r12
	
	;mrs	spsr,r4		;若任务运行模式不为sys则,spsr需要保存,否则,依然压入CPSR
	stmfd	sp!,{r4}		;r4中为CPSR
	;到此为止,我们的标准栈结构已经建立起来,所有需要的现场已被我们压到sp_sys
	
	;后面的内容和OSCtxSw的后半部分相同,依次为
;OSPrioCur = OSPrioHighRdy
;OSTCBCur->OSTCBStkPtr=SP	保存SP到当前任务控制块
;SP=OSTCBHighRdy->OSTCBStkPtr
;OSTCBCur=OSTCBHighRdy
;弹出现场
		
	LDR	r4, addr_OSPrioCur
	LDR	r5, addr_OSPrioHighRdy
	LDRB	r6, [r5]
	STRB	r6, [r4]
	; OSPrioCur = OSPrioHighRdy
		
	; Get current task TCB address
	LDR	r4, addr_OSTCBCur
	LDR	r5, [r4]
	STR	sp, [r5]		; store sp in preempted tasks's TCB
	;OSTCBCur->OSTCBStkPtr=SP	保存SP到当前任务控制块
	
	; Get highest priority task TCB address
	LDR	r6, addr_OSTCBHighRdy
	LDR	r6, [r6]
	LDR	sp, [r6]		; get new task's stack pointer
	;SP=OSTCBHighRdy->OSTCBStkPtr	更新SP为即将运行的任务
	
	STR	r6, [r4]		; set new current task TCB address
	; OSTCBCur = OSTCBHighRdy
	
	LDMFD	sp!, {r4}		; YYY+
	;MSR	SPSR_cxsf, r4		; YYY+运行在sys模式下,无需SPSR的更新
	LDMFD	sp!, {r4}		; YYY+
	MSR	CPSR_cxsf, r4		; YYY+
	LDMFD	sp!, {r0-r12, lr, pc}	; YYY+
	;现场恢复完毕
其来源于:OS_CPU_a.s


小结:OSIntCtxSw是一个较为复杂的函数,作为任务的中断终结者,他面临的是IRQ的堆栈,有用的现场数据在IRQ进入时便被压到IRQ栈的最低层,CSPR、SP、PC、LR全部被换。因此,我们只能先回到SYS的任务模式,而后由IRQ的栈中从底而上取出数据,按标准格式压入任务堆栈。其后的事情与OS_CTX_SW相同。
此外,如果在OSIntExit()的判断中,没有新任务需要切换运行,OSIntCtxSw便不会被运行,其会层层返回正常的回到被中断的任务。
OSIntCtxSw对堆栈的处理方式使我们不可以实现IRQ嵌套,但是,OSIntCtxSw是健壮的——不论前面嵌套了多少的函数,堆栈被压入多少的内容,我们都可以用OSIntCtxSw直接的回到任务。




未实现的技术和对未来的想法:
周立功的ARM书上有关于ucOS系统和用户代码分开编译加载的内容,它是将系统单独编译,将系统调用的函数地址组合成一张表格放到确定的地方,烧写到flash中;用户程序加载后,到这张表格中调用系统服务。
我有一种更好的想法:ARM有SWI软中断的功能,完全可以将所有的系统调用加载在SVC状态下的SWI处理函数中。这样,系统烧写在flash中,其后加载用户任务,直接执行软中断即可。


丁一
于ucOS移植成功后总结
ws_dy@sina.com
2004-4-7

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -