uC/OSIII时钟节拍处理过程,尤其是调度的过程!

2024-08-31 09:08

本文主要是介绍uC/OSIII时钟节拍处理过程,尤其是调度的过程!,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!


 

时钟节拍涉及到的函数调用过程:
从main开始,创建了第一个任务AppTaskStart,在其一开始执行时,对BSP和CPU进行初始化,调用BSP_CPU_TickInit()函数;
该函数定义在bsp文件夹下的bsp.c文件中,该函数先读取时钟频率,然后按OSCfg_TickRate_Hz值计算应该为CM3的systick定时器设置的到期值。
void BSP_CPU_TickInit (void)
{
    CPU_INT32U cpu_clk_freq;
    CPU_INT32U cnts;
   cpu_clk_freq = BSP_CPU_ClkFreq(); /* Determine SysTick reference freq. */ 
    cnts = (cpu_clk_freq / OSCfg_TickRate_Hz); /* Determine nbr SysTick increments */ 
    OS_CPU_SysTickInit(cnts); /* Initialize the SysTick. */ 
}
这里的OSCfg_TickRate_Hz又在ucosiii\source文件夹的OS_cfg_app.c中被定义为:
OS_RATE_HZ const OSCfg_TickRate_Hz = (OS_RATE_HZ )OS_CFG_TICK_RATE_HZ;
OS_CFG_TICK_RATE_HZ是对用户开放的,在st\eval\ucosiii文件夹的OS_cfg_app.h中被如下定义,如下:
#define OS_CFG_TICK_RATE_HZ 1000u /* Tick rate in Hertz (10 to 1000 Hz) */
在上面的void BSP_CPU_TickInit (void)函数中,调用OS_CPU_SysTickInit(cnts)(这个函数定义在ucosiii\source\MDK的os_cpu_c.c中最后一段),因为该函数直接设置CM3的systick定时器寄存器,属于与编译器相关的:
void OS_CPU_SysTickInit (CPU_INT32U cnts)
{
    CPU_INT32U prio;
    CPU_REG_NVIC_ST_RELOAD = cnts - 1u;  /* Set SysTick handler prio. */
    prio = CPU_REG_NVIC_SHPRI3;
    prio &= DEF_BIT_FIELD(24, 0); /* 将bit32到bit24清0 */
    prio |= DEF_BIT_MASK(OS_CPU_CFG_SYSTICK_PRIO, 24);/* 默认为最高优先级0 */
    CPU_REG_NVIC_SHPRI3 = prio;   /* Enable timer. */
    CPU_REG_NVIC_ST_CTRL |= CPU_REG_NVIC_ST_CTRL_CLKSOURCE |
    CPU_REG_NVIC_ST_CTRL_ENABLE;   /* Enable timer interrupt. */
    CPU_REG_NVIC_ST_CTRL |= CPU_REG_NVIC_ST_CTRL_TICKINT;
}
这里设置完成后,CM3的systick定时器即被开启,会按1000Hz的频率定时产生中断。
中断处理函数在它的同一个文件(os_cpu_c.c)中,它上面几行即是时钟中断处理函数:
void OS_CPU_SysTickHandler (void)
{
    CPU_SR_ALLOC();
   CPU_CRITICAL_ENTER(); 
    OSIntNestingCtr++; /* Tell uC/OS-III that we are starting an ISR */
    CPU_CRITICAL_EXIT();
    OSTimeTick(); /* Call uC/OS-III's OSTimeTick() */
    OSIntExit(); /* Tell uC/OS-III that we are leaving the ISR */
}
可见,时钟到期时,会调用OSTimeTick(),然后会调用OSIntExit()函数。注意OSIntExit()这个函数是专门用于中断退出时做任务调度使用的,它内部做了两个工作:一、找到当前的最高优先级的任务;二、调用PendSV软中断,进行任务切换调度。源码不展开分析了。题外话,PendSV软中断用于任务调度是ucos与CM3珠联璧合的结果。
然后具体分析一下中断处理中的核心OSTimeTick()函数。这个函数被定义在ucosiii\source文件夹下的OS_time.c文件中,位于该文件最后一段。
先讲明该函数的动作:它主要有两部分代码,是选择性编译的。方案一、如果配置了打开中断延迟处理任务(这个开关名字叫OS_CFG_ISR_POST_DEFERRED_EN,在st\eval\ucosiii的os_cfg.h中被定义),就执行 OS_IntQPost(...)向中断延迟处理任务OS_IntQTask()发送一个队列,然后就退出了,不做其他操作,接下来会退出中断。中断延迟处理任务OS_IntQTask() 优先级为0,在中断退出后肯定会被调度,由它再负责向时钟tick处理任务OS_TickTask发信号量,这是后话。方案二、如果没有打开中断延迟处理任务,就要在时钟节拍中断中自己向时钟tick处理任务OS_TickTask发信号量,并且及时更新时间片,和定时器任务,这会占用中断处理时间。
可以看出,方案一的优点就是明显减小了关中断时间,但同时增加了禁止任务调度的时间。这在ucosiii手册中有详细说明,具体是用方案一还是方案二,要看自己的实际需求。另,ucosiii默认为方案二。
void OSTimeTick (void)
{
    OS_ERR err;
    OSTimeTickHook(); /* Call user definable hook */ 
#if OS_CFG_ISR_POST_DEFERRED_EN > 0u   /*如果开启了延迟处理任务,ucosiii特有*/
   ts = OS_TS_GET(); /* Get timestamp */ 
    OS_IntQPost((OS_OBJ_TYPE) OS_OBJ_TYPE_TICK, /* Post to ISR queue */
                (void *)&OSRdyList[OSPrioCur],
                (void *) 0,
                (OS_MSG_SIZE) 0u,
                (OS_FLAGS ) 0u,
                (OS_OPT ) 0u,
                (CPU_TS ) ts,
                (OS_ERR *)&err);
#else         /*如果没有开启延迟处理任务,以下代码全部是在中断中做的,与ucosii相同*/
  (void)OSTaskSemPost((OS_TCB *)&OSTickTaskTCB, /* Signal tick task */ 
                       (OS_OPT ) OS_OPT_POST_NONE,
                       (OS_ERR *)&err);
#if OS_CFG_SCHED_ROUND_ROBIN_EN > 0u
    OS_SchedRoundRobin(&OSRdyList[OSPrioCur]);
#endif
#if OS_CFG_TMR_EN > 0u
    OSTmrUpdateCtr--;
    if (OSTmrUpdateCtr == (OS_CTR)0u) {
        OSTmrUpdateCtr = OSTmrUpdateCnt;
        OSTaskSemPost((OS_TCB *)&OSTmrTaskTCB, /* Signal timer task */
                      (OS_OPT ) OS_OPT_POST_NONE,
                      (OS_ERR *)&err);
    }
#endif
#endif
}
因为方案二是方案一的子集,这里从方案一开始看起:
 OS_IntQPost是从中断中直接向中断延迟处理函数OS_IntQTask (void *p_arg)推送队列。
