编辑
2025-04-03
记录知识
0

根据之前的理解,rtems可以在一定条件下主动调度,同样的,也可以在某些条件下调用yield,让出调度。这种让出和主动调度不一样的点在于,让出调度是将当前任务挂起一段时间(以系统时钟滴答 ticks 为单位),使其进入延迟状态,并在指定时间后自动唤醒并恢复就绪状态。

一、让出调度函数

让出调度通过函数_Scheduler_Yield实现,其实先基于_Scheduler_Schedule,但与其不同的是,schedule不会修改就绪队列,而yield会将自己任务从就绪队列删除, 然后从tail再次加入。这样每次调用yield的情况下,自身任务永远在队尾

二、测试代码

让出调度可以在多个场景下测试,只要能够体现让出调度相比于主动调度情况下,让出调度会将自己就绪队列拿出并重新插入。本文还是基于信号的测试来验证让出调度。其函数为

rtems_status_code rtems_task_wake_after( rtems_interval ticks ) { /* * It is critical to obtain the executing thread after thread dispatching is * disabled on SMP configurations. */ Thread_Control *executing; Per_CPU_Control *cpu_self; cpu_self = _Thread_Dispatch_disable(); executing = _Per_CPU_Get_executing( cpu_self ); if ( ticks == 0 ) { _Thread_Yield( executing ); } else { _Thread_Set_state( executing, STATES_WAITING_FOR_TIME ); _Thread_Wait_flags_set( executing, THREAD_WAIT_STATE_BLOCKED ); _Thread_Add_timeout_ticks( executing, cpu_self, ticks ); } _Thread_Dispatch_direct( cpu_self ); return RTEMS_SUCCESSFUL; }

可以看到,如果ticks不为0,及时等待,如为0,则开始让出。根据上面的代码展示,其测试代码如下

rtems_asr Process_asr( rtems_signal_set the_signal_set ) { rtems_status_code status; printf( "ASR - ENTRY - signal => %08" PRIxrtems_signal_set "\n", the_signal_set ); switch( the_signal_set ) { case RTEMS_SIGNAL_16: case RTEMS_SIGNAL_17: case RTEMS_SIGNAL_18 | RTEMS_SIGNAL_19: break; case RTEMS_SIGNAL_0: case RTEMS_SIGNAL_1: puts( "ASR - rtems_task_wake_after - yield processor" ); status = rtems_task_wake_after( RTEMS_YIELD_PROCESSOR ); directive_failed( status, "rtems_task_wake_after yield" ); break; case RTEMS_SIGNAL_3: Asr_fired = TRUE; break; } printf( "ASR - EXIT - signal => %08" PRIxrtems_signal_set "\n", the_signal_set ); }

此代码需要结合RTEMS调度器-主动调度的测试代码,此文章仅提供了信号回调函数Process_asr的实现

可以发现,对于0,1信号,默认会让出cpu,从而使得其他task正常运行。

为了体现两个task之间的相互让出,测试代码可以在task2上发送信号并主动让出,如下

rtems_task Task_2( rtems_task_argument argument ) { rtems_status_code status; puts( "TA2 - rtems_signal_send - RTEMS_SIGNAL_17 to TA1" ); status = rtems_signal_send( Task_id[ 1 ], RTEMS_SIGNAL_17 ); directive_failed( status, "rtems_signal_send" ); puts( "TA2 - rtems_task_wake_after - yield processor" ); status = rtems_task_wake_after( RTEMS_YIELD_PROCESSOR ); directive_failed( status, "rtems_task_wake_after" ); puts("TA2 - rtems_signal_send - RTEMS_SIGNAL_18 and RTEMS_SIGNAL_19 to TA1"); status = rtems_signal_send( Task_id[ 1 ], RTEMS_SIGNAL_18 | RTEMS_SIGNAL_19 ); directive_failed( status, "rtems_signal_send" ); TEST_END(); rtems_test_exit( 0 ); }

可以发现,task2会发送signal 17,然后主动让出后再发送信号18和19.我们查看运行后的日志

