uCOS2调度原理总结_ucos掌握操作系统中进程设计原理-程序员宅基地

技术标签: STM32_uCOS2_内核调度  uCOS2调度  

uCOS2内核调度原理

作者:JCY

来自09级安徽宿州学院电子创新实验室

此文中对uCOS2内核调度的理解,若有错误之处请指出,不胜感激!

 

在uCOS2操作系统当中有程序会处于五种状态:运行态、就绪态、挂起态、睡眠态、中断服务态。

运行态:某一个任务正在运行,独占CPU的使用权。

就绪态:某一个任务已经有了运行的准备,可以随时被调度。

挂起态:某一个任务需要等待某一个事件的发生,例如等待信号量、互斥信号量、时间到等。

睡眠状态:任务被任务删除函数后,不能再被操作系统调度。虽然在程序存储区还存在该任务,但是该任务永远都不会执行。

中断服务态:某一个中断发生后,就会执行中断服务程序,那么程序就会处于中断服务态。

具体各任务间的关系如下(来自于一本uCOS2方面的书籍):

从图中可以看出各任务之间就是通过调用各种系统函数来实现,这也是我们在编写程序时使用的的函数。

对于操作系统来说,我们只需要它提供的操作函数来操作系统提供的功能。在此基础上我们必须要了解每一个系统函数的函数原型,每一个参数的意义,参数限制等。但是这只是最低级的工作,了解每一个函数如何实现的,深入到操作系统的源码,才可以说对操作系统熟悉了。

操作系统的调度都围绕这一个数据结构体OS_TCB,我们叫它任务控制块。结构体的定义原型如下:

typedef struct os_tcb {

    OS_STK          *OSTCBStkPtr;           /* Pointer to current top of stack                         */

#if OS_TASK_CREATE_EXT_EN > 0

    void            *OSTCBExtPtr;           /* Pointer to user definable data for TCB extension        */

    OS_STK          *OSTCBStkBottom;        /* Pointer to bottom of stack                              */

    INT32U           OSTCBStkSize;          /* Size of task stack (in number of stack elements)        */

    INT16U           OSTCBOpt;              /* Task options as passed by OSTaskCreateExt()             */

    INT16U           OSTCBId;               /* Task ID (0..65535)                                      */

#endif

    struct os_tcb   *OSTCBNext;             /* Pointer to next     TCB in the TCB list                 */

    struct os_tcb   *OSTCBPrev;             /* Pointer to previous TCB in the TCB list                 */

#if (OS_EVENT_EN) || (OS_FLAG_EN > 0)

    OS_EVENT        *OSTCBEventPtr;         /* Pointer to          event control block                 */

#endif

#if (OS_EVENT_EN) && (OS_EVENT_MULTI_EN > 0)

    OS_EVENT       **OSTCBEventMultiPtr;    /* Pointer to multiple event control blocks                */

#endif

#if ((OS_Q_EN > 0) && (OS_MAX_QS > 0)) || (OS_MBOX_EN > 0)

    void            *OSTCBMsg;              /* Message received from OSMboxPost() or OSQPost()         */

#endif

#if (OS_FLAG_EN > 0) && (OS_MAX_FLAGS > 0)

#if OS_TASK_DEL_EN > 0

    OS_FLAG_NODE    *OSTCBFlagNode;         /* Pointer to event flag node                              */

#endif

    OS_FLAGS         OSTCBFlagsRdy;         /* Event flags that made task ready to run                 */

#endif

    INT16U           OSTCBDly;              /* Nbr ticks to delay task or, timeout waiting for event   */

    INT8U            OSTCBStat;             /* Task      status                                        */

    INT8U            OSTCBStatPend;         /* Task PEND status                                        */

    INT8U            OSTCBPrio;             /* Task priority (0 == highest)                            */

    INT8U            OSTCBX;                /* Bit position in group  corresponding to task priority   */

    INT8U            OSTCBY;                /* Index into ready table corresponding to task priority   */

#if OS_LOWEST_PRIO <= 63

    INT8U            OSTCBBitX;             /* Bit mask to access bit position in ready table          */

    INT8U            OSTCBBitY;             /* Bit mask to access bit position in ready group          */

#else

    INT16U           OSTCBBitX;             /* Bit mask to access bit position in ready table          */

    INT16U           OSTCBBitY;             /* Bit mask to access bit position in ready group          */

#endif

#if OS_TASK_DEL_EN > 0

    INT8U            OSTCBDelReq;           /* Indicates whether a task needs to delete itself         */

#endif

#if OS_TASK_PROFILE_EN > 0

    INT32U           OSTCBCtxSwCtr;         /* Number of time the task was switched in                 */

    INT32U           OSTCBCyclesTot;        /* Total number of clock cycles the task has been running  */

    INT32U           OSTCBCyclesStart;      /* Snapshot of cycle counter at start of task resumption   */

    OS_STK          *OSTCBStkBase;          /* Pointer to the beginning of the task stack              */

    INT32U           OSTCBStkUsed;          /* Number of bytes used from the stack                     */

#endif

#if OS_TASK_NAME_SIZE > 1

    INT8U            OSTCBTaskName[OS_TASK_NAME_SIZE];

#endif

} OS_TCB;

