网站首页代码在哪里,wordpress suxing,宁波网站建设的公司,酒店网站方案1、前言 本文是以RISC-V架构为例进行讲解#xff0c;在汇编代码层面和ARM架构不一样#xff0c;但是整体框架是一样的侧重任务调度底层机制讲解#xff0c;讲解代码只保留了基本功能#xff0c;可配置的功能基本都已经删除本文是以可抢占式调度机制进行讲解RISC-V架构只支持…1、前言 本文是以RISC-V架构为例进行讲解在汇编代码层面和ARM架构不一样但是整体框架是一样的侧重任务调度底层机制讲解讲解代码只保留了基本功能可配置的功能基本都已经删除本文是以可抢占式调度机制进行讲解RISC-V架构只支持M模式并且中断只处理时间中断和ecall调用其余异常没有相应的处理代码想要更好理解任务调度机制最好先去了解freertos的链表因为任务切换涉及链表操作 2、任务状态切换 任务创建好后处于就绪态每次任务调度时在就绪态任务中选择最高优先级的任务执行任务因为等待某个事件、休眠而变成阻塞态休眠时间到、等待的时间发生会从阻塞态变为就绪态任务执行vTaskSuspend函数进入挂起态必须由其他任务调用vTaskResume唤醒进入就绪态
3、任务控制块TCB
typedef struct tskTaskControlBlock
{//记录任务栈空间中的栈顶切换任务时从这里开始获取/保存任务运行现场。必须是任务控制块的第一个元素这个任务切换有关volatile StackType_t * pxTopOfStack; ListItem_t xStateListItem; //用来把任务控制块挂接到不同状态的任务链表中比如就绪链表、挂起链表、 阻塞链表 ListItem_t xEventListItem; //当任务因为等待某个时间事件而阻塞时将任务控制块挂接到对于事件的阻塞链表事件发生时会唤醒对应链表 UBaseType_t uxPriority; //优先级 StackType_t * pxStack; //任务的栈空间这里记录的是栈空间的最低地址 char pcTaskName[ configMAX_TASK_NAME_LEN ]; //任务的名字 } tskTCB;4、任务的创建
4.1、任务创建函数的参数分析 pxTaskCode任务函数的地址pcName任务的名字usStackDepth任务的栈大小这里的单位是字而不是字节pvParameters任务函数的传参uxPriority任务的优先级pxCreatedTask返回的任务句柄也就是构建的TCB结构体
4.2、任务创建函数分析 prvInitialiseNewTask函数 初始化栈空间将栈空间内容初始化成特殊值保存任务名字到pcTaskName变量保存任务优先级到uxPriority变量初始化链表包括状态链表和事件链表初始化任务上下文pxPortInitialiseStack函数
prvAddNewTaskToReadyList函数 如果是创建的第一个任务要初始化任务调度相关链表判断当前创建的链表是不是比已经存在的链表优先级更高如果更高则把下次调度的任务改为本任务按优先级把TCB挂载到对应的就绪链表
4.3、任务创建参数保存在何处? 5、开启任务调度器 6、任务切换上下文
6.1、切换任务的时机 发生任务切换有两种情况 任务的时间片耗尽 每隔tick时间就会产生一次时钟中断中断里要判断下一次切换哪个任务中断里需要设置MTIMECMP寄存器周期性产生tick 任务主动发起调度让出CPU 任务不需要继续执行时可主动发起任务调度主动发起任务调度在底层通过ecall指令实现
6.2、保存/恢复任务上下文 参考博客《freertos任务切换的现场保存、恢复任务栈空间深度分析以RISC-V架构为例》参考博客《freeRTOS异常处理函数分析以RISC-V架构进行分析》 6.3、xTaskIncrementTick( )函数分析
BaseType_t xTaskIncrementTick( void )
{TCB_t * pxTCB;TickType_t xItemValue;BaseType_t xSwitchRequired pdFALSE;//判断调度器是否被挂起等于pdFALSE表示没有被挂起if( uxSchedulerSuspended ( UBaseType_t ) pdFALSE ){//系统启动以来产生的tick数1const TickType_t xConstTickCount xTickCount ( TickType_t ) 1;//更改系统的tick数如果溢出则等于0xTickCount xConstTickCount;//xTickCount溢出if( xConstTickCount ( TickType_t ) 0U ) /*lint !e774 if does not always evaluate to false as it is looking for an overflow. */{//把两个延时链表翻转taskSWITCH_DELAYED_LISTS();}//如果现在的tick数大于任务解除阻塞的时间则进入循环//xNextTaskUnblockTime记录的是当前被阻塞的任务里时间最短的阻塞时间if( xConstTickCount xNextTaskUnblockTime ){for( ; ; ){if( listLIST_IS_EMPTY( pxDelayedTaskList ) ! pdFALSE ){xNextTaskUnblockTime portMAX_DELAY; /*lint !e961 MISRA exception as the casts are only redundant for some ports. */break;}else{//获取延迟任务列表头部的任务控制块pxTCB listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList ); /*lint !e9079 void * is used as this macro is used with timers and co-routines too. Alignment is known to be fine as the type of the pointer stored and retrieved is the same. *///获取延迟时间xItemValue listGET_LIST_ITEM_VALUE( ( pxTCB-xStateListItem ) );//说明还没有到任务解除阻塞的时间if( xConstTickCount xItemValue ){//更新最近要被解除阻塞的时间xNextTaskUnblockTime xItemValue;break; }//从阻塞链表中移除listREMOVE_ITEM( ( pxTCB-xStateListItem ) );//从事件阻塞链表中移除if( listLIST_ITEM_CONTAINER( ( pxTCB-xEventListItem ) ) ! NULL ){listREMOVE_ITEM( ( pxTCB-xEventListItem ) );}//添加到就绪队列prvAddTaskToReadyList( pxTCB );//如果是抢占式调度则判断解除阻塞的任务优先级是否高于当前正在执行的任务//如果比当前执行的任务优先级高则需要切换任务#if ( configUSE_PREEMPTION 1 ){if( pxTCB-uxPriority pxCurrentTCB-uxPriority ){xSwitchRequired pdTRUE;}}#endif /* configUSE_PREEMPTION */}}}//如果是抢占式调度并且是时间片轮转当前正在执行的任务的优先级就绪链表中成员个数大于1//需要调度,因为同优先级的任务要轮流执行#if ( ( configUSE_PREEMPTION 1 ) ( configUSE_TIME_SLICING 1 ) ){if( listCURRENT_LIST_LENGTH( ( pxReadyTasksLists[ pxCurrentTCB-uxPriority ] ) ) ( UBaseType_t ) 1 ){xSwitchRequired pdTRUE;}}#endif /* ( ( configUSE_PREEMPTION 1 ) ( configUSE_TIME_SLICING 1 ) ) *///如果是抢占式调度并且调度功能没有被挂起则要切换任务#if ( configUSE_PREEMPTION 1 ){if( xYieldPending ! pdFALSE ){xSwitchRequired pdTRUE;}}#endif /* configUSE_PREEMPTION */}else{xPendedTicks;}return xSwitchRequired;
}6.4、vTaskSwitchContext( )函数分析
void vTaskSwitchContext( void )
{//不为零则调度器被挂起if( uxSchedulerSuspended ! ( UBaseType_t ) pdFALSE ){//调度器被挂起不允许任务切换xYieldPending pdTRUE;}else{xYieldPending pdFALSE;//检查任务的栈是否溢出taskCHECK_FOR_STACK_OVERFLOW(); //从就绪链表中选择出最高优先级的任务taskSELECT_HIGHEST_PRIORITY_TASK(); }
}7、任务优先级的实现
7.1、重要的链表介绍
//就绪链表configMAX_PRIORITIES是定义的当前支持最大的优先级数字越大优先级越高
//就绪链表有多个每个优先级有一个就绪链表
PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ]; //两个都是挂起休眠任务的之所以有两个是为了解决tickCount超过表示范围产生翻转
PRIVILEGED_DATA static List_t xDelayedTaskList1;
PRIVILEGED_DATA static List_t xDelayedTaskList2;//这是两个链表指针用于指向上面的两个休眠链表
PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList;
PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList; //任务调度器挂起期间解除阻塞条件得到满足的阻塞任务在任务调度器恢复工作后
//这些任务会被移动到就绪链表组中变为就绪状态。
PRIVILEGED_DATA static List_t xPendingReadyList; //这个保存被删除的任务等待空闲链表去回收资源
static List_t xTasksWaitingTermination; //这是任务调度器开启时被挂起的任务
static List_t xSuspendedTaskList; /* Tasks that are currently suspended. */xDelayedTaskList1和xDelayedTaskList2 两个链表都是用来保存被阻塞的任务定义两个链表是解决xTickCount溢出问题xTickCount是记录开启任务调度后发生tick的次数在32位系统里是int类型过一段时间xTickCount就可能溢出。溢出是指当xTickCount0xFFFFFFFF时再加一xTickCount的值就会变成0疑问为什么不直接在32位CPU中用long long类型变量来定义xTickCount这样就不用考虑溢出问题 任务控制块TCBTask COntrol Bloc中xStateListItem和xEventListItem变量就是用来挂接到上面的各个链表中想理解TCB是如何挂接到上述的链表需要理解freertos的链表实现阅读源码list.c
7.2、根据任务优先级进行调度
在创建任务时需要指定优先级在构建好TCB后挂接到对应优先级的就绪链表中如果任务发生阻塞、挂起被挂接到阻塞链表、挂起链表当重新变为就绪态时还是挂接到对应优先级的就绪链表发生任务调度时先扫描高优先级的就绪链表只有高优先级的就绪链表是空才会扫描低优先级的就绪链表选择扫描到的当前最高优先级的就绪态任务进行调度并且在调度后把该任务插入到本优先级就绪链表的尾部总结 选择就绪态中最高优先级的任务进行调度同优先级的就绪态任务轮流执行
7.3、从就绪链表中选择出最高优先级的就绪任务 uxTopReadyPriority变量 uxTopReadyPriority是采用位图的形式来保存优先级每个bit位表示一个优先级比如当前有优先级是5的任务进入就绪态则会把uxTopReadyPriority的bit5置一即uxTopReadyPriority | (1 5);
8、tick的产生 使用RISC-V架构自带的定时器每1ms产生一次定时器中断周期性设置MTIMECMP、MTIME寄存器
9、栈空间溢出检测 在构建TCB时根据创建参数申请栈空间大小 在任务切换时检查栈空间是否溢出 在申请栈时将栈空间整个初始化成特殊值在切换任务时检查栈空间最低4个字节是不是特殊值RISC-V使用满减栈如果不是特殊值说明栈空间最后四个字节被使用过此时判断栈溢出 如果栈溢出则扩大栈空间再次测试是否溢出选择合适的栈空间
10、 任务的删除过程分析
调用vTaskDelet( )函数删除任务 把被删除TCB从挂接的链表中删除判断是否需要更新当前就绪最高优先级即uxTopReadyPriority变量如果删除的是正在运行的任务 把TCB插入到xTasksWaitingTermination链表 如果删除的不是正在运行的任务 判断是否需要更新最近被唤醒任务的时间即xNextTaskUnblockTime变量释放任务栈空间、TCB空间 空闲任务(prvIdleTask) 把被删除的任务TCB从xTasksWaitingTermination链表读取出来释放任务栈空间、TCB空间