当前位置: 首页 > news >正文

ecos启动流程分析

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

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

相关文章:

  • Android 九宫格密码锁进入程序
  • 泛型通配符?的使用
  • 通过JAVA反射修改JDK1.6*当中DNS缓存内容
  • 开发人员拒绝写技术博客的几个理由
  • 制作网线
  • 基于单决策树的AdaBoost
  • 【SICP练习】53 练习2.21
  • php 基础算法(用*表示金字塔)通过hash 比較两个数组同样的数
  • sass带来的变革
  • QlikView ETL - 分隔字符串的方法 SubField
  • 微软职位内部推荐-Senior Development Lead
  • WSS(Windows Storage Server)2008R2使用指南(二)安装篇
  • javascript——DOM之元素的宽高
  • 【原创】开源Math.NET基础数学类库使用(08)C#进行数值积分
  • SQL Server 2008 R2 安全性专题(一):安全原则
  • [分享]iOS开发 - 实现UITableView Plain SectionView和table不停留一起滑动
  • “Material Design”设计规范在 ComponentOne For WinForm 的全新尝试!
  • angular学习第一篇-----环境搭建
  • CSS魔法堂:Absolute Positioning就这个样
  • JAVA SE 6 GC调优笔记
  • SpiderData 2019年2月16日 DApp数据排行榜
  • 不用申请服务号就可以开发微信支付/支付宝/QQ钱包支付!附:直接可用的代码+demo...
  • 大主子表关联的性能优化方法
  • 分类模型——Logistics Regression
  • 利用DataURL技术在网页上显示图片
  • 爬虫进阶 -- 神级程序员:让你的爬虫就像人类的用户行为!
  • 通过来模仿稀土掘金个人页面的布局来学习使用CoordinatorLayout
  • 一个SAP顾问在美国的这些年
  • 硬币翻转问题,区间操作
  • NLPIR智能语义技术让大数据挖掘更简单
  • 翻译 | The Principles of OOD 面向对象设计原则
  • ​草莓熊python turtle绘图代码(玫瑰花版)附源代码
  • #1015 : KMP算法
  • #单片机(TB6600驱动42步进电机)
  • #在 README.md 中生成项目目录结构
  • $var=htmlencode(“‘);alert(‘2“); 的个人理解
  • (cos^2 X)的定积分,求积分 ∫sin^2(x) dx
  • (LeetCode) T14. Longest Common Prefix
  • (zhuan) 一些RL的文献(及笔记)
  • (分布式缓存)Redis持久化
  • (译)2019年前端性能优化清单 — 下篇
  • (转)C#开发微信门户及应用(1)--开始使用微信接口
  • .[hudsonL@cock.li].mkp勒索病毒数据怎么处理|数据解密恢复
  • .NET Core WebAPI中使用Log4net 日志级别分类并记录到数据库
  • .net 简单实现MD5
  • .NET/C# 避免调试器不小心提前计算本应延迟计算的值
  • .net6解除文件上传限制。Multipart body length limit 16384 exceeded
  • .NET建议使用的大小写命名原则
  • .net连接oracle数据库
  • @hook扩展分析
  • [【JSON2WEB】 13 基于REST2SQL 和 Amis 的 SQL 查询分析器
  • [2016.7 day.5] T2
  • [AR Foundation] 人脸检测的流程
  • [C/C++]数据结构 栈和队列()
  • [iOS]让Xcode 4.2生成的app支持老的iOS设备(armv6)