首页 简易0S设计

简易0S设计

举报
开通vip

简易0S设计 多任务抢占式调度器 1 本文档以 ARM9(三星 2410/2440)为平台,介绍一个多任务抢占式调度器------抢占式 任务调度,提供延时,挂起,恢复任务操作。最精简化,没有加入信号量邮箱等同步通信机制。 只实现一个基本任务调度器的功能。 虽然不能称为操作系统,但已体现了小型嵌入式操作系统的精髓。OS代码不到 1.5K, 核心函数只有几个,思路简单明了。比起 UCOS,更适合用作多任务系统原理的学习入门。 对初学者来说,看 UCOS的源代码很容易迷糊。 回想初学嵌入式多任...

简易0S设计
多任务抢占式调度器 1 本文档以 ARM9(三星 2410/2440)为平台,介绍一个多任务抢占式调度器------抢占式 任务调度,提供延时,挂起,恢复任务操作。最精简化,没有加入信号量邮箱等同步通信机制。 只实现一个基本任务调度器的功能。 虽然不能称为操作系统,但已体现了小型嵌入式操作系统的精髓。OS代码不到 1.5K, 核心函数只有几个,思路简单明了。比起 UCOS,更适合用作多任务系统原理的学习入门。 对初学者来说,看 UCOS的源代码很容易迷糊。 回想初学嵌入式多任务系统时,什么都不懂,Jean J.Labrosse 的经典之作《嵌入式实 时操作系统 uc/osII》看得我一头雾水。事实上,使我对多任务的原理印象最深的是网上的 一篇文章----《建立一个属于自己的 AVR的 RTOS》。 学习就应该这样,循序渐进。把一步步把简单的东西弄懂了,便没有复杂的了,所谓 水到渠成。 这篇文章是面对初学者的,把很多问 快递公司问题件快递公司问题件货款处理关于圆的周长面积重点题型关于解方程组的题及答案关于南海问题 简化了。希望对刚接触嵌入式多任务系统的兄 弟有所帮助。 必定存在不少 bug,欢迎指正。 多任务抢占式调度器 2 简易多任务 OS设计 -------ARM9上运行的简单多任务调度器 By lisuwei http://group.ednchina.com/999/20520.aspx 这里提供一个简易的多任务抢占任务调度器供大家学习。虽然太简单不实用,但对理 解多任务抢占式调度的原理是很有益处的。 先直观地看一下多任务系统中的代码与单任务程序(前后台系统)的区别。 Main.c int Main(void) { TargetInit(); //初始化目标板 OSInit(); //初始化操作系统 OSTaskCreate(Task0,&StackTask0[StackSizeTask0 - 1],PrioTask0); // 创建一个任务 Uart_Printf("Ready to start OS\n"); OSStart(); //运行操作系统 return 0; //程序不会运行至此 } void Task0(void) { TargetStart(); //设置中断向量,启动操作系统的硬件定时器中断 Uart_Printf("Start OS\n"); // 创建其他任务 OSTaskCreate(Task1,&StackTask1[StackSizeTask1 - 1],PrioTask1); OSTaskCreate(Task2,&StackTask2[StackSizeTask2 - 1],PrioTask2); OSTaskCreate(Task3,&StackTask3[StackSizeTask2 - 1],PrioTask3); while(1) { Uart_Printf("Task0\n"); OSTimeDly(100); //1秒运行一次 } } void Task1(void) { while(1) { Uart_Printf("Task1\n"); OSTimeDly(300); //3秒运行一次 } } 多任务抢占式调度器 3 void Task2(void) { while(1) { Uart_Printf("Task2\n"); OSTaskSuspend(PrioTask2); // 使自己进入挂起状态 } } void Task3(void) { while(1) { Uart_Printf("Resume Task2\n"); OSTaskResume(PrioTask2); // 恢复任务 2 OSTimeDly(800); } } 程序中创建了四个任务,任务 0每 1秒运行一次,任务 1每 3秒运行一次,任务 2运 行一次即把自己挂起,任务 3每 8秒运行一次并把任务 2恢复。 在终端的运行结果如下图: 多任务抢占式调度器 4 什么是多任务系统? 就像我们用电脑时可以同时听歌,上网,编辑文档等。在多任务系统中,可以同时 执行多个并行任务,各个任务之间互相独立。通过操作系统执行任务调度而实现宏观上 的“并发运行”。从宏观上不同的任务并发运行,好像每个任务都有自己的 CPU一样。 其实在单一 CPU的情况下,是不存在真正的多任务机制的,存在的只有不同的任务 轮流使用 CPU,所以本质上还是单任务的。但由于 CPU执行速度非常快,加上任务切换 十分频繁并且切换的很快,所以我们感觉好像有很多任务同时在运行一样。这就是所谓 的多任务机制。 多任务的最大好处是充分利用硬件资源,如在单任务时(大循环结构,如大部分 51 程序)遇到 delay函数时,CPU在空转;而在多任务系统,遇到 delay或需等待资源时系 统会自动运行下一个任务,等条件满足再回来运行先前的任务,这样就充分利用了 CPU, 提高了效率。 任务有下面的特性: l 动态性。任务并不是随时都可以运行的,而一个已经运行的任务并不能保证 一直占有 CPU直到运行完。一般有就绪态,运行态,挂起态等。 运行态。一个运行态的任务是一个正在使用 CPU的任务。任何时刻有且只有 一个运行着的任务。 就绪态。一个就绪态任务是可运行的,等待占有 CPU的任务释放 CPU。 挂起态。某些条件不满足而挂起不能运行的状态。 任务三种状态转换图 下面我们来分析代码是如何实现状态转换的。 RTOS.h INT32U OSRdyTbl; /* 就绪任务表 */ 上面定义一个 32 位变量,每一位代表一个任务,0 表示挂起状态,1 表示就绪状 态。它 记录 混凝土 养护记录下载土方回填监理旁站记录免费下载集备记录下载集备记录下载集备记录下载 了各任务的就绪与否状态,称它为就绪表。OSRdyTbl定义为 32位变 量,对应 32个任务。当然,定义为 64位的话,便最多能支持 64个任务。 多任务抢占式调度器 5 这样,可以定义两个宏,实现把任务的状态变为就绪或挂起态。 RTOS.h /* 在就绪表中登记就绪任务 */ #define OSSetPrioRdy(prio) \ { \ OSRdyTbl |= 0x01< 0 ) /* 当延时有效 */ { OS_ENTER_CRITICAL(); OSDelPrioRdy(OSPrioCur); /* 把任务从就绪表中删去 */ TCB[OSPrioCur].OSTCBDly = ticks; /* 设置任务延时节拍数 */ OS_EXIT_CRITICAL(); OSSched(); /* 重新调度 */ } } 多任务抢占式调度器 8 在 T0的中断服务函数中,依次对各个延时任务的延时节拍数减 1。若发现某个任务 的延时节拍数变为 0,则把它从挂起态置为就绪态。 RTOS.c void TickInterrupt(void) { static INT8U i; OSTime ++; //Uart_SendByte('I'); for(i = 0; i < OS_TASKS; i++) /* 刷新各任务时钟 */ { if(TCB[i].OSTCBDly ) { TCB[i].OSTCBDly --; if(TCB[i].OSTCBDly == 0) /* 当任务时钟到时,必须是由定时 器减时的才行*/ { OSSetPrioRdy(i); /* 使任务可以重新运行 */ } } } rSRCPND |= BIT_TIMER0; rINTPND |= BIT_TIMER0; } 系统自身创建了一个空闲任务,并设它为最低优先级,当系统没有任何任务就绪时, 则运行这个任务,让 CPU“有事可干”。用户程序可以在这个任务中加入一些“无关紧要” 的功能,如统计 CPU使用率等。 RTOS.c void IdleTask(void) { IdleCount = 0; while(1) { IdleCount++; //Uart_Printf("IdleCount %d\n",IdleCount); } } TIPS: 不要在空闲任务中运行有可能使任务挂起的函数。空闲任务应该一直处于就绪状态。 多任务抢占式调度器 9 如何实现多任务? 只有一个 CPU,如何在同一时间实现多个独立程序的运行?要实现多任务,条件是 每个任务互相独立。人如何才能独立,有自己的私有财产。任务也一样,如果一个任务 有自己的 CPU,堆栈,程序代码,数据存储区,那这个任务就是一个独立的任务。 下面我们来看看是任务是如何“独立”的。 首先是程序代码,每个任务的程序代码与函数一样,与 51的裸奔程序一样,每个任 务都是一个大循环。 void task ( ) { //initialize while(1) { //your code } } TIPS: 如果一个任务正在运行某个公共函数时(如 Printf),被另一个高优先级的任务抢占, 那么当这个高优先级的任务也调用同一个公共函数时,极有可能破坏原任务的数据。 因为两个任务可能共用一套数据。为了防止这种情况发生,常采用两种措施:可重入设 计和互斥调用。 可重入函数中所有的变量均为局部变量,局部变量在调用时临时分配空间,所以不 同的任务在不同的时刻调用该函数时,它们的同一个局部变量所分配的存储空间并不 相同(任务私有栈中),互不干扰。另外,如果可重入函数调用了其他函数,则这些被 调用的函数也必须是可重入函数。 互斥调用稍后 说明 关于失联党员情况说明岗位说明总经理岗位说明书会计岗位说明书行政主管岗位说明书 。 然后是数据存储区,由于全局变量是系统共用的,各个任务共享,不是任务私有, 所以这里的数据存储区是指任务的私有变量,如何变成私有?局部变量也。编译器是把局 部变量保存在栈里的,所以好办,只要任务有个私有的栈就行。 多任务抢占式调度器 10 TIPS: 临界资源是一次仅允许一个任务使用的共享资源。每个任务中访问临界资源的那段程 序称为临界区。 在多任务系统中,为保障数据的可靠性和完整性,共享资源要互斥(独占)访问,所 以全局变量(只读的除外)不能同时有多个任务访问,即一个任务访问的时候不能被其他 任务打断。共享资源是一种临界资源。 实现互斥(独占)访问的 方法 快递客服问题件处理详细方法山木方法pdf计算方法pdf华与华方法下载八字理论方法下载 有关中断,关调度,互斥信号量,计数信号量等。 定义了如下处理临界资源访问的代码, 主要用于在进入临界区之前关闭中断,在退出 临界区后恢复原来的中断状态。 RTOS.h INT32U cpu_sr; /* 进入临界代码区时保存 CPU状态 */ /* 进入临界资源代码区 */ #define OS_ENTER_CRITICAL() (cpu_sr = OSCPUSaveSR()) /* 关总中断 */ /* 退出临界代码区 */ #define OS_EXIT_CRITICAL() (OSCPURestoreSR(cpu_sr)) /* 恢复原来中断状态*/ RTOS_ASM.S OSCPUSaveSR mrs r0,CPSR ; 保存当前中断状态,参阅汇编子程序调用 ATPCS规则, ; r0保存传递的参数 orr r1,r0,#NOINT ; 关闭所有中断 msr CPSR_c,r1 mov pc,lr OSCPURestoreSR msr CPSR_c,r0 ; 恢复原来的中断状态 mov pc,lr 为了不影响程序当前的中断状态,先当前的 CPSR保存起来,退出临界区后再恢复。 注意 OS_ENTER_CRITICAL()和 OS_EXIT_CRITICAL()要成对使用。 示例: OS_ENTER_CRITICAL(); Printf(…);//临界代码 OS_EXIT_CRITICAL(); 这样,在临界区内,只要任务不主动放弃 CPU使用权,别的任务就没有占用 CPU的 机会,相当与独占 CPU了。临界区的代码要尽量短,因为它会使系统响应性能降低。 多任务抢占式调度器 11 私有栈的作用是存放局部变量,函数的参数,它是一个线性的空间,所以可以 申请 关于撤销行政处分的申请关于工程延期监理费的申请报告关于减免管理费的申请关于减租申请书的范文关于解除警告处分的申请 一 个静态数组,把栈顶指针 SP指向栈的数组的首元素(递增栈)或最后一个元素(递减栈)。 即可打造一个人工的栈出来。 Main.c ///******************任务堆栈大小定义***********/// #define StackSizeTask0 512 ///******************建立任务堆栈***************/// INT32U StackTask0[StackSizeTask0]; TIPS: 若在任务中使用 Printf函数,栈要设得大些,否则栈越界溢出,会有意想不到的问题。 每个任务还要有记录自己栈顶指针的变量,保存在任务控制块(TCB)中。 什么是任务控制块?系统中的每个任务具有一个任务控制块,任务控制块记录任务执 行的环境,这里的任务控制块比较简单,只包含了任务的堆栈指针和任务延时节拍数。 RTOS.h struct TaskCtrBlock /* 任务控制块数据结构 */ { INT32U OSTCBStkPtr; /* 保存任务的堆栈顶 */ INT32U OSTCBDly; /* 任务延时时钟 */ }; struct TaskCtrBlock TCB[OS_TASKS + 1]; /* 定义任务控制块 */ 任务控制块是任务的身份证。它把任务的程序与数据联系起来,找到它就可以得到任 务的所有资源。 void task ( ) { //initialize while(1) { //your code } } 运行地址 PC 程序代码、私有堆栈、任务控制块是任务的三要件 OSTCBStkPtr OSTCBDly 多任务抢占式调度器 12 这里把 OSTCBStkPtr 放在结构体的最前面是有原因的,目的是使得在汇编中访问这 个变量比较容易。因为结构体的地址就是它的首元素的地址,要在汇编中访问 OSTCBStkPtr 这个变量,只需取得结构体的地址即可。 在 C语言中不能直接实现如 TCB. TCB[OSPrioCur].OSTCBStkPtr = SP的功能,只 能用汇编语言完成。 定义如下指向结构体的指针变量: RTOS.h struct TaskCtrBlock *p_OSTCBCur ; /* 指向当前任务控制块的指针 */ 在 C语言中,先让 p_OSTCBCur指向当前运行任务的 TCB。 p_OSTCBCur = &TCB[OSPrioCur]; /* 目的是在汇编中引用任务的 TCB地 址取得栈顶指针 */ 这样,运行下面的汇编代码即可把当前任务的堆顶指针取出到 SP中。 RTOS_ASM.S SaveSPToCurTcb ; 保存当前任务的堆顶指针到它的 ldr r4,=p_OSTCBCur ; 取出当前任务的 PCB地址 ldr r5,[r4] str sp,[r5] ; 保存当前任务的堆顶指针到它的 ; TCB(因为 TaskCtrBlock地址亦即 ; OSTCBStkPtr的地址) 多任务抢占式调度器 13 最后来看看任务是如何“拥有”自己的 CPU的。只有一个 CPU,各个任务共享,轮 流使用。如何才能实现?我们先来看看中断的过程,当中断来临时,CPU 把当前程序的运 行地址,寄存器等现场数据保存起来(一般保存在栈里),然后跳到中断服务程序执行。待 执行完毕,再把先前保存的数据装回 CPU又回到原来的程序执行。这样就实现了两个不同 程序的交叉运行。 借鉴这种思想不就能实现多任务了吗!模仿中断的过程就可以实现任务切换运行。 任务切换时,把当前任务的现场数据保存在自己的任务栈里面,再把待运行的任务 的数据从自己的任务栈装载到 CPU中,改变 CPU的 PC,SP,寄存器等。可以说,任务的 切换是任务运行环境的切换。而任务的运行环境保存在任务栈中,也就是说,任务切换的 关键是把任务的私有堆栈指针赋予处理器的堆栈指针 SP。 两个任务的切换过程如下: 当前任务的运行环境保存 待运行的任务的数据从自己的任务栈装载到 CPU 程序调用下面函数进行任务切换: RTOS.c void OSSched (void) { OS_ENTER_CRITICAL(); OSGetHighRdy(); /* 找出就绪表中优先级最高的任务 */ if(OSPrioHighRdy != OSPrioCur) /* 如果不是当前运行的任务,进行任务调度 */ { p_OSTCBCur = &TCB[OSPrioCur]; /* 目的是在汇编中引用任务的 TCB地 址取得栈顶指针 */ p_OSTCBHighRdy = &TCB[OSPrioHighRdy]; OSPrioCur = OSPrioHighRdy; /* 更新 OSPrioCur */ OS_TASK_SW(); /* 调度任务 */ } OS_EXIT_CRITICAL(); } 多任务抢占式调度器 14 RTOS_ASM.S OS_TASK_SW ; 任务级的任务切换 ; stmfd sp!,{lr} ; PC 入栈 stmfd sp!,{r0-r12,lr} ; r0-r12,lr入栈 PUAH_PSR mrs r4,cpsr stmfd sp!,{r4} ; cpsr入栈 SaveSPToCurTcb ; 保存当前任务的堆顶指针到它的 TCB. ; TCB[OSPrioCur].OSTCBStkPtr = SP; ldr r4,=p_OSTCBCur ; 取出当前任务的 PCB地址 ldr r5,[r4] str sp,[r5] ; 保存当前任务的堆顶指针到它的 TCB(因为 ;TaskCtrBlock 地址亦即 OSTCBStkPtr 的地址) GetHigTcbSP ; 取出更高优先级任务的堆顶指针到 SP , ; SP = TCB[OSPrioCur].OSTCBStkPtr ldr r6,=p_OSTCBHighRdy ; 取出更高优先级就绪任务的 PCB的地址 ldr r6,[r6] ldr sp,[r6] ; 取出更高优先级任务的堆顶指针到 SP b POP_ALL ; 根据设定的栈结构顺序出栈 任务切换流程图如下: 保存当前任务的运行环境 待运行的任务的栈顶指针装载到 CPU的 SP 恢复待运行任务的运行环境 多任务抢占式调度器 15 如何建立任务? OSTaskCreate函数在创建任务时调用,如下: OSTaskCreate(Task0,&StackTask0[StackSizeTask0 - 1],PrioTask0); // 创建一个任务 任务未运行或被剥夺运行权后,它的运行环境以一定的结构保存在私有栈中。这个 规定的顺序是十分重要的,以后任务切换时都要按这个顺序入栈出栈。 建立任务时栈结构的初始化如下, RTOS.c void OSTaskCreate(void (*Task)(void),INT32U *p_Stack,INT8U TaskID) { if(TaskID <= OS_TASKS) { *(p_Stack) = (INT32U)Task; /* pc */ *(--p_Stack) = (INT32U)13; /* lr */ *(--p_Stack) = (INT32U)12; /* r12*/ *(--p_Stack) = (INT32U)11; /* r11*/ *(--p_Stack) = (INT32U)10; /* r10*/ *(--p_Stack) = (INT32U)9; /* r9 */ *(--p_Stack) = (INT32U)8; /* r8 */ *(--p_Stack) = (INT32U)7; /* r7 */ *(--p_Stack) = (INT32U)6; /* r6 */ *(--p_Stack) = (INT32U)5; /* r5 */ *(--p_Stack) = (INT32U)4; /* r4 */ *(--p_Stack) = (INT32U)3; /* r3 */ *(--p_Stack) = (INT32U)2; /* r2 */ *(--p_Stack) = (INT32U)1; /* r1 */ *(--p_Stack) = (INT32U)0; /* r0 */ *(--p_Stack) = (INT32U)(SVCMODE|0x0); /* SREG 保存在任务栈中*/ TCB[TaskID].OSTCBStkPtr = (INT32U)p_Stack; /* 将人工堆栈的栈顶,保存到 堆栈的数组中*/ TCB[TaskID].OSTCBDly = 0; /* 初始化任务延时 */ OSSetPrioRdy(TaskID); /* 在任务就绪表中登记 */ } else { Uart_Printf("TaskID Error\n"); while(1); } } 多任务抢占式调度器 16 上面函数的作用就是创建一个任务。它接收三个参数,分别是任务的入口地址,任务 堆栈的首地址和任务的优先级。调用本函数后,系统会根据用户给出的参数初始化任务栈, 并把栈顶指针保存到任务控制块中,在任务就绪表标记该任务为就绪状态。最后返回,这 样一个任务就创建成功了。 可见,初始化后的任务堆栈空间由高到低将依次保存着 PC,LR,R12…R0,CPSR。 当一个任务将要运行时,便通过取得它的堆栈指针(保存在任务控制块中)将这些寄存器 出栈装入 CPU相应的位置即可。 初始状态的堆栈要与任务切换后的堆栈保存结构相一致,因为任务被创建后并不是直 接就获得执行的,而是通过 OSStartHighRdy()或OSSched()函数进行调度分配,满足 执行条件后才能获得执行的。为了使这个调度简单一致,把栈结构统一。 TIPS: 为方便处理,所有任务运行在管理模式下。 系统通过运行 OSStartHighRdy来开始第一个任务。 该函数是在多任务系统启动后,负责从第一个任务的 TCB控制块中获得该任务的堆 栈指针 SP,通过 SP依次将 CPU 现场恢复。这时系统就将控制权交给该任务,直到该任 务被阻塞或者被其它任务抢占 CPU。该函数仅仅在多任务启动时被执行一次,用来启动第 一个运行的任务。 RTOS_ASM.S OSStartHighRdy ldr r4,=p_OSTCBHighRdy ; 取出最高优先级就绪任务的 PCB的地址 ldr r4,[r4] ; 取得任务的栈顶指针(因为 TaskCtrBlock地址 ; 亦即 OSTCBStkPtr的地址) ldr sp,[r4] ; 任务的栈顶指针赋给 SP b POP_ALL ; 根据设定的栈结构顺序出栈 RTOS_ASM.S ; 根据设定的栈结构顺序出栈 POP_ALL ldmfd sp!,{r4} ; psr出栈 msr CPSR_cxsf,r4 ldmfd sp!,{r0-r12,lr,pc} ; r0-r12,lr,pc出栈 多任务抢占式调度器 17 如何实现抢占式调度? 基于任务优先级的抢占式调度,也就是最高优先级的任务一旦处于就绪状态,则立 即抢占正在运行的低优先级任务的处理器资源。 为了保证 CPU总是执行处于就绪条件下优先级最高的任务,每当任务状态改变后, 即判断当前运行的任务是否是就绪任务中优先级最高的,否则进行任务切换。 任务状态会在什么时候发生改变呢?有下面两种情况: l 高优先级的任务因为需要某种资源或延时,主动请求挂起,让出处理器,此时将调度 就绪状态的低优先级任务获得执行,这种调度称为任务级的切换。 如任务执行 OSTimeDly()或 OSTaskSuspend()把自身挂起就属于这种。 l 高优先级的任务因为时钟节拍到来,或在中断处理结束后,内核发现更高优先级任务 获得了执行条件(如延时的时钟到时),则在中断后直接切换到更高优先级任务执行。这 种调度也称为中断级的切换。 第一种任务级的切换已经分析过了,这里来分析中断级的任务切换。 在目标板初始化函数 TargetInit中有 TargetInit.c pISR_IRQ = (INT32U)ASM_IRQHandler; 把 ASM_IRQHandler的地址赋给中断向量服务程序入口,只要发生中断,系统便跳 到 ASM_IRQHandler函数处执行,它统管了系统的所有中断。 RTOS_ASM.S ASM_IRQHandler ; 中断入口地址,在中断向量表初始化时被设置 sub lr,lr,#4 ; 计算中断返回地址 stmfd sp!,{r0-r3,r12,lr} ; 保护现场,此时处于中断模式下,sp指向中 ; 断模式下的堆栈.不能进行任务切换(各任务 ; 堆栈处在管理模式堆栈). ; R4-R11装的是局部变量,在进行函数跳转时, ; 编译器它会自动保护它们, ; 即 C语言函数返回时,寄存器 R4-R11、SP ; 不会改变,无需人为保护 bl OSIntEnter bl C_IRQHandler ; 调用 c语言的中断处理程序 bl OSIntExit ; 判断中断后是否有更高优先级的任务进入就 ; 绪而需要进行任务切换 ldr r0,=OSIntCtxSwFlag ;if(OSIntCtxSwFlag == 1) OSIntCtxSw() ldr r1,[r0] cmp r1,#1 beq OSIntCtxSw ; 有更高优先级的任务进入了就绪状态,则跳 ; 转到 OSIntCtxSw进行中断级的任务切换 ldmfd sp!,{r0-r3,r12,pc}^ ; 不进行任务切换,出栈返回被中断的任务。 ; 寄存器出栈同时将 spsr_irq的值复制到 ; CPSR,实现模式切换 多任务抢占式调度器 18 TIPS: 在 ADS编译器中,“__irq”专门用来声明 IRQ中断服务程序,如果用“__irq”来声 明一个函数,那么表示该函数为一个 IRQ中断服务程序,汇编时编译器便会自动在该函 数体内加入中断现场保护和恢复的代码。 但在这里,中断由系统管理,中断服务函数不能使用“__irq”关键字进行声明。 在保存返回地址和现场数据后(注意是保存在中断模式下的栈中),随即跳到 OSIntEnter 去执行。OSIntEnter()是为了实现中断嵌套而调用的函数,OSIntNesting 记录 的是中断嵌套层数,每进入一次中断,便把 OSIntNesting 的值加 1,在退出中断时,若 OSIntNesting不为 0,则说明中断仍未完成,要待所有的中断完成后才能返回到任务中。 RTOS.c void OSIntEnter (void) { if (OSIntNesting < 255) { OSIntNesting++; } } 随即进入 C语言中断入口程序 C_IRQHandler,它通过判断 INTOFFSET寄存器来 识别当前发生的中断,然后跳转到相应的中断服务函数。 RTOS.c void C_IRQHandler(void) { (* ((void(*)()) (*((U32 *)((int)&pISR_EINT0 + (rINTOFFSET<<2)))))) (); } 多任务抢占式调度器 19 执行完中断服务程序后,不会马上退出返回先前的任务,它将 OSIntNesting 的值减 1,当 OSIntNesting的值为 0时,表示所有的嵌套中断都完成了。可以返回原来的任务或 进行中断级任务切换。这时判断之前运行的任务是否仍然是最高优先级的就绪任务,如 果中断让更高优先级的任务就绪,则需要切换任务,通过置位 OSIntCtxSwFlag变量来标 记。 RTOS.c void OSIntExit(void) { OS_ENTER_CRITICAL(); if (OSIntNesting > 0) { OSIntNesting --; } if (OSIntNesting == 0) /* 退出所有中断才能进行任务切换*/ { OSGetHighRdy(); /* 找出就绪表中优先级最高的任务 */ if(OSPrioHighRdy != OSPrioCur) { p_OSTCBCur = &TCB[OSPrioCur]; /* 目的是在汇编中引用 任务的 TCB地址取得栈顶指针 */ p_OSTCBHighRdy = &TCB[OSPrioHighRdy]; OSPrioCur = OSPrioHighRdy; /* 更新 OSPrioCur */ OSIntCtxSwFlag = TRUE; /* 进行中断级任务调度 */ } } OS_EXIT_CRITICAL(); } 最后对OSIntCtxSwFlag标志进行判断,若OSIntCtxSwFlag置位则执行OSIntCtxSw 函数准备任务切换,否则退出中断返回先前的任务。 多任务抢占式调度器 20 OSIntCtxSw 函数是中断后需任务切换时的跳转程序,由它转到中断级任务切换程 序。为什么需要它?. 若中断后需任务切换,则中断结束后不返回先前的任务,而去执行中断级任务切换 函数 OS_TASK_SW_INT。这是正确的。但此时仍处于中断模式下,SP指向中断模式下 的堆栈.,不能进行任务切换(各任务堆栈处在管理模式堆栈)。所以要待退出中断后才进 行任务切换。如何保证退出中断后马上运行 OS_TASK_SW_INT?这得做点手脚。方法 是把中断返回地址换成 OS_TASK_SW_INT函数的地址。因为中断返回地址保存了在中 断模式的堆栈中(中断时先保存在中断模式的 lr 中,之后压栈),这里把它用一个变量 Int_Return_Addr_Save 保存下来,待后面任务切换时再把它取出压入先前任务的堆栈中 (管理模式下)。这样,中断返回时,出栈的 PC值就是 OS_TASK_SW_INT的地址。 RTOS_ASM.S OSIntCtxSw ; 把中断的返回地址保存到 ; Int_Return_Addr_Save变量中 ldr r0,=OSIntCtxSwFlag ; 清 OSIntCtxSwFlag标志位 mov r1,#0 str r1,[r0] ; OSIntCtxSwFlag = 0 add sp,sp,#20 ; 调整SP,使之指向之前入栈的 lr(中断模 ; 式下的 lr保存的是中断返回地址), ldr r0,[sp] ; lr -> r0 ldr r1,=Int_Return_Addr_Save ; &Int_Return_Addr_Save -> r1 str r0,[r1] ; lr -> Int_Return_Addr_Save ldr r0,=OS_TASK_SW_INT str r0,[sp] ; OS_TASK_SW_INT -> lr sub sp,sp,#20 ; 调整 SP回到栈顶 ldmfd sp!,{r0-r3,r12,pc}^ ; 退出中断后跳到 OS_TASK_SW而不返回 OSIntCtxSw函数完成的功能 多任务抢占式调度器 21 这样,执行 ldmfd sp!,{r0-r3,r12,pc}^后,PC便指向 OS_TASK_SW_INT函数,执行 中断级任务切换。此时的栈为进入中断之前运行的任务的栈,要把这个任务的运行环境 保存到栈中。之后的代码便与任务级的切换的一样。 RTOS_ASM.S OS_TASK_SW_INT ; 中断级任务切换,中断服务时使用另外 ; 的堆栈,所以要回到管理模式中才进行任务切换 sub sp,sp,#4 ; 为 PC保留位置 stmfd sp!,{r0-r12,lr} ; r0-r12,lr入栈 ldr r0,=Int_Return_Addr_Save; 取出进入中断之前保存的 PC ldr r0,[r0] ; Int_Return_Addr_Save -> r0 add sp,sp,#56 ; 调整 SP stmfd sp,{r0} ; r0(PC)入栈 sub sp,sp,#56 ; 调整 SP回到栈顶 b PUAH_PSR PUAH_PSR mrs r4,cpsr stmfd sp!,{r4} ; cpsr入栈 SaveSPToCurTcb . ; TCB[OSPrioCur].OSTCBStkPtr = SP; ldr r4,=p_OSTCBCur ; 取出当前任务的 PCB地址 ldr r5,[r4] str sp,[r5] ; 保存当前任务的堆顶指针到它的 TCB(因为 ;TaskCtrBlock 地址亦即 OSTCBStkPtr 的地址) GetHigTcbSP ; 取出更高优先级任务的堆顶指针到 SP , ; SP = TCB[OSPrioCur].OSTCBStkPtr ldr r6,=p_OSTCBHighRdy ; 取出更高优先级就绪任务的 PCB的地址 ldr r6,[r6] ldr sp,[r6] ; 取出更高优先级任务的堆顶指针到 SP b POP_ALL ; 根据设定的栈结构顺序出栈 把保存在 Int_Return_Addr_Save中的内容取出来保存在任务栈中 待运行的任务的栈顶指针装载到 CPU的 SP 恢复待运行任务的运行环境 把保存在 Int_Return_Addr_Save中的地址取出来和其它寄存器一起入栈 保存当前任务的堆顶指针到它的 TCB 多任务抢占
本文档为【简易0S设计】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
该文档来自用户分享,如有侵权行为请发邮件ishare@vip.sina.com联系网站客服,我们会及时删除。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。
下载需要: 免费 已有0 人下载
最新资料
资料动态
专题动态
is_455214
暂无简介~
格式:pdf
大小:374KB
软件:PDF阅读器
页数:0
分类:生产制造
上传时间:2013-10-26
浏览量:12