freertos内核走读2——task任务调度机制(一)

本文为jorhai原创,转载请注明,谢谢!
Task部分是freertos核心中的核心,本文以代码走读的方式介绍内核,大量贴源码,在添加自己的理解时,保留了源码里的注释。
在开始走读任务调度之前,先抛出几个问题:freertos是如何实现任务调度的;如何保证实时性的;如何实现抢占式的内核的;任务如何通讯,如何阻塞,如何被唤醒;任务的优先级可以动态提升吗;任务可以在什么时候创建,又是如何被删除的呢,等等。这些问题只有走进task.c源码中才能找到答案。

首先介绍下任务存在下面几种状态
运行:此时CPU正在运行任务。
就绪:ready,已经具备执行条件,但是需要CPU进行调度,才能成为运行任务。
阻塞:如果任务当前正在等待某个时序或外部中断,我们就说这个任务处于阻塞状态。比如一个任务调用vTaskDelay()后会阻塞到延时周期到为止。任务也可能阻塞在队列或信号量事件上。进入阻塞状态的任务通常有一个“超时”周期,当事件超时后解除阻塞。
挂起:被暂时打入冷宫的任务,调用vTaskSuspend()可以使任务进入挂起状态。这些任务无法被调度器调度到,除非调用xTaskResume()将任务从挂起状态移除。

Freertos使用TaskHandle_t 标识任务句柄:

typedef void * TaskHandle_t;

TaskHandle_t 其实指向tskTCB——任务上下文,标识一个任务的详细信息和运行时的堆栈等。
在开始介绍tskTCB前,先看一下task.c中的关键全局变量:

  • pxCurrentTCB:记录现在运行的任务;
  • pxReadyTasksLists:记录处于ready状态,等待被调度运行的任务,这是一个链表数组,ready list安装优先级进行分类,这样在调度时就可以从优先级高的readylist中先进行调度。
  • xDelayedTaskList1:定义了一个delay的task链表。
  • xDelayedTaskList2:定义了一个delay的task链表,delay的task链表是指调用了taskdelay()或者因为阻塞动作被延时的任务,延时的单位为tick。Delaylist按照delay的tick时间进行排序,之所以定义了两个,是为了防止xTickCount发生反转时,一个list无法完全标识。
  • xTickCount:无符号数字,标识运行后到现在的系统产生的tick数,每个tick发生时,xTickCount++,当xTickCount发生翻转时,pxDelayedTaskList和pxOverflowDelayedTaskList进行对调,Overflowlist变为正常的delay list。
  • pxDelayedTaskList和pxOverflowDelayedTaskList是链表指针,分包指向xDelayedTaskList1和xDelayedTaskList2。
  • xPendingReadyList:任务进入就绪状态,但是没有放入readylist链表。这种情况发生在调度器被停止时,有些任务进入到ready状态,这时就讲任务加入到xPendingReadyList,等待调度器开始时,从新进行一次调度。
    我们知道任何操作系统都有临界区或者在执行某段代码时不想被打断,防止破坏某些关键操作的完整性。Freertos可以采取多种方式避免,如果是不希望被中断打断,需要调用:
#define taskENTER_CRITICAL()        portENTER_CRITICAL()
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
#define taskEXIT_CRITICAL()         portEXIT_CRITICAL()
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )

这些函数里都是关掉中断,一般是设置中断优先级mask,ICCPMR来暂时屏蔽中断,不同的处理器需要设置configMAX_API_CALL_INTERRUPT_PRIORITY(不同的处理器会设置不同的值,例如arm cotexM值越小优先级越高,而A9相反)来实现中断屏蔽,注意只是暂时的屏蔽,当ICCPMR恢复时,中断还需要再次送到CPU进行响应。
taskENTER_CRITICAL()是最紧急的临界区了,因为中断被禁止后,tick中断和任何硬件中断都不能响应,CPU只能执行taskENTER_CRITICAL()范围内的代码。
taskENTER_CRITICAL和taskENTER_CRITICAL_FROM_ISR()的区别是,taskENTER_CRITICAL在普通任务中调用,而taskENTER_CRITICAL_FROM_ISR需要在中断回调里调用。
Freertos下有很多带from_isr后缀的函数,都是指定在中断回调时调用。当程序进入中断回调时,我们期待其运行方式和普通任务不太一致,例如中断不能进行阻塞等,因此中断在对队列(包括建立在队列机制上的其他操作)进行操作时,需使用带from_isr后缀的函数。
如果说task是内核的骨,那么queue就是freertos内核的架, from_isr后缀的函数几乎全部关联了freertos内核queue相关操作(同样调用queue的带from_isr后缀的函数),这时使用的portSET_INTERRUPT_MASK_FROM_ISR()而不是taskENTER_CRITICAL(),后面再介绍queue时会详细说明。
portENTER_CRITICAL()调用了vPortEnterCritical,当首次进入临界区时,检查ulPortInterruptNesting(中断嵌套层数累计,每次中断时++,中断回调结束时–),如果发现中断嵌套不为0,那么至少有一次中断是发生的,这时候表明你在某个中断回调里其实调用了非from_isr的操作,继而调用了taskENTER_CRITICAL()->vPortEnterCritical,因此会出现断言。