任务控制块贯穿了整个uCOS2操作系统,如果不了解这个结构体在操作系统当中的用法,那就不可能对uCOS2深入的理解。

在Ucos_ii.h当中有一个

OS_EXT  OS_TCB            OSTCBTbl[OS_MAX_TASKS + OS_N_SYS_TASKS];   /* Table of TCBs                  */

它是定义一个或者外部声明一个OSTCBTbl的数组。这两个作用需要靠OS_GLOBALS是否宏定义来选择。当然在整个程序代码当中可能只有一个C文件中定义了OSTCBTbl变量,其他的C文件中只是对它进行外部变量声明。

在Linux操作系统编写增加链表和删除链表程序时,都会伴随着内存空间的申请和释放。但是在uCOS2操作系统当中没有这么强悍的内存管理。一个任务控制块OS_TCB管理者一个任务,所以OSTCBTbl的元素个数必须大于在应用程序当中建立的任务数。从定义中可以看出元素个数由两个宏定义决定,第一OS_MAX_TASKS:就是在应用程序当中可能建立的最大任务数,第二OS_N_SYS_TASKS:是调用系统函数后有系统建立的任务数。这两个数值是可以改变的,因为在程序当中必须要有一个空闲任务,而计算CPU效率的统计任务是由用户选择的,所以OS_N_SYS_TASKS的值受到OS_TASK_STAT_EN的控制。宏定义OS_TASK_STAT_EN的值决定了,是否使能统计任务。

#if OS_TASK_STAT_EN > 0

#define  OS_N_SYS_TASKS               2u                /* Number of system tasks                      */

#else

#define  OS_N_SYS_TASKS               1u

#endif

任务控制块主要由两个链表来控制,一个是正在使用的任务控制块链表OSTCBList和空闲任务控制块链表OSTCBFreeList。OSTCBList是一个双向链表,只要知道了链表当中的一个元素就能够找到该链表中所有的元素,即能访问每一个任务。OSTCBFreeList是一个单向链表,存储着还没有使用的任务控制块。需要建立一个任务时,就会从空闲任务控制块OSTCBFreeList中取出一个任务控制块,并将空闲任务控制块指针OSTCBFreeList指向下一个空闲任务控制块。从空闲任务控制链表中取出的任务控制块(暂且把该任务控制块叫做OSTCB1)后,需要将人任务控制块加入到任务控制块双向链表OSTCBList中,该任务控制块指针指向了最后一个加入到任务控制块链表的任务控制块。将OSTCB1当中的任务控制块指针OSTCBNext指向OSTCBList指向的任务控制块,OSTCBList指向的任务控制块当中也有指向任务控制块的变量OSTCBPrev,将OSTCBPrev指向OSTCB1任务控制块。然后再将OSTCBList指向OSTCB1任务控制块,这样就将新的任务控制块加入到了任务控制块链表当中。

关系图如下:

我们来看一下,系统初始化后,两链表OSTCBList,OSTCBFreeList的形式。查看源码文件Os_core.c,在文件中有一个函数OSInit()。这个函数必须被应用程序调用,并且只能调用一次。该函数中调用了OS_InitTCBList。在OS_InitTCBList函数中对与任务控制块相关联的代码进行初始化。源码如下:

static  void  OS_InitTCBList (void)