OS_IntQPost函数被定义在ucosiii\source的os_int.c文件中,(注意这个推送是中断函数普适的,也就是说可以在任何中断中使用,可以传递任意内核对象到任意任务,包括信号量、标志组、队列等等,它里面还为tick处理专门开出一个特例,就是这里重点要讲的东西)它做两件事:一、将要推送的队列消息内容保存到os_int_q队列中,这是一个缓冲队列,可以存放多个推送消息,中断延迟任务将从这里依次取出队列消息进行投递;二、将OS_IntQTask置入就绪队列优先级0的位置上,确保中断退出后接下来就能调度优先级为0的OS_IntQTask处理任务。
源码不贴出来了。下面来看一下方案一从中断退出后执行的操作,退出中断后,因为OS_IntQTask(void *p_arg)处理任务优先级为0是最高,所以一定会执行OS_IntQTask(void *p_arg) (该函数也是在ucosiii\source的os_int.c文件中),注意这里有个比较有意思的事情:通常一个post函数都是对应一个pend函数,但是OS_IntQPost函数向OS_IntQTask中断延迟处理任务推送队列时,在OS_IntQTask中并没有相应的pend函数,这是因为在OS_IntQPost中就将OS_IntQTask置入就绪了,不需要查询任务挂起表(题外话,任务就绪表和任务挂起表,ucosiii中最重要的两个数据结构)。下面看一下OS_IntQTask:
void OS_IntQTask (void *p_arg)
{
    CPU_BOOLEAN done;
    CPU_TS ts_start;
    CPU_TS ts_end;
    CPU_SR_ALLOC();
    p_arg = p_arg; /* Not using 'p_arg', prevent compiler warning */ 
    while (DEF_ON) {
        done = DEF_FALSE;
        while (done == DEF_FALSE) {
            CPU_CRITICAL_ENTER();
            if (OSIntQNbrEntries == (OS_OBJ_QTY)0u) {
                OSRdyList[0].NbrEntries = (OS_OBJ_QTY)0u; /* Remove from ready list */
                OSRdyList[0].HeadPtr = (OS_TCB *)0;
                OSRdyList[0].TailPtr = (OS_TCB *)0;
                OS_PrioRemove(0u); /* Remove from the priority table */
                CPU_CRITICAL_EXIT();
                OSSched();
                done = DEF_TRUE; /* No more entries in the queue, we are done */
            } else {
                CPU_CRITICAL_EXIT();
                ts_start = OS_TS_GET();
                OS_IntQRePost();
                ts_end = OS_TS_GET() - ts_start; /* Measure execution time of tick task */
                if (OSIntQTaskTimeMax < ts_end) {
                    OSIntQTaskTimeMax = ts_end;
                }
                CPU_CRITICAL_ENTER();
                OSIntQOutPtr = OSIntQOutPtr->NextPtr; /* Point to next item in the ISR queue */
                OSIntQNbrEntries--;
                CPU_CRITICAL_EXIT();
            }
        }
    }
}
它的执行过程是:不断的从os_int_q队列中取出消息,执行OS_IntQRePost()函数,该函数也是在ucosiii\source的os_int.c文件中,这个函数os_int_q队列取出消息再按消息type字段和obj字段,将不同的内核对象转发到不同的任务中,直到os_int_q队列为空,把优先级0从就绪表上给摘下来,执行调度OSSched(),接下来就要看哪个就绪的任务优先级高,就调用哪个任务。
OS_IntQRePost()函数从os_int_q队列中取出的消息,本意就是将不同的内核对象转发到不同的任务中,但同时它为时钟tick专门开了一个特殊处理,就是这里重点要讲的,先看一下OS_IntQRePost()函数的源码片断:
switch (OSIntQOutPtr->Type) {
        ...... 
case OS_OBJ_TYPE_TICK:
#if OS_CFG_SCHED_ROUND_ROBIN_EN > 0u
             OS_SchedRoundRobin(&OSRdyList[OSPrioSaved]);
#endif
             (void)OS_TaskSemPost((OS_TCB *)&OSTickTaskTCB, /* Signal tick task */
                                  (OS_OPT ) OS_OPT_POST_NONE,
                                  (CPU_TS ) OSIntQOutPtr->TS,
                                  (OS_ERR *)&err);
#if OS_CFG_TMR_EN > 0u
             OSTmrUpdateCtr--;
             if (OSTmrUpdateCtr == (OS_CTR)0u) {
                 OSTmrUpdateCtr = OSTmrUpdateCnt;
                 ts = OS_TS_GET(); /* Get timestamp */
                 (void)OS_TaskSemPost((OS_TCB *)&OSTmrTaskTCB, /* Signal timer task */
                                      (OS_OPT ) OS_OPT_POST_NONE,
                                      (CPU_TS ) ts,
                                      (OS_ERR *)&err);
             }
#endif
             break;
}
可以看出来,这里的处理过程与方案二中在OS_CPU_SysTickHandler (void)中后半段的处理过程完全一样,向OSTickTaskTCB发送信号量,然后再向OSTmrTaskTCB发送信号量。注意按道理在多个post连用的时候,在第一个post中应当使用OS_OPT_POST_NO_SCHED选项,暂不执行调度,以确保当前任务不被打断,但这里没有这样使用,而是两个post都是使用OS_OPT_POST_NONE选项,这并无问题,因为OS_IntQRePost()函数是在OS_IntQTask (void *p_arg)中执行的,它的优先级是0,没有任务能打断它。至此,两种方案殊途同归了。
再向下分析,就没有难点了,已经向OSTmrTaskTCB发送了信号量,而OSTickTaskTCB对应的是OS_TickTask(void *p_arg)函数(该任务控制块在ucosiii\source文件夹中的os_tick.c中被OS_TickTaskInit (OS_ERR *p_err)函数创建,而该函数在ucosiii\source文件夹中的os_core.c中被调用,同时被调用的还有Idle等系统必备任务),因为是直接向OSTickTaskTCB 任务post了任务信号量,可以大胆推断:该任务将从pend状态转为就绪状态,等待时机被执行。以下是OS_TickTask(void *p_arg)函数的源码,正和猜测的一样(该函数在ucosiii\source文件夹中的os_tick.c中):
void OS_TickTask (void *p_arg)
{
    OS_ERR err;
    CPU_TS ts;
   p_arg = p_arg; /* Prevent compiler warning */ 
   while (DEF_ON) { 
        (void)OSTaskSemPend((OS_TICK )0,
                            (OS_OPT )OS_OPT_PEND_BLOCKING,
                            (CPU_TS *)&ts,
                            (OS_ERR *)&err); /* Wait for signal from tick interrupt */
        if (err == OS_ERR_NONE) {
            if (OSRunning == OS_STATE_OS_RUNNING) {
                OS_TickListUpdate(); /* Update all tasks waiting for time */
            }
        }
    }
}
可以看出,该任务回调函数主要就是调用了在同一文件(os_tick.c)中的OS_TickListUpdate()函数。这个函数负责查询时钟节拍列表中的各个“辐条”,将到期的任务放入任务就绪表中。该函数比较大,这里不列写源码,主要是算法过程,各种查找排序。
经过OS_TickListUpdate()的一顿折腾,该醒来的任务就都就绪了。然后OS_TickTask()遇到花括号,又走入while (DEF_ON)中,继续pend它自己的信号量。这一pend,就进入了ucosIII的调度点,tick处理过程至此结束,可以让位给任务就绪表中优先级最高的其他任务了,这很可能是新就绪的任务。
题外话,提到调度点,ucosiii共有14个调度点,很重要,要写出好的应用,这些调度点应当死记,它们分别是:
1.任务释放信号量给另一个任务,或者向另一个任务发消息(post函数);
2.任务调用延时函数OSTimeDly()或者OSTimeDlyHMSM();
3.任务等待事件发生,而事件还没有发生(pend函数);
4.任务取消等待(pendAbort函数);
5.创建任务;
6.删除任务;
7.删除一个内核对象(信号量、标志组、队列等);
8.任务改变自身的优先级或者其他任务的优先级;
9.任务将自身挂起(OSTaskSuspend函数);
10.解挂某任务(OSTaskResume函数);
11.退出所有的嵌套中断(OSIntExit);
12.调度器解锁(OSSchedUnlock函数);
13.任务放弃自己的时间片(RoundRobinYield);
14.用户自己调用OSSched()函数。
另外,题外话,从ucosiii3.04版本开始,ucos放弃了它一直得意的“辐条”机制,改为回归链条机制。例如一个延迟10tick,一个延迟3tikc,就是一条链上会有两个任务,各自的到期时间使用累加排序算法,为:3+7,这样只从第一个任务每次减1,到期就调走,转入减下一个任务的时间值,确实也很方便,但为啥从ucosii开始它就说是“辐条”机制好呢,自己打自己脸啊。定时器管理的辐条也是这样被无情抛弃了,与时钟节拍辐条改动不同的是,如果一个延迟为10,一个延迟为3,在各自的计数字段里就是10和3,每节拍减1,看谁到期就调用谁,连排序算法都没用。
回归正题,总结一下时钟节拍处理过程:
方案一:OS_CFG_ISR_POST_DEFERRED_EN ==1u
OS_CPU_SysTickHandler ->OSTimeTick-> OS_IntQPost ->OSIntExit退出中断,执行调度 -> OS_IntQTask ->OS_IntQRePost -> OS_TaskSemPost((OS_TCB *)&OSTmrTaskTCB) -> OS_TickTask ->使到期的任务就绪,执行调度。
方案二:OS_CFG_ISR_POST_DEFERRED_EN ==0u
OS_CPU_SysTickHandler -> OSTimeTick->OS_TaskSemPost((OS_TCB *)&OSTmrTaskTCB) -> OS_TickTask ->使到期的任务就绪,因为还在中断中,不会执行调度 ->OSIntExit退出中断,执行调度 。

