ucos在s3c2410上运行过程整体剖析--多任务调度及运行

发布者:rho27最新更新时间:2017-01-19 来源: eefocus关键字:ucos  s3c2410  任务调度 手机看文章 扫描二维码
随时随地手机看文章

直接开始说明ucos创建任务时的步骤:

1,  初始化任务堆栈

2,  初始化任务控制块

3,  把刚创建的任务设置为就绪态(即置位就绪表)

上面提到的任务堆栈,控制块,就绪表我们前面已经说过了,下面就直接看代码。

INT8U  OSTaskCreate (void (*task)(void *pd), void *p_arg, OS_STK *ptos, INT8U prio)

{

    OS_STK    *psp;

    INT8U      err;

#if OS_CRITICAL_METHOD == 3                  /* Allocate storage for CPU status register               */

    OS_CPU_SR  cpu_sr;

    cpu_sr = 0;                              /* Prevent compiler warning                               */

#endif   

#if OS_ARG_CHK_EN > 0

    if (prio > OS_LOWEST_PRIO) {             /* Make sure priority is within allowable range           */

        return (OS_PRIO_INVALID);

    }

#endif

    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 *)1;    /* Reserve the priority to prevent others from doing ...  */

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

        OS_EXIT_CRITICAL();

        psp = (OS_STK *)OSTaskStkInit(task, p_arg, ptos, 0);    /* Initialize the task's stack *///初始化任务的堆栈

        err = OS_TCBInit(prio, psp, (OS_STK *)0, 0, 0, (void *)0, 0);

        if (err == OS_NO_ERR) {

            if (OSRunning == TRUE) {         /* Find highest priority task if multitasking has started */

                OS_Sched(); //如果创建任务时ucos已经开始任务调度,那么创建完任务后需要进行任务调度

            }

        } else {

            OS_ENTER_CRITICAL();

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

            OS_EXIT_CRITICAL();

        }

        return (err);

    }

    OS_EXIT_CRITICAL();

    return (OS_PRIO_EXIST);

}

下面是初始化堆栈的函数:

OS_STK * OSTaskStkInit (void (*task)(void *pd), void *pdata, OS_STK *ptos, INT16U opt)

{//函数需要4个参数,一个指向任务函数的指针,任务运行时需要的参数,堆栈指针,扩展参数

    unsigned int * stk;

 

    stk    = (unsigned int *)ptos;          /* Load stack pointer */

    //USE_ARG(opt);

    opt++;

 

    /* build a stack for the new task */

    *--stk = (unsigned int) task;      /* pc */

    *--stk = (unsigned int) task;      /* lr */

    *--stk = 12;                       /* r12 */

    *--stk = 11;                       /* r11 */

    *--stk = 10;                       /* r10 */

    *--stk = 9;                        /* r9 */

    *--stk = 8;                        /* r8 */

    *--stk = 7;                        /* r7 */

    *--stk = 6;                        /* r6 */

    *--stk = 5;                        /* r5 */

    *--stk = 4;                        /* r4 */

    *--stk = 3;                        /* r3 */

    *--stk = 2;                        /* r2 */

    *--stk = 1;                        /* r1 */

    *--stk = (unsigned int) pdata;     /* r0 */

    *--stk = (SUPMODE);                          /* cpsr */

    *--stk = (SUPMODE);                          /* spsr */

 

    return ((OS_STK *)stk);

}

关于堆栈,我们前面已经讲过,这里用的即递减的满堆栈。

对于任务,其实就是一个无限循环的函数,那怎么控制它的运行那,这就是操作系统要干的活,操作系统根据调度算法实现对任务的调度以及任务的切换。实现了多个任务共享cpu。

我们已经知道,堆栈对任务的重要性,一:c语言执行需要堆栈空间。二:当发生任务切换时需要把程序运行的现场保存到任务的堆栈中。