{

    INT8U    i;

    OS_TCB  *ptcb1;

    OS_TCB  *ptcb2;

    OS_MemClr((INT8U *)&OSTCBTbl[0],     sizeof(OSTCBTbl));      /* Clear all the TCBs                 */

    OS_MemClr((INT8U *)&OSTCBPrioTbl[0], sizeof(OSTCBPrioTbl));  /* Clear the priority table           */

    ptcb1 = &OSTCBTbl[0];

    ptcb2 = &OSTCBTbl[1];

    for (i = 0; i < (OS_MAX_TASKS + OS_N_SYS_TASKS - 1); i++) {  /* Init. list of free TCBs            */

        ptcb1->OSTCBNext = ptcb2;

#if OS_TASK_NAME_SIZE > 1

        ptcb1->OSTCBTaskName[0] = '?';                           /* Unknown name                       */

        ptcb1->OSTCBTaskName[1] = OS_ASCII_NUL;

#endif

        ptcb1++;

        ptcb2++;

    }

    ptcb1->OSTCBNext = (OS_TCB *)0;                              /* Last OS_TCB                        */

#if OS_TASK_NAME_SIZE > 1

    ptcb1->OSTCBTaskName[0] = '?';                               /* Unknown name                       */

    ptcb1->OSTCBTaskName[1] = OS_ASCII_NUL;

#endif

    OSTCBList               = (OS_TCB *)0;                       /* TCB lists initializations          */

    OSTCBFreeList           = &OSTCBTbl[0];

}

函数OS_MemClr可以对参数中所指示的空间进行清零。第一个参数是空间对象的首地址,第二个参数是需要清零的空间大小(以字节为单位)。对于具体的源码可以自行分析,在此不再叙述。

在程序当中蓝色的代码将OSTCBTbl中的所有的元素组成一个单向的链表,最有一个元素中的任务控制块指针OSTCBNext 指向空。

形式如下(摘自某书籍):

初始化的时候并没有任何的任务被创建,所以在程序当中OSTCBList指向了空。

OSInit()当中调用OS_InitTaskIdleOS_InitTaskIdle调用OSTaskCreateExt创建了一个空闲任务。在该函数中涉及到知识较多,先不表述。必要的基础部分写出后,在写这块。

uCOS2操作系统怎样标记某一个任务进入就绪状态呐!

先把涉及到的数据变量列出来,以供讲解。

OS_EXT  INT8U             OSRdyGrp;                        /* Ready list group                         */

OS_EXT  INT8U             OSRdyTbl[OS_RDY_TBL_SIZE];       /* Table of tasks which are ready to run    */

把一本书上总结出来的图列出:

8*8的方框就是就绪表,在表中每一位代表一个优先级。例如OSRdyTbl[0].0代表第0优先级,即优先级最高。OSRdyTbl[8].7代表第63个优先级,即优先级最低。若某一个优先级处于就绪状态那么就将就绪表OSRdyTbl中的相应的位置零。OSRdyGrp也是一个8位的变量,它指示那个组当中有就绪的任务。OSRdyTbl每一个元素被称为一个组,若在组中有任务处于就绪状态那么就将OSRdyGrp中的相应的位置1,否则清零。例如优先级4处于就绪状态了,那么就将该OSRdyGrp的第零位置1,因为第四个优先级被划归第零组。其实OSRdyGrp的位号与组号相对应,而组号又和OSRdyTbl的元素下标号相同,所以这种关系就清楚了。

uCOS2是基于优先级调度算法的,怎样从OSRdyGrp和OSRdyTbl找到处于最高的就绪任务呐!

当然算法有很多,不使用OSRdyGrp也是可以找到的,但是uCOS2出于对调度时间的考虑,它使用了OSRdyGrp变量。在算法中加入了一些查找算法,是程序运行更快。

算法中使用的查找表有一个OSUnMapTbl,2.86版本的uCOS2还使用了OSMapTbl。

OSMapTbl表的功能也可以使用1<<Index来实现。使用查找表只是减少系统函数的实行时间,这是以空间来换取时间的做法。但是在2.86版本的uCOS2中没有用到,直接使用1<<Index来实现。这对系统基本无影响。

