这里以ep9315平台为例,分析ecos3.0的启动过程。ecos的配置情况为启用了posix兼容,应用程序从main开始。这里就分析从系统启动后,到执行main之前,系统做了哪些工作。
一、关于C++构造函数的自动执行
众所周知,arm9平台上电后ecos将运行一段汇编代码,这段汇编代码将要对cpu做一些基本的初始化,完成后跳转到c程序中执行。CPU初始化不是我们今天要关注的重点,今天关注的重点是关于C++构造函数的“自动”执行。
进入自动执行代码的入口在hal/arm/arch/v3_0/src/vectors.S文件中,有下面一行:
bl cyg_hal_invoke_constructors |
这里的cyg_hal_invoke_constructors就是执行的C++的构造函数。这个函数的具体位置在 hal/arm/arch/v3_0/src/hal_misc.c文件中,全部定义如下: void cyg_hal_invoke_constructors (void) { #ifdef CYGSEM_HAL_STOP_CONSTRUCTORS_ON_FLAG static pfunc *p = &CONSTRUCTORS_START; cyg_hal_stop_constructors = 0; for (; p != CONSTRUCTORS_END; NEXT_CONSTRUCTOR(p)) { (*p)(); if (cyg_hal_stop_constructors) { NEXT_CONSTRUCTOR(p); break; } } #else pfunc *p; for (p = &CONSTRUCTORS_START; p != CONSTRUCTORS_END; NEXT_CONSTRUCTOR(p)) (*p)(); #endif } |
可以发现,这个函数实际上就是执行了一个函数数组。来看看这几个宏的定义,位于同一个文件中:
extern pfunc __init_array_start__[]; extern pfunc __init_array_end__[]; #define CONSTRUCTORS_START (__init_array_start__[0]) #define CONSTRUCTORS_END (__init_array_end__) #define NEXT_CONSTRUCTOR(c) ((c)++) |
而这个__init_array_start__[]和__init_array_end__[]是什么?看看链接文件中的定义,链接文件是hal/arm/arch/v3_0/src/arm.ld,有如下定义:
#define SECTION_data(_region_, _vma_, _lma_) \ .data _vma_ : _lma_ \ { __ram_data_start = ABSOLUTE (.); \ *(.data*) *(.data1) *(.gnu.linkonce.d.*) MERGE_IN_RODATA \ . = ALIGN (4); \ KEEP(*( SORT (.ecos.table.*))) ; \ . = ALIGN (4); \ __init_array_start__ = ABSOLUTE (.); KEEP (*(SORT (.init_array.*))) \ KEEP (*(SORT (.init_array))) __init_array_end__ = ABSOLUTE (.); \ *(.dynamic) *(.sdata*) *(.gnu.linkonce.s.*) \ . = ALIGN (4); *(.2ram.*) } \ > _region_ \ __rom_data_start = LOADADDR (.data); \ __ram_data_end = .; PROVIDE (__ram_data_end = .); _edata = .; PROVIDE (edata = .); \ PROVIDE (__rom_data_end = LOADADDR (.data) + SIZEOF(.data)); |
可以发现,__init_array_start__[]和__init_array_end__[]实际上是和.init_array相关的段。在编译好的ELF文件中,我没有发现有.init_array段,倒是在arm的官方文档《C++ ABI for the ARM architecture》上查到一些相关的解释:
The compiler is responsible for sequencing the construction of top-level static objects defined in a translation unit in accordance with the requirements of the C++ standard.The run-time environment (helper-function library) sequences the initialization of one translation unit after another. The global constructor vector provides the interface between these agents as follows. Each translation unit provides a fragment of the constructor vector in an ELF section called .init_array of type SHT_INIT_ARRAY (=0xE) and section flags SHF_ALLOC + SHF_WRITE. |
意思是说,编译器会把源文件中定义的静态类的构造函数添加到ELF文件中的名叫.init_array的段中。那就好理解了,即.init_array段中包含的就是源文件中定义的静态类的构造函数,这些构造函数是一个函数数组,因此,上面的cyg_hal_invoke_constructors 函数就是执行了各个源文件中定义的静态类的构造函数,使C++的构造函数得以“自动”执行。
二、关于cyg_start
汇编代码执行到最后,将会跳入第一个C函数,cyg_start。这个函数位于infra/v3_0/src/startup.cxx文件中:
void cyg_start( void ) { CYG_REPORT_FUNCTION(); CYG_REPORT_FUNCARGVOID(); cyg_prestart(); cyg_package_start(); cyg_user_start(); #ifdef CYGPKG_KERNEL Cyg_Scheduler::start(); #endif CYG_REPORT_RETURN(); } |
他的重要工作有两个:一是调用cyg_user_start,二是调用了调度器的start函数,开始线程的调度。
从网上的其他文档我们知道,启动应用程序的方式有两种,一个是通过main函数,另外一种就是通过cyg_user_start。我们使用的是main函数的方式,如果使用cyg_user_start来启动我们的应用程序的话,必须注意我们的cyg_user_start不能是一个死循环的函数,因为若cyg_user_start不退出,则调度器的start永远得不到运行,调度器就不能工作,多线程当然就没法实现了。
我们使用main函数的时候,这个cyg_user_start就是用内核中自己定义的一个,是一个空函数。因此,若使用main,那么这个cyg_start函数在启动了调度器后就结束了,那么我们的main函数是如何得以执行的?
三、关于posix兼容层
关于posix,使用官方的说法就是:
For UNIX systems, a standardized C language threads programming interface has been specified by the IEEE POSIX 1003.1c standard. Implementations that adhere to this standard are referred to as POSIX threads, or Pthreads. |
我添加这个兼容层的目的是因为项目中使用了minigui,而minigui库中使用了这个接口。
添加这个兼容层,导致main函数的启动与不添加时有一定的差异。这里先提出来,以便于下面的继续分析。
四、main函数的启动
在第二节中我们提到,系统起来后调用cyg_start,但是这个cyg_start并没有直接调用我们的main函数。那么我们的main函数是怎样执行的呢?
前面我们还提到,在进入cyg_start函数以前,系统首先执行了各个静态类的构造函数,那么这些构造函数有哪些呢?我们重点关注和线程相关的构造函数。首先大家都知道,多线程的系统中,总有一个idle线程,当其他线程都不需要执行的时候,系统就运行这个idle线程。在源文件kernel/v3_0/src/common/thread.cxx中,定义了一个idle线程:
Cyg_IdleThread idle_thread[CYGNUM_KERNEL_CPU_MAX] CYG_INIT_PRIORITY( IDLE_THREAD ); |
再看看Cyg_IdleThread的构造函数:
Cyg_IdleThread::Cyg_IdleThread() : Cyg_Thread( CYG_THREAD_MIN_PRIORITY, idle_thread_main, 0, (char *)"Idle Thread", (CYG_ADDRESS)idle_thread_stack[this-&idle_thread[0]], CYGNUM_KERNEL_THREADS_IDLE_STACK_SIZE) { CYG_REPORT_FUNCTION(); // Call into scheduler to set up this thread as the default // current thread for its CPU. Cyg_Scheduler::scheduler.set_idle_thread( this, this-&idle_thread[0] ); CYG_REPORT_RETURN(); } |
这个类是继承至Cyg_Thread类,因此这个构造函数同时初始化了一个Cyg_Thread类,使用的优先级为CYG_THREAD_MIN_PRIORITY,即系统的最低优先级(被定义为31)。在没有比它高优先级的任务运行时,就运行这个线程。那么这里若只有一个线程,在调度器起来以后(cyg_start中启动了调度器),就只有这一个idle线程可以调度了,我们的main函数还是没有执行,因此应该还有其他地方启动了我们的main函数。
第三节中提到了posix兼容层,在文件compat/posix/v3_0/src/startup.cxx中,有:
class cyg_posix_startup_dummy_constructor_class { public: cyg_posix_startup_dummy_constructor_class() { #ifdef CYGPKG_POSIX_PTHREAD cyg_posix_pthread_start(); #endif #ifdef CYGPKG_POSIX_SIGNALS cyg_posix_signal_start(); cyg_posix_exception_start(); #endif #ifdef CYGPKG_POSIX_TIMERS cyg_posix_clock_start(); #endif } }; static cyg_posix_startup_dummy_constructor_class cyg_posix_startup_obj
CYGBLD_ATTRIB_INIT_PRI(CYG_INIT_COMPAT);
|
即定义了一个cyg_posix_startup_dummy_constructor_class类,并定义了这个类的一个实例cyg_posix_startup_obj。从第一节我们知道这个实例的构造函数在进入cyg_start之前就执行了。而他的构造函数中执行了cyg_posix_pthread_start函数,我们看看这个函数的实现,位于compat/posix/v3_0/src/pthread.cxx中:
externC void cyg_posix_pthread_start( void ) { // Initialize the per-thread data key map. for( cyg_ucount32 i = 0; i < (PTHREAD_KEYS_MAX/KEY_MAP_TYPE_SIZE); i++ ) { thread_key[i] = ~0; } // Create the main thread pthread_attr_t attr; struct sched_param schedparam; schedparam.sched_priority = CYGNUM_POSIX_MAIN_DEFAULT_PRIORITY; pthread_attr_init( &attr ); pthread_attr_setinheritsched( &attr, PTHREAD_EXPLICIT_SCHED ); pthread_attr_setstackaddr( &attr, &main_stack[sizeof(main_stack)] ); pthread_attr_setstacksize( &attr, sizeof(main_stack) ); pthread_attr_setschedpolicy( &attr, SCHED_RR ); pthread_attr_setschedparam( &attr, &schedparam ); pthread_create( &main_thread, &attr, call_main, NULL ); } |
这个函数的末尾,调用pthread_create创建了一个线程,这个线程的入口函数是call_main,因此我们看看call_main的实现,位于同一个文件中:
externC void cyg_libc_invoke_main( void ); static void *call_main( void * ) { cyg_libc_invoke_main(); return NULL; // placate compiler } |
他实际上就是调用了cyg_libc_invoke_main函数。从函数名字我们就知道这个函数和libc相关。它位于language/c/libc/startup/v3_0/src/invokemain.cxx中,只有包含了C库时,才会编译这个文件,因此要从main启动程序,必须包含c库。这个函数定义如下:
externC void cyg_libc_invoke_main( CYG_ADDRWORD ) { CYG_REPORT_FUNCNAME( "cyg_libc_invoke_main" ); CYG_REPORT_FUNCARG1( "argument is %s", "ignored" ); #ifdef CYGSEM_LIBC_INVOKE_DEFAULT_STATIC_CONSTRUCTORS // finish invoking constructors that weren't called by default cyg_hal_invoke_constructors(); #endif // argv[argc] must be NULL according to the ISO C standard 5.1.2.2.1 char *temp_argv[] = CYGDAT_LIBC_ARGUMENTS ; int rc; rc = main( (sizeof(temp_argv)/sizeof(char *)) - 1, &temp_argv[0] ); CYG_TRACE1( true, "main() has returned with code %d. Calling exit()", rc ); #ifdef CYGINT_ISO_PTHREAD_IMPL // It is up to pthread_exit() to call exit() if needed pthread_exit( (void *)rc ); CYG_FAIL( "pthread_exit() returned!!!" ); #else exit(rc); CYG_FAIL( "exit() returned!!!" ); #endif CYG_REPORT_RETURN(); } |
可以看到这个函数的关键代码就是引入了我们定义的main函数。到这里,我们的应用程序就得以执行了。
从本节的分析我们知道,系统起来后创建了两个线程:一个线程是idle线程,在没有其他任务运行的时候运行这个idle线程;另外一个线程就是我们的main函数的线程,这个线程用来启动我们的应用程序。当然,在main函数起来以后,我们可以任意启动其他线程。
这是本人第一次分析一个os,错误之处在所难免,欢迎大家讨论!
http://yqliu.cublog.cn
QQ:371310524