Skip to content

使用

创建任务

c
    BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
                            const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
                            const configSTACK_DEPTH_TYPE usStackDepth,
                            void * const pvParameters,
                            UBaseType_t uxPriority,
                            TaskHandle_t * const pxCreatedTask )
  • 函数名
  • 栈的深度
  • 函数的参数
  • 优先级
  • 函数的句柄

使用这一个函数的时候会动态进行分配任务的控制块, 使用malloc函数, 这个函数会分配一块heap的内存, 之后可以使用free函数进行释放, 释放的时候一般在这一段内存的前面会有一块内存保存有这一段内存的长度等信息用于内存管理

c
    TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
                                    const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
                                    const uint32_t ulStackDepth,
                                    void * const pvParameters,
                                    UBaseType_t uxPriority,
                                    StackType_t * const puxStackBuffer,
                                    StaticTask_t * const pxTaskBuffer )

使用的时候需要打开对应的宏

c
vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, &ulIdleTaskStackSize );

StackType_t xIdleTaskStack[100];
StaticTask_t    xIdleTaskTCB;
void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer,
                                    StackType_t ** ppxIdleTaskStackBuffer,
                                    uint32_t * pulIdleTaskStackSize ){
    *ppxIdleTaskTCBBuffer =    &xIdleTaskTCB;
    *ppxIdleTaskStackBuffer  =   xIdleTaskStack;
    *pulIdleTaskStackSize = 100; 
}

需要实现这一个函数, 初始化一个空闲任务

  • 优先级

设置的数值越小优先级越低, 最高为configMAX_PRIORITIES-1

image-20231116094523489

image-20231116094607339

image-20231116094637695

优先级相同的时候

image-20231116094859614

在当前的调度模式下面高优先级的任务会一直霸占CPU

  • 删除函数

可以在其他函数里面进行删除另一个任务

c
/* If null is passed in here then it is the calling task that is
             * being deleted. */
    void vTaskDelete( TaskHandle_t xTaskToDelete );

在参数为NULL的时候会把自己删除掉

使用动态创建的时候最后传入的TCB就是参数, 使用静态创建的时候使用的是返回的参数

  • 栈长度

在任务中保存有这一些函数的参数等信息, 如果溢出就会破坏掉下一块的头部和TCB等信息

延时函数

  • vTaskDelay

任务进去和退出的时间, 时间是固定的, 传入的参数是任务休息的时钟数

image-20231116191401056

执行时间不同, 但是间隔时间相同

  • vTaskDelayUntil

如果希望一个任务在执行的间隔是相等的使用这一个函数, 周期性的执行这一个函数, 当其他函数执行的时间不确定的时候还是可以保证任务间隔不变, 指定一个终点的时间

有两个参数, 使用函数xTaskGetTickCount()函数可以获取当前的时钟, 第一个参数是上面那个函数的返回值为TickType_t类型的, 传入的为指针, 返回的时候会更新里面的值, 第二个参数为下次执行的时间

image-20231116191619004

任务的启动是周期性的

空闲函数以及钩子函数

**测试:**在一个任务里面不断的创建删除任务, 测试堆是否会被消耗完

结果: 程序没有崩溃

**测试:**在一个任务中创建另外一个任务, 在另一个任务里面自己终结自己

结果: 程序崩溃了

一个函数在自己终结自己的时候由于没有办法继续执行, 所以有一些内存没有办法清除, 需要有空闲任务帮助自己清理掉

  • 空闲任务

会在vTaskStartScheduler();函数里面创建, 可以添加一个空闲任务的钩子函数

  1. 执行一些低优先级, 后台的, 需要连续执行的函数
  2. 测量系统的空闲时间, 算出CPU占用率
  3. 让系统进入省电模式

使用一个宏configUSE_IDLE_HOOK

c
#if ( configUSE_IDLE_HOOK == 1 )
{
    extern void vApplicationIdleHook( void );

    /* Call the user defined function from within the idle task.  This
             * allows the application designer to add background functionality
             * without the overhead of a separate task.
             * NOTE: vApplicationIdleHook() MUST NOT, UNDER ANY CIRCUMSTANCES,
             * CALL A FUNCTION THAT MIGHT BLOCK. */
    vApplicationIdleHook();
}

不能让空闲任务进入阻塞或者暂停状态, 建议不要占据太多的时间

image-20231116194041819

在优先级比较高的函数里面使用延时函数, 这时候就不会因为创建内存让所有的内存消耗掉

任务调度算法

  • 是否抢占: configUSE_PREEMPTION
    • 可以: 可抢占调度, 高优先级马上执行
      • 不可以, 成为合作调度模式
        • 当前任务执行的时候高优先级的任务不能马上执行, 只能等待当前的任务让出CPU
          • 其他优先级相同的任务也只能等待, 更高优先级的任务也不能抢占, 平级的更不可以了
  • 在可以抢占的时候其他的任务是否可以执行configUSE_TIME_SLICING时间片轮转
    • 不使用的时候就会一直执行到一个任务主动放弃CPU
  • 时间片轮转的时候空闲任务会不会进行礼让configIDLE_SHOULD_YIELD
    • 使用的时候当有与空闲任务相同优先级的任务时候, 空闲任务会主动触发调度, 礼让给其他的函数
c
    #if ( ( configUSE_PREEMPTION == 1 ) && ( configIDLE_SHOULD_YIELD == 1 ) )
    {
        /* When using preemption tasks of equal priority will be
         * timesliced.  If a task that is sharing the idle priority is ready
         * to run then the idle task should yield before the end of the
         * timeslice.
         *
         * A critical region is not required here as we are just reading from
         * the list, and an occasional incorrect value will not matter.  If
         * the ready list at the idle priority contains more than one task
         * then a task other than the idle task is ready to execute. */
        if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ tskIDLE_PRIORITY ] ) ) > ( UBaseType_t ) 1 )
        {
            taskYIELD();
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    #endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configIDLE_SHOULD_YIELD == 1 ) ) */

描述一个任务

使用面向对象的思想, 使用一个结构体描述任务

c
/*
 * Task control block.  A task control block (TCB) is allocated for each task,
 * and stores task state information, including a pointer to the task's context
 * (the task's run time environment, including register values)
 */
typedef struct tskTaskControlBlock       /* The old naming convention is used to prevent breaking kernel aware debuggers. */
{
    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. */
    #endif

    ListItem_t xStateListItem;                  /*< 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 ) || ( configRECORD_STACK_HIGH_ADDRESS == 1 ) )
        StackType_t * pxEndOfStack; /*< Points to the highest valid address for the stack. */
    #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 )
        configRUN_TIME_COUNTER_TYPE ulRunTimeCounter; /*< Stores the amount of time the task has spent in the Running state. */
    #endif

    #if ( ( configUSE_NEWLIB_REENTRANT == 1 ) || ( configUSE_C_RUNTIME_TLS_SUPPORT == 1 ) )
        configTLS_BLOCK_TYPE xTLSBlock; /*< Memory block used as Thread Local Storage (TLS) Block for the task. */
    #endif

    #if ( configUSE_TASK_NOTIFICATIONS == 1 )
        volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
        volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
    #endif

    /* See the comments in FreeRTOS.h with the definition of
     * tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE. */
    #if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e731 !e9029 Macro has been consolidated for readability reasons. */
        uint8_t ucStaticallyAllocated;                     /*< Set to pdTRUE if the task is a statically allocated to ensure no attempt is made to free the memory. */
    #endif

    #if ( INCLUDE_xTaskAbortDelay == 1 )
        uint8_t ucDelayAborted;
    #endif

    #if ( configUSE_POSIX_ERRNO == 1 )
        int iTaskErrno;
    #endif
} tskTCB;

任务的切换原理

会使用一个tick中断, 在运行的时候后创建的任务会先运行

c
#define configTICK_RATE_HZ			( ( TickType_t ) 1000 )

配置切换任务的时间

  • 任务的状态
  1. 运行状态
  2. ready状态: 等待运行
  3. 阻塞状态: 等待某事发生以后运行
  4. 暂停状态: 主动(被动)休息

image-20231116105641774

事件可以是中断也可以是其他函数

事件有两类

  • 时间相关的
    • 设置超时时间, 在指定时间里面阻塞
      • 使用时间相关的函数, 可以实现周期性的功能
  • 同步事件
    • 在任务等待某些信息, 别的任务或者中断中的任务会给他发送信息
      • 怎么发送
        • 任务通知
          • 队列
          • 事件组
          • 信号量
          • 互斥量
  • 管理

使用链表

属于某一个状态的任务会在某一个链表里面

同步与互斥的概念

同步是指在多个进程或线程之间协调执行的方式。同步的目的是为了保证程序以预期的方式运行,确保多个进程或线程协同工作,避免出现竞争条件,比如一个线程在另一个线程还没有完成相关操作之前完成了操作。

互斥是一种同步机制,用于控制并发进程或线程对共享资源的访问。互斥允许一个进程或线程独占资源,其他进程或线程必须等待资源释放后才能访问。互斥锁是一种互斥机制,用于保护数据结构,控制对共享资源的并发访问,避免出现数据竞争和不一致性问题。

综上所述,同步和互斥是一种针对多进程或线程间协作的机制,同步可以保证多个进程或线程按照一定的顺序协调工作,而互斥可以保证多个进程或线程访问共享资源时不会发生冲突。

  • 具体的问题

多个任务使用串口的时候打印的信息会缠在一起

使用全局变量的时候会出现的问题

c
if(!flog)
{
    //如果在这里切换任务就会出问题
    flog = 1;
    任务
    flog = 0;
    vTaskDelay(1);//保证其他的任务可以执行,否则会因为立即进入循环而循环的时间比较长导致这一个任务一直使用
}

需要实现

  • 正确性
  • 效率: 等待的时候进入阻塞
  • 多种解决方式
  • 具体的实现
  1. 队列, FIFO
  2. 事件组event group, 使用一个变量的不同位表示不同的事件状态, 任务等待若干个事件的状态
  3. 信号量: 使用一个计数值, 对计数值加一或者减一
  4. 任务通知
  5. 互斥量

文件

每一个版本的FreeRTOS里面都有一个protmacro.h文件, 里面有两个数据类型

  • TickType_t:
    • 配置了一个周期性的时钟中断Tick Interrupt
      • 每一次发生时钟中断, 这一个数值就增加一下tick count
      • 这个变量的类型就是上面的TickType_t
      • 可以是16位或者是32位的
      • 对于32位的架构建议配置为32位
  • BaseType_t
    • 架构最高效的数据类型
      • 32位里面就是uint32_t
      • 通常作为简单的函数返回值, 还有逻辑值pdTRUE/pdFALSE

变量名

变量使用前缀名加上一个描述符

函数名则使用返回类型, 存在的文件以及函数名, prv=>私有函数

image-20231115215329139

函数名

包括前缀, 文件名, 函数名

image-20231115215402882

宏的前缀是文件名

信号量的函数是一个宏定义, 但是是遵循函数名的命名方法, 有一个小写的前缀

image-20230706103041491

image-20231115215418497