uC/OS II 任务切换原理

今天学习了uC/OS II的任务切换,知道要实现任务的切换,要将原先任务的寄存器压入任务堆栈,再将新任务中任务堆栈的寄存器内容弹出到CPU的寄存器,其中的CS、IP寄存器没有出栈和入栈指令,所以只能引发一次中断,自动将CS、IP寄存器压入堆栈,再利用中断返回,将新任务的任务断点指针弹出到CPU的CS、IP寄存器中,实现任务切换。虽然明白个大概,但是其中的细节却有点模糊,为什么调用IRET中断返回指令后,弹入CPU的CS、IP寄存器的断点指针是新任务的断点指针,而不是当前任务的,UCOS II是如何实现这个过程的?网上的文章没有提及任务切换的细节,然而这些细节却是理解的重点,所以才有了今天这一篇文章!

在uC/OS II中,任务的调度由任务调度器完成,其主要的工作有两项:

1.在任务就绪表中查找最高优先级的就绪任务,该任务将是未来切换执行的任务;

2.实现任务的切换。

任务级的任务调度器,由OS_Sched()实现。

 1     //!!!为简单说明问题,OS_Sched中特意删减了一些语句
 2     void OS_Sched (void)
 3     {
 4     INT8U y;
 5     OS_ENTER_CRITICAL();
 6     y =OSUnMapTbl[OSRdyGrp];
 7     //得到最高优先级任务,OSPrioHighRdy从此为就绪表中最高优先级任务的优先级别
 8     OSPrioHighRdy=(INT8U)((y <<3)+OSUnMapTbl[OSRdyTbl[y]]);
 9     //判断当前任务是否为就绪表中最高优先级任务
10     if(OSPrioHighRdy!=OSPrioCur)
11     {
12     //得到新任务的任务控制块的指针
13     OSTCBHighRdy=OSTCBPrioTbl[OSPrioHighRdy];
14     //统计任务切换次数的计数器加1
15     OSCtxSwCtr++;
16     //此句关键!使用软件中断切换任务,OS_TASK_SW()其实为INT 0x80,128号中断
17     //中断产生后,CPU依次将PSW状态寄存器、断点指针段地址:偏移地址压入当前任务的堆栈中
18     OS_TASK_SW();
19     }
20     OS_EXIT_CRITICAL();
21     }

  从上述代码,可以知道,OS_Sched的任务切换步骤分为两步:

1.获得最高优先级任务的任务控制块指针;

2.利用中断实现断点数据的切换。(当前任务中止运行时,CPU寄存器CS、IP、PSW和通用寄存器等中的数据就称为断点数据)

在获得了新任务的任务控制块指针,相当于获得了任务控制块中包含的任务堆栈,而任务堆栈中又包含了该任务运行时,CPU寄存器的初始内容,即断点数据。前面已经提到过 “要实现任务的切换,要将原先任务的寄存器压入任务堆栈,再将新任务中任务堆栈的寄存器内容弹出到CPU的寄存器”,除了CS、IP寄存器,其他寄存器只需要利用出栈和入栈指令即可,而CS、IP寄存器的出栈、入栈要利用中断INT 0x80和IRET。好吧,那问题来了,uC/OS是如何利用中断实现断点指针CS、IP的切换?

产生INT 0x80后,CPU做了什么,当然是去执行和INT 0x80相关的中断服务程序去了(说明:每个中断都有一个对应好的中断服务程序,每当产生该中断,则由CPU自动调用该对应的中断服务程序)。

在uC/OS II中,INT 0x80对应的中断服务程序是

 1     _OSCtxSw PROC FAR
 2     ;
 3     ;保存当前任务寄存器内容,将它们压入当前任务堆栈,AX、CX、DX、BX、SP(原始值)、BP、SI 及 DI
 4     PUSHA ;
 5     PUSH ES ;
 6     PUSH DS ;
 7     ;
 8     ;将当前任务的任务控制块的指针段值赋值给DS寄存器
 9     MOV AX, SEG _OSTCBCur;Reload DS in case it was altered