这篇关于uC/OSIII时钟节拍处理过程,尤其是调度的过程!的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1123467

相关文章

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

作业提交过程之HDFSMapReduce

作业提交全过程详解 (1)作业提交 第1步:Client调用job.waitForCompletion方法,向整个集群提交MapReduce作业。 第2步:Client向RM申请一个作业id。 第3步:RM给Client返回该job资源的提交路径和作业id。 第4步:Client提交jar包、切片信息和配置文件到指定的资源提交路径。 第5步:Client提交完资源后,向RM申请运行MrAp

第10章 中断和动态时钟显示

第10章 中断和动态时钟显示 从本章开始,按照书籍的划分,第10章开始就进入保护模式(Protected Mode)部分了,感觉从这里开始难度突然就增加了。 书中介绍了为什么有中断(Interrupt)的设计,中断的几种方式:外部硬件中断、内部中断和软中断。通过中断做了一个会走的时钟和屏幕上输入字符的程序。 我自己理解中断的一些作用: 为了更好的利用处理器的性能。协同快速和慢速设备一起工作

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

搭建Kafka+zookeeper集群调度

前言 硬件环境 172.18.0.5        kafkazk1        Kafka+zookeeper                Kafka Broker集群 172.18.0.6        kafkazk2        Kafka+zookeeper                Kafka Broker集群 172.18.0.7        kafkazk3

