打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
eCos启动流程解析

这里以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-levelstatic 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 tothis 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,错误之处在所难免,欢迎大家讨论!

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
同步
Linux多线程pthread
Linux下线程用法总结
QNX system architecture 2
Linux 线程操作函数技能总结
Posix线程编程指南(5)
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服