也就是说,任务堆栈中应该保存的是任务运行时函数调用的情况以及被打断时的状态信息,可是问题来了,我们刚创建一个任务时,这个任务并没有运行过呀。这个好办,我们就模拟这个任务被打断过的迹象,任务没执行过,那么这个函数调用栈帧就不复存在。我们只模拟函数运行环境的保存。看上面代码,我们首先保存的是PC和LR,因为任务函数还没有执行过,因此这个PC和LR就应该是函数的首地址,也就是函数的名称指针。比如说你定义了一个任务函数

void Task1(void *Id)

{

       for(;;){

              printf("run task1\n");

              OSTimeDly(1000);

       }

}

那就把Task1这个函数指针赋给PC和LR。接着是R1~R12这些通用寄存器,由于函数还没有执行过,这些通用寄存器的值是什么就不太重要,可以随便赋值,你看,这里就是给R1赋值1,给R2赋值2 …………  给R12赋值12。当然你也可以给这些寄存器赋其他值,这些无关紧要,但当任务运行过后,那再保存程序执行现场时就要按章程来了,即这些寄存器被切换的时候里面的值是什么就应该保存什么。下面就要初始化CPSR和SPSR了,这两个值要根据你的操作系统要运行在处理器的那种模式下,任务运行时应该开中断的。你像我们这个把CPSR的值赋成SUPMODE(这个宏的值是0x13),就是说这个任务运行时在SVC模式下并且开中断。咱们原来说过ucos初始化过程CPSR的中断一直是关着的,CPSR的中断位就是当最高优先级任务运行时就已经开中断了。

一句话说那,现在初始化任务堆栈就是未雨绸缪。

所以,“初始化堆栈”实际上是做了一个“未雨绸缪”的工作。这个过程中有两点是必须慎重考虑的,一是PC该如何定位,二是CPU的其它寄存器(除PC之外)该怎么处理。先说第一点,因为任务是第一次运行,而任务从实质上将就是一段代码,所以PC指针应该定位到这段代码的第一行处,即所谓的入口地址(Entry Point)处,这个地址由任务指针保存着,所以把该指针值赋给PC即可。第二,这段代码还未被履行过,所以代码中的变量与CPU的其它寄存器一点关系也没有,因此R0-R12,R14可随便给值,或者不赋值也可,让这些寄存器保持原来的值,显然后者更为简单。最后再给CPSR赋值,用户可以根据实际需要使系统运行于系统模式或管理模式。

 

关于初始化任务控制块的代码,我想我们有了前面的有关TCB的相关知识应该很容易就会理解。我们说我们创建了一个任务,其唯一标识就是新创建了一个任务控制块。对这个任务控制块的初始化操作就是对里面的各个选项赋予相应的值而已。至于每一个选项的具体意义,源码里都有详细的说明,要是不懂可以参考邵贝贝翻译的书。

 

任务创建完成后,要把这个任务置为就绪态。关于怎么置位就绪表是怎么实现的,我也不想多说,前面介绍的就绪表的实现原理要仔细看。你懂了原理,实现就是用c语言的位操作相关的知识。包括或操作、与操作等,自己看源码哦!

 

现在我们已经知道了创建任务的方法。

我们假设我们共创建了4个任务,一个系统级任务,两个用户任务,还有一个空闲任务。

 

下面我们就说明一下,这个ucos是怎么开始多任务调度的。

这个系统级任务,就是任务优先级最高的任务,ucos一开始调度会找到优先级最高的任务并开始运行,这个系统级任务有它自己的特殊任务,就是一,把OSRunning置成TRUE,标志ucos开始调度运行。二:开启定时器,并开启定时器中断响应。

 

这是最系统级任务的运行代码:

static void SYS_Task(void *Id)

{

       OSRunning=TRUE;       //begin OS

 

       uHALr_InstallSystemTimer();

 

       printk("start system task.\n");

       for (;;)

       {

              OSTimeDly(1000);

       }

}

不过,创建完这个任务并不会执行,而是等到ucos操作系统调度到这个任务时,它才开始真正运行。

我们创建任务完毕后,会调用OSStart,多任务调度开始函数

 

