本帖最后由 caijie001 于 2018-8-12 11:18 编辑
FreeRTOS 时间管理
时间管理包括两个方面:系统节拍以及任务延时管理。
系统节拍:
在前面的**也讲得很多,想要系统正常运行,那么时钟节拍是必不可少的,FreeRTOS的时钟节拍通常由SysTick提供,它周期性的产生定时中断,所谓的时钟节拍管理的核心就是这个定时中断的服务程序。FreeRTOS的时钟节拍isr中核心的工作就是调用vTaskIncrementTick()函数。具体见上之前的**。
今天主要讲解延时的实现:
FreeRTOS提供了两个系统延时函数:
相对延时函数vTaskDelay()
绝对延时函数vTaskDelayUntil()。
这些延时函数可不像我们以前用裸机写代码的延时函数操作系统不允许CPU在死等消耗着时间,因为这样效率太低了。
同时,要告诫学操作系统的同学,千万别用裸机的思想去学操作系统。
任务延时
任务可能需要延时,两种情况,一种是任务被vTaskDelay或者vTaskDelayUntil延时,另外一种情况就是任务等待事件(比如等待某个信号量、或者某个消息队列)时候指定了timeout(即最多等待timeout时间,如果等待的事件还没发生,则不再继续等待),在每个任务的循环中都必须要有阻塞的情况出现,否则比该任务优先级低的任务就永远无法运行。
相对延时与绝对延时的区别
相对延时:vTaskDelay():
相对延时是指每次延时都是从任务执行函数vTaskDelay()开始,延时指定的时间结束
绝对延时:vTaskDelayUntil():
绝对延时是指调用vTaskDelayUntil()的任务每隔x时间运行一次。也就是任务周期运行。
相对延时:vTaskDelay()
相对延时vTaskDelay()是从调用vTaskDelay()这个函数的时候开始延时,但是任务执行的时候,可能发生了中断,导致任务执行时间变长了,但是整个任务的延时时间还是1000个tick,这就不是周期性了,简单看看下面代码:
- 1 void vTaskA( void * pvParameters )
- 2 {
- 3 while (1) {
- 4 // ...
- 5 // 这里为任务主体代码
- 6 // ...
- 7
- 8 /* 调用相对延时函数,阻塞1000个tick */
- 9 vTaskDelay( 1000 );
- 10 }
- 11
可能说的不够明确,可以看看图解。
当任务运行的时候,假设被某个高级任务或者是中断打断了,那么任务的执行时间就更长了,然而延时还是延时1000个tick这样子,整个系统的时间就混乱了。
如果还不够明确,看看vTaskDelay()的源码:
- 1 void vTaskDelay( const TickType_t xTicksToDelay )
- 2 {
- 3 BaseType_t xAlreadyYielded = pdFALSE;
- 4
- 5 /* 延迟时间为零只会强制切换任务。 */
- 6 if ( xTicksToDelay > ( TickType_t ) 0U ) {
- 7 configASSERT( uxSchedulerSuspended == 0 );
- 8 vTaskSuspendAll();
- 9 {
- 10 traceTASK_DELAY();
- 11 /*将当前任务从就绪列表中移除,并根据当前系统节拍
- 12 计数器值计算唤醒时间,然后将任务加入延时列表 */
- 13 prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );
- 14 }
- 15 xAlreadyYielded = xTaskResumeAll();
- 16 } else {
- 17 mtCOVERAGE_TEST_MARKER();
- 18 }
- 19
- 20 /* 强制执行一次上下文切换 */
- 21 if ( xAlreadyYielded == pdFALSE ) {
- 22 portYIELD_WITHIN_API();
- 23 } else {
- 24 mtCOVERAGE_TEST_MARKER();
- 25 }
- 26 }
(1):如果传递进来的延时时间是0,只能进行强制切换任务了,调用的是portYIELD_WITHIN_API(),它其实是一个宏,真正起作用的是portYIELD(),下面是它的源码:
- 1
- 2 #define portYIELD() \
- 3 { \
- 4 /* 设置PendSV以请求上下文切换。 */ \
- 5 portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; \
- 6
- 7 __dsb( portSY_FULL_READ_WRITE );
- 8 \
- 9 __isb( portSY_FULL_READ_WRITE );
- 10 \
- 11 }
(2):挂起当前任务
然后将当前任务从就绪列表删除,然后加入到延时列表。是调用函数prvAddCurrentTaskToDelayedList()完成这一过程的。由于这个函数篇幅过长,就不讲解了,有兴趣可以看看,我就简单说说过程。在FreeRTOS中有这么一个变量,是用来记录systick的值的。
- 1
- 2 PRIVILEGED_DATA static volatile TickType_t xTickCount = ( TickType_t ) 0U;
在每次tick中断时xTickCount加一,它的值表示了系统节拍中断的次数,那么啥时候唤醒被加入延时列表的任务呢?其实很简单,FreeRTOS的做法将xTickCount(当前系统时间)+xTicksToDelay(要延时的时间)即可。当这个相对的延时时间到了之后就唤醒了,这个(xTickCount+ xTicksToDelay)时间会被记录在该任务的任务控制块中。
看到这肯定有人问,这个变量是TickType_t类型(32位)的,那肯定会溢出啊,没错,是变量都会有溢出的一天,可是FreeRTOS乃是世界第一的操作系统啊,FreeRTOS使用了两个延时列表:
xDelayedTaskList1和xDelayedTaskList2
并使用两个列表指针类型变量pxDelayedTaskList和pxOverflowDelayedTaskList分别指向上面的延时列表1和延时列表2(在创建任务时将延时列表指针指向延时列表)如果内核判断出xTickCount+xTicksToDelay溢出,就将当前任务挂接到列表指针 pxOverflowDelayedTaskList指向的列表中,否则就挂接到列表指针pxDelayedTaskList指向的列表中。当时间到了,就会将延时的任务从延时列表中删除,加入就绪列表中,当然这时候就是由调度器觉得任务能不能运行了,如果任务的优先级大于当前运行的任务,那么调度器才会进行任务的调度。
绝对延时:vTaskDelayUntil()
vTaskDelayUntil()的参数指定了确切的滴答计数值
调用vTaskDelayUntil()是希望任务以固定频率定期执行,而不受外部的影响,任务从上一次运行开始到下一次运行开始的时间间隔是绝对的,而不是相对的。假设主体任务被打断0.3s,但是下次唤醒的时间是固定的,所以还是会周期运行。
下面看看vTaskDelayUntil()的使用方法,注意了,这vTaskDelayUntil()的使用方法与vTaskDelay()不一样:
|