Chinaunix首页 | 论坛 | 博客
  • 博客访问: 515497
  • 博文数量: 87
  • 博客积分: 4086
  • 博客等级: 上校
  • 技术积分: 900
  • 用 户 组: 普通用户
  • 注册时间: 2007-12-23 15:55
文章分类

全部博文(87)

文章存档

2012年(3)

2010年(13)

2009年(7)

2008年(64)

我的朋友

分类: LINUX

2010-09-29 19:02:21

这里以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

                                                                                                                         2010-9-29
阅读(7336) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~