10     MOV DS, AX ;
11     ;
12     ; LES指令作用,取得DS:_OSTCBCur任务控制块指针所指向的内容(因为任务控制块的第一个成员为任务堆栈指针,所以ES:[BX+0]指向的是任务堆栈),低字存放于BX,高字存放于ES,说明任务堆栈的前4字节是用于存放CPU堆栈相关寄存器的,有关LES详细查看另一篇文章!
13     ;同时由于任务控制块的第一成员为任务堆栈指针变量,所以任务堆栈指针成员变量的地址和任务控制块的地址一样
14     LES BX, DWORD PTR DS:_OSTCBCur;取得任务堆栈指针ES:[BX]
15     MOV ES:[BX+2], SS ;将当前SS(栈的基地址)寄存器值存放至当前任务堆栈的2,3内存单元
16     MOV ES:[BX+0], SP ;将当前SP(栈顶的偏移量)存放至当前任务堆栈的0,1内存单元
17     ;
18     CALL FAR PTR _OSTaskSwHook;Call user defined task switch hook
19     ;
20     ;存储器寻址,DS:_OSTCBHighRdy+2存放的是新任务的栈基址SS,DS:_OSTCBHighRdy存放的是栈顶偏移量SP
21     MOV AX, WORD PTR DS:_OSTCBHighRdy+2;将OSTCBHighRdy赋值给OSTCBCur,使OSTCBCur指向新的任务控制块
22     MOV DX, WORD PTR DS:_OSTCBHighRdy;OSTCBCur=OSTCBHighRdy
23     MOV WORD PTR DS:_OSTCBCur+2, AX ;
24     MOV WORD PTR DS:_OSTCBCur, DX ;
25     ;
26     MOV AL, BYTE PTR DS:_OSPrioHighRdy;OSPrioCur=OSPrioHighRdy
27     MOV BYTE PTR DS:_OSPrioCur, AL ;
28     ;
29     ;将最高级优先级任务的任务控制块包含的SS和SP寄存器值,CPU的SS和SP指向了新的任务堆栈
30     LES BX, DWORD PTR DS:_OSTCBHighRdy; SS:SP = *(OSTCBHighRdy->OSTCBStkPtr),为什么是*,因为存放SS和SP的地方其实是任务堆栈指针OSTCBStkPtr所指向的前4字节内存单元,而并不是任务堆栈指针本身!
31     MOV SS, ES:[BX+2];
32     MOV SP, ES:[BX];
33     ;
34     ;注意:这里弹出的是新任务的堆栈,而不是旧的,因为CPU的SS和SP已经指向了新的任务堆栈
35     POP DS ;Loadnew task‘s context
36     POP ES ;
37     POPA ;
38     ;
39     ;当新任务的堆栈都弹出的时候,只剩下新任务的CS、IP指针,刚好运行IRET弹到CPU的CS、IP寄存器,开始新任务的运行
40     IRET ;Return to new task
41     ;
42     _OSCtxSw ENDP

请仔细将上面代码对照着注释看一遍,注释已经说得很清楚了,_OSCtxSw的堆栈操作,可以看下面两幅图

(0)在调用_OSCtxSw中断服务程序前,CPU将PSW和任务断点指针CS、IP压入当前任务堆栈

(1)将CPU寄存器AX、CX、DX、BX、SP(原始值)、BP、SI 及 DI压入当前任务堆栈

(2)将当前CPU的堆栈指针SS和SP存入当前任务堆栈的偏移量0,1的内存位置和偏移量2,3的内存位置,即下面任务堆栈ptos所指位置SEG pdata和OFF pdata

说明:至此,当前任务的断点数据已经全部保存,所以在每一个新任务运行之前,uC/OS都会将旧任务的断点数据全部按照顺序压入私有堆栈中,同时每一个新任务的任务堆栈都已保存初始化好的或以前任务中止时,CPU保存的断点数据。

(3)OSTCBCur = OSTCBHighRdy、OSPrioCur = OSPrioHighRdy

(4)使CPU的SS和SP堆栈指针指向新的任务堆栈

(5)将新的任务堆栈的内容弹出到CPU寄存器,顺序为DS、ES、DI、SI、BP、SP、BX、DX、CX、AX。

说明:注意是新任务堆栈在进行弹栈操作,而新任务堆栈在弹栈前,和旧任务的断点数据全部压栈后的结构一模一样,都是顺序为DS、ES、DI、SI、BP、SP、BX、DX、CX、AX、IP、CS、PSW。经过POP DS POP ES POPA,这3条指令之后,新任务堆栈便只剩下IP、CS、PSW,IP和CS这断点指针指向新任务的任务代码,只要将它们弹出到CPU的CS、IP寄存器,就可以实现任务切换。

(6)运行IRET,将新任务断点指针弹出到CPU的CS、IP指针寄存器,PSW弹出到CPU的PSW寄存器

(7)至此,uC/OS开始运行新任务

参考链接:

μC-OS-Ⅱ中通过中断返回指令实现任务切换  http://www.docin.com/p-223857136.html

我对OSCtxSW()任务级任务切换函数的理解(以图例的方式) http://blog.163.com/muren20062094%40yeah/blog/static/1618444162011727102557489/

uCOSii中断处理过程详解 http://www.doc88.com/p-1498746979512.html

uC/OS II任务调度 http://blog.sina.com.cn/s/blog_6f36f4fb0100n3or.html

uC/OS-II任务栈处理的一种改进方法 http://www.3edu.net/lw/qrs/lw_46692.html

本文链接:http://www.cnblogs.com/cposture/p/4291503.html

时间: 2024-10-14 21:44:08

uC/OS II 任务切换原理的相关文章

uC/OS II 函数说明 之–OSTaskCreate()与OSTaskCreateExt()

1. OSTaskCreate()    OSTaskCreate()建立一个新任务,能够在多任务环境启动之前,或者执行任务中建立任务.注意,ISR中禁止建立任务,一个任务必须为无限循环结构.        源码例如以下: #if OS_TASK_CREATE_EN > 0                    /* 条件编译,是否同意任务的创建               */INT8U  OSTaskCreate (void (*task)(void *pd), /* 函数指针,void *

