Bootstrap

[嵌入式系统-51]:RT-Thread-内核:启动过程(不包括uboot情况下的RTT自组织初始化流程)

目录

前言:

一、系统架构

1.1 精简版:Nano版本操作系统:只有操作系统内核

1.2 标准版版本操作系统:所有程序在同一个进程中

1.3 Smart版本操作系统:用户与内核隔离,多进程间隔离

二、系统构建过程

2.0 概述

2.1 总体构建流程

2.2 CPU级或板级-汇编语言初始化

2.3 C语言板级初始化

2.4 RTT入口初始化:rtthread_startup()

三、RTT_startup解析

1、rt_hw_interrupt_disable(); //CPU厂家提供的汇编语言驱动

2、rt_hw_board_init();  /* drivers/board.c */

3、    rt_show_version(); /* rt-thread/src/Kservice.c */

4、    rt_system_timer_init(); /* rt-thread/src/Timer.c*/

5、    rt_system_scheduler_init(); /* rt-thread/src/scheduler.c */

6、    rt_system_signal_init();  /* rt-thread/src/signal.c */

7、    rt_application_init();  /* rt-thread/src/component.c */

8、    rt_system_timer_thread_init(); 

9、    rt_thread_idle_init(); /* rt-thread/src/idle.c */

10    rt_hw_spin_lock(&_cpus_lock);  /* *.*/cpu.c*/

11    rt_system_scheduler_start();


前言:

文本讲解的是:在没有uboot和其他bootloader的情况下,自组织的RTT系统是如何一步步初始化和构建起来的,因此,除了操作系统内核组件外,还包括对特定CPU和板级的初始化。

一、系统架构

1.1 精简版:Nano版本操作系统:只有操作系统内核

1.2 标准版版本操作系统:所有程序在同一个进程中

1.3 Smart版本操作系统:用户与内核隔离,多进程间隔离

1.4 3.0架构

二、系统构建过程

2.0 概述

RT-Thread 的启动流程

RT-Thread 的启动流程,大致可以分为四个部分:

(1)初始化与系统相关的硬件;

(2)初始化系统内核对象,例如定时器、调度器、信号;

(3)创建main线程,在main线程中对各类模块依次进行初始化;

(4)初始化定时器线程、空闲线程,并启动调度器

一般来说,在系统里添加新的功能模块的时候,在使用前都必须先初始化, 通常的做法是在主程序运行前手动添加调用初始化函数。

而 RT-Thread 提供了另一种低耦合高内聚的初始化方式,它不需要我们再 手动添加调用初始化函数,它能在系统运行前自动完成各模块的初始化。

组件初始化方式要求初始化函数主动通过一些宏接口进行申明链接器自动收集所有被申明的初始化函数,放到特定的数据段中,数据段中的 所有函数在系统初始化时会被调用。

组件初始化的所有宏接口及其被初始化的顺序如下表所示:

初始化顺序接口描述
1INIT_BOARD_EXPORT(fn)硬件的初始化,此时调度器还未启动
2INIT_PREV_EXPORT(fn)主要是用于纯软件的初始化、没有太多依赖的函数
3INIT_DEVICE_EXPORT(fn)外设驱动初始化相关,比如网卡设备
4INIT_COMPONENT_EXPORT(fn)组件初始化,比如文件系统或者LWIP
5INIT_ENV_EXPORT(fn)系统环境初始化,比如挂载文件系统
6INIT_APP_EXPORT(fn)应用初始化,比如GUI应用

INIT_BOARD_EXPORT(fn)声明的函数会被rt_components_board_init()调用, 其他宏声明的函数会被rt_components_init()调用。

2.1 总体构建流程

RT-Thread API参考手册: 系统初始化

2.2 CPU级或板级-汇编语言初始化