INT8U  const  OSUnMapTbl[256] = {

    0, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x00 to 0x0F                       */

    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x10 to 0x1F                             */

    5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x20 to 0x2F                             */

    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x30 to 0x3F                             */

    6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x40 to 0x4F                             */

    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x50 to 0x5F                             */

    5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x60 to 0x6F                             */

    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x70 to 0x7F                             */

    7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x80 to 0x8F                             */

    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x90 to 0x9F                             */

    5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0xA0 to 0xAF                             */

    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0xB0 to 0xBF                             */

    6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0xC0 to 0xCF                             */

    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0xD0 to 0xDF                             */

    5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0xE0 to 0xEF                             */

    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0        /* 0xF0 to 0xFF                             */

};

这个表的意义很简单,就是给出一个8位的数字,然后找出其二进制数中1所占的最低位的位号。

例如

0x38 = (0011_1000)2经查表OSUnMapTbl[0x38]的值为4。

0x56 = (0101_1100)2经查表OSUnMapTbl[0x56]的值为2。

现在把查找优先级的代码列出如下:

    INT8U   y;

    y             = OSUnMapTbl[OSRdyGrp];

    OSPrioHighRdy = (INT8U)((y << 3) + OSUnMapTbl[OSRdyTbl[y]]);

假设OSRdyGrp = 0x82 = (1000_0010);从OSRdyGrp 的值可以知道在所有就绪的优先级当中, 最高的那个优先级属于第一组。第一组有8个优先级,还要查看OSRdyTbl[1]中最高优先级处在哪个位置,进而可以确定最高优先级的优先级号。例如如果OSRdyTbl[1] = 0x55 = (0101_0101)2。那么查表后OSUnMapTbl[0x55] = 0,所以可以确定优先级为1<<3 + 0 = 8。

找到了最高的优先级了,那么怎样通过这个最高优先级号找到所对应的任务控制块呐?这是通过任务控制块优先级表OSTCBPrioTbl,这里面最放着任务控制块指针,数组的元素数等于最低优先级的加1,

并且会大于等于在应用程序建立的任务数和系统建立的任务数之和。若某一个任务的优先级为10,那么OSTCBPrioTbl[10]就只向优先级为10所对应的任务控制块。

OSTCBPrioTbl在Ucos_ii.h当中定义,定义的原型如下:

OS_EXT  OS_TCB  *OSTCBPrioTbl[OS_LOWEST_PRIO + 1];/* Table of pointers to created TCBs        */

 以便于下面的讲解,要说明的是,对于每一个任务都有自己的堆栈,并且任务的堆栈是由用户建立的。在任务控制块中含有指向堆栈的指针,在堆栈中存放着进行任务调度时需要保存的与本任务相关CPU寄存器值,和任务调用用函数时存放的一些中间值。

  

看看创建任务都做了哪些事情?以空闲任务的建立为例来说明。

    (void)OSTaskCreateExt(OS_TaskIdle,

                          (void *)0,                                 /* No arguments passed to OS_TaskIdle() */

                          &OSTaskIdleStk[OS_TASK_IDLE_STK_SIZE - 1], /* Set Top-Of-Stack                     */

                          OS_TASK_IDLE_PRIO,                         /* Lowest priority level                */

                          OS_TASK_IDLE_ID,

                          &OSTaskIdleStk[0],                         /* Set Bottom-Of-Stack                  */

                          OS_TASK_IDLE_STK_SIZE,

                          (void *)0,                                 /* No TCB extension                     */

                          OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR);/* Enable stack checking + clear stack  */

参数说明:

OS_TaskIdle:空闲任务的任务函数,通常所说的任务的执行就是执行执行这个函数,任务的切换也是中断此函数或者从中断处继续执行此函数。

(void *)0:是传给任务函数的参数,对于每一个任务函数都会有一个参数,用来从外界传递重要的数据。

&OSTaskIdleStk[OS_TASK_IDLE_STK_SIZE - 1]:指向栈顶定的指针。

 OS_TASK_IDLE_PRIO:此是优先级号,任务的每个优先级是不同的。

 OS_TASK_IDLE_ID:是任务的ID,每个任务的ID是不同的,但可以和优先级号相同。

&OSTaskIdleStk[0]:指向栈底的指针,由此可知栈的增长方向是向下增长的。

OS_TASK_IDLE_STK_SIZE:空闲任务的栈空间大小。当然这个可以由栈顶和栈底的地址得到。

