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

    在学习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时的打印情况是为什么呢?希望又大佬看见了能为小弟解答一二。

### 嵌入式系统的常见BUG及其解决方案 #### 多任务环境下的资源竞争 在实时操作系统(RTOS)环境中,多个任务可能访问相同的全局函数或变量。这种情况下容易引发竞态条件(race condition),即不任务之间相互干扰导致程序行为异常。为了防止此类问题的发生,应当遵循可重入(reentrant)原则设计共享功能模块[^2]。 ```c // 非线程安全的例子 int global_counter; void increment() { int temp = global_counter; // 可能被其他任务打断 temp += 1; global_counter = temp; // 再次被打断可能导致错误更新 } // 改进后的版本使用互斥锁保护临界区 #include "FreeRTOS.h" #include "semphr.h" SemaphoreHandle_t counterMutex; void init_mutex(){ counterMutex = xSemaphoreCreateMutex(); } void safe_increment(void){ if (xSemaphoreTake(counterMutex, portMAX_DELAY)) { static volatile int local_counter; local_counter++; xSemaphoreGive(counterMutex); } } ``` #### 中断处理与任务调度冲突 当应用程序既包含普通任务又涉及中断服务例程(ISR)时,两者之间的协调至关重要。如果ISR执行时间过长,则会延迟高优先级任务响应;反之亦然。因此,在编写代码之前应该仔细规划好各部分的工作量以及它们之间的交互方式。 #### 栈溢出风险防范措施 由于嵌入式设备通常拥有有限的内存容量,所以开发者必须谨慎对待每一个局部对象占用的空间大小。特别是那些位于自动存储期内的大型数组,很容易耗尽当前上下文所允许的最大限度而造成崩溃。建议采用动态分配机制或将这些实体移至静态作用域内声明[^3]。 ```c #define STACK_SIZE_LIMIT 512 /* 定义合理的栈尺寸 */ /* 不推荐的做法 */ void risky_function(){ char big_buffer[STACK_SIZE_LIMIT * 2]; // 这里可能会超出范围 } /* 推荐做法之一:利用heap代替stack*/ void safer_approach_with_heap(){ void* buffer = malloc(STACK_SIZE_LIMIT); free(buffer); } /* 或者改为全局/静态定义 */ static char stable_storage[STACK_SIZE_LIMIT]; void another_safe_way(){ memset(stable_storage ,0,sizeof(stable_storage)); } ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值