在嵌入式系统中,startup.S文件通常用于实现复位初始化过程。在这个文件中,会包含一些关键的函数和汇编代码,用于初始化处理器、设置堆栈指针、初始化全局数据、清零未初始化的数据等。下面是一般情况下startup.S文件实现复位初始化的一些步骤:

  1. 设置堆栈指针(SP):在复位初始化过程中,需要设置正确的堆栈指针,通常是将堆栈顶地址加载到堆栈指针寄存器(如SP寄存器)中。

  2. 初始化处理器:根据具体处理器的要求,进行一些必要的处理器初始化工作,例如关闭全局中断、设置处理器模式、设置系统时钟等。

  3. 初始化全局数据:初始化数据段(.data段)中的全局变量,通常是将全局变量的初始值从ROM复制到RAM中。

  4. 清零未初始化的数据段:清零未初始化数据段(.bss段)中的全局变量,这样可以确保未初始化的全局变量在运行时被正确初始化为0。

  5. 调用main函数或:初始化完成后,一般会跳转至C语言的主函数(如main函数),开始执行应用程序的主要功能。

通过正确实现startup.S文件中的复位初始化过程,可以确保系统在复位时进行必要的初始化工作,从而保证系统能够正常启动并运行应用程序。

2.3 C语言板级初始化

不同的硬件CPU, 进入rtthread_startup的方式不同,

2.4 RTT入口初始化:rtthread_startup()

三、RTT_startup解析

1、rt_hw_interrupt_disable(); //CPU厂家提供的汇编语言驱动

rt_hw_interrupt_disable是RT Thread中用于禁止中断的函数

在实时系统中,中断是一种常见的事件处理机制,可以在处理器执行期间随时打断当前执行的代码,并转而执行中断服务程序。通过禁止中断,可以确保在关键时刻代码的执行不会被中断打断,以确保系统的正确运行

具体而言,rt_hw_interrupt_disable函数会屏蔽当前处理器的中断使能位,从而禁止所有中断的触发。当需要临界区保护时,可以使用这个函数来禁止中断,避免并发执行带来的问题。一般情况下,在进入临界区前会调用这个函数,然后在临界区结束后再调用rt_hw_interrupt_enable函数来恢复中断使能位,以确保正常的中断处理机制能够继续正常运行。

要注意,在使用rt_hw_interrupt_disable函数时,需要谨慎考虑中断的优先级和影响范围,以避免出现死锁等问题。因为禁用中断可能会影响到系统的响应性能和实时性,因此应该尽量将禁用中断的时间和范围控制在必要的临界区内,同时保证系统的稳定运行。

    /* board level initialization
     * NOTE: please initialize heap inside board initialization.
     */

2、rt_hw_board_init();  /* drivers/board.c */

板级硬件初始化

初始化硬件相关功能

rt_hw_board_init()函数是RT-Thread中用于初始化硬件板级支持的函数

在嵌入式系统中,硬件板级支持包括对各种外设和接口的初始化,例如GPIO、UART、SPI、I2C等。通过调用rt_hw_board_init()函数,可以完成对硬件板级支持的初始化工作,为系统的正常运行和外设驱动提供必要的支持

具体而言,rt_hw_board_init()函数通常会包含以下工作:

  1. 初始化系统时钟:设置处理器的主频以及各种外设的时钟频率,确保系统的时钟配置正确。

  2. 初始化外设接口:对各种外设接口进行初始化,包括GPIO口初始化、UART口初始化、SPI口初始化、I2C口初始化等。

  3. 初始化存储器:对系统中的存储器进行初始化,包括SDRAM、NAND Flash、nor Flash等。

  4. 初始化时钟中断:配置处理器的时钟中断,并设置适当的优先级和中断服务程序。

  5. 初始化其他外设:根据具体硬件平台的需要,初始化其他外设,例如定时器、看门狗等。

通过调用rt_hw_board_init()函数,可以在系统启动时完成对硬件板级支持的初始化工作,为后续的应用程序和驱动提供必要的硬件支持。同时,要根据具体硬件平台的需求,自定义rt_hw_board_init()函数,确保系统能够正确初始化和运行。

/**
 * This function will initialize beaglebone board
 */
void rt_hw_board_init(void)
{
    /* initialize hardware interrupt */
    rt_hw_interrupt_init();
    /* initialize system heap */
    rt_system_heap_init(HEAP_BEGIN, HEAP_END);

    rt_components_board_init();
	
    rt_console_set_device(RT_CONSOLE_DEVICE_NAME);

    rt_thread_idle_sethook(idle_wfi);

#ifdef RT_USING_SMP
    /* install IPI handle */
    rt_hw_ipi_handler_install(RT_SCHEDULE_IPI, rt_scheduler_ipi_handler);
#endif
}

    /* show RT-Thread version */

