ecos启动流程分析

一、关于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, orPthreads.

我添加这个兼容层的目的是因为项目中使用了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

                                                                                                                         Yqliu29@163.com

                                                                                                                         QQ:371310524

                                                                                                                         2010-9-29

转载于:https://my.oschina.net/yuyang/blog/375232

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值