TA1 - rtems_signal_catch - RTEMS_INTERRUPT_LEVEL( 0 ) TA1 - rtems_signal_send - RTEMS_SIGNAL_16 to self ASR - ENTRY - signal => 00010000 ASR - EXIT - signal => 00010000 TA1 - rtems_signal_send - RTEMS_SIGNAL_0 to self ASR - ENTRY - signal => 00000001 ASR - rtems_task_wake_after - yield processor TA2 - rtems_signal_send - RTEMS_SIGNAL_17 to TA1 TA2 - rtems_task_wake_after - yield processor ASR - ENTRY - signal => 00020000 ASR - EXIT - signal => 00020000 ASR - EXIT - signal => 00000001

上面日志可以发现,TA1默认发送RTEMS_SIGNAL_0 后,主动让出了,然后此时TA2任务得到执行,如上发送信号RTEMS_SIGNAL_17 to TA1,然后TA2主动让出,此时Process_asr的顺序是

先发送RTEMS_SIGNAL_1,在RTEMS_SIGNAL_0中,主动让出了,所以有日志 ENTRY - signal => 00000001 。等RTEMS_SIGNAL_17 发送完成后 asr handler得到日志ASR - ENTRY - signal => 00020000 和ASR - EXIT - signal => 00020000 。最后因为让出调度会插入调度队列的tail,所以最后打印

ASR - EXIT - signal => 00000001

三、总结

至此,根据上面的测试代码和测试日志,可以非常清楚的了解了主动调度的逻辑。主动调度会放弃cpu,并将自己插入就绪队列的尾部。

编辑
2025-04-03
记录知识
0

还是基于rtems_task_wake_after的测试例子,如果我们设置tick不为0,那么线程将进行休眠等待。当等待事件超过,会主动触发thread unlock,此时会调用调度器的_Scheduler_Unblock函数。本文延续之前的测试例子,演示一下rtems的恢复阻塞调度函数

一、测试程序

还是在rtems_task_wake_after中,我们原子的设置了thread的flag,然后设置了ticks的timeout 如下

_Thread_Set_state( executing, STATES_WAITING_FOR_TIME ); _Thread_Wait_flags_set( executing, THREAD_WAIT_STATE_BLOCKED ); _Thread_Add_timeout_ticks( executing, cpu_self, ticks );

二、代码解析

对于设置timeout,我们可以看到函数回调_Thread_Timeout,如下

static inline void _Thread_Add_timeout_ticks( Thread_Control *the_thread, Per_CPU_Control *cpu, Watchdog_Interval ticks ) { ISR_lock_Context lock_context; _ISR_lock_ISR_disable_and_acquire( &the_thread->Timer.Lock, &lock_context ); the_thread->Timer.header = &cpu->Watchdog.Header[ PER_CPU_WATCHDOG_TICKS ]; the_thread->Timer.Watchdog.routine = _Thread_Timeout; _Watchdog_Per_CPU_insert_ticks( &the_thread->Timer.Watchdog, cpu, ticks ); _ISR_lock_Release_and_ISR_enable( &the_thread->Timer.Lock, &lock_context ); }

_Thread_Timeout函数如下

void _Thread_Timeout( Watchdog_Control *the_watchdog ) { Thread_Control *the_thread; the_thread = RTEMS_CONTAINER_OF( the_watchdog, Thread_Control, Timer.Watchdog ); _Thread_Continue( the_thread, STATUS_TIMEOUT ); }

当超时的时候,就会进入关键函数_Thread_Continue,如下