3、    rt_show_version(); /* rt-thread/src/Kservice.c */

rt_show_version()函数是RT-Thread实时操作系统中用于显示版本信息的函数。通过调用该函数,可以获取当前系统运行的RT-Thread版本信息,包括版本号、发布日期等。这对于调试和排查问题时非常有用,可以确认系统所采用的RT-Thread版本是否正确,以及是否需要进行更新或修复。

在调用rt_show_version()函数时,通常会在系统启动时或者通过命令行界面中进行,以方便用户查看系统版本信息。该函数的具体实现会输出RT-Thread版本信息到终端设备,使用户可以直观地了解系统所运行的RT-Thread版本。

总之,通过调用rt_show_version()函数,可以方便地获取RT-Thread实时操作系统的版本信息,帮助开发者了解系统的运行环境和版本信息,从而更好地进行系统调试和开发工作。

    /* timer system initialization */

4、    rt_system_timer_init(); /* rt-thread/src/Timer.c*/

rt_system_timer_init()函数是RT-Thread实时操作系统中用于初始化系统硬件定时器的函数。在实时系统中,系统定时器是非常重要的组件,用于定时触发系统的一些任务和事件,例如任务调度、时间片轮转、定时器任务等。通过调用rt_system_timer_init()函数,可以完成对系统定时器的初始化工作,确保系统的定时功能能够正常运行

具体而言,rt_system_timer_init()函数通常包含以下几个方面的工作:

  1. 设置系统定时器的时钟源和时基:确定系统定时器所采用的时钟源以及时基,例如基于定时器硬件的计数器或者外部时钟源等。

  2. 初始化系统定时器的计数器:对系统定时器所使用的计数器进行初始化,设置初始值和计数方式,确保系统定时器的计时能够准确、可靠地进行。

  3. 配置系统定时器中断:设置系统定时器中断的优先级和中断服务程序,以确保在定时器触发时能够及时地响应和处理

  4. 启动系统定时器:最后,启动系统定时器,使其开始计时和触发定时事件,为系统的正常运行提供定时支持。

通过调用rt_system_timer_init()函数,可以完成系统定时器的初始化工作,确保系统的定时功能运行正常,并为系统的实时性和稳定性提供保障。在实际应用中,可以根据具体需求调整系统定时器的参数和配置,以满足系统的实时性和性能要求。

    /* scheduler system initialization */

5、    rt_system_scheduler_init(); /* rt-thread/src/scheduler.c */

rt_system_scheduler_init()函数是RT-Thread实时操作系统中用于初始化系统调度器的函数。

系统调度器是实时操作系统中的核心组件,负责任务调度处理器资源的分配,以确保各个任务按照优先级和时间片轮转的方式执行。通过调用rt_system_scheduler_init()函数,可以完成对系统调度器的初始化工作,为系统的任务调度和运行提供必要的支持。

具体而言,rt_system_scheduler_init()函数通常包含以下几个方面的工作:

  1. 初始化任务就绪表:建立任务就绪表,记录当前系统中所有处于就绪状态的任务,并根据任务的优先级排序。

  2. 设置系统时钟节拍:确定系统的时钟节拍间隔,即系统在执行任务调度时的最小时间片长度,确保任务能够按照一定的时间片轮转执行。

  3. 配置任务调度策略:根据系统要求和资源情况,配置任务调度策略,包括抢占式调度、非抢占式调度等。

  4. 初始化任务调度器:初始化任务调度器的相关参数和数据结构,为系统任务的调度和执行做好准备工作。

通过调用rt_system_scheduler_init()函数,可以完成系统调度器的初始化工作,确保系统能够按照预定的调度策略和优先级执行任务,为系统的实时性和性能提供保障。在系统运行过程中,系统调度器将根据任务的优先级和时间片轮转的方式,动态地进行任务调度和资源分配,实现多任务并发运行。

#ifdef RT_USING_SIGNALS
    /* signal system initialization */

6、    rt_system_signal_init();  /* rt-thread/src/signal.c */

#endif