void  OSStart (void)

{

    INT8U y;

    INT8U x;

 

 

    if (OSRunning == FALSE) {

        y             = OSUnMapTbl[OSRdyGrp];  /* Find highest priority's task priority number   */

        x             = OSUnMapTbl[OSRdyTbl[y]];

        OSPrioHighRdy = (INT8U)((y << 3) + x);

        OSPrioCur     = OSPrioHighRdy;

        OSTCBHighRdy  = OSTCBPrioTbl[OSPrioHighRdy]; /* Point to highest priority task ready to run    */

        OSTCBCur      = OSTCBHighRdy;

        OSStartHighRdy();                  /* Execute target specific code to start task     */

    }

}

这个函数的主要任务就是找到最高优先级的任务的控制块,然后根据创建任务时的堆栈内容,把堆栈里的内容恢复到各个寄存器中,那这最高优先级的任务就开始真正运行了。这时ucos操作系统就掌握了整体的运行了。

 

关于怎么找到的最高优先级,是根据OSRdyGrp和OSRdyTbl[]这两个变量,至于方法,我们在就绪表的相关内容一节已经讲解。知道了最高优先级就知道了最高优先级任务的TCB,知道了TCB就得到了任务堆栈。把此任务的任务堆栈恢复到各个寄存器,这个任务就开始真正运行了。

OSStartHighRdy();  这个函数是用汇编写的,很简单就是恢复寄存器现场。

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

 

         STR  r5, [r4]                 ; set new current task TCB address

 

         LDMFD    sp!, {r4}             ; YYY

         MSR SPSR_cxsf, r4

         LDMFD    sp!, {r4}             ; get new state from top of the stack

         MSR CPSR_cxsf, r4              ; CPSR should be SVC32Mode

         LDMFD    sp!, {r0-r12, lr, pc }     ; start the new task

代码要自己好好分析,不要调皮哦。

好了,现在已经是优先级最高的任务开始运行了,它要干什么那?前面已经把代码贴出来了。

下面就说说这个定时器,哈。

 

前面说了,这个ucos操作系统的核心就是定时器,定时器就是ucos的心脏。

 

进程切换的原因很多,如一个进程要等待一个资源但资源还没有出现,那这个进程要被挂起,让另一个进程来执行。我们说一个最普遍的引起任务切换的原因。时间,在操作系统里有很多控制都是基于时间的。比如,你一个任务执行时什么资源都不缺少,那我可不能让你一直运行下去,你一直运行下去的话,就违背了的操作系统设计的初衷。所以任何任务都有时间限制。时间一到就进行切换。那时间谁记那?当然是定时器了,定时器隔固定的时间产生相应的中断来提示操作系统运行了多长时间。嘻嘻,时钟中断就好像操作系统的心脏跳动一样。

 

在我们现在的状态下,已经创建的任务除了处理器之外不用等待其他资源,任务主动放弃cpu的使用权是产生调度的一个原因,这时候发生的是任务级任务切换。比如在ucos中有一个任务延时函数,这个函数会让当前任务挂起并延时指定的时间。那谁记录这些时间那,当然是定时器,当时间到了会产生任务切换,当然这时候是中断级的任务切换。

 

既然定时器如此重要,我们看看ucos是怎么利用定时器的中断服务程序来实现这些功能的。

 

首先,定时器中断的入口是arm的外部中断入口。当定时器定时时间到时,就产生定时器中断,通过判断中断定时器的内容来跳转到指定的中断服务程序里去执行。至于很多中断源都通过一个入口,怎么区分到底是哪个中断来了那,这是arm中断控制器的相关章节内容。Datasheet第十四章 interrupt controllor 。

 

我们前面提到过,ucos这个工程中为了统一,把所有的外部中断服务程序都写到不同的中断结构体里了。这些中断结构体在ucos初始化的时候有一些简单的初始化。

在系统级任务里调用了这样一个函数:

SetISR_Interrupt(IRQ_TIMER4, TimerTickHandle, NULL);