【原创】uC/OS 中LES BX,DWORD PTR DS:_OSTCBCur的作用及原理

1 LES BX, DWORD PTR DS:_OSTCBCur ;取得任务堆栈指针ES:[BX] 2 MOV ES:[BX+2], SS ;将当前SS(栈的基地址)寄存器值存放至当前任务堆栈的2,3内存单元 3 MOV ES:[BX+0], SP ;将当前SP(栈顶的偏移量)存放至当前任务堆栈的0,1内存单元 首先讲讲LES指针的功能:LES的功能有点像C语言的*. LES REG,MEM 参与操作的寄存器不仅有REG,还有ES寄存器.在16位系统中,寄存器为16位,很显然,MEM所指向的内存

关于uC/OS的简单学习(转)

1.微内核 与Linux的首要区别是,它是一个微内核,内核所实现的功能非常简单,主要包括: 一些通用函数,如TaskCreate(),OSMutexPend(),OSQPost()等. 中断处理函数,且处理函数非常简单,一般仅是向相应的Task发消息,唤醒该Task来处理中断任务. 一个高效的调度器,这是OS的灵魂,实现多任务间的调度(包括调度点.调度算法.任务切换等). 好像就这么点,呵呵.它不支持内存保护,即不像Linux那样分用户空间.内核空间.如一个Task运行时,可调用内核函数Task

uC/OS 的任务调度解析 (转)

uC/OS 的任务调度解析 1.任务调度器启动之后(初始化,主要是TCB的初始化),就可以创建任务,开始任务调度了,实际上第一个任务准确的说不是进行任务切换,而是进行启动当前最高优先级任务.uC/OS使用的是OSStartHighRdy OSStartHighRdy LDR R0, =NVIC_SYSPRI14 ; Set the PendSV exception priority LDR R1, =NVIC_PENDSV_PRI STRB R1, [R0] MOVS R0, #0 ; Set

uc/os 笔记(转)

1.uC/OS-II中使用互斥信号对象应该注意 互斥信号对象(Mutual Exclusion Semaphore)简称Mutex,是uC/OS-II的内核对象之一,用于管理那些需要独占访问的资源,并使其适应多任务环境. 创建每一个Mutex,都需要指定一个空闲的优先级号,这个优先级号的优先级必须比所有可能使用此Mutex的任务的优先级都高! uC/OS-II的Mutex实现原理大致如下:    当一个低优先级的任务A申请并得到了Mutex,于是它获得资源访问权.如果此后有一个高优先级的任务B开

uc os相关的C语言知识点1-函数指针

开始读uc os的代码了,发现很多C语言的东西,之前没搞懂的,慢慢积累. 就象某一数据变量的内存地址可以存储在相应的指针变量中一样,函数的首地址也以存储在某个函数指针变量里的.这样,我就可以通过这个函数指针变量来调用所指向的函数了. 形式1:返回类型(*函数名)(参数表) ,例子如下: #include<stdio.h> void (*funp)(int); //定义一个函数指针,注意函数的返回类型和参数类型和指针的一致,才可以用. void print(int n); //函数申明 int

uc/os iii移植到STM32F4---IAR开发环境

也许是先入为主的原因,时钟用不惯Keil环境,大多数的教程都是拿keil写的,尝试将官方的uc/os iii 移植到IAR环境. 1.首先尝试从官网上下载的官方移植的代码,编译通过,但是执行会报堆栈溢出警告(为何keil没有报堆栈溢出??),网上有人说不用理会,但是实际使用时发生了错误(定义的常量数组值被改变,怀疑是堆栈溢出导致),发现使用的IAR版本不能完美支持使用的STM32芯片,换用高版本测试..(高版本正确,与低版本对芯片的支持有关) 2.开始时虽然会堆栈溢出,但是能够进入异常中断,进入

x01.os.9: 进程切换

进入内核后,当然不能无所事事.先创建三个进程,分别打印 A,B,C.虽然只是简单的打印,但却是一切扩展的基础,不可等闲视之. 进程切换,涉及一系列的寄存器需要保护,于是,就有了 ProcessStack 结构,代码如下: typedef struct { u32 gs; u32 fs; u32 es; u32 ds; u32 edi; u32 esi; u32 ebp; u32 KernelEsp; u32 ebx; u32 edx; u32 ecx; u32 eax; u32 RetAddr;

H5单页面手势滑屏切换原理

H5单页面手势滑屏切换是采用HTML5 触摸事件(Touch) 和 CSS3动画(Transform,Transition)来实现的,效果图如下所示,本文简单说一下其实现原理和主要思路. 1.实现原理 假设有5个页面,每个页面占屏幕100%宽,则创建一个DIV容器viewport,将其宽度(width) 设置为500%,然后将5个页面装入容器中,并让这5个页面平分整个容器,最后将容器的默认位置设置为0,overflow设置为hidden,这样屏幕就默认显示第一个页面. <div id="v