rt_system_signal_init()函数是RT-Thread实时操作系统中用于初始化系统信号量的函数。系统信号量是一种同步机制,用于处理多任务并发访问共享资源的情况,确保对资源的互斥访问和临界区的保护。通过调用rt_system_signal_init()函数,可以完成对系统信号量的初始化工作,为系统的资源访问和同步提供必要的支持。

具体而言,rt_system_signal_init()函数通常包含以下几个方面的工作:

  1. 创建系统信号量:初始化系统中需要使用的信号量,为共享资源的访问和同步设置信号量控制块。

  2. 设置信号量初始值:确定信号量的初始值,用于指示资源的可用数量或某一条件的满足情况。

  3. 配置信号量操作接口:设置系统信号量的操作接口,包括信号量的获取(等待)和释放(发送)等操作,以实现对资源的控制和同步。

  4. 初始化信号量控制块:初始化系统中的信号量控制块,包括控制信号量数量的数据结构、等待队列等,为信号量的使用提供支持。

通过调用rt_system_signal_init()函数,可以初始化系统信号量,为系统的资源管理和同步提供支持,确保多任务之间能够安全地共享资源并避免竞态条件的发生。在实际应用中,可以根据具体需求调整信号量的参数和配置,以满足系统的资源管理和同步要求。

    /* create init_thread */

7、    rt_application_init();  /* rt-thread/src/component.c */

 rt_application_init()函数是RT-Thread实时操作系统中用于初始化应用程序的函数。包括系统的各种组件和main线程。RT thread系统架构中的大部分组件,都是在这个初始化的。

应用程序初始化函数负责完成应用程序的初始化工作,包括任务创建、设备初始化、系统资源配置等,为应用程序的正常运行提供必要的支持。

具体而言,rt_application_init()函数通常包含以下几个方面的工作:

  1. 创建应用程序任务:初始化应用程序中需要的任务,并指定任务的入口函数、优先级、时间片等参数,以确保任务能够按照预定的执行顺序和优先级运行。

  2. 初始化设备和驱动:初始化应用程序中需要使用的设备和驱动程序,包括外设设备、通信接口、文件系统等,以满足应用程序对硬件资源的访问需求。

  3. 设置系统资源:配置系统的内存管理、时钟设置、中断控制等系统资源,为应用程序的运行提供必要的硬件支持。

  4. 执行应用程序主程序:执行应用程序的主函数或主程序入口,启动应用程序的业务逻辑,完成应用程序的初始化和运行。

通过调用rt_application_init()函数,可以完成应用程序的初始化过程,为应用程序的正常运行提供必要的支持和环境。在实际应用中,可以根据具体的需求和应用场景,对应用程序初始化函数进行定制和扩展,以满足不同应用的需求和要求。

void rt_application_init(void)
{
    rt_thread_t tid;

#ifdef RT_USING_HEAP
    tid = rt_thread_create("main", main_thread_entry, RT_NULL,
                           RT_MAIN_THREAD_STACK_SIZE, RT_MAIN_THREAD_PRIORITY, 20);
    RT_ASSERT(tid != RT_NULL);
#else
    rt_err_t result;

    tid = &main_thread;
    result = rt_thread_init(tid, "main", main_thread_entry, RT_NULL,
                            main_stack, sizeof(main_stack), RT_MAIN_THREAD_PRIORITY, 20);
    RT_ASSERT(result == RT_EOK);

    /* if not define RT_USING_HEAP, using to eliminate the warning */
    (void)result;
#endif

    rt_thread_startup(tid);  //启动该主线程,但是否调度,取决于调度器
}

/* the system main thread */
void main_thread_entry(void *parameter)
{
    extern int main(void);
    extern int $Super$$main(void);
    

    /*  组件初始化 ; 组件初始化 */
#ifdef RT_USING_COMPONENTS_INIT
    /* RT-Thread components initialization */
    rt_components_init(); 
#endif    


#ifdef RT_USING_SMP
    rt_hw_secondary_cpu_up();
#endif

    /* invoke system main function */
#if defined(__CC_ARM) || defined(__CLANG_ARM)

    $Super$$main(); /* for ARMCC. */

#elif defined(__ICCARM__) || defined(__GNUC__)

    main();  //应用程序的入口函数,被主线程调用

#endif
}

void rt_system_heap_init(void * begin_addr,
void * end_addr 
)

系统内存堆的配置和初始化

该函数将初始化系统内存堆。

参数

begin_addr系统内存堆的起始地址
end_addr系统内存堆的结束地址。

void rt_application_init()

创建用户线程

由此创建一个用户main()线程,而 main()函数是RT-Thread的用户代码入口, 用户可以在main()函数里添加自己的应用。

    /* timer thread initialization */

8、    rt_system_timer_thread_init(); 

rt_system_timer_thread_init函数是RT-Thread实时操作系统中用于初始化系统定时器线程的函数。系统定时器线程是用来处理系统定时器事件的线程,它负责管理系统中所有的定时器,包括定时器的创建、删除、启动和停止等操作,以确保系统中定时器事件的准确触发和处理

具体而言,rt_system_timer_thread_init函数通常包含以下几个方面的工作:

  1. 创建定时器线程:初始化系统定时器线程,并设置线程的优先级、栈大小等参数,以确保系统定时器线程能够按时运行并处理定时器事件。

  2. 初始化定时器管理结构:设置系统中定时器的管理数据结构,包括定时器链表、定时器任务控制块等,用于管理系统中所有的定时器。

  3. 启动定时器线程:启动系统定时器线程,并进入线程循环,等待定时器事件的到来并处理相应的定时器回调函数。

  4. 注册系统定时器服务:将系统定时器服务注册到系统服务表中,以便其他任务或组件可以使用系统定时器功能。

通过调用rt_system_timer_thread_init函数,可以初始化系统定时器线程,为系统中所有的定时器事件提供必要的处理和管理,确保系统中的定时器功能正常运行并能够满足系统对时间精度和事件触发的要求。在实际应用中,可以根据具体需求对系统定时器线程进行定制和扩展,以满足系统的实时性和可靠性需求。

    /* idle thread initialization */

9、    rt_thread_idle_init(); /* rt-thread/src/idle.c */

/**
 * @ingroup SystemInit
 *
 * This function will initialize idle thread, then start it.
 *
 * @note this function must be invoked when system init.
 */
void rt_thread_idle_init(void)
{
    rt_ubase_t i;
    char tidle_name[RT_NAME_MAX];

    for (i = 0; i < _CPUS_NR; i++)
    {
        rt_sprintf(tidle_name, "tidle%d", i);
        rt_thread_init(&idle[i],
                tidle_name,
                rt_thread_idle_entry,
                RT_NULL,
                &rt_thread_stack[i][0],
                sizeof(rt_thread_stack[i]),
                RT_THREAD_PRIORITY_MAX - 1,
                32);
#ifdef RT_USING_SMP
        rt_thread_control(&idle[i], RT_THREAD_CTRL_BIND_CPU, (void*)i);
#endif
        /* startup */
        rt_thread_startup(&idle[i]);
    }
}
static void rt_thread_idle_entry(void *parameter)
{
#ifdef RT_USING_SMP
    if (rt_hw_cpu_id() != 0)
    {
        while (1)
        {
            rt_hw_secondary_cpu_idle_exec();
        }
    }
#endif

    while (1)
    {
#ifdef RT_USING_IDLE_HOOK
        rt_size_t i;

        for (i = 0; i < RT_IDLE_HOOK_LIST_SIZE; i++)
        {
            if (idle_hook_list[i] != RT_NULL)
            {
                idle_hook_list[i]();
            }
        }
#endif

        rt_thread_idle_excute();
#ifdef RT_USING_PM        
        rt_system_power_manager();
#endif
    }
}


#ifdef RT_USING_SMP

10    rt_hw_spin_lock(&_cpus_lock);  /* *.*/cpu.c*/

#endif /*RT_USING_SMP*/

rt_hw_spin_lock()函数是RT-Thread实时操作系统中用于实现自旋锁(Spin Lock)的函数

自旋锁是一种简单的同步机制,用于保护共享资源,避免多个线程同时访问导致数据竞争和错误。

在RT-Thread中,rt_hw_spin_lock()函数通常包含以下几个主要工作:

  1. 获取自旋锁:当线程需要访问共享资源时,调用rt_hw_spin_lock()函数获取自旋锁,如果自旋锁已被其他线程占用,则当前线程会一直处于自旋等待状态,直到获取到自旋锁为止。

  2. 自旋等待:当线程在获取自旋锁时遇到锁已被占用的情况,会使用一个忙等的循环(自旋)来等待其他线程释放该锁,而不会立即进入睡眠状态,以减少上下文切换和锁竞争的开销。

  3. 释放自旋锁:当线程操作完共享资源后,需要调用对应的释放自旋锁函数(如rt_hw_spin_unlock())来释放自旋锁,允许其他线程继续获取该锁。

通过使用rt_hw_spin_lock()函数实现自旋锁,可以有效地保护共享资源,避免多线程并发访问导致的数据不一致性和竞争条件。在实际应用中,应该谨慎使用自旋锁,避免长时间的自旋等待造成系统性能下降。

    /* start scheduler */

11    rt_system_scheduler_start();

rt_system_scheduler_start()函数是RT-Thread实时操作系统中用于启动系统调度器的函数。系统调度器是RT-Thread中的核心组件,负责管理和调度系统中的各个线程,根据线程的优先级和状态来动态调度线程的执行。

具体而言,rt_system_scheduler_start()函数通常包含以下几个主要工作:

  1. 初始化调度器:在系统启动时,需要调用rt_system_scheduler_start()函数来初始化系统调度器,建立并启动调度器的相关组件和数据结构,以便后续进行线程调度。

  2. 启动调度器:启动系统调度器后,系统会按照一定的调度算法和策略来动态调度系统中的线程执行,确保优先级高的线程先被执行,并根据线程的状态进行合适的调度。

  3. 线程调度:系统调度器会根据线程的状态和优先级来进行线程调度,包括线程的切换、挂起、唤醒等操作,以保证系统的正常运行和各个线程的执行顺序。

通过调用rt_system_scheduler_start()函数,可以启动系统调度器,让系统正常进行线程调度和执行。在实际应用中,系统调度器的调度算法和策略可以根据系统的需求和性能要求进行调整和优化,以提高系统的稳定性和性能表现。

/**
 * @ingroup SystemInit
 * This function will startup scheduler. It will select one thread
 * with the highest priority level, then switch to it.
 */
void rt_system_scheduler_start(void)
{
    register struct rt_thread *to_thread;
    rt_ubase_t highest_ready_priority;

    to_thread = _get_highest_priority_thread(&highest_ready_priority);

#ifdef RT_USING_SMP
    to_thread->oncpu = rt_hw_cpu_id();
#else
    rt_current_thread = to_thread;
#endif /*RT_USING_SMP*/

    rt_schedule_remove_thread(to_thread);
    to_thread->stat = RT_THREAD_RUNNING;

    /* switch to new thread */
#ifdef RT_USING_SMP
    rt_hw_context_switch_to((rt_ubase_t)&to_thread->sp, to_thread);
#else
    rt_hw_context_switch_to((rt_ubase_t)&to_thread->sp);
#endif /*RT_USING_SMP*/

    /* never come back */
}

四、main函数,应用程序的入口: /* application/Main.c */

RT-Thread中线程的main函数主要用于定义线程的执行逻辑。在main函数中,您可以编写线程需要执行的代码,包括初始化资源、执行任务和处理事件等。线程的main函数通常由一个无返回值的函数来定义,例如:

static void thread_entry(void *parameter)
{
    // 在这里编写线程的执行逻辑
}

通过这个main函数,您可以为线程指定具体的业务逻辑,使线程在被调度执行时能够按照您需要的方式运行。在main函数中,也可以通过参数parameter传递必要的数据给线程,以便在执行过程中使用。

当创建一个线程时,可以通过rt_thread_create函数指定线程的main函数作为入口点。例如:

rt_thread_t thread;
thread = rt_thread_create("example_thread", thread_entry, NULL, 1024, 10, 10);
if (thread != RT_NULL) {
    rt_thread_startup(thread);
}

在这个示例中,创建了一个名为"example_thread"的线程,它的main函数为thread_entry。线程被启动时,会以指定的堆栈大小、优先级和时间片来运行。

通过这种方式,您可以为RT-Thread中的线程定义特定的执行逻辑,实现不同线程间的并发执行和任务协作。希望这个解释能帮助您理解RT-Thread中线程main函数的功能。

int main(void)
{
    printf("hello rt-thread\n");

    return 0;
}
;