Solr 使用Facet分组过程中与分词的矛盾解决办法

对于一般查询而言  ,  分词和存储都是必要的  .  比如  CPU  类型  ”Intel  酷睿  2  双核  P7570”,  拆分成  ”Intel”,”  酷睿  ”,”P7570”  这样一些关键字并分别索引  ,  可能提供更好的搜索体验  .  但是如果将  CPU  作为 Facet  字段  ,  最好不进行分词  .  这样就造成了矛盾  ,  解决方法

Python:豆瓣电影商业数据分析-爬取全数据【附带爬虫豆瓣,数据处理过程,数据分析,可视化,以及完整PPT报告】

**爬取豆瓣电影信息,分析近年电影行业的发展情况** 本文是完整的数据分析展现,代码有完整版,包含豆瓣电影爬取的具体方式【附带爬虫豆瓣,数据处理过程,数据分析,可视化,以及完整PPT报告】   最近MBA在学习《商业数据分析》,大实训作业给了数据要进行数据分析,所以先拿豆瓣电影练练手,网络上爬取豆瓣电影TOP250较多,但对于豆瓣电影全数据的爬取教程很少,所以我自己做一版。 目

一种改进的red5集群方案的应用、基于Red5服务器集群负载均衡调度算法研究

转自: 一种改进的red5集群方案的应用: http://wenku.baidu.com/link?url=jYQ1wNwHVBqJ-5XCYq0PRligp6Y5q6BYXyISUsF56My8DP8dc9CZ4pZvpPz1abxJn8fojMrL0IyfmMHStpvkotqC1RWlRMGnzVL1X4IPOa_  基于Red5服务器集群负载均衡调度算法研究 http://ww

Golang进程权限调度包runtime

关于 runtime 包几个方法: Gosched:让当前线程让出 cpu 以让其它线程运行,它不会挂起当前线程,因此当前线程未来会继续执行GOMAXPROCS:设置最大的可同时使用的 CPU 核数Goexit:退出当前 goroutine(但是defer语句会照常执行)NumGoroutine:返回正在执行和排队的任务总数GOOS:目标操作系统NumCPU:返回当前系统的 CPU 核数量 p

ORACLE语法-包(package)、存储过程(procedure)、游标(cursor)以及java对Result结果集的处理

陈科肇 示例: 包规范 CREATE OR REPLACE PACKAGE PACK_WMS_YX IS-- Author : CKZ-- Created : 2015/8/28 9:52:29-- Purpose : 同步数据-- Public type declarations,游标 退休订单TYPE retCursor IS REF CURSOR;-- RETURN vi_co_co