编辑
2025-04-16
记录知识
0
请注意,本文编写于 72 天前,最后修改于 59 天前,其中某些信息可能已经过时。

目录

测试代码
输出结果
分析结果
总结

之前我们讨论了EDF调度器的实施策略,在rtems上,我们可以通过修改测试程序来演示一下edf调度器对任务调度的现象。

测试代码

为了能够开始测试代码,我们需要首先创建三个任务,如下

Task_name[ 1 ] = rtems_build_name( 'T', 'A', '1', ' ' ); Task_name[ 2 ] = rtems_build_name( 'T', 'A', '2', ' ' ); Task_name[ 3 ] = rtems_build_name( 'T', 'A', '3', ' ' ); for ( index = 1 ; index <= 3 ; index++ ) { status = rtems_task_create( Task_name[ index ], 1, RTEMS_MINIMUM_STACK_SIZE, RTEMS_DEFAULT_MODES, RTEMS_DEFAULT_ATTRIBUTES, &Task_id[ index ] ); directive_failed( status, "rtems_task_create loop" ); } for ( index = 1 ; index <= 3 ; index++ ) { status = rtems_task_start( Task_id[ index ], Task_1_through_3, index ); directive_failed( status, "rtems_task_start loop" ); }

3个任务都运行了函数Task_1_through_3,我们可以查看Task_1_through_3函数的实现如下

rtems_task Task_1_through_3( rtems_task_argument argument ) { rtems_id rmid; rtems_id test_rmid; rtems_time_of_day time; rtems_status_code status; int start = 0; status = rtems_rate_monotonic_create( argument, &rmid ); directive_failed( status, "rtems_rate_monotonic_create" ); status = rtems_rate_monotonic_ident( argument, &test_rmid ); directive_failed( status, "rtems_rate_monotonic_ident" ); if ( rmid != test_rmid ) { printf( "RMID's DO NOT MATCH (0x%" PRIxrtems_id " and 0x%" PRIxrtems_id ")\n", rmid, test_rmid ); rtems_test_exit( 0 ); } switch ( argument ) { case 1: while ( FOREVER ) { status = rtems_rate_monotonic_period( rmid, RTEMS_MILLISECONDS_TO_TICKS(8000)); directive_failed( status, "rtems_rate_monotonic_period" ); status = rtems_clock_get_tod( &time ); directive_failed( status, "rtems_clock_get_tod" ); put_name( Task_name[ argument ], FALSE ); print_time( " - executing - ", &time, "\n" ); rtems_task_wake_after(RTEMS_MILLISECONDS_TO_TICKS(2000)); status = rtems_clock_get_tod( &time ); directive_failed( status, "rtems_clock_get_tod" ); put_name( Task_name[ argument ], FALSE ); print_time( " - finished - ", &time, "\n" ); if ( time.second >= 30 && time.minute == 0) { printf( "PERIODS CHECK OK 30s\n"); TEST_END(); rtems_test_exit( 0 ); } } case 2: while ( FOREVER ) { status = rtems_rate_monotonic_period( rmid, RTEMS_MILLISECONDS_TO_TICKS(6000)); directive_failed( status, "rtems_rate_monotonic_period" ); status = rtems_clock_get_tod( &time ); directive_failed( status, "rtems_clock_get_tod" ); put_name( Task_name[ argument ], FALSE ); print_time( " - executing - ", &time, "\n" ); rtems_task_wake_after(RTEMS_MILLISECONDS_TO_TICKS(3000)); directive_failed( status, "rtems_task_wake_after" ); status = rtems_clock_get_tod( &time ); directive_failed( status, "rtems_clock_get_tod" ); put_name( Task_name[ argument ], FALSE ); print_time( " - finished - ", &time, "\n" ); } break; case 3: while ( FOREVER ) { status = rtems_rate_monotonic_period( rmid, RTEMS_MILLISECONDS_TO_TICKS(4000)); directive_failed( status, "rtems_rate_monotonic_period" ); status = rtems_clock_get_tod( &time ); directive_failed( status, "rtems_clock_get_tod" ); put_name( Task_name[ argument ], FALSE ); print_time( " - executing - ", &time, "\n" ); rtems_task_wake_after(RTEMS_MILLISECONDS_TO_TICKS(1000)); directive_failed( status, "rtems_task_wake_after" ); status = rtems_clock_get_tod( &time ); directive_failed( status, "rtems_clock_get_tod" ); put_name( Task_name[ argument ], FALSE ); print_time( " - finished - ", &time, "\n" ); } break; } }

上面的代码,我在每个任务中,按照要求定义了三个任务如下

  • A任务运行时间为2s,A任务的截止时间(周期)是8s
  • B任务运行时间为3s,B任务的截止时间(周期)是6s
  • C任务运行时间为1s,C任务的截止时间(周期)是4S