void vPortEnterCritical( void )
{
    /* Mask interrupts up to the max syscall interrupt priority. */
    ulPortSetInterruptMask();

    /* Now interrupts are disabled ulCriticalNesting can be accessed
    directly.  Increment ulCriticalNesting to keep a count of how many times
    portENTER_CRITICAL() has been called. */
    ulCriticalNesting++;

    /* This is not the interrupt safe version of the enter critical function so
    assert() if it is being called from an interrupt context.  Only API
    functions that end in "FromISR" can be used in an interrupt.  Only assert if
    the critical nesting count is 1 to protect against recursive calls if the
    assert function also uses a critical section. */
    if( ulCriticalNesting == 1 )
    {
        configASSERT( ulPortInterruptNesting == 0 );
    }
}

#define portSET_INTERRUPT_MASK_FROM_ISR()       ulPortSetInterruptMask()

另外一种级别稍低的是vTaskSuspendAll和xTaskResumeAll,即通过暂停调度器的方式,保证现在执行的任务不被调度器切走,但是会被中断打断。
当然还可以通过freertos的mutex和lock实现,这些将在后面章节进行说明。

  • xPendingReadyList就是在调度器被暂停时,新的任务进入了ready状态,因此先保存起来,在xTaskResumeAll时再加入到相应的ready list中。
  • xTasksWaitingTermination:表示等待结束的任务,注意是为了释放这些任务的资源,在任务删除时会有详细的介绍。
  • xSuspendedTaskList:表示被暂停的任务列表,调用tasksuspend函数。
  • xIdleTaskHandle:表示空闲任务的句柄,freertos默认为我们创建了一个IDLE函数,一般优先级为0,即最低。
  • uxCurrentNumberOfTasks:目前所有的任务数。
  • xTickCount:系统开始后所有的tick计数。
  • uxTopReadyPriority:记录当前ready list中优先级最高的任务,这个是个32位的无符号整形数据,如果你使能了configUSE_PORT_OPTIMISED_TASK_SELECTION,uxTopReadyPriority使用bit mask的方式标识就绪状态的最高优先级(freertos最大优先级数为32),前提是你的C运行库支持__builtin_clz(返回某个数据的前导0个数),这个功能一般会开启,因此就限制了最大优先级为32。如果不开启configUSE_PORT_OPTIMISED_TASK_SELECTION,最大优先级没有限制,但是会增大ready list占用的资源。
  • xSchedulerRunning:调度器是否运行。
  • uxPendedTicks:前面讲到的vTaskSuspendAll,暂停了调度器,如果这期间tick的timer发送中断,这时uxPendedTicks记录了未被处理的ticks个数。
  • xYieldPending:在某种临界状态下,任务状态发生改变,需要等待从新调度。
  • xNumOfOverflows:记录tick计数翻转的次数。
  • uxTaskNumber:用来记录全局任务数,为新建的任务分配一个task number。
  • xNextTaskUnblockTime:记录了延时链表中,第一个需要被唤醒的任务时间点。
  • uxSchedulerSuspended:调度器暂定标志。
PRIVILEGED_DATA TCB_t * volatile pxCurrentTCB = NULL;

/* Lists for ready and blocked tasks. --------------------*/
PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];/*< Prioritised ready tasks. */
PRIVILEGED_DATA static List_t xDelayedTaskList1;                        /*< Delayed tasks. */
PRIVILEGED_DATA static List_t xDelayedTaskList2;                        /*< Delayed tasks (two lists are used - one for delays that have overflowed the current tick count. */
PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList;             /*< Points to the delayed task list currently being used. */
PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList;     /*< Points to the delayed task list currently being used to hold tasks that have overflowed the current tick count. */
PRIVILEGED_DATA static List_t xPendingReadyList;                        /*< Tasks that have been readied while the scheduler was suspended.  They will be moved to the ready list when the scheduler is resumed. */

