rtems系统的程序通过Init开始默认执行,本文基于此现象,分享Init函数的原理
Init函数声明如下:
#ifndef CONFIGURE_INIT_TASK_ENTRY_POINT rtems_task Init( rtems_task_argument ); #define CONFIGURE_INIT_TASK_ENTRY_POINT Init #ifndef CONFIGURE_INIT_TASK_ARGUMENTS extern const char *bsp_boot_cmdline; #define CONFIGURE_INIT_TASK_ARGUMENTS \ ( (rtems_task_argument) &bsp_boot_cmdline ) #endif #endif
这里可以知道,默认情况下,Entry是Init函数,形参是全局变量bsp_boot_cmdline,对于bsp_boot_cmdline我们可以在bootcard看到如下:
void boot_card( const char *cmdline ) { bsp_boot_cmdline = cmdline; }
对于Init函数如何来的,我们首先关注如下全局变量
const rtems_initialization_tasks_table _RTEMS_tasks_User_task_table = { CONFIGURE_INIT_TASK_NAME, CONFIGURE_INIT_TASK_STACK_SIZE, CONFIGURE_INIT_TASK_PRIORITY, CONFIGURE_INIT_TASK_ATTRIBUTES, _CONFIGURE_ASSERT_NOT_NULL( rtems_task_entry, CONFIGURE_INIT_TASK_ENTRY_POINT ), CONFIGURE_INIT_TASK_INITIAL_MODES, CONFIGURE_INIT_TASK_ARGUMENTS };
其结构体如下:
typedef struct { rtems_name name; size_t stack_size; rtems_task_priority initial_priority; rtems_attribute attribute_set; rtems_task_entry entry_point; rtems_mode mode_set; rtems_task_argument argument; } rtems_initialization_tasks_table;
我们打印实际的值如下:
(gdb) p/x _RTEMS_tasks_User_task_table $36 = { name = 0x55493120, stack_size = 0x2000, initial_priority = 0x1, attribute_set = 0x1, entry_point = 0x19100, mode_set = 0x0, argument = 0x1028a0 }
对于此,解析如下:
name: “'U', 'I', '1', ' '” staci_size:8192 task_priority: 1 attr: 1 entry: Init函数地址 mode_set: 设置调度模式是asr argument:这里指向 bsp_boot_cmdline
然后,我们关心rtems_task_start函数,这里构造了Thread_Entry_information如下
Thread_Entry_information entry = { .adaptor = _Thread_Entry_adaptor_numeric, .Kinds = { .Numeric = { .entry = entry_point, .argument = argument } } };
我们留意这个information的如下成员
adaptor Kinds.Numeric.entry
对于adaptor的实现如下:
void _Thread_Entry_adaptor_numeric( Thread_Control *executing ) { const Thread_Entry_numeric *numeric = &executing->Start.Entry.Kinds.Numeric; ( *numeric->entry )( numeric->argument ); }
这里可以发现,通过adaptor的封装,直接调用的实际上是rtems_task_start的形参entry_point函数指针。
此时我们留意Thread_Entry_information entry,它被_Thread_Start( the_thread, &entry, &lock_context );调用,然后直接赋值给Entry成员
the_thread->Start.Entry = *entry;
根据上面的代码分析,我们找到了Entry指针是Init函数。但是如何初始化的问题并没有解析到,所以继续查看
这里我们需要额外注意the_thread,在_Thread_Load_environment中会构造线程的上下文,如下:
_Context_Initialize( &the_thread->Registers, the_thread->Start.Initial_stack.area, the_thread->Start.Initial_stack.size, the_thread->Start.isr_level, _Thread_Handler, the_thread->is_fp, the_thread->Start.tls_area );
这里上下文初始化的实现如下:
void _CPU_Context_Initialize( Context_Control *the_context, void *stack_area_begin, size_t stack_area_size, uint64_t new_level, void (*entry_point)( void ), bool is_fp, void *tls_area ) { (void) new_level; the_context->register_sp = (uintptr_t) stack_area_begin + stack_area_size; the_context->register_lr = (uintptr_t) entry_point; the_context->isr_dispatch_disable = 0; the_context->thread_id = (uintptr_t) tls_area; if ( tls_area != NULL ) { the_context->thread_id = (uintptr_t) _TLS_Initialize_area( tls_area ); } }
可以发现lr寄存器的值就是entry_point,这里就是_Thread_Handler函数,也就是线程完全初始化完成之后,默认的第一个x30寄存器就是_Thread_Handler函数
根据上面的函数,我们关注两个重点,1是the_thread的地址,2是the_context的地址。gdb如下:
(gdb) p the_thread $2 = (Thread_Control *) 0x1056e8 <_RTEMS_tasks_Objects> (gdb) p &the_thread->Registers $4 = (Context_Control *) 0x105920 <_RTEMS_tasks_Objects+568>
此时我们回到_Thread_Start_multitasking函数,这里开始执行除idle线程外的第一个线程。如下
void _Thread_Start_multitasking( void ) { Per_CPU_Control *cpu_self = _Per_CPU_Get(); Thread_Control *heir; heir = _Thread_Get_heir_and_make_it_executing( cpu_self ); _CPU_Start_multitasking( &heir->Registers ); }
这里对于aarch64,实际上是_AArch64_Start_multitasking的实现在cpu_asm.S
DEFINE_FUNCTION_AARCH64(_AArch64_Start_multitasking) mov x1, x0 GET_SELF_CPU_CONTROL reg_2 /* Switch the stack to the temporary interrupt stack of this processor */ add sp, x2, #(PER_CPU_INTERRUPT_FRAME_AREA + CPU_INTERRUPT_FRAME_SIZE) /* Enable interrupts */ msr DAIFClr, #0x2 b .L_check_is_executing
这里GET_SELF_CPU_CONTROL是获取TPIDR_EL1的值,也就是当前cpu的per cpu information。如下
.macro GET_SELF_CPU_CONTROL REG #ifdef RTEMS_SMP /* Use Thread ID Register (TPIDR_EL1) */ mrs \REG, TPIDR_EL1 #else ldr \REG, =_Per_CPU_Information #endif .endm
这里_AArch64_Start_multitasking读取了per cpu值,然后设置了sp,并打开终端,然后跳转到L_check_is_executing。L_check_is_executing的实现如下:
.L_check_is_executing: /* Check the is executing indicator of the heir context */ add x3, x1, #AARCH64_CONTEXT_CONTROL_IS_EXECUTING_OFFSET ldaxrb w4, [x3] cmp x4, #0 bne .L_get_potential_new_heir /* Try to update the is executing indicator of the heir context */ mov x4, #1 stlxrb w5, w4, [x3] cmp x5, #0 bne .L_get_potential_new_heir dmb SY #endif /* Start restoring context */ .L_restore: ldr x3, [x1, #AARCH64_CONTEXT_CONTROL_THREAD_ID_OFFSET] ldr x4, [x1, #AARCH64_CONTEXT_CONTROL_ISR_DISPATCH_DISABLE] #ifdef AARCH64_MULTILIB_VFP add x5, x1, #AARCH64_CONTEXT_CONTROL_D8_OFFSET ldp d8, d9, [x5] ldp d10, d11, [x5, #0x10] ldp d12, d13, [x5, #0x20] ldp d14, d15, [x5, #0x30] #endif msr TPIDR_EL0, x3 str w4, [x2, #PER_CPU_ISR_DISPATCH_DISABLE] ldp x19, x20, [x1] ldp x21, x22, [x1, #0x10] ldp x23, x24, [x1, #0x20] ldp x25, x26, [x1, #0x30] ldp x27, x28, [x1, #0x40] ldp fp, lr, [x1, #0x50] ldr x4, [x1, #0x60] mov sp, x4 ret
上述汇编我们先看L_check_is_executing的含义
add x3, x1, #AARCH64_CONTEXT_CONTROL_IS_EXECUTING_OFFSET # 将x1的值+0xb8给x3 ldaxrb w4, [x3] # 将x3存放地址计算值给w4,这时候w4应该是0。 ldaxrb是load acquire(ldr) 和 exclusive (xrb) cmp x4, #0 # 比较x4的值和0 bne .L_get_potential_new_heir # 如果x4的值不为0,则说明说明本线程已经被人置1.避免重复激活已经运行的线程 /* Try to update the is executing indicator of the heir context */ mov x4, #1 # 将x4设置为1 stlxrb w5, w4, [x3] # 将x4的值存放在x3指向的地址上,如果存放成功则w5为0,如果存放失败则w5为1. cmp x5, #0 # 比较x5是否为0 bne .L_get_potential_new_heir # 如果不为0,则store release失败 dmb SY # 设置内存屏障,禁止乱序执行
通过上面可以发现,这里通过原子设置一个内存地址的值,从而确定当前线程激活运行。
接下来查看上下文的保存代码:
/* Start restoring context */ .L_restore: ldr x3, [x1, #AARCH64_CONTEXT_CONTROL_THREAD_ID_OFFSET] # 将x1的值+0x70的地址的值读给x3 ldr x4, [x1, #AARCH64_CONTEXT_CONTROL_ISR_DISPATCH_DISABLE] # x1的值+0x68的地址的值读给x4 #ifdef AARCH64_MULTILIB_VFP add x5, x1, #AARCH64_CONTEXT_CONTROL_D8_OFFSET # 将x1的值+0x78后赋值给x5 ldp d8, d9, [x5] ldp d10, d11, [x5, #0x10] ldp d12, d13, [x5, #0x20] ldp d14, d15, [x5, #0x30] #endif msr TPIDR_EL0, x3 # 将x3给 tpidr_el0 str w4, [x2, #PER_CPU_ISR_DISPATCH_DISABLE] # 将w4的值存放在 x2+PER_CPU_ISR_DISPATCH_DISABLE 的位置上 ldp x19, x20, [x1] # 将x1和下一个值 赋值给x19和x20 ldp x21, x22, [x1, #0x10] # 以此类推 ldp x23, x24, [x1, #0x20] # 以此类推 ldp x25, x26, [x1, #0x30] # 以此类推 ldp x27, x28, [x1, #0x40] # 以此类推 ldp fp, lr, [x1, #0x50] # 以此类推 ldr x4, [x1, #0x60] # 以此类推 mov sp, x4 # 将x4的值给sp寄存器 ret # 返回
上述这段的ldp指令,对应结构体如下:
typedef struct { uint64_t register_x19; uint64_t register_x20; uint64_t register_x21; uint64_t register_x22; uint64_t register_x23; uint64_t register_x24; uint64_t register_x25; uint64_t register_x26; uint64_t register_x27; uint64_t register_x28; uint64_t register_fp; uint64_t register_lr; uint64_t register_sp; uint64_t isr_dispatch_disable; uint64_t thread_id; #ifdef AARCH64_MULTILIB_VFP uint64_t register_d8; uint64_t register_d9; uint64_t register_d10; uint64_t register_d11; uint64_t register_d12; uint64_t register_d13; uint64_t register_d14; uint64_t register_d15; #endif #ifdef RTEMS_SMP volatile bool is_executing; #endif
故,这里是加载线程上下文的寄存器到系统寄存器上。这里我们需要注意lr寄存器,之前提到是entry_point,如下:
the_context->register_lr = (uintptr_t) entry_point;
所以我们接下来关注函数:
void _Thread_Handler( void )
这里调用了adaptor回调,如下:
( *executing->Start.Entry.adaptor )( executing );
根据上面的分析,这里adaptor是 _Thread_Entry_adaptor_numeric
,然后函数调用entry,这里是entry_point,也就是Init函数指针
至此,这个Init函数的初始化介绍完全完整