这个函数就实现了对定时器有关的中断服务程序的初始化操作,并且打开了定时器中断的掩码寄存器标志位。我们知道,一开始我们的arm的cpsr的中断是禁止的,这个是在系统级任务运行时就打开了的(还记得未雨绸缪时的堆栈cpsr的内容不)。现在又打开了定时器中断的掩码寄存器标志位,好了,原来的双层保护都打开了,从现在开始ucos就开始响应定时器中断了。那中断服务程序都干了些什么那?

看下面代码 ,

unsigned int irq=GetISROffsetClr();  //得到中断向量的偏移地址

 

       irq=fixup_irq(irq);

 

       if(irq>=NR_IRQS)

              return;

       if(InterruptFunc[irq].InterruptHandlers==NULL){

              InterruptFunc[irq].ack_irq(irq);  //clear pending

              return;

       }

 

       OSIntEnter();

        // Call interrupt service routine

       InterruptFunc[irq].InterruptHandlers(irq, InterruptFunc[irq].data);

       InterruptFunc[irq].ack_irq(irq);  //clear pending

 

       OSIntExit();

}

得到中断服务程序的偏移量,即确定是什么外部中断。然后执行响应的中断服务程序,然后进入ucos的中断OSIntEnter();,在这里没干什么,只是对OSIntNesting这个变量加一。然后OSIntExit();,在这里面干的就多了,如果有更高的优先级的任务就绪,那直接切换到高优先级任务去执行,即在这发生中断级任务切换。

最后推出中断。

那定时器中断服务程序(就是那个OSTimeTick()函数)具体都干什么那?

下面是部分代码:

ptcb = OSTCBList;                                  /* Point at first TCB in TCB list               */

        while (ptcb->OSTCBPrio != OS_IDLE_PRIO) { /* Go through all TCBs in TCB list              */

            OS_ENTER_CRITICAL();

            if (ptcb->OSTCBDly != 0) {             /* No, Delayed or waiting for event with TO     */

                if (--ptcb->OSTCBDly == 0) {     /* Decrement nbr of ticks to end of delay       */

                                      /* Check for timeout                            */

                    if ((ptcb->OSTCBStat & OS_STAT_PEND_ANY) != OS_STAT_RDY) {

                        ptcb->OSTCBStat   &= ~OS_STAT_PEND_ANY;  /* Yes, Clear status flag   */

                        ptcb->OSTCBPendTO  = TRUE;      /* Indicate PEND timeout    */

                    } else {

                        ptcb->OSTCBPendTO  = FALSE;

                    }

 

                    if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY) {  /* Is task suspended?       */

                        OSRdyGrp               |= ptcb->OSTCBBitY;  /* No,  Make ready */

                        OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;

                    }

                }

            }

            ptcb = ptcb->OSTCBNext;       /* Point at next TCB in TCB list                */

            OS_EXIT_CRITICAL();

 

通过阅读,简单分析你就可以知道这个定时器中断服务程序,就是对所有的通过调用延时函数的任务的TCB进行对OSTCBDly进行减一操作。延时函数OSTimeDly干的活就是把当前任务挂起,并给这个任务的TCB中的OSTCBDly赋值。当这个值减到一就说明这个任务的延时时间到了,把这个任务再置成就绪态,等待调度。

 

综上所述,我们知道了。我们现在创建的任务都是自己调用OSTimeDly();函数自动放弃cpu的使用权,然后通过定时器中断函数计时,当延时时间到时把任务置为就绪态,等待调度和切换。

在这个例子中,就有两种产生调度的原因,一:任务自愿调用OSTimeDly()产生了一次任务调度和切换,定时器中断服务程序负责对延时计时,当时间到时,置位就绪表,并产生任务调度和切换。

 

关于这四个任务的运行情况,可以参考我利用程序打印的代码来说明分析:

多任务调度实例.TXT

