2. OSAL实现时间片轮转的分析
1. OSAL初始化及其OSAL相关结构体
-
紧接上文,我们打开
osal_main.c
文件,我们可以看到osal_init_system();
。这个函数是初始化osal
的,我们查看它具体的代码。uint8 osal_init_system( void ) { // 内存分配初始化 --- 1 osal_mem_init(); // 消息队列初始化 --- 2 osal_qHead = NULL; #if defined( OSAL_TOTAL_MEM ) osal_msg_cnt = 0; #endif // 系统时间初始化 --- 3 osalTimerInit(); // 任务链表初始化 --- 4 osal_init_TaskHead(); return ( ZSUCCESS ); }
-
内存分配初始化在动态申请内存章节进行分析。
-
消息队列初始化在消息队列章节进行分析
-
osalTimerInit
系统事件初始化函数,我们进去看看它做了什么。void osalTimerInit( void ) { //初始化硬件定时器,这个函数调用的为systick的初始化,因为我们是有stm32cudemx进行初始化, // 本函数并没有使用 OSAL_TIMER_TICKINIT(); // 初始化任务定时器更新时自减的数值单位 // 在 osal_timer.h 中宏定义 TIMER_DECR_TIME为1,也就是systick定时器中断一次,事件过去 // 1ms tmr_decr_time = TIMER_DECR_TIME; // 关闭系统定时器 osal_timer_activate( FALSE ); timerActive = FALSE; // 系统时间初始化,osal_systemClock 为系统事件的全局变量,用来记录系统事件。 osal_systemClock = 0; }
-
osal_init_TaskHead()
为系统任务链表指针初始化,查看具体代码如下:void osal_init_TaskHead( void ) { // 初始化任务链表头指针,下方三个变量都为全局变量 TaskHead = (OsalTadkREC_t *)NULL; // 当前活动任务指针 TaskActive = (OsalTadkREC_t *)NULL; // 任务ID统计 Task_id = 0; }
-
在分析系统链表初始化的时候,我们发现
OsalTadkREC_t
结构体,这个结构体为 任务链表,是系统非常重要的结构体。//任务链表 typedef struct OSALTaskREC { struct OSALTaskREC *next; pTaskInitFn pfnInit; //任务初始化函数指针 pTaskEventHandlerFn pfnEventProcessor; //任务事件处理函数指针 uint8 taskID; //任务ID uint8 taskPriority; //任务优先级 uint16 events; //任务事件 } OsalTadkREC_t; // 其中 pTaskInitFn 任务初始化函数指针格式如下 typedef void (*pTaskInitFn)( uint8 task_id ); // pfnEventProcessor 任务事件处理函数指针格式如下 typedef uint16 (*pTaskEventHandlerFn)(uint8 task_id,uint16 task_event); // osal用这个结构体来管理整个系统的任务,通过链表的方式将一个一个的任务串联起来,我们接下来 // 分析如下进行任务的创建,也就是创建这个任务链表
-
2. OSAL任务的创建
-
我们是有
osal_add_Task(pTaskInitFn pfnInit, pTaskEventHandlerFn pfnEventProcessor, uint8 taskPriority)
来创建任务,第一个参数为任务初始化函数,第二个参数为任务循环函数,第三个参数为任务优先级。其创建的代码如下:void osal_add_Task(pTaskInitFn pfnInit, pTaskEventHandlerFn pfnEventProcessor, uint8 taskPriority) { // TaskNew 为新任务指针,TaskSech为任务链表指针,TaskPTR为任务链表指针的指针 OsalTadkREC_t *TaskNew; OsalTadkREC_t *TaskSech; OsalTadkREC_t **TaskPTR; // 申请任务内存,内存申请在下个章节会讲 TaskNew = osal_mem_alloc(sizeof(OsalTadkREC_t)); if(TaskNew) { // 初始化任务指针 TaskNew->pfnInit = pfnInit; // 初始化任务事件处理函数指针 TaskNew->pfnEventProcessor = pfnEventProcessor; // 初始化任务ID,全局变量Task_id记录着当前创建了几个任务 TaskNew->taskID = Task_id++; // 初始化任务事件 TaskNew->events = 0; // 初始化任务优先级 TaskNew->taskPriority = taskPriority; // 初始化任务链表指针,链表的下一个肯定为Null,因为当前还没有创建 TaskNew->next = (OsalTadkREC_t *)NULL; // 任务链表指针的指针 TaskPTR = &TaskHead; // TaskHead 为全局变量,存储着当前的系统任务链表 TaskSech = TaskHead; tasksCnt++; //任务数量统计 // 进行链表循环,TaskSech是一个指针,在不为Null的时候,进去进行遍历 while(TaskSech) { // 遍历任务优先级 比 当前任务链表 指针指向的任务优先级高 // 此处进行任务优先级的判断,因为OSAL基于时间片,在链表前面的函数会更早执行 // 所有高优先级的要添加在前面 if(TaskNew->taskPriority > TaskSech->taskPriority) { // 插入到遍历到的任务链表指针的前面 TaskNew->next = TaskSech; *TaskPTR = TaskNew; return; } // 遍历任务链表,进行指针的下移 TaskPTR = &TaskSech->next; TaskSech = TaskSech->next; } // 没有任务,直接插入到任务链表头 // 当TaskSech为空时,则直接将TaskNew的地址赋值到TaskHead *TaskPTR = TaskNew; } return; }
3. OSAL任务初始化
-
在完成所有的任务注册之后,就要进行任务的初始化了。注意:任务的注册必须在初始化之前,初始化的函数非常简单,就是按照链表的顺序一次调用注册时的初始化函数。代码如下:
void osal_Task_init(void) { // 遍历任务链表,初始化任务 // 下方代码非常简单,遍历链表,依次调用初始化函数 TaskActive = TaskHead; while(TaskActive) { if(TaskActive->pfnInit) { TaskActive->pfnInit(TaskActive->taskID); } TaskActive = TaskActive->next; } TaskActive = (OsalTadkREC_t *)NULL; }
4. OSAL任务循环事件的设置
-
在进行任务初始化之后,就需要设置任务按照某个时间进行运行。这是根据时间片流转的必要条件,函数如下:
uint8 osal_start_reload_timer( uint8 taskID, uint16 event_id, uint16 timeout_value )
,其中taskID
为任务ID,在创建任务的时候会返回,我们需要进行保存。event_id
为消息ID,因为任务不会只接收一个任务消息,所以需要创建的时候传入消息ID。timeout_value
为任务循环事件。创建的具体代码如下:uint8 osal_start_reload_timer( uint8 taskID, uint16 event_id, uint16 timeout_value ) { // 新建任务定时器 --- 1 osalTimerRec_t *newTimer; HAL_ENTER_CRITICAL_SECTION(); // Add timer 添加新的定时器 newTimer = osalAddTimer( taskID, event_id, timeout_value ); // --2 // 如果申请成功 if ( newTimer ) { // Load the reload timeout value // 重装定时时间 newTimer->reloadTimeout = timeout_value; // 申请成功,将重装载事件设timeout_value } if ( newTimer ) { // 如果硬件定时器没有运行,启动硬件定时器 if ( timerActive == FALSE ) { // 打开硬件定时器 osal_timer_activate( TRUE ); } } HAL_EXIT_CRITICAL_SECTION(); return ( (newTimer != NULL) ? SUCCESS : NO_TIMER_AVAIL ); } // 1. 出现了第一个没见过的结构体,osalTimerRec_t。它也是一个链表,存储着所有创建的定时器 // 并且用任务ID与任务进行联系 typedef struct { void *next; uint16 timeout; //定时时间,每过一个系统时钟会自减 uint16 event_flag; //定时事件,定时时间减完产生任务事件 uint8 task_id; //响应的任务ID uint16 reloadTimeout; //重装定时时间 } osalTimerRec_t; //任务定时器,链表结构 // 2. 第点为 osalAddTime创建定时器函数,这个函数将定时器创建完成,并且返回。 osalTimerRec_t * osalAddTimer( uint8 task_id, uint16 event_flag, uint16 timeout ) { osalTimerRec_t *newTimer; osalTimerRec_t *srchTimer; // 查找注册的定时器是否已经存在,这里查找的方式为查找任务ID,和消息ID是否同时存在 newTimer = osalFindTimer( task_id, event_flag ); // 如果存在定时器,更新定时时间 if ( newTimer ) { // Timer is found - update it. newTimer->timeout = timeout; return ( newTimer ); } // 如果不存在定时器,新建定时器 else { // 申请内存,新建定时器 newTimer = osal_mem_alloc( sizeof( osalTimerRec_t ) ); // 如果申请成功 if ( newTimer ) { // 结构体进行初始化 newTimer->task_id = task_id; newTimer->event_flag = event_flag; newTimer->timeout = timeout; newTimer->next = (void *)NULL; newTimer->reloadTimeout = 0; // 如果为空,新建任务定时器链表,timerHead为定时器链表头,如果当前为链表第一个元素 // 那么newTimer 就是头,将 newTimer的地址赋值给timerHead if ( timerHead == NULL ) { timerHead = newTimer; } // 如果不为空,添加到链表尾部 else { srchTimer = timerHead; // 查找链表尾部 while ( srchTimer->next ) srchTimer = srchTimer->next; srchTimer->next = newTimer; } // 返回新建的任务定时器 return ( newTimer ); } // 如果申请失败,返回空指针 else return ( (osalTimerRec_t *)NULL ); } // 查找定时器的代码非常简单 osalTimerRec_t *osalFindTimer( uint8 task_id, uint16 event_flag ) { osalTimerRec_t *srchTimer; // Head of the timer list srchTimer = timerHead; // Stop when found or at the end while ( srchTimer ) { if ( srchTimer->event_flag == event_flag && srchTimer->task_id == task_id ) break; // Not this one, check another srchTimer = srchTimer->next; } return ( srchTimer ); }
5. OSAL主任务循环函数
-
OSAL主任务循环函数
osal_start_system
主要进行任务逻辑的处理,当某个任务的时间到达之后,进行任务循环函数处理。具体代码如下:void osal_start_system( void ) { uint16 events; uint16 retEvents; while(1) { // 返回下一次需要处理事件的任务指针,TaskActive为当前处理的任务,在每次循环中进行切换,切换到下一个需要处理任务,osalNextActiveTask任务。需要处理的标识就是任务事件 events。 TaskActive = osalNextActiveTask(); // -- 1 // 如果有任务需要处理事件 if ( TaskActive ) { HAL_ENTER_CRITICAL_SECTION(); // 获取任务事件 events = TaskActive->events; // Clear the Events for this task // 清除任务事件 TaskActive->events = 0; HAL_EXIT_CRITICAL_SECTION(); // 如果有事件需要处理 if ( events != 0 ) { // Call the task to process the event(s) // 调用任务事件处理函数 if ( TaskActive->pfnEventProcessor ) { // 任务事件处理函数返回值 retEvents = (TaskActive->pfnEventProcessor)( TaskActive->taskID, events ); // Add back unprocessed events to the current task HAL_ENTER_CRITICAL_SECTION(); // 处理完成事件,清除标志位 TaskActive->events |= retEvents; HAL_EXIT_CRITICAL_SECTION(); } } } } } // osalNextActiveTask 函数如下,非常的简单 OsalTadkREC_t *osalNextActiveTask( void ) { OsalTadkREC_t *TaskSech; // Start at the beginning TaskSech = TaskHead; // When found or not while ( TaskSech ) { if ( TaskSech->events) { // task is highest priority that is ready return TaskSech; } TaskSech = TaskSech->next; } return NULL; }
6.OSAL SystemClock时钟函数
-
SystemClock
函数通常会以1000Hz的周期调用,每次调用时间增加1ms。具体实现如下:void osalTimerUpdate( uint16 updateTime ) { osalTimerRec_t *srchTimer; osalTimerRec_t *prevTimer; // 关闭中断 HAL_ENTER_CRITICAL_SECTION(); // 更新系统时间 osal_systemClock += updateTime; // 重新打开中断 HAL_EXIT_CRITICAL_SECTION(); // 查找空闲的定时器 if ( timerHead != NULL ) { // srchTimer 定时器表头 srchTimer = timerHead; // prevTimer 定时器表头的前一个定时器 prevTimer = (void *)NULL; // Look for open timer slot while ( srchTimer ) { osalTimerRec_t *freeTimer = NULL; // 关闭中断 HAL_ENTER_CRITICAL_SECTION(); // Hold off interrupts. // 检查定时器是否超时 if (srchTimer->timeout <= updateTime) { // 超时,置0 srchTimer->timeout = 0; } else { // 未超时,减去更新时间 srchTimer->timeout = srchTimer->timeout - updateTime; } // Check for reloading // 检查是否需要重装定时时间 /** * srchTimer->timeout 定时时间 * srchTimer->reloadTimeout 重装定时时间 * srchTimer->event_flag 定时事件 */ if ( (srchTimer->timeout == 0) && (srchTimer->reloadTimeout) && (srchTimer->event_flag) ) { // Notify the task of a timeout // 通知任务超时事件 osal_set_event( srchTimer->task_id, srchTimer->event_flag ); // Reload the timer timeout value // 重装定时时间 srchTimer->timeout = srchTimer->reloadTimeout; } // 定时时间为0或者事件标志位为0 if ( srchTimer->timeout == 0 || srchTimer->event_flag == 0 ) { // Take out of list // 从链表中删除 if ( prevTimer == NULL ) timerHead = srchTimer->next; else prevTimer->next = srchTimer->next; // 释放内存 freeTimer = srchTimer; // 获取下一个定时器 srchTimer = srchTimer->next; } else { // 获取下一个定时器 prevTimer = srchTimer; srchTimer = srchTimer->next; } // 重新打开中断 HAL_EXIT_CRITICAL_SECTION(); // 释放内存 if ( freeTimer ) { // 如果定时器超时时间为0 if ( freeTimer->timeout == 0 ) { // 设置任务事件 osal_set_event( freeTimer->task_id, freeTimer->event_flag ); } // 释放内存 osal_mem_free( freeTimer ); } } } }
-
以上,OSAL时间片的主要函数就是这些,还有一个无关紧要的函数。读懂上面这些函数之后就非常容易理解。完整的代码请查看第一章节。