eCos系统启动流程
张龙 2010-9-24
通过调研,总结了eCos系统的启动流程,我认为可以分为以下两大步:
1. HAL的启动;
2. 内核的启动。
一.HAL的启动
HAL是对硬件进行初始化操作, 当然跟硬件平台有关,不过大致流程是相似的,这里以基于PC的硬件平台为例,图1.1是HAL的启动流程图。
Hardware PowerupReset_vector_startHal_cpu_initHal_hardware_initSetup interrupt stackHal_mon_initClear bss sectionSetup C function call stackHal_platform_initHal_MMU_initHal_enable_cachesHal_IRQ_initCyg_hal_invoke_constructorsInitialize_stubCyg_start On to Kernal startup..
图1.1 HAL启动流程图
下面具体解说一下流程图1.1:
1. 系统启动的开端是电复位,一般是从0X0开始;
2. 电复位之后,跳转到reset_vector,在arch文件夹下的文件vectors.S中,这个文件包含所有HAL包的启动点,reset vector应用最小的处理器寄存器配置来使得系统进行初始化操作;
3. Reset vector跳转到_start函数处,这个函数仍然存在于vectors.S文件中,_start是整个HAL初始化的开始处;
4. 跳转到hal_cpu_init子程序,这个函数存在于variant.inc或者是arch.inc文件中,这个函数设置处理器特定的寄存器,如禁用指令和数据高速缓存,以确保处理器在其余的初始化过程中是一个已知的状态;
5. 跳转到hal_hardware_init子程序,进行cache配置、设置中断寄存器到一个默认的状态,关闭看门狗,设置实时时钟寄存器,和进行基于特殊硬件平台的芯片选择寄存器的配置等操作;
6. 建立中断堆栈区域,当出现中断时用来保存处理器的状态信息;
7. 进行hal_mon_init程序,它位于文件variant.inc或platform.inc中,为了能够正确地访问RAM, ROM 和 I/O设备,设立所有的CPU内存控制器。除非完成了这个动作,否则不可能访问到RAM。当然这个对于不同的存储器会存在差异;
8. 清空BSS段,它包含了所有未初始化的局部和全局变量,还包括静态存储类;
9. 设置堆栈,为执行C语言代码做好准备,通常可以把SP的值设置在上面所分配的RAM空间的最顶端(堆栈向下生长);
10. 执行hal_platform_init程序,位于hal_aux.c文件中,进行平台初始化,这个要根据硬件平台的不同,初始化的操作也有所不同,如进行I/O接口的初始化、PCI的初始化等等;
11. 初始化MMU,MMU处理逻辑地址和物理地址之间的映射,还提供保护机制和缓存机制。在位于hal_misc.c文件中的函数hal_MMU_init中;
12. 使能数据catch和指令catch,函数名为hal_enable_catches,位于文件hal_misc.c中;
13. 对于基于PC处理器的硬件平台来说,此时会运行hal_IRQ_init函数,用来设置通信处理模块(Communications Processor Module),它用来接收和排序内部和外部中断,这个函数存在于文件hal_intr.c中;
14. 在cyg_hal_invoke_constructors()中调用所有全局C++对象的构造函数,这个函数存在于hal_misc.c文件中;
15. 如果配置是一个debug环境,这里会调用位于generic_stub.c文件中的initialize_stub函数,它负责安装标准陷阱处理和初始化硬件来debug;
16. 读取内核映像和根文件系统从Flash或ROM到RAM空间中,转入内核的初始化,调用函数cyg_start;
二.内核的启动
内核的启动时由HAL的启动结束时唤醒的,内核的启动被包含在函数cyg_start中,
它通过调用一些启动函数来处理各种各样的初始化工作。Cyg_start位于文件startup.cxx中。图2.1描述了内核启动的过程。
HAL_startup ProcedureCyg_startCyg_prestartCyg_package_startCyg_user_startStart the scheduler 图2.1 内核启动流程图
1. 启动cyg_start()
启动cyg_start()函数是ecos启动机制的核心。该函数位于源文件infra/current/src/startup.cxx,它依次调用下列函数:
cyg_prestart()
cyg_package_start()
cyg_user_start()
如果在配置时选择了调度器,它还将启动被选择的ecos调度器。
这只是ecos所提供的一个实现方法。用户服务在进行自己的开发时,也可以使用下面的函数原型对该函数进行修改:
void cyg_start(void)
一般来说不需要对该函数进行修改,因为cyg_prestart()函数和cyg_user_start()函数具有足够的灵活性,允许用户加入其他程序代码,几乎可以满足所有的应用需求。
2. 函数cyg_prestart()
该函数位于源文件infra/current/src/prestart.cxx内。ecos提供了默认的cyg_prestart()函数,不做任何操作。如果在进行其他系统级初始化操作之前还需要进行某些初始化操作,则可以使用该函数来实现。该函数原型为:
void cyg_prestart(void)
3. 函数cyg_package_start()
该函数位于源程序infra/current/src/pkgstart.cxx内。它允许在进入用户主程序之前对个别包进行初始化操作。
cyg_package_start()函数包含了两个包:UTTRON包和标准C库包。基础结构包中包含了两个配置选项 CYGSEM_START_UITRON_COMPATIBILITY
和
CYGSEM_START_ISO_COMPATIBILITY,它们用于控制这 些特殊的包的初始化。函数原型为:
void cyg_package_start()
用户可以根据该原型编写自己的cyg_package_start()函数,但在初始化默认包时必须加以小心。下面是用户编写该函数的一个例子:
void cyg_package_start(start)
{
#ifdef CYGSEM_START_UITRON_COMPATABILITY
cyg_uitron_start(); /*keep the uITRON initialization */
#endif
my_package_start(); /* make sure I initialize my package */
}
4.函数cyg_user_start()
该函数位于源文件infra/current/src/userstart.cxx内。cyg_user_start()函数是用户程序的正常入口点。ecos源码提供的该函数不做任何操作。这是用户创建自己线程的一个理想的地方。如果在配置时没有选择ISO标准C库包,则必须实现该函数,此时它是用户程序的一个强制性入口。其函数原型为:
void cyg_user_start(void)
用户可以编写自己的cyg_user_start()函数。ecos为该函数提供了一个默认实现,但不做任何工作。应用程序使用该函数作为入口时,将覆盖其默认实现,实现应用程序与ecos系统的连接。
如果配置时选择了ISO标准C库包,这个库提供了一个默认的cyg_user_start()函数,这个默认的函数会产生一个线程,来调用用户的main函数。[1]
当从cyg_user_start()函数返回时,cyg_start()函数将启动调度器,用户在cyg_user_start()函数中所产生和唤醒的所有线程都将开始执行。另一个值得注意的地方是由于cyg_user_start()函数是在调度器启动之前执行的,因此在该函数中不要使用任何需要调度器的内核服务。
在开始调度的时候,cyg_Scheduler::start_cpu()开启系统需要的中断。
三.具体的从main()函数启动的过程
通过查看我们的配置文件,可以看到是包含了ISO标准C库的,其配置选项为:CYGPKG_LIBC,基本流程如图3.1图:
Cyg_startCyg_prestartCyg_package_startCYGSEM_LIBC_STARTUP_MAIN_THREADCYGSEM_LIBC_STARTUP_MAIN_INITCONTEXTCyg_iso_c_startCyg_user_startCyg_libc_main_threadCyg_libc_invoke_mainMain 图3.1 调用main函数的过程
下面具体地讲解一下这个过程:
1.从cyg_package_start()说起,通过下面这一段代码可以看出在配置了ISO标准C库的时候,即配置了CYGPKG_LIBC时,调用cyg_iso_c_start()函数。
externC void
cyg_package_start( void )
{
#ifdef CYGPKG_LIBC
cyg_iso_c_start();
#else
(void)main(0, NULL);
#endif
} // cyg_package_start()
2. 进入cyg_iso_c_start(void)函数之后,通过查看
\\packages\\language\\c\\libc\\startup\\current\\src\\Cstartup.cxx,里面有这样的一段代码:
#ifdef CYGSEM_LIBC_STARTUP_MAIN_THREAD
extern Cyg_Thread cyg_libc_main_thread;
// FUNCTIONS
externC void
cyg_iso_c_start( void )
{
static int initialized=0;
CYG_REPORT_FUNCNAME( \"cyg_iso_c_start\" );
CYG_REPORT_FUNCARGVOID();
if (initialized++ == 0) {
CYG_TRACE0( true, \"Resuming cyg_libc_main_thread\" );
cyg_libc_main_thread.resume();
}
CYG_REPORT_RETURN();
} // cyg_iso_c_start()
就是说当配置了CYGSEM_LIBC_STARTUP_MAIN_THREAD时,就是如果配置了从线程启动main函数的时候,就定义一个线程cyg_libc_main_thread,然后cyg_iso_c_start()函数中就启动这个线程。
而如果没有配置CYGSEM_LIBC_STARTUP_MAIN_THREAD,而配置了CYGSEM_LIBC_STARTUP_MAIN_INITCONTEXT时,就重定义cyg_user_start()函数,在这个函数里面调用了另一个函数:cyg_libc_invoke_main(0),这时的cyg_iso_c_start()函数中也调用了这个函数:cyg_libc_invoke_main(0)。
#elif defined( CYGSEM_LIBC_STARTUP_MAIN_INITCONTEXT )
externC void
cyg_user_start(void)
{
cyg_libc_invoke_main(0);
}
externC void
cyg_iso_c_start( void )
{
static int initialized=0;
CYG_REPORT_FUNCNAME( \"cyg_iso_c_start\" );
CYG_REPORT_FUNCARGVOID();
// In case they want to explicitly invoke the C library from
// cyg_user_start() themselves
if (initialized++ == 0) {
cyg_libc_invoke_main(0);
}
CYG_REPORT_RETURN();
}
而我们的配置里面是走的前者,就是说配置了CYGSEM_LIBC_STARTUP_MAIN_THREAD,这样的话,就是通过cyg_iso_c_start()函数启动了一个线程cyg_libc_main_thread。
3. 再来看线程cyg_libc_main_thread,可以在文件mainthread.cxx中看到:
Cyg_Thread cyg_libc_main_thread CYGBLD_ATTRIB_INIT_PRI(CYG_INIT_LIBC) =
Cyg_Thread(CYGNUM_LIBC_MAIN_THREAD_PRIORITY,
&cyg_libc_invoke_main, (CYG_ADDRWORD) 0,
\"main\",
(CYG_ADDRESS) &cyg_libc_main_stack[0],
#ifdef CYGSEM_LIBC_MAIN_STACK_FROM_SYSTEM
CYGNUM_LIBC_MAIN_DEFAULT_STACK_SIZE
#else
cyg_libc_main_stack_size
#endif
);
这是这个线程的定义,启动它的时候,就会通过函数cyg_libc_invoke_main调用main()函数。
4. 下面来看一下函数cyg_libc_invoke_main(),可以在文件invokemain.cxx中看到:
externC void
cyg_libc_invoke_main( CYG_ADDRWORD )
{
CYG_REPORT_FUNCNAME( \"cyg_libc_invoke_main\" );
CYG_REPORT_FUNCARG1( \"argument is %s\
#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();
} // cyg_libc_invoke_main()
可以看到这里面通过配置选项CYGDAT_LIBC_ARGUMENTS为main()函数传递参数,然后完成了mian()函数的调用。
下面说说上面第2步中的配置CYGSEM_LIBC_STARTUP_MAIN_THREAD和配置CYGSEM_LIBC_STARTUP_MAIN_INITCONTEXT两者之间的区别,虽然两者最后都用到了函数cyg_libc_invoke_main(),但是还是有所不同:
·前者呢,是通过产生一个eCos线程cyg_libc_main_thread来调用main()函数,那么线程就必须在启用调度后才开启,就是cyg_Scheduler调度器启动后,才会根据优先级、调度策略等等来启动这个线程,然后调用main()函数;
·后者呢,它是通过重载的cyg_user_start()函数调用的,是在系统初始化的时候运行,而不是eCos内核调度器启动的时候运行, 这就意味着应用程序不能和内核进行交互,必须进行操作。
我们的配置是前者。
参考文献
1. Anthony J.Massa,《Embedded software development with eCos》.
2.《嵌入式可配置实时操作系统eCos技术及实现机制》.
3.《嵌入式可配置实时操作系统eCos开发与应用》.
4. 网上资源:凌阳大学计划论坛eCos技术讨论区www.unsp.com.