#if ( INCLUDE_vTaskDelete == 1 )

    PRIVILEGED_DATA static List_t xTasksWaitingTermination;             /*< Tasks that have been deleted - but their memory not yet freed. */
    PRIVILEGED_DATA static volatile UBaseType_t uxTasksDeleted = ( UBaseType_t ) 0U;

#endif

#if ( INCLUDE_vTaskSuspend == 1 )

    PRIVILEGED_DATA static List_t xSuspendedTaskList;                   /*< Tasks that are currently suspended. */

#endif

#if ( INCLUDE_xTaskGetIdleTaskHandle == 1 )

    PRIVILEGED_DATA static TaskHandle_t xIdleTaskHandle = NULL;         /*< Holds the handle of the idle task.  The idle task is created automatically when the scheduler is started. */

#endif

/* Other file private variables. --------------------------------*/
PRIVILEGED_DATA static volatile UBaseType_t uxCurrentNumberOfTasks  = ( UBaseType_t ) 0U;
PRIVILEGED_DATA static volatile TickType_t xTickCount               = ( TickType_t ) 0U;
PRIVILEGED_DATA static volatile UBaseType_t uxTopReadyPriority      = tskIDLE_PRIORITY;
PRIVILEGED_DATA static volatile BaseType_t xSchedulerRunning        = pdFALSE;
PRIVILEGED_DATA static volatile UBaseType_t uxPendedTicks           = ( UBaseType_t ) 0U;
PRIVILEGED_DATA static volatile BaseType_t xYieldPending            = pdFALSE;
PRIVILEGED_DATA static volatile BaseType_t xNumOfOverflows          = ( BaseType_t ) 0;
PRIVILEGED_DATA static UBaseType_t uxTaskNumber                     = ( UBaseType_t ) 0U;
PRIVILEGED_DATA static volatile TickType_t xNextTaskUnblockTime     = ( TickType_t ) 0U; /* Initialised to portMAX_DELAY before the scheduler starts. */

/* Context switches are held pending while the scheduler is suspended.  Also,
interrupts must not manipulate the xGenericListItem of a TCB, or any of the
lists the xGenericListItem can be referenced from, if the scheduler is suspended.
If an interrupt needs to unblock a task while the scheduler is suspended then it
moves the task's event list item into the xPendingReadyList, ready for the
kernel to move the task from the pending ready list into the real ready list
when the scheduler is unsuspended.  The pending ready list itself can only be
accessed from a critical section. */
PRIVILEGED_DATA static volatile UBaseType_t uxSchedulerSuspended    = ( UBaseType_t ) pdFALSE;

介绍完一些关键的全局变量,在来看任务上下文的定义。

  • pxTopOfStack:定义了栈的顶部地址,当freertos在中断处理或者任务切换时,会对任务的栈进行压栈和出栈,此时pxTopOfStack则指向sp的地址。

  • portUSING_MPU_WRAPPERS:使用MPU时定义。

  • xGenericListItem:一个链表的item,用于将任务添加到不同的链表,比如ready链表delay链表或者其他。
  • xEventListItem:一个链表的item,用于添加到某个事件的list中,比如某个queue有一个wait list记录有多少任务等待queue资源,xEventListItem用于表示任务在等待的具体事件。
  • uxPriority:任务优先级,这个优先级是指任务当前的优先级,以为优先级可能出现提升,因此谨记这个uxPriority是指当前任务运行的优先级。
  • pxStack:记录sp的指针。
  • pxEndOfStack:portSTACK_GROWTH表示栈是向上生长的,因此增加了栈结束指针。
  • uxCriticalNesting:如果在port.c中没有维护临界区的嵌套,那么需要在任务上下文中维护。uxCriticalNesting的具体含义请看上文提到的vPortEnterCritical。
  • uxTaskNumber:configUSE_TRACE_FACILITY定义时,表示freertos开启了trace功能,支持trace
    task states,系统运行信息,每个任务的运行时间。uxTCBNumber协助进行信息打印。

  • configUSE_MUTEXES:配置时使用mutex,mutex的本质是queue,后面讲freertos信号量时会详细说明,mutex支持优先级提升,以防止过度的优先级翻转现象.

  • uxBasePriority:是指优先级提升前,保存下来的原优先级.
  • uxMutexesHeld:表示任务获取的mutex数,当uxMutexesHeld获取数为0时,将其优先级还原到初始优先级,否则该任务优先级为等待mutex的任务中优先级最高者。
  • configUSE_APPLICATION_TASK_TAG和pxTaskTag:为任务分配一个标签,具体的应用场景还需要考证。
  • configNUM_THREAD_LOCAL_STORAGE_POINTERS使能时,允许pvThreadLocalStoragePointers保存任务本地的指针,有pvTaskGetThreadLocalStoragePointer和vTaskSetThreadLocalStoragePointer配合使用。
  • configGENERATE_RUN_TIME_STATS:使能时,ulRunTimeCounter记录了当前任务自运行以来被调度到后执行的时间总和,通过该信息就可以实现获取任务cpu占用率信息
  • configUSE_NEWLIB_REENTRANT:如果使能,使用了newlib
    运行库,为了保证重入性,就不得不为每个任务实现一套struct _reent
    xNewLib_reent;默认使用的glibc,如果你的操作系统资源不够,而不得不选择newlib,就必须打开该宏.

  • configUSE_MUTEXES:配置时使用mutex,mutex的本质是queue,后面讲freertos信号量时会详细说明,mutex支持优先级提升,以防止过度的优先级翻转现象。