void _Thread_Continue( Thread_Control *the_thread, Status_Control status ) { Thread_queue_Context queue_context; Thread_Wait_flags wait_flags; bool unblock; _Thread_queue_Context_initialize( &queue_context ); _Thread_queue_Context_clear_priority_updates( &queue_context ); _Thread_Wait_acquire( the_thread, &queue_context ); wait_flags = _Thread_Wait_flags_get( the_thread ); if ( wait_flags != THREAD_WAIT_STATE_READY ) { Thread_Wait_flags wait_class; bool success; _Thread_Wait_cancel( the_thread, &queue_context ); the_thread->Wait.return_code = status; wait_class = wait_flags & THREAD_WAIT_CLASS_MASK; success = _Thread_Wait_flags_try_change_release( the_thread, wait_class | THREAD_WAIT_STATE_INTEND_TO_BLOCK, THREAD_WAIT_STATE_READY ); if ( success ) { unblock = false; } else { _Assert( _Thread_Wait_flags_get( the_thread ) == ( wait_class | THREAD_WAIT_STATE_BLOCKED ) ); _Thread_Wait_flags_set( the_thread, THREAD_WAIT_STATE_READY ); unblock = true; } } else { unblock = false; } _Thread_Wait_release( the_thread, &queue_context ); _Thread_Priority_update( &queue_context ); if ( unblock ) { _Thread_Wait_tranquilize( the_thread ); _Thread_Unblock( the_thread ); #if defined(RTEMS_MULTIPROCESSING) if ( !_Objects_Is_local_id( the_thread->Object.id ) ) { _Thread_MP_Free_proxy( the_thread ); } #endif } }

这里我们清楚的看到wait_flags != THREAD_WAIT_STATE_READY 如果线程不是就绪态,则通过等待success = _Thread_Wait_flags_try_change_release,如果原子操作成功,则设置线程为就绪态,_Thread_Wait_flags_set( the_thread, THREAD_WAIT_STATE_READY );。

在设置线程的就绪态之后,会将unblock标准设置为true。unblock = true; 这样,最后根据此标志位调用_Thread_Unblock( the_thread );

_Thread_Unblock的调用流程是:_Thread_Unblock--->_Thread_Clear_state--->_Thread_Clear_state_locked--->_Scheduler_Unblock

_Scheduler_Unblock的作用就是将本任务加入就绪队列。

三、测试结果

对于thread设置的timeout,其堆栈如下

#0 _Thread_Timeout (the_watchdog=0x105be0 <_RTEMS_tasks_Objects+2024>) at ../../../cpukit/score/src/threadtimeout.c:110 #1 0x0000000000022234 in _Watchdog_Do_tickle (header=header@entry=0x1023a8 <_Per_CPU_Information+808>, first=0x105be0 <_RTEMS_tasks_Objects+2024>, now=102, lock=lock@entry=0x102398 <_Per_CPU_Information+792>, lock_context=lock_context@entry=0x105178 <_ISR_Stack_area_begin+8056>) at ../../../cpukit/score/src/watchdogtick.c:66 #2 0x00000000000222f4 in _Watchdog_Tick (cpu=0x102080 <_Per_CPU_Information>) at ../../../cpukit/score/src/watchdogtick.c:105 #3 0x0000000000026c3c in rtems_timecounter_tick () at ../../../cpukit/include/rtems/timecounter.h:101 #4 Clock_driver_timecounter_tick (arg=<optimized out>) at ../../../bsps/aarch64/include/../../shared/dev/clock/clockimpl.h:124 #5 Clock_isr (arg=<optimized out>) at ../../../bsps/aarch64/include/../../shared/dev/clock/clockimpl.h:237 #6 0x0000000000026d9c in bsp_interrupt_dispatch_entries (entry=0x1026e8 <arm_gt_interrupt_entry>) at ../../../bsps/include/bsp/irq-generic.h:571 #7 bsp_interrupt_handler_dispatch_unchecked (vector=30) at ../../../bsps/include/bsp/irq-generic.h:627 #8 bsp_interrupt_dispatch () at ../../../bsps/shared/dev/irq/arm-gicv2.c:98 #9 0x0000000000029410 in .AArch64_Interrupt_Handler () at ../../../cpukit/score/cpu/aarch64/aarch64-exception-interrupt.S:87

当_Thread_Timeout 之后,其调用堆栈如下

#0 _Scheduler_priority_Unblock (scheduler=0x2d248 <_Scheduler_Table>, the_thread=0x1059e0 <_RTEMS_tasks_Objects+1512>, node=0x105de0 <_RTEMS_tasks_Objects+2536>) at ../../../cpukit/include/rtems/score/schedulerimpl.h:108 #1 0x0000000000025008 in _Scheduler_Unblock (the_thread=0x1059e0 <_RTEMS_tasks_Objects+1512>) at ../../../cpukit/include/rtems/score/schedulerimpl.h:344 #2 _Thread_Clear_state_locked (the_thread=the_thread@entry=0x1059e0 <_RTEMS_tasks_Objects+1512>, state=state@entry=805396479) at ../../../cpukit/score/src/threadclearstate.c:65 #3 0x0000000000025070 in _Thread_Clear_state (the_thread=the_thread@entry=0x1059e0 <_RTEMS_tasks_Objects+1512>, state=805396479) at ../../../cpukit/score/src/threadclearstate.c:81 #4 0x0000000000021b18 in _Thread_Unblock (the_thread=0x1059e0 <_RTEMS_tasks_Objects+1512>) at ../../../cpukit/include/rtems/score/threadimpl.h:1098 #5 _Thread_Continue (the_thread=0x1059e0 <_RTEMS_tasks_Objects+1512>, status=STATUS_TIMEOUT) at ../../../cpukit/score/src/threadtimeout.c:91

四、结果

至此,我们通过测试rtems_task_wake_after函数,将其设置了1s的timeout,可以验证调度器的 block和unblock。unblock会在超时函数之后,清楚block标志位后,直接调用_Scheduler_Unblock函数

编辑
2025-04-03
记录知识
0

rtems提供了更新任务优先级的函数,最常用的场景是主动调用rtems_task_set_priority来调整任务的优先级,当前其他相关线程的操作都可以操作优先级,本文仅以最简单和通用的方式解释更新任务优先级

一、函数实现

rtems_task_set_priority的函数实现如下:

rtems_status_code rtems_task_set_priority( rtems_id id, rtems_task_priority new_priority, rtems_task_priority *old_priority_p )_RTEMS_tasks_Set_priority { Thread_Control *the_thread; Thread_queue_Context queue_context; const Scheduler_Control *scheduler; Priority_Control old_priority; rtems_status_code status; if ( old_priority_p == NULL ) { return RTEMS_INVALID_ADDRESS; } _Thread_queue_Context_initialize( &queue_context ); _Thread_queue_Context_clear_priority_updates( &queue_context ); the_thread = _Thread_Get( id, &queue_context.Lock_context.Lock_context ); if ( the_thread == NULL ) { #if defined(RTEMS_MULTIPROCESSING) return _RTEMS_tasks_MP_Set_priority( id, new_priority, old_priority_p ); #else return RTEMS_INVALID_ID; #endif } _Thread_Wait_acquire_critical( the_thread, &queue_context ); scheduler = _Thread_Scheduler_get_home( the_thread ); old_priority = _Thread_Get_priority( the_thread ); if ( new_priority != RTEMS_CURRENT_PRIORITY ) { status = _RTEMS_tasks_Set_priority( the_thread, scheduler, new_priority, &queue_context ); } else { _Thread_Wait_release( the_thread, &queue_context ); status = RTEMS_SUCCESSFUL; } *old_priority_p = _RTEMS_Priority_From_core( scheduler, old_priority ); return status; }

对于此函数,其作用主要是获取线程后加锁然后更新优先级,最后释放锁后返回旧的优先级, 所以我们关心主要函数_RTEMS_tasks_Set_priority。其调用路径如下

_RTEMS_tasks_Set_priority--->_Thread_Priority_update--->_Scheduler_Update_priority

二、测试程序

测试调整优先级的方法是在task创建之后的任意时间,根据taskid来调整优先级,故部分代码如下

status = rtems_task_start( Task_id[ 1 ], Task_1, 0 ); directive_failed( status, "rtems_task_start of TA1" ); rtems_task_priority previous_priority; status = rtems_task_set_priority( Task_id[ 1 ], 253, &previous_priority ); printf("Kylin: set priority 254 ret=%d prev=%d \n", status, previous_priority ); status = rtems_task_start( Task_id[ 2 ], Task_2, 0 ); directive_failed( status, "rtems_task_start of TA2" ); status = rtems_task_set_priority( Task_id[ 2 ], 254, &previous_priority ); printf("Kylin: set priority 255 ret=%d prev=%d \n", status, previous_priority );

我们可以调整task1和task2的优先级,这样task1和task2的启动顺序会出现不一样。日志如下

task1 优于 task2

Kylin: set priority 254 ret=0 prev=4 Kylin: set priority 255 ret=0 prev=4 TA1 - rtems_signal_send - RTEMS_SIGNAL_16 to self TA1 - rtems_signal_send - RTEMS_SIGNAL_0 to self TA2 - rtems_signal_send - RTEMS_SIGNAL_17 to self TA2 - rtems_signal_send - RTEMS_SIGNAL_18 and RTEMS_SIGNAL_19 to self

task2优于task1

Kylin: set priority 254 ret=0 prev=4 Kylin: set priority 255 ret=0 prev=4 TA2 - rtems_signal_send - RTEMS_SIGNAL_17 to self TA2 - rtems_signal_send - RTEMS_SIGNAL_18 and RTEMS_SIGNAL_19 to self TA1 - rtems_signal_send - RTEMS_SIGNAL_16 to self TA1 - rtems_signal_send - RTEMS_SIGNAL_0 to self

三、总结

本文简单的演示了调整任务优先级,由于目前未了解不同调度器的实现,故此实验比较简单

编辑
2025-04-03
记录知识
0

在rtems中,创建一个任务的时候会绑定一个调度器节点,_Scheduler_Node_initialize函数就是初始化调度器节点。本文基于rtems的任务创建函数rtems_task_create来介绍其初始化调度器节点的过程

一、代码流程

根据示例,代码可以调用rtems_task_create来创建一个task,如

status = rtems_task_create( Task_name[ 1 ], 4, RTEMS_MINIMUM_STACK_SIZE * 2, RTEMS_DEFAULT_MODES, RTEMS_DEFAULT_ATTRIBUTES, &Task_id[ 1 ] );

本文关注其初始化调度器节点,所以其调用流程如下

rtems_task_create--->_RTEMS_tasks_Create--->_Thread_Initialize--->_Thread_Try_initialize--->_Thread_Initialize_scheduler_and_wait_nodes--->_Scheduler_Node_initialize

二、关键代码

对于每个task的初始化过程中,需要初始化调度器节点,这里核心代码如下

do { Priority_Control priority; if ( scheduler == config->scheduler ) { priority = config->priority; home_scheduler_node = scheduler_node; } else { /* * Use the idle thread priority for the non-home scheduler instances by * default. */ priority = _Scheduler_Map_priority( scheduler, scheduler->maximum_priority ); } _Scheduler_Node_initialize( scheduler, scheduler_node, the_thread, priority ); /* * Since the size of a scheduler node depends on the application * configuration, the _Scheduler_Node_size constant is used to get the next * scheduler node. Using sizeof( Scheduler_Node ) would be wrong. */ scheduler_node = (Scheduler_Node *) ( (uintptr_t) scheduler_node + _Scheduler_Node_size ); ++scheduler; ++scheduler_index; } while ( scheduler_index < _Scheduler_Count ); /* * The thread is initialized to use exactly one scheduler node which is * provided by its home scheduler. */ _Assert( home_scheduler_node != NULL ); _Chain_Initialize_one( &the_thread->Scheduler.Wait_nodes, &home_scheduler_node->Thread.Wait_node ); _Chain_Initialize_one( &the_thread->Scheduler.Scheduler_nodes, &home_scheduler_node->Thread.Scheduler_node.Chain );
  • 如果当前调度器是主调度器,则设置优先级,并将节点设置为主调度器节点
  • 如果不是主调度器,则设置为maximum_priority优先级
  • 根据设置的优先级,初始化调度器节点
  • 定位下一个调度器节点,将调度器指针自加,如果有多个调度器,则循环初始化

三、总结

根据上面的代码演示,我们知道在每个task创建的时候,都会初始化调度器节点。由于此功能没有实际演示的效果,故无需编写测试代码演示

编辑
2025-04-03
记录知识
0

rtems系统默认的初始化的section名字不是init,而是rtemsroset,在开启过程中,通过遍历rtemsroset下的函数指针,调用此函数指针从而对系统进行初始化动作,这里根据代码解析rtems的初始化流程

一、rtemsroset

这个section是rtems的初始化section,不同于常规的elf的.init,我们可以通过如下获得section信息

# rtems-exeinfo -S build/aarch64/zynqmp_qemu/testsuites/samples/hello.exe RTEMS Executable Info 6.ca7bcc490ee8-modified rtems-exeinfo -S build/aarch64/zynqmp_qemu/testsuites/samples/hello.exe exe: build/aarch64/zynqmp_qemu/testsuites/samples/hello.exe Compilation: Producers: 2 | GNU AS 2.43: 13 objects | GNU C17 13.3.0 20240521 (RTEMS 6, RSB b1aec32059aa0e86385ff75ec01daf93713fa382-modified, Newlib 1b3dcfd): 282 objects Common flags: 7 | -ftls-model=local-exec -mno-outline-atomics -mcpu=cortex-a53 -mfix-cortex-a53-835769 -mfix-cortex-a53-843419 -mlittle-endian -mabi=lp64 Sections: 38 -------------- addr: 0x00000000 0x00000000 size: 0 align: 0 relocs: 0 .bss WA------------ addr: 0x00100780 0x00103618 size: 11928 align: 64 relocs: 0 .comment ---MS--------- addr: 0x00000000 0x0000006c size: 108 align: 1 relocs: 0 .data WA------------ addr: 0x00100000 0x00100768 size: 1896 align: 16 relocs: 0 .debug_abbrev -------------- addr: 0x00000000 0x000290ae size: 168110 align: 1 relocs: 0 .debug_aranges -------------- addr: 0x00000000 0x00004e00 size: 19968 align: 16 relocs: 0 .debug_frame -------------- addr: 0x00000000 0x00007e10 size: 32272 align: 8 relocs: 0 .debug_gdb_scripts ---MS--------- addr: 0x00000000 0x00000087 size: 135 align: 1 relocs: 0 .debug_info -------------- addr: 0x00000000 0x0014e550 size: 1369424 align: 1 relocs: 0 .debug_line -------------- addr: 0x00000000 0x00039263 size: 234083 align: 1 relocs: 0 .debug_line_str ---MS--------- addr: 0x00000000 0x00004a60 size: 19040 align: 1 relocs: 0 .debug_loclists -------------- addr: 0x00000000 0x0004b84b size: 309323 align: 1 relocs: 0 .debug_rnglists -------------- addr: 0x00000000 0x0000aa8b size: 43659 align: 1 relocs: 0 .debug_str ---MS--------- addr: 0x00000000 0x00012110 size: 74000 align: 1 relocs: 0 .eh_frame -A------------ addr: 0x00032a28 0x00032ae0 size: 184 align: 8 relocs: 0 .fini -AE----------- addr: 0x00030064 0x00030098 size: 52 align: 4 relocs: 0 .fini_array WA------------ addr: 0x00032b00 0x00032b08 size: 8 align: 8 relocs: 0 .got WA------------ addr: 0x00032b08 0x00032b68 size: 96 align: 8 relocs: 0 .init -AE----------- addr: 0x00030030 0x00030064 size: 52 align: 4 relocs: 0 .init_array WA------------ addr: 0x00032af8 0x00032b00 size: 8 align: 8 relocs: 0 .nocachenoload WA------------ addr: 0x3fec0000 0x3ffc0000 size: 1048576 align: 1 relocs: 0 .noinit WA------------ addr: 0x00105640 0x00106360 size: 3360 align: 8 relocs: 0 .robarrier W------------- addr: 0x00030098 0x00030098 size: 0 align: 1 relocs: 0 .rodata -A------------ addr: 0x000300a0 0x00032a21 size: 10625 align: 16 relocs: 0 .rtemsroset -A------------ addr: 0x00032b68 0x00032c18 size: 176 align: 8 relocs: 0 .rtemsstack WA------------ addr: 0x00103640 0x00105640 size: 8192 align: 64 relocs: 0 .rwbarrier WA------------ addr: 0x00032c18 0x00100000 size: 840680 align: 1 relocs: 0 .shstrtab -------------- addr: 0x00000000 0x00000178 size: 376 align: 1 relocs: 0 .stack W------------- addr: 0x3fec0000 0x3fec0000 size: 0 align: 1 relocs: 0 .start -AE----------- addr: 0x00018000 0x00018868 size: 2152 align: 16 relocs: 0 .strtab -------------- addr: 0x00000000 0x00005a5f size: 23135 align: 1 relocs: 0 .symtab -------------- addr: 0x00000000 0x0000b6a0 size: 46752 align: 8 relocs: 0 .tbss WA-------T---- addr: 0x00032af8 0x00032b28 size: 48 align: 8 relocs: 0 .tdata WA-------T---- addr: 0x00032ae0 0x00032af8 size: 24 align: 8 relocs: 0 .text -AE----------- addr: 0x00019000 0x00030030 size: 94256 align: 2048 relocs: 0 .vector W------------- addr: 0x00100000 0x00100000 size: 0 align: 1 relocs: 0 .work WA------------ addr: 0x00106360 0x3fec0000 size: 1071357088 align: 1 relocs: 0 .xbarrier W------------- addr: 0x00018868 0x00018868 size: 0 align: 1 relocs: 0

上面我们看到了.rtemsroset。我们接下来解析如下:

# rtems-exeinfo -I build/aarch64/zynqmp_qemu/testsuites/samples/hello.exe RTEMS Executable Info 6.ca7bcc490ee8-modified rtems-exeinfo -I build/aarch64/zynqmp_qemu/testsuites/samples/hello.exe exe: build/aarch64/zynqmp_qemu/testsuites/samples/hello.exe Compilation: Producers: 2 | GNU AS 2.43: 13 objects | GNU C17 13.3.0 20240521 (RTEMS 6, RSB b1aec32059aa0e86385ff75ec01daf93713fa382-modified, Newlib 1b3dcfd): 282 objects Common flags: 7 | -ftls-model=local-exec -mno-outline-atomics -mcpu=cortex-a53 -mfix-cortex-a53-835769 -mfix-cortex-a53-843419 -mlittle-endian -mabi=lp64 Init sections: 2 .init 0xa9bf7bfd no symbol (maybe static to a module) 0xa9bf73fb no symbol (maybe static to a module) 0xa9bf6bf9 no symbol (maybe static to a module) 0xa9bf63f7 no symbol (maybe static to a module) 0xa9bf5bf5 no symbol (maybe static to a module) 0xa9bf53f3 no symbol (maybe static to a module) .rtemsroset 0x00025910 _Workspace_Handler_initialization 0x0001f900 _Malloc_Initialize 0x00025a60 bsp_start 0x00025670 zynq_uart_kernel_init 0x0001e440 _User_extensions_Handler_initialization 0x000206c0 rtems_initialize_data_structures 0x0001ed30 _Scheduler_Ensure_exactly_one_processor 0x000240e0 _RTEMS_tasks_Manager_initialization 0x00021a30 _Thread_Create_idle 0x00024e10 bsp_r1_heap_extend 0x0001f6f0 rtems_libio_init

可以发现.rtemsroset有多个函数指针如下

0x00025910 _Workspace_Handler_initialization 0x0001f900 _Malloc_Initialize 0x00025a60 bsp_start 0x00025670 zynq_uart_kernel_init 0x0001e440 _User_extensions_Handler_initialization 0x000206c0 rtems_initialize_data_structures 0x0001ed30 _Scheduler_Ensure_exactly_one_processor 0x000240e0 _RTEMS_tasks_Manager_initialization 0x00021a30 _Thread_Create_idle 0x00024e10 bsp_r1_heap_extend 0x0001f6f0 rtems_libio_init

1.1 完整解析

这里我们通过rtems-exeinfo来读取rtemsroset的section的信息,但是这个工具读取初始化流程的函数并不完整,完整的初始化流程如下。

0x00025910 _Workspace_Handler_initialization 0x0001f900 _Malloc_Initialize 0x00025a60 bsp_start 0x00025670 zynq_uart_kernel_init 0x0001e440 _User_extensions_Handler_initialization 0x000206c0 rtems_initialize_data_structures 0x0001ed30 _Scheduler_Ensure_exactly_one_processor 0x000240e0 _RTEMS_tasks_Manager_initialization 0x00021a30 _Thread_Create_idle 0x00024e10 bsp_r1_heap_extend 0x0001f6f0 rtems_libio_init 0x00019150 rtems_filesystem_initialize 0x00019200 _Console_simple_Initialize 0x0001c290 _RTEMS_tasks_Initialize_user_task 0x00019790 rtems_libio_post_driver

值得注意的是,rtems-exeinfo程序解析的不对,少了如下

0x00019150 rtems_filesystem_initialize 0x00019200 _Console_simple_Initialize 0x0001c290 _RTEMS_tasks_Initialize_user_task 0x00019790 rtems_libio_post_driver

二、RTEMS_SYSINIT_ITEM

我们搜索代码,可以知道这些函数指针通过RTEMS_SYSINIT_ITEM宏来设置,这里解析这个宏定义,如下

#define RTEMS_SYSINIT_ITEM( handler, module, order ) \ _RTEMS_SYSINIT_ITEM( handler, module, order )

上面只是封装,所以我们查看_RTEMS_SYSINIT_ITEM

#define _RTEMS_SYSINIT_ITEM( handler, module, order ) \ _RTEMS_SYSINIT_INDEX_ITEM( handler, 0x##module##order )

这里组合了module, order作为地址,我们继续看_RTEMS_SYSINIT_INDEX_ITEM

#define _RTEMS_SYSINIT_INDEX_ITEM( handler, index ) \ enum { _Sysinit_##handler = index }; \ RTEMS_LINKER_ROSET_ITEM_ORDERED( \ _Sysinit, \ rtems_sysinit_item, \ handler, \ index \ ) = { handler }

这里定义了一个enum值,并且设置了RTEMS_LINKER_ROSET_ITEM_ORDERED,我们查看其定义

#define RTEMS_LINKER_ROSET_ITEM_ORDERED( set, type, item, order ) \ RTEMS_LINKER_SET_ALIGN( type ) type const _Linker_set_##set##_##item \ RTEMS_SECTION( ".rtemsroset." #set ".content.0." RTEMS_XSTRING( order ) ) \ RTEMS_USED

这里看到

  • set 作为变量名字组合标签
  • type 作为变量类型
  • item 将函数名字组合变量名
  • order 作为__section__的的地址

这里假设第一个初始化函数_Thread_Create_idle ,其定义如下:

RTEMS_SYSINIT_ITEM( _Thread_Create_idle, RTEMS_SYSINIT_IDLE_THREADS, RTEMS_SYSINIT_ORDER_MIDDLE );

其解析后如下:

enum { _Sysinit__Thread_Create_idle = 0x001d0080 }; __attribute__(( __aligned__( _Alignof( rtems_sysinit_item ) ) )) rtems_sysinit_item const _Linker_set__Sysinit__Thread_Create_idle __attribute__(( __section__( ".rtemsroset." "_Sysinit" ".content.0." "0x001d0080" ) )) __attribute__(( __used__ )) = { _Thread_Create_idle }

三、RTEMS_LINKER_SET_FOREACH

我们在rtems_initialize_executive中可以看到调用如下

/* Invoke the registered system initialization handlers */ RTEMS_LINKER_SET_FOREACH( _Sysinit, item ) { ( *item->handler )(); }

此时查看RTEMS_LINKER_SET_FOREACH定义

#define RTEMS_LINKER_SET_FOREACH( set, item ) \ for ( \ item = (void *) _Linker_set_Obfuscate( RTEMS_LINKER_SET_BEGIN( set ) ) ; \ item != RTEMS_LINKER_SET_END( set ) ; \ ++item \ )

然后查看两个宏定义

#define RTEMS_LINKER_SET_BEGIN( set ) \ _Linker_set_##set##_begin #define RTEMS_LINKER_SET_END( set ) \ _Linker_set_##set##_end

确定内联函数_Linker_set_Obfuscate

static inline uintptr_t _Linker_set_Obfuscate( const void *ptr ) { uintptr_t addr; addr = (uintptr_t) ptr; RTEMS_OBFUSCATE_VARIABLE( addr ); return addr; }

至此,我们可以知道,通过遍历_Linker_set__Sysinit_begin到_Linker_set__Sysinit_end获取到了每个item,然后之间调用handler函数指针,即如下

typedef void ( *rtems_sysinit_handler )( void ); typedef struct { rtems_sysinit_handler handler; } rtems_sysinit_item;

这里可以看到如下

(gdb) p _Linker_set__Sysinit_begin $49 = 0x32b68 <_Linker_set__Sysinit__Workspace_Handler_initialization> (gdb) p _Linker_set__Sysinit_end $50 = 0x32be0 <_Copyright_Notice>

四、总结

至此,我们清楚的知道了rtems的初始化时序