对于任务的周期创建,这里通过标准接口rtems_rate_monotonic_creatertems_rate_monotonic_period来设置。其中RTEMS_MILLISECONDS_TO_TICKS会将ms转换成系统的tick值

对于任务的运行时间,这里通过rtems_task_wake_after来实现,它会默认将进程让出就绪队列,然后休眠超时后,将任务加入就绪队列。

当系统任务运行结束时间超过30s时,会主动退出此测试程序

接下来我们编译运行,查看运行结果

输出结果

将上述代码运行之后,可以得到如下日志

TA3 - executing - 09:00:00 04/16/2025 TA3 - finished - 09:00:01 04/16/2025 TA2 - executing - 09:00:02 04/16/2025 TA1 - executing - 09:00:04 04/16/2025 TA3 - executing - 09:00:04 04/16/2025 TA2 - finished - 09:00:05 04/16/2025 TA3 - finished - 09:00:05 04/16/2025 TA1 - finished - 09:00:06 04/16/2025 TA2 - executing - 09:00:08 04/16/2025 TA3 - executing - 09:00:08 04/16/2025 TA3 - finished - 09:00:09 04/16/2025 TA2 - finished - 09:00:11 04/16/2025 TA1 - executing - 09:00:12 04/16/2025 TA3 - executing - 09:00:12 04/16/2025 TA3 - finished - 09:00:13 04/16/2025 TA2 - executing - 09:00:14 04/16/2025 TA1 - finished - 09:00:14 04/16/2025 TA3 - executing - 09:00:16 04/16/2025 TA2 - finished - 09:00:17 04/16/2025 TA3 - finished - 09:00:17 04/16/2025 TA1 - executing - 09:00:20 04/16/2025 TA2 - executing - 09:00:20 04/16/2025 TA3 - executing - 09:00:20 04/16/2025 TA3 - finished - 09:00:21 04/16/2025 TA1 - finished - 09:00:22 04/16/2025 TA2 - finished - 09:00:23 04/16/2025 TA3 - executing - 09:00:24 04/16/2025 TA3 - finished - 09:00:25 04/16/2025 TA2 - executing - 09:00:26 04/16/2025 TA1 - executing - 09:00:28 04/16/2025 TA3 - executing - 09:00:28 04/16/2025 TA2 - finished - 09:00:29 04/16/2025 TA3 - finished - 09:00:29 04/16/2025 TA1 - finished - 09:00:30 04/16/2025 PERIODS CHECK OK 30s

分析结果

其实根据日志的输出,已经很明显看出edf调度器的工作机制了,这里逐步分析一下,方便加深edf调度算法的理解

我逐步分析如下

  • 任务3优先级最高,在1s时直接执行完成,然后执行任务2,预计第5s完成
  • 在第4s时,任务1作为优先级最低开始执行,预计在第6s时结束
  • 因为任务3的周期是4s,所以从第4s开始,任务3也开始执行,预计第5s完成
  • 我们知道任务2的周期是6s,任务3的周期是4s,第8s是第二个周期。而第6s之后,所有任务都完成了。
  • 从第8s开始,任务2和任务3都得到执行,第12s时任务1得以执行
  • 以此类推

根据上面的分析,我们可以发现

  1. 任务3优先级最高,任务2次之,任务1最低。由截止时间决定
  2. 在一个周期内,任务一定会运行一次

根据上面的推论,我们还可以发现

  • 任务1优先级最低,所以等到任务2和任务3完成之后才能得以运行,也就是一个周期的第3+1s时
  • 同理,任务2优先级次之,它需要等到任务1完成之后得以执行
  • 同理,任务1优先级最高,所以每次周期开始,它都直接运行

根据上面的日志,我们可能发现,任务2每次在周期内都是在第2秒才运行,也就是2/8/20/26。而任务1时间只有1s中,那么我们可以推论,此时操作系统中,还有一个任务周期为1s的任务。它的优先级在任务3和任务2之间。不过此任务不影响我们对edf任务调度的观测和推论。

最后,我们关心任务的执行此时,如下

# cat task.log | grep TA1 | wc -l 8 # cat task.log | grep TA2 | wc -l 10 # cat task.log | grep TA3 | wc -l 16

我们以30s作为任务结束为计数,可以得到如下

  • TA1 = 8/2 times * 8s = 32s
  • TA2 = 10/2 times * 6s = 30s
  • TA3 = 16/2 times * 4s = 32s

可以发现,完全符合edf的调度情况

总结

根据上面的演示,我们清晰的了解了edf调度的运行机制。