任务上下文数据结构如下:

typedef struct tskTaskControlBlock
{
    volatile StackType_t    *pxTopOfStack;  /*< Points to the location of the last item placed on the tasks stack.  THIS MUST BE THE FIRST MEMBER OF THE TCB STRUCT. */

    #if ( portUSING_MPU_WRAPPERS == 1 )
        xMPU_SETTINGS   xMPUSettings;       /*< The MPU settings are defined as part of the port layer.  THIS MUST BE THE SECOND MEMBER OF THE TCB STRUCT. */
        BaseType_t      xUsingStaticallyAllocatedStack; /* Set to pdTRUE if the stack is a statically allocated array, and pdFALSE if the stack is dynamically allocated. */
    #endif

    ListItem_t          xGenericListItem;   /*< The list that the state list item of a task is reference from denotes the state of that task (Ready, Blocked, Suspended ). */
    ListItem_t          xEventListItem;     /*< Used to reference a task from an event list. */
    UBaseType_t         uxPriority;         /*< The priority of the task.  0 is the lowest priority. */
    StackType_t         *pxStack;           /*< Points to the start of the stack. */
    char                pcTaskName[ configMAX_TASK_NAME_LEN ];/*< Descriptive name given to the task when created.  Facilitates debugging only. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */

    #if ( portSTACK_GROWTH > 0 )
        StackType_t     *pxEndOfStack;      /*< Points to the end of the stack on architectures where the stack grows up from low memory. */
    #endif

    #if ( portCRITICAL_NESTING_IN_TCB == 1 )
        UBaseType_t     uxCriticalNesting;  /*< Holds the critical section nesting depth for ports that do not maintain their own count in the port layer. */
    #endif

    #if ( configUSE_TRACE_FACILITY == 1 )
        UBaseType_t     uxTCBNumber;        /*< Stores a number that increments each time a TCB is created.  It allows debuggers to determine when a task has been deleted and then recreated. */
        UBaseType_t     uxTaskNumber;       /*< Stores a number specifically for use by third party trace code. */
    #endif

    #if ( configUSE_MUTEXES == 1 )
        UBaseType_t     uxBasePriority;     /*< The priority last assigned to the task - used by the priority inheritance mechanism. */
        UBaseType_t     uxMutexesHeld;
    #endif

    #if ( configUSE_APPLICATION_TASK_TAG == 1 )
        TaskHookFunction_t pxTaskTag;
    #endif

    #if( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )
        void *pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
    #endif

    #if ( configGENERATE_RUN_TIME_STATS == 1 )
        uint32_t        ulRunTimeCounter;   /*< Stores the amount of time the task has spent in the Running state. */
    #endif

    #if ( configUSE_NEWLIB_REENTRANT == 1 )
        /* Allocate a Newlib reent structure that is specific to this task.
        Note Newlib support has been included by popular demand, but is not
        used by the FreeRTOS maintainers themselves.  FreeRTOS is not
        responsible for resulting newlib operation.  User must be familiar with
        newlib and must provide system-wide implementations of the necessary
        stubs. Be warned that (at the time of writing) the current newlib design
        implements a system-wide malloc() that must be provided with locks. */
        struct  _reent xNewLib_reent;
    #endif

    #if ( configUSE_TASK_NOTIFICATIONS == 1 )
        volatile uint32_t ulNotifiedValue;
        volatile eNotifyValue eNotifyState;
    #endif

} tskTCB;

这些变量需要理解后记忆,结合英文命名,还是比较好记的。
下面的章节会介绍任务的的相关函数操作。

  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值