(void *)0:对任务控制块的数据进行扩展,NULL代表不对任务控制块扩展

OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR:这是任务的属性。使能任务的核查,和在没有使用任务栈时,对该任务的整个栈空间进行初始化。

传递的参数是不是非常明了呀,那我们就看看OSTaskCreateExt的实现吧!该函数中主要的代码如下:

    OS_ENTER_CRITICAL();

    if (OSIntNesting > 0) {                  /* Make sure we don't create the task from within an ISR  */

        OS_EXIT_CRITICAL();

        return (OS_ERR_TASK_CREATE_ISR);

    }

    if (OSTCBPrioTbl[prio] == (OS_TCB *)0) { /* Make sure task doesn't already exist at this priority  */

        OSTCBPrioTbl[prio] = OS_TCB_RESERVED;/* Reserve the priority to prevent others from doing ...  */

                                             /* ... the same thing until task is created.              */

        OS_EXIT_CRITICAL();

#if (OS_TASK_STAT_STK_CHK_EN > 0)

        OS_TaskStkClr(pbos, stk_size, opt);                    /* Clear the task stack (if needed)     */

#endif

        psp = OSTaskStkInit(task, p_arg, ptos, opt);           /* Initialize the task's stack          */

        err = OS_TCBInit(prio, psp, pbos, id, stk_size, pext, opt);

        if (err == OS_ERR_NONE) {

            if (OSRunning == OS_TRUE) {                        /* Find HPT if multitasking has started */

                OS_Sched();

            }

        } else {

            OS_ENTER_CRITICAL();

            OSTCBPrioTbl[prio] = (OS_TCB *)0;                  /* Make this priority avail. to others  */

            OS_EXIT_CRITICAL();

        }

        return (err);

    }

    OS_EXIT_CRITICAL();

    return (OS_ERR_PRIO_EXIST);

由红色的代码可知,不能再中断函数中调用此函数,否则会产生错误返回。蓝色的代码指示先把OSTCBPrioTbl的优先级占用,防止在执行非临界代码时发生任务的切换,而在将要正在执行的任务当中也执行任务创造函数,并且两者的优先级相同。

这一部分代码就是对任务的栈空间进行清零,具体的源码如下:

在此函数中的核心代码如下:

然后就开始执行任务堆栈初始化函数OSTaskStkInit()。此函数就是初始化栈顶的一部分空间,这部分栈内容存放着该任务在CPU当中的状态。需要执行该任务时只需要把该空间的值存放在CPU寄存器当中就可以了。当时这只使用于第一次执行该任务的时候。若执行该任务超过了两个或者两次以上,需要存放到CPU寄存器在栈中的位置就不确定了。任务堆栈初始化函数如下:

图中的p_arg就是在第一次调用该任务时所需要的参数,该参数是从任务创造函数中依次传递过来的。这里面的值有些是可以改动的。如R0-R11的值,R0和PC是不能改动的。

因为这是第一次调用任务创造函数,在调用之前OSRunning被赋值为假了。该语句是在OS_InitMisc函数中定义的,代码如下图:

                  

创建任务就介绍完了。  假设建立了两个任务,然后开始执行多任务。这是通过在主函数中调用 OSStart来实现的。代码如下图:

该函数首先就是调用OS_SchedNew函数找到最高优先级的优先级号,然后将任务优先级号放在OSPrioHighRdy当中。代码图如下:

这部分的代码看起来虽然长,但是不难。如果最低优先级为63,那么只会编译最上面的三行代码。如果你的最低优先级超过了63,那么就是只会编译下面的代码。所以我们可以知道uCOS2的2.86版本支持256的优先级。对于当256个优先级的就续表查找方法和最低优先级为63个的查找方法是相同的。在这里就不在赘述了。

然后继续执行OSStart函数。接下来就是对当前优先级OSPrioCur、就绪态最高优先级的任务控制块指针OSTCBHighRdy、当前要执行的任务控制块指针OSTCBCur进行赋值。这三个变量都是全局变量,存储正在执行的任务信息。执行每一个任务的时候都会更新。OSTCBCur和OSTCBHighRdy指向的任务控制块是相同的。

然后开始执行函数OSStartHighRdy。在C语言文件当中你是查不到这个函数的,即使找也只是找到它的定义。对于函数的实现是在汇编代码当中。那么就进入这个汇编文件OS_CPU_ASM.asm吧!

OSStartHighRdy函数的代码如下:

OSStartHighRdy

    LDR     R0, =NVIC_SYSPRI14                                  ; Set the PendSV exception priority

    LDR     R1, =NVIC_PENDSV_PRI

    STRB    R1, [R0]

    MOVS    R0, #0                                              ; Set the PSP to 0 for initial context switch call

    MSR     PSP, R0

    LDR     R0, =OSRunning                                      ; OSRunning = TRUE

    MOVS    R1, #1

    STRB    R1, [R0]

    LDR     R0, =NVIC_INT_CTRL                                  ; Trigger the PendSV exception (causes context switch)

    LDR     R1, =NVIC_PENDSVSET

    STR     R1, [R0]

    CPSIE   I                                                   ; Enable interrupts at processor level

OSStartHang

    B       OSStartHang                                         ; Should never get here

在该程序中的最后一个语句,就是一个while(1);说明程序调用OSStartHighRdy函数后永远不会退出。

在代码中红色的部分是为了设置PendSV的优先级,设置为最低0xff;NVIC_SYSPRI14 是系统处理器优先级寄存器的地址。具体的解释是在CM3技术参考手册里面,截图如下:

蓝色部分是将CPU的堆栈指针设置为0,而后再对OSRunning全局变量设置为1,这个变量指示了操作系统处在运行状态。

           

该函数代码是为了使用软件的方式触发PendSV中断。接着程序一直在处于死循环当中。开始执行PendSV 中断服务程序。中断服务程序的代码如下:

  

代码图片不太清楚,放大之后还是蛮清楚的。即使不清楚还是用图,是因为可以看到后面的注释。

第一个执行的语句是 CPSID   I 

这个语句是为了禁止中断,此中断服务程序就是为了做任务之间的切换,在任务切换期间允许有被打断,因为设置的中断优先级最低,所以禁止中断时非常有必要的。

第二个语句就是为了获得CPU堆栈中的值给R0,我们知道此中断第一次执行时栈指针寄存器PSP的值为0(调用OSStartHighRdy函数时)。

CBZ 是一个跳转指令,判断R0的值是否为0,为0则跳转。

下面的代码只有在第一次执行中断服务程序时才不会执行,在以后的再进入该中断服务程序还会都会执行此代码。

    SUBS    R0, R0, #0x20                                       ; Save remaining regs r4-11 on process stack

    STM     R0, {R4-R11};将当前处理器的R4-R11的值存储到任务控制块的堆栈当中

    LDR     R1, =OSTCBCur                                       ; OSTCBCur->OSTCBStkPtr = SP;

    LDR     R1, [R1]

STR     R0, [R1]; R0 is SP of process being switched out将当前的堆栈指针保存到任务控制块的第一个栈指针域

之后每次进入OS_CPU_PendSVHandler函数都会把每一行语句执行了。

需要指出的是当产生中断后,当前程序的CPU寄存器xPSR,PC,R14,R12,R3,R2,  R1,  R0值为自动被处理器保存,并且保存到该任务控制块的栈中,所以这几个寄存器的值不需要再保存到栈了,而只需要把寄存器R11-R4的值保存到栈中就可以了。用文字难以表述,先列出流程图。

     

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/wuwuhuizheyisheng/article/details/8473080

智能推荐

c# 调用c++ lib静态库_c#调用lib-程序员宅基地

文章浏览阅读2w次,点赞7次,收藏51次。四个步骤1.创建C++ Win32项目动态库dll 2.在Win32项目动态库中添加 外部依赖项 lib头文件和lib库3.导出C接口4.c#调用c++动态库开始你的表演...①创建一个空白的解决方案,在解决方案中添加 Visual C++ , Win32 项目空白解决方案的创建:添加Visual C++ , Win32 项目这......_c#调用lib

deepin/ubuntu安装苹方字体-程序员宅基地

文章浏览阅读4.6k次。苹方字体是苹果系统上的黑体,挺好看的。注重颜值的网站都会使用,例如知乎:font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, W..._ubuntu pingfang

html表单常见操作汇总_html表单的处理程序有那些-程序员宅基地

文章浏览阅读159次。表单表单概述表单标签表单域按钮控件demo表单标签表单标签基本语法结构<form action="处理数据程序的url地址“ method=”get|post“ name="表单名称”></form><!--action,当提交表单时,向何处发送表单中的数据,地址可以是相对地址也可以是绝对地址--><!--method将表单中的数据传送给服务器处理,get方式直接显示在url地址中,数据可以被缓存,且长度有限制;而post方式数据隐藏传输,_html表单的处理程序有那些

PHP设置谷歌验证器(Google Authenticator)实现操作二步验证_php otp 验证器-程序员宅基地

文章浏览阅读1.2k次。使用说明:开启Google的登陆二步验证(即Google Authenticator服务)后用户登陆时需要输入额外由手机客户端生成的一次性密码。实现Google Authenticator功能需要服务器端和客户端的支持。服务器端负责密钥的生成、验证一次性密码是否正确。客户端记录密钥后生成一次性密码。下载谷歌验证类库文件放到项目合适位置(我这边放在项目Vender下面)https://github.com/PHPGangsta/GoogleAuthenticatorPHP代码示例://引入谷_php otp 验证器

【Python】matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距-程序员宅基地

文章浏览阅读4.3k次,点赞5次,收藏11次。matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距

docker — 容器存储_docker 保存容器-程序员宅基地

文章浏览阅读2.2k次。①Storage driver 处理各镜像层及容器层的处理细节,实现了多层数据的堆叠,为用户 提供了多层数据合并后的统一视图②所有 Storage driver 都使用可堆叠图像层和写时复制(CoW)策略③docker info 命令可查看当系统上的 storage driver主要用于测试目的,不建议用于生成环境。_docker 保存容器

随便推点

网络拓扑结构_网络拓扑csdn-程序员宅基地

文章浏览阅读834次,点赞27次,收藏13次。网络拓扑结构是指计算机网络中各组件(如计算机、服务器、打印机、路由器、交换机等设备)及其连接线路在物理布局或逻辑构型上的排列形式。这种布局不仅描述了设备间的实际物理连接方式,也决定了数据在网络中流动的路径和方式。不同的网络拓扑结构影响着网络的性能、可靠性、可扩展性及管理维护的难易程度。_网络拓扑csdn

JS重写Date函数,兼容IOS系统_date.prototype 将所有 ios-程序员宅基地

文章浏览阅读1.8k次,点赞5次,收藏8次。IOS系统Date的坑要创建一个指定时间的new Date对象时,通常的做法是:new Date("2020-09-21 11:11:00")这行代码在 PC 端和安卓端都是正常的,而在 iOS 端则会提示 Invalid Date 无效日期。在IOS年月日中间的横岗许换成斜杠,也就是new Date("2020/09/21 11:11:00")通常为了兼容IOS的这个坑,需要做一些额外的特殊处理,笔者在开发的时候经常会忘了兼容IOS系统。所以就想试着重写Date函数,一劳永逸,避免每次ne_date.prototype 将所有 ios

如何将EXCEL表导入plsql数据库中-程序员宅基地

文章浏览阅读5.3k次。方法一:用PLSQL Developer工具。 1 在PLSQL Developer的sql window里输入select * from test for update; 2 按F8执行 3 打开锁, 再按一下加号. 鼠标点到第一列的列头,使全列成选中状态,然后粘贴,最后commit提交即可。(前提..._excel导入pl/sql

Git常用命令速查手册-程序员宅基地

文章浏览阅读83次。Git常用命令速查手册1、初始化仓库git init2、将文件添加到仓库git add 文件名 # 将工作区的某个文件添加到暂存区 git add -u # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,不处理untracked的文件git add -A # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,包括untracked的文件...

分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120-程序员宅基地

文章浏览阅读202次。分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120

【C++缺省函数】 空类默认产生的6个类成员函数_空类默认产生哪些类成员函数-程序员宅基地

文章浏览阅读1.8k次。版权声明:转载请注明出处 http://blog.csdn.net/irean_lau。目录(?)[+]1、缺省构造函数。2、缺省拷贝构造函数。3、 缺省析构函数。4、缺省赋值运算符。5、缺省取址运算符。6、 缺省取址运算符 const。[cpp] view plain copy_空类默认产生哪些类成员函数

推荐文章

热门文章

相关标签