可以发现,其实程序一直都被定时器打断,还有计时cpu大部分时间都被空闲任务占有。因为我们的那三个任务都是执行一段代码后就延时。而空闲任务不延时。它都是在定时器中断服务程序里被迫的让出cpu使用权。请仔细分析这个多任务调度实例,在头脑中形成一个宏观现象,有利于对ucos的整体掌握。

 

下章,我们仔细分析一下这个任务级任务切换和中断级任务切换时怎么在arm9上实现的,因为这两个函数都是用汇编书写的,又和硬件有一些联系,可能比较难理解一些。我们做详细讲解。下章见。


关键字:ucos  s3c2410  任务调度 引用地址:ucos在s3c2410上运行过程整体剖析--多任务调度及运行

上一篇:ucos在s3c2410上运行过程整体剖析-- 整体运行环境及工具说明
下一篇:ucos在s3c2410上运行过程整体剖析-从main函数到UCOS初始化完毕

推荐阅读最新更新时间:2024-03-16 15:31

基于S3C2410微处理器和Linux实现嵌入式网关的设计
介绍了一种基于ARM9的嵌入式以太网的设计方案。基于ARM内核微处理器S3C2410的功能特点及其外扩组成部分,分析了以太网控制器芯片RTL8019AS的性能以及与S3C2410的接口。在软件设计上,分析了BootLoader的作用,介绍了Linux的移植,并给出了通信程序的流程图,实现了嵌入式以太网的数据传输。 无所不在的网络给网络接入设备带来了巨大的发展机遇。而随着网络接入市场的迅速增长,嵌入式网络接入开始成为嵌入式系统技术中最令人关注的一个领域。在嵌入式网络技术的推动下将会形成这样一个局面:在网络上传输的信息中,将有70%的信息来自嵌入式系统,也许将有数以亿计的汽车、通信设备、家用电气以及工厂系统接入到不同网络中,然后再
[单片机]
基于<font color='red'>S3C2410</font>微处理器和Linux实现嵌入式网关的设计
uCOS-II的中断-ARM7实现中断嵌套的方法探究
在uCOS-II,或者是任何一个可剥夺型OS系统中,中断嵌套是一个必须要解决的问题。从结论上来说,并不是所有的CPU都支持中断嵌套的,即便是ARM系列内核。对于ARM7系列,例如LPC2xxx系列芯片,硬件上是不支持中断嵌套的,而对于新的CortexM3系列,中断嵌套是可配置的,但是中断嵌套时保存现场的操作并不完整,并没有把R0~R15所有寄存器都保存到堆栈中,而是只保存R0~R4。这就需要我们手动软件实现全部或部分的中断现场保护机制。不过首先通过分析ARM7系列芯片来看看中断嵌套的硬件要求。 ARM7系列的内核一共有7中模式,如下图所示: 其中 (1) User Mode:用户模式。唯一非特权模式,可执行指令受限。(读写
[单片机]
<font color='red'>uCOS</font>-II的中断-ARM7实现中断嵌套的方法探究
S3C2410:DMA介紹
之所以要介绍DMA,因为它对性能太重要了!只有活用了DMA,CPU的性能才能上去!S3c2410有四个DMA,每个DMA支持工作方式基本相同,但支持的source Dest可能略有不同,具体见Datasheet。 这里具体DMA CONTROL寄存器(DCON)的配置说明,进而引出DMA的各种工作方式。 Atomic transfer:指的是DMA的单次原子操作,它可以是Unit模式(传输1个data size),也可以是burst模式(传输4个data size),具体对应DCON 。 Data Size:指的是单次原子操作的数据位宽,8、16、32,具体对应DCON 。 Request Source:DMA请求的来源有
[单片机]
基于S3C2410的智能家居数据采集系统设计
O 引言 智能家居是以住宅为平台,兼备建筑设备、网络通信、信息家电和设备自动化,集系统、结构、服务、管理为一体的高效、舒适、安全、便利、环保的居住环境。它利用先进的计算机技术、网络通信技术和综合布线技术,将与家居生活有关的各种系统有机地结合在一起,通过统筹管理,让家居生活更加舒适、安全。家庭自动化、家庭网络、网络家电、信息家电等产品都属于智能家居系统产品。 数据的采集、处理以及传输是实现智能小区控制作用的核心。在此设计了利用嵌入式系统作为开发平台,利用TCP/IP协议作为信息传输方式的业主基本信息数据采集的方案。 由于Internet的发展和普及,采用TCP/IP协议简单、方便、成本低,开放性好,标准化程度高。物业管理
[测试测量]
基于<font color='red'>S3C2410</font>的智能家居数据采集系统设计
怎样数s3c2410的interrupt controller中的中断源数量
通过数据手册的INTERRUPT SOURCES即可看出所有中断源,其中Descriptions列有讲述,带多个中断源的用括弧表述出来了。 以下中断源寄存器通过各自MAST连接到INTPND(只能一个位置一) SRCPND(可有多个位置一) 0~31共32-2(保留)=30个中断源——其实是56个中断源 上述的28个中断源中包括4个带子中断的中断源 INT_UART0(INT_RXD0,INT_TXD0,INT_ERR0) INT_UART1(INT_RXD1,INT_TXD1,INT_ERR1) INT_UART2(INT_RXD2,INT_TXD2,INT_ERR2) INT_ADC (INT_ADC/INT_TC) SUB
[单片机]
基于ARM的多人对战游戏平台
  游戏不仅能开发人的智力,使人头脑反应灵敏,还能满足人的精神需求(如冒险、创造力、情感等),极具娱乐性和趣味性,深受人们的喜爱。随着消费类电子产业的蓬勃发展,越来越多的嵌入式电子产品走进了千家万户,催生出了诸如GBA(Game Boy Advance)、PSP(Play-Station Portabk)以及最近才在我国上市的iPad等一大批专业的并且销量惊人的明星级移动娱乐游戏设备。   然而上述游戏平台通常造价昂贵,且不具有开放性。例如备受推崇的PSP,开发授权问题和昂贵的专用开发套件(软硬件)使得PSP游戏的开发门槛很高。这在很大程度上限制了这些游戏平台的普及。如果利用通用的处理器和常用的嵌入式操作系统(如WinCE、Li
[单片机]
基于ARM的多人对战游戏平台
s3c2410 s3c2440 有何区别
三星2440 16/32-bit RISC 微处理器. 三星2440是一款专用的以手持设备为主而设计的芯片,其特点有低功耗, 高速的处理计算能力. 为了减少系统的耗费,2440使用了如下组件: 2440基于ARM920T内核的,0.13Um cmos 标准单元和存储单元复合体. 它功耗及小,简单,稳定的设计非常适合对电源要求较高的产品上. 她采用了新的总线构架(AMBA). 2440提供了杰出的特性,因为其内核为32bit的先进处理器。 The arm920T 实现了mmu,amba bus,and 哈佛缓存体系构架 通过分离的16kb 指令缓存 and 16kb data caches(采用8 -word line length)
[单片机]
ucoss3c2410上运行过程整体剖析之基础知识--ADS编译、链接器
我在学习嵌入式操作系统UCOS时,有很大的迷茫之处,在于我不知道我用的工具到底帮我干了些神马工作。下面就说一下我当时的疑问: 1:编译器和链接器干了些神马?我现在也还没能力知道它是怎么干的这些工作。以后慢慢来呗。但当时我连它干了神马都不清楚。 2:它输入的是源程序,那他输出的是什么?有格式吗?是什么样子那? 重重疑问。 ADS编译器的目的是怎么把高级语言编程能在ARM平台上直接运行的东东。 而我们的目的是看看他干了些神马,让我们了解一下它以及他链接出来的东东是什么样子。对嵌入式整个工程开发做了哪些贡献,这样我们就能更好的使用它了。是不是。 关于ARM嵌入式开发的要点和步骤请参考 基于ARM的嵌入式系统程序开发要点.pdf ,这
[单片机]
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved