FreeRTOS相同优先级任务可以相互打断吗?

本文探讨了FreeRTOS中任务调度的两种方式,特别是时间片调度的实现原理,以及作者通过实验验证了相同优先级任务可以相互打断,无论是否执行完整个时间片。实验还揭示了TICK_RATE_HZ对时间片的影响,引发关于最大时间片限制的疑问。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    在学习FreeRTOS任务调度的时候,一般分为抢占式调度和时间片调度,抢占式调度用于不同优先级的任务,而时间片调度用于相同优先级的任务。时间片调度就是几个同样优先级的任务轮流执行,每个任务执行一个时间片; 如果一个优先级就序列表中有ABC三个任务,那么首先执行A任务,下次再进来就执行B任务,然后是C任务,再是A任务。实现方式是改变该优先级就序列表的pxindex指针,开始指向A任务这个列表项就返回A任务句柄,然后指针指向B任务列表项,以此类推。

    但是学到后面我发现,任务切换的本质就是PendSV中断服务函数,而时间片调度之所以可以完成也只是每个时间片过后进入Systick中断时调用了PendSV中断服务函数而已,这样就保证了相同优先级每个任务执行一个时间片。那我就有个疑问,假设任务A运行了半个时间片,这个时候使用任务切换,那任务A会不会被切换到任务B去呢?这个时候任务A可还没运行一个时间片,其实这个问题就相当于相同优先级的任务能不能相互打断?。我们知道,所谓高优先级任务就绪后打断低优先级任务,并不是高优先级处于就绪态了就会有个中断完成打断,而是高优先级处于就绪态后执行了一次任务切换,高优先级才能打断低优先级(这个任务切换可以在systick中断中进行,其次freertos基本上涉及到任务阻塞态和就绪态之间转换的api函数中都会带一个任务切换函数portYIELD(),以此保证当高优先级任务为就绪态时能够及时执行),那么按照理论来说,根据时间片调度的实现原理,ABC三个相同优先级任务,只要调用了任务切换函数,不管任务ABC哪一个有没有把当前时间片执行完,都会切换到另一个任务执行,所以时间片调度的本质应该只是在systick中断中调用了一次任务切换。你可以人为的调用任务切换函数来打破时间片调度的规则。

    因此我们可以从理论上得到一个结论:FreeRTOS相同优先级任务是可以相互打断的,只要执行了任务切换函数。

   结论有了,剩下的就是用实验去验证了,我的实验方法是:设置FreeRTOS时间片为100ms;创建两个相同优先级的任务,任务一和任务二,他们一直处于就绪态;做两组对照试验,一组是任务一和任务二正常执行,按理来说应该每个任务执行100ms再切换到另一个任务。另一组是在任务一和任务二执行几十ms的时候调用一次任务切换函数,看看有没有发生任务切换。如果发生任务切换就说明相同优先级是能够相互打断的,且这个打断不用一定要遵守时间片规则,只要另一个同优先级任务就绪且调用任务切换函数即可。

实验环境:stm32Cubemx的FreeRTOS系统,版本为CMSIS_V2,时间片为100ms

IDE:keil5

开发板:正点原子miniV3.0

实验的代码如下:

  第一组实验:

freertos.c:

void StartTask01(void *argument)
{
  /* USER CODE BEGIN StartTask01 */
  /* Infinite loop */
	
  for(;;)
  {
      printf("任务一刻度:0\r\n");
	  Delay_Ms(20);
	 //portYIELD();
      printf("任务一刻度:1\r\n");
	  Delay_Ms(20);
	//portYIELD();
	  printf("任务一刻度:2\r\n");
	  Delay_Ms(20);
	//portYIELD();
	  printf("任务一刻度:3\r\n");
	  Delay_Ms(20);
	//portYIELD();
		 
    LED_RED_Toggle();
  }
  /* USER CODE END StartTask01 */
}


void StartTask02(void *argument)
{
  /* USER CODE BEGIN StartTask02 */
  /* Infinite loop */
  for(;;)
  { 
	  printf("任务二刻度:0\r\n");
	  Delay_Ms(20);
	//portYIELD();
      printf("任务二刻度:1\r\n");
	  Delay_Ms(20);
	//portYIELD();
	  printf("任务二刻度:2\r\n");
	  Delay_Ms(20);
	//portYIELD();
	  printf("任务二刻度:3\r\n");
	  Delay_Ms(20);
	//portYIELD();
		 
		LED_GREEN_Toggle();
  }
  /* USER CODE END StartTask02 */
}

按理来说输出结果应该为:每个任务执行100ms。下图结果符合理论

  第二组实验:在第一组实验的代码基础上,取消所有的portYIELD()函数的注释,每隔20ms进行一次任务切换。结果应该是任务一打印一次,任务二打印一次,任务一打印一次以此类推。下图结果符合结论。

接下来我们再添加一组实验,第三组实验,我们在第一组实验代码基础上,只取消第一个portYIELD()函数的注释,如下:

freertos.c:

void StartTask01(void *argument)
{
  /* USER CODE BEGIN StartTask01 */
  /* Infinite loop */
	
  for(;;)
  {
      printf("任务一刻度:0\r\n");
	  Delay_Ms(20);
	 portYIELD();
      printf("任务一刻度:1\r\n");
	  Delay_Ms(20);
	//portYIELD();
	  printf("任务一刻度:2\r\n");
	  Delay_Ms(20);
	//portYIELD();
	  printf("任务一刻度:3\r\n");
	  Delay_Ms(20);
	//portYIELD();
		 
    LED_RED_Toggle();
  }
  /* USER CODE END StartTask01 */
}


void StartTask02(void *argument)
{
  /* USER CODE BEGIN StartTask02 */
  /* Infinite loop */
  for(;;)
  { 
	  printf("任务二刻度:0\r\n");
	  Delay_Ms(20);
	portYIELD();
      printf("任务二刻度:1\r\n");
	  Delay_Ms(20);
	//portYIELD();
	  printf("任务二刻度:2\r\n");
	  Delay_Ms(20);
	//portYIELD();
	  printf("任务二刻度:3\r\n");
	  Delay_Ms(20);
	//portYIELD();
		 
		LED_GREEN_Toggle();
    
  }
  /* USER CODE END StartTask02 */
}

这次实验的结果应该是,任务一打印一次,任务二打印一次,任务一打印四次,任务二打印四次;一个循环以此类推,让我们看看结果:

欸嘿,怎么不符合预期呢?按理来说任务二打印四次再切任务一啊,而且任务一只能打印四次然后调用任务切换,再让任务二来打印啊?大家不要忘了,systick中断也会调用任务切换函数,每100ms进行一次任务切换。我们看执行到红框的时候刚刚差不多执行了100ms,这个时候systick中断调用一次任务切换,因此任务二打印三次就切任务一了。而任务一打印四次之后,又差不多过了100ms,这时我们又人为调用了一次任务切换,相当于短时间内切了两次,任务一切任务二,任务二又切任务一,就导致任务一一直在打印。

    所以,理论结合实验,我们得出来再FreeRTOS中,相同优先级是可以相互打断的,就像高优先级可以打断低优先级一样。不过相同优先级遵循依次打断,还是ABC三个任务,都处于就绪态,A运行时调用任务切换函数,B来运行,B运行时调用任务切换函数C来运行这样子。不一定非要一个时间片完了才能切换另一个同优先级任务,毕竟本质都是调用任务切换函数罢了。

/************************      下面说一下我做实验遇到的一个问题     **********************/

FreeRTOS时间片最长为多久?我看cubemx中可以配置这个频率为1-1000HZ,当1000HZ时时间片为1ms,10HZ时间片为100ms,1HZ时间片应该为1s。但是实测1HZ时的时间片反而比10HZ时的时间片要更短一些,这让我很疑惑?实验如下:

把TICK_RATE_HZ改为1,这时时间片应该为1s,同样调用实验一的代码,应该任务一打印几十次,完了任务二也打印几十次,以此类推,但实际结果如下:每个任务只打印了几次

把TICK_RATE_HZ改为2,这时时间片应该为500ms,同样调用实验一的代码,应该任务一打印20次左右,完了任务二也打印20次左右,以此类推,实际结果如下:每个任务同样只打印

把TICK_RATE_HZ改为5,这时时间片应该为200ms,同样调用实验一的代码,应该任务一打印8次左右,完了任务二也打印8次左右,以此类推,实际结果如下:和理论相符合

这让我百思不得其解,难道Freertos说是最长设置为1s时间片,实际时间片最多只能到200ms?为什么呢?还有TICK_RATE_HZ改为1和2时的打印情况是为什么呢?希望又大佬看见了能为小弟解答一二。

<think>嗯,用户的问题是关于FreeRTOS中低优先级任务不释放线程时,高优先级任务能否抢占。这涉及到实时操作系统的核心调度机制。 用户可能正在开发基于FreeRTOS的嵌入式系统,遇到了任务调度相关的问题。从提问方式看,用户对FreeRTOS有一定了解,但可能对抢占机制的具体边界条件存在疑惑。这类问题在实际开发中很关键,直接关系到系统实时性。 参考引用材料[1]和[3]都明确指出FreeRTOS采用优先级抢占式调度:数值越大的任务优先级越高。引用[4]则详细说明了任务调度的两种场景——这正是用户问题的核心矛盾点。 需要特别注意两个关键机制:1) 高优先级任务就绪时会立即触发PendSV中断,2) 实际抢占发生在当前指令执行完毕后。这解释了为什么"低优先级不释放线程"时高优先级仍能抢占——因为"不释放"仅指不主动调用阻塞函数,但CPU执行流仍会被硬件中断强制打断。 用户可能隐含的担忧是:如果低优先级任务执行死循环且关中断,是否会完全锁死系统?这需要补充说明(虽然问题中未提及),因为此时硬件中断被禁用,抢占确实无法发生。不过这是异常设计情况,正常使用FreeRTOS时应避免。 在回答中需要强调"阻塞"与"抢占"的本质区别:阻塞是任务主动行为,而抢占是调度器被动响应。引用[2]关于中断优先级的说明也很重要,因为最终抢占是通过PendSV这个系统异常实现的。</think>在FreeRTOS中,**高优先级任务可以抢占正在运行的低优先级任务**,即使低优先级任务不主动释放CPU(即不调用阻塞函数)。这是FreeRTOS优先级抢占式调度的核心特性。以下是关键机制分析: ### 1. **抢占发生的条件** - **高优先级任务就绪**:当高优先级任务从阻塞态进入就绪态(例如等待的信号量到达、延时结束等)。 - **硬件中断触发**:外部事件(如定时器中断、外设中断)唤醒了高优先级任务。 - **无需低优先级任务配合**:抢占是强制性的,不依赖低优先级任务主动释放CPU。 ### 2. **抢占的执行过程** 1. **中断触发**:硬件中断(如信号量释放触发的`xSemaphoreGiveFromISR()`)或系统事件使高优先级任务就绪。 2. **请求上下文切换**:中断服务程序(ISR)调用`portYIELD_FROM_ISR()`,触发PendSV异常。 3. **调度器介入**:PendSV异常处理程序中,调度器比较就绪任务优先级: - 若存在更高优先级任务就绪,**立即保存当前任务上下文**。 - 切换到高优先级任务执行。 4. **低优先级任务被挂起**:原低优先级任务被强制移出运行态,进入就绪队列等待。 ### 3. **关键场景验证 - **低优先级任务死循环**: ```c void LowPriorityTask(void *pvParameters) { while(1) { // 无任何阻塞调用(如vTaskDelay()、队列接收等) // 纯计算密集型循环 } } ``` 当高优先级任务就绪时,仍能**立即抢占**该循环,因为FreeRTOS依赖硬件中断机制强制切换。 - **优先级任务对比**: - 若多个任务**优先级**且不阻塞,则需启用时间片轮转(`configUSE_TIME_SLICING=1`)才能公平调度。 - **不优先级**时,高优先级任务无需时间片机制即可抢占(见引用[3][^3])。 ### 4. **限制与注意事项 - **中断优先级影响**: - 若抢占请求发生在**高于`configMAX_SYSCALL_INTERRUPT_PRIORITY`的中断**中,需等待中断退出才会触发调度(引用[2][^2])。 - 低优先级中断可安全调用调度相关API(如`xQueueSendFromISR()`)。 - **临界区保护**: - 若低优先级任务进入临界区(`taskENTER_CRITICAL()`),会临时关闭中断,此时抢占会被延迟到临界区退出。 ### 5. **设计建议 - **避免低优先级任务长期占用CPU**: - 即使高优先级任务可抢占,长时间阻塞低优先级任务可能影响系统实时性。 - 在低优先级任务中**加入微小延时**(如`vTaskDelay(1)`),主动让出CPU。 - **优先级规划**: - 关键任务(如实时控制)应设为最高优先级。 - 非实时任务(如日志记录)设为低优先级。 ### 总结 | **场景** | **能否抢占** | **原因** | |----------------------------------|--------------|--------------------------------------------------------------------------| | 低优先级任务运行中(不阻塞) | **能** | 调度器通过硬件中断强制切换 | | 低优先级任务处于临界区 | **不能** | 中断被关闭,抢占请求延迟处理 | | 优先级任务运行(无时间片轮转) | **不能** | 需任务主动阻塞或启用`configUSE_TIME_SLICING`(引用[4][^4]) | > 因此,在正常设计下,**高优先级任务无需等待低优先级任务释放CPU即可抢占**,这是保障FreeRTOS实时性的基础机制[^1][^3]。 --- ### 相关问题 1. **如何验证FreeRTOS中高优先级任务成功抢占了低优先级任务?** (可通过任务执行时间戳或调试器观察上下文切换) 2. **若高优先级任务频繁抢占,导致低优先级任务始终无法执行,该如何解决?** (需分析优先级反转风险,考虑使用互斥量优先级继承机制) 3. **FreeRTOS的临界区保护对任务抢占有何影响?临界区应如何合理使用?** (临界区会临时禁用中断,需保持极短执行时间) 4. **在资源受限的嵌入式系统中,如何确定最优的任务优先级数量?** (需平衡实时性要求与调度开销,避免优先级反转)
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值