打印

一步步移值 RT-Thread

一步步移值 RT-Thread

最近考虑移植新板子,顺便以这个为基础把移植的文档详细清楚:

一、RT-Thread和移植相关的目录结构
[code:1]
rtt
    bsp
        lumit4510
    libcpu
        arm
            common
            s3c4510
        coldfire
[/code:1]

这里面主要包含两个:bsp和libcpu
- bsp放置的是和具体板子相关的文件。
- libcpu放置的具体的CPU/MCU相关文件,由CPU/MCU将决定整个系统架构。

从目前的情况也可以看出libcpu下的arm目录下包含了common和s3c4510目录,common目录下是和ARM体系结构相关的乘除法运算实现,一般来说这部分几个ARM体系都相类似。s3c4510目录中包含了和s3c4510芯片密切相关的信息,同时因为s3c4510是一款SoC芯片,带有一定的外围设备,这些外围设备也可以看成是s3c4510芯片独有的,所以相应的驱动也会放置到这个目录中。

bsp目录是和具体开发板密切相关的,例如memory的布局,开发板上通过IO接口等外扩的设备等驱动文件将放在这里,同时链接脚本也会放在这里(链接脚本通常是和整个memory布局是密切相关的)。

TOP

libcpu目录下实现文件详述

libcpu目录详细描述

libcpu目录下是各个具体芯片相关的实现,但其分类首先是按照体系结构来分类的,例如arm,ia32等等,再下面才是具体的芯片描述,如ARM7TDMI中的s3c4510,m68k中的coldfire系列芯片等。

在一些体系结构目录下,会存在一个common目录,存放本体系结构的一些公共信息。例如对于ARM体系结构,common目录中包含了
div0.c divsi3.S,这些是针对ARM体系结构实现的乘除法,否则核心将不得不链接libgcc.a库。

对于具体的芯片,以下以s3c4510芯片为例来描述,s3c4510包含了一个ARM7TDMI核心,也有其他各类的外设,例如串口,网口等等,它被归类到ARM体系结构下,具体实现分为:
context.S,上下文切换相关代码,主要是
    rt_hw_interrupt_disable, rt_hw_interrupt_enable中断使能函数;
    rt_hw_context_switch,保存当前上下文到任务栈中,恢复切换过去任务的上下文
    rt_hw_context_switch_interrupt,中断状态中上下文的切换,主要是设置了一个切换标志rt_thread_switch_interrput_flag。
cpu.c,和CPU相关的一些实现,例如cache,reset等,这个和操作系统整体相关性并不大
interrupt.c,系统中断相关功能,必须实现的函数
    rt_hw_interrupt_mask/rt_hw_interrupt_unmask,屏蔽或去屏蔽某个中断
    rt_hw_interrupt_install,装载某个中断服务例程
    同时这个文件中也包含了缺省的中断服务处理,rt_hw_interrupt_handle
    中断控制器初始化在函数rt_hw_interrupt_init中进行
s3c4510.h,和s3c4510芯片密切相关的数据定义
serial.c,实现打印输出,主要是
    rt_serial_init, 进行串口的初始化操作,例如波特率,停止位等
    rt_console_puts, (使用rt_serial_putc)向串口输出一个字符,这个函数会被rt_kprintf调用,同时这个函数调用应不会引起中断(例如串口数据发送完成中断),因为rt_kprintf是被设计成可以在中断服务例程中使用的。
    rt_serial_putc, 向串口输出一个字符
    rt_serial_getc, 从串口中读入一个字符
stack.c,任务栈的初始化
    函数rt_hw_stack_init,在初始化一个线程时这个函数将被调用用来设置线程栈的初始状态,入口,参数等。和线程是密切相关的。
start.S,系统启动代码,完成系统初始启动时的一些设置,例如各个模式栈的配置,装载上中断向量等,这部分将专门讲述。
trap.c,针对ARM特有的几个异常处理,例如Undef,SWI,PABT及DABT等等.

libcpu建议移植步骤:(假设针对AT91SAM7S系列芯片)

  • 基本的芯片数据定义头文件,AT91SAM7S.h
  • 启动文件start.S,ARM7TDMI的异常入口设置,初始化系统,关闭所有中断、定时器,各个模式的栈配置,跳转进入C代码rtthread_startup
  • trap.c,各个异常入口,可以在原来s3c4510文件的基础上修改
  • serial.c,串口初始化
  • context.S/stack.c,上下文切换及线程栈初始化,因为同是ARM7TDMI结构,可以直接使用原来s3c4510文件
  • cpu.c,根据芯片情况实现,对于AT91SAM7S,cache相关的函数不需要实现
  • interrupt.c,中断管理
    [/list]

TOP

bsp目录下实现详述

bsp目录下放置了各类开发板/平台的具体实现,包括开发板/平台的初始化,外设的驱动等。以lumit.org的lumit4510开发板为例进行说明,它包含了如下文件:
application.c
board.c
startup.c
lumit4510.lds

application.c 用户应用程序初始化接口的桩,用于生成基本可运行的操作系统核心。
board.c 开发板相关的实现,一般是初始化一些基本设施,例如GPIO复用,时钟电源等。
startup.c RT-Thread Kernel启动入口,在这其中将初始化各个操作系统模块,例如核心对象系统,调度器,内存管理器等。
lumit4510.lds 是链接脚本,用于把操作系统的各个段放到开发板的指定内存地址。这个文件将在下一节详细描述。

这里可以先看看RT-Thread在lumit4510开发板的内存布局:
lumit4510默认的bootloader是把映像文件下载到0x8000的位置,下载的时候已经做过内存的remap动作了,所以在操作系统中不需要重复这个动作。因为是下载到0x8000位置,所以RT-Thread的开始位置是0x8000。

然后,因为ARM的异常捕获必须是位于0x0000地址的一组向量,所以RT-Thread启动后需要把自身的地址复制一份到0x0000地址去。所以最终的内存布局会是这样
[code:1]
+-----------+ <--- 0x1000000
|           |
|           |
..    .    ..
|           |
+-----------+ <--- __bss_end
|           |
+-----------+ <--- __bss_start
|           |
|  kernel   |
+-----------+
|  vector   |
+-----------+ <--- 0x8000
|           |
|  vector   |
+-----------+ <--- 0x0000
[/code:1]
其中__bss_start/__bss_end指示出BSS段的位置,在RT-Thread启动时需要把它给清零。

上图中0x0000的vector就是从0x8000复制过来的。

接下来看看RT-Thread的初始化流程
[code:1]
/**
* This function will startup RT-Thread RTOS.
*/
void rtthread_startup(void)
{
        /* clear .bss */
        rt_hw_clear_bss();

        /* enable cpu cache */
        rt_hw_cpu_icache_enable();
        rt_hw_cpu_dcache_enable();

        /* init hardware interrupt */
        rt_hw_interrupt_init();

        /* init board */
        rt_hw_board_init();

        /* init hardware serial */
        rt_serial_init();

        rt_show_version();

        /* init tick */
        rt_system_tick_init();

        /* init kernel object */
        rt_system_object_init();

        /* init timer system */
        rt_system_timer_init();

        /* init memory system */
#ifdef __CC_ARM
        rt_system_page_init((void*)__bss_end, 0x1000000);
#else
        rt_system_page_init(&__bss_end, 0x1000000);
#endif

        /* init scheduler system */
        rt_system_scheduler_init();

        /* set idle thread hook */
        rt_thread_idle_sethook(led_flash);

        /* init application */
        rt_application_init();

        /* init finsh */
        finsh_system_init();

        /* unmask interrupt */
        rt_hw_interrupt_umask(INTGLOBAL);

        /* init threads and start scheduler */
        rt_system_thread_init();

        /* never reach here */
        return ;
}
[/code:1]
初始化做的第一件事就是把BSS段清零,然后是硬件相关的一些初始化:启用CPU的cache,中断初始化,设置串口等。接下来的是内核各个模块初始化:系统节拍归零,对象系统初始化,定时器系统初始化,如果使用了动态内存管理,把实际的内存空间传递给页管理器进行管理,初始化调度器。而设置空闲线程钩子及启动finsh都是可选的。以上这些完成后调用rt_hw_interrupt_umask(INTGLOBAL)把系统的中断打开,调用rt_system_thread_init初始化线程管理器然后就开始进行线程调度了,RT-Thread操作系统开始运行!

TOP

启动文件详细说明

TOP

链接脚本详细说明

为了精确的把内核文件映射到内存地址中,必须手动给出ld的链接脚本文件,这一节就根据lumit4510开发板的情况进行详细讲述链接脚本。

lumit4510链接脚本
[code:1]
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
        . = 0x00000000;

        . = ALIGN(4);
        .text :        {
                *(.init)
                *(.text)
        }

        . = ALIGN(4);
        .rodata : { *(.rodata) }

        . = ALIGN(4);
        .data : { *(.data) }

        . = ALIGN(4);
        __bss_start = .;
        .bss : { *(.bss) }
        __bss_end = .;

        /* stabs debugging sections. */
        .stab 0 : { *(.stab) }
        .stabstr 0 : { *(.stabstr) }
        .stab.excl 0 : { *(.stab.excl) }
        .stab.exclstr 0 : { *(.stab.exclstr) }
        .stab.index 0 : { *(.stab.index) }
        .stab.indexstr 0 : { *(.stab.indexstr) }
        .comment 0 : { *(.comment) }
        .debug_abbrev 0 : { *(.debug_abbrev) }
        .debug_info 0 : { *(.debug_info) }
        .debug_line 0 : { *(.debug_line) }
        .debug_pubnames 0 : { *(.debug_pubnames) }
        .debug_aranges 0 : { *(.debug_aranges) }

        _end = .;
}
[/code:1]
这个文件可以分成几部分,文件头及各个输出段的信息。
文件头
[code:1]
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
[/code:1]
指示出输出文件的格式,采用arm的elf小端格式,入口地址显式的指示出是_start,这个地址在启动汇编文件libcpu/arm/s3c4510/start.S中定义,并作为启动的入口。

接下来的是各个输出段。一般来说,可执行程序可以分成几个公共段:
正文段,在elf中表示为.text
数据段,在elf中表示为.data,以及只读数据段.rodata
BSS段,在elf中表示为.bss

正文段放置的是可执行程序指令代码等,一般被标志为可读(r),可执行(x),由于它完全是只读的,所以这部分也可以放置到NORFlash中直接运行(由于NORFlash的擦除特性并不好,而读特性几乎可以看成是内存,能够直接寻址,也比较快)。
数据段与只读数据段,一般标志为可读可写(rw)及可读(r)。数据段应用于程序中的全局变量,相当于这部分数据空间在编译链接阶段就已经分配。而只读数据段和数据段的区别在于只读数据段用于全局常量,在整个运行过程中都不会被修改。
BSS段,在内核映像文件中并不占用空间,而是在程序启动后直接从内存地址中划分出来。BSS段用于程序中未初始化的全局变量。

这几个段在链接脚本中都分别被指示出来:
[code:1]
        .text :        {
                *(.init)
                *(.text)
        }
[/code:1]
.text : {} 指示的是输出段,大括号内的是输入段。从上面可以看出,输入段包含了两个,.init和.text。.init在启动汇编文件中定义,包含了ARM的异常向量,所以通过这个, 就可以在程序中精确定位异常向量的位置并且也能准确知道入口的位置。
[code:1]
        .rodata : { *(.rodata) }
        .data : { *(.data) }
[/code:1]
这两段描述了紧接着.text的是.rodata和.data段。
[code:1]
        __bss_start = .;
        .bss : { *(.bss) }
        __bss_end = .;
[/code:1]
这段描述指示的是.bss段,位置是接着上一个段的位置。在这里插入了两个符号__bss_start和__bss_end。"= ."意思是值是当前位置点。这两个符号可以在代码中访问,在bsp/lumit4510/startup.c中用于把.bss段清零。

接下去的是几个调试信息段,这里就不详细描述了。

回过头来再看看输出段描述中的第一个描述:
[code:1]
        . = 0x00000000;
[/code:1]
它指示出输出文件的起始位置(或者输出段当前点,也就是开始点)是从0x0000地址开始的。这个和前面说的RT-Thread起始位置并不相附和,那么就需要在ld的调用参数上输入这个地址值,当然也完全可以直接把链接脚本这里这个值填上0x8000。

TOP