【问题标题】:How to write simple start up code in C++?如何用 C++ 编写简单的启动代码?
【发布时间】:2018-05-29 13:50:06
【问题描述】:

我在互联网上找到了 arm cortex m 内核的启动代码并使用了这些来源,但我对来源中的功能有一些疑问,在这里我粘贴了代码和此处使用的相应链接器脚本。

// very simple startup code with definition of handlers for all cortex-m cores

// location of these variables is defined in linker script
extern unsigned __data_load;

extern unsigned __data_start;
extern unsigned __data_end;

extern unsigned __bss_start;
extern unsigned __bss_end;

extern unsigned __heap_start;

extern unsigned __init_array_start;
extern unsigned __init_array_end;

extern unsigned __fini_array_start;
extern unsigned __fini_array_end;

// main application
extern void main_app();

void copy_data() {
    unsigned *src = &__data_load;
    unsigned *dst = &__data_start;
    while (dst < &__data_end) {
        *dst++ = *src++;
    }
}

void zero_bss() {
    unsigned *dst = &__bss_start;
    while (dst < &__bss_end) {
        *dst++ = 0;
    }
}

void fill_heap(unsigned fill=0x55555555) {
    unsigned *dst = &__heap_start;
    register unsigned *msp_reg;
    __asm__("mrs %0, msp\n" : "=r" (msp_reg) );
    while (dst < msp_reg) {
        *dst++ = fill;
    }
}

void call_init_array() {
    unsigned *tbl = &__init_array_start;
    while (tbl < &__init_array_end) {
        ((void (*)())*tbl++)();
    }
}

void call_fini_array() {
    unsigned *tbl = &__fini_array_start;
    while (tbl < &__fini_array_end) {
        ((void (*)())*tbl++)();
    }
}

// reset handler
void RESET_handler() {
    copy_data();
    zero_bss();
    fill_heap();
    call_init_array();
    // run application
    main_app();
    // call destructors for static instances
    call_fini_array();
    // stop
    while (true);
}

以下是正在使用的链接器描述

SECTIONS {
    . = ORIGIN(FLASH);
    .text : {
        KEEP(*(.stack))
        KEEP(*(.vectors))
        KEEP(*(.vectors*))
        KEEP(*(.text))
        . = ALIGN(4);
        *(.text*)
        . = ALIGN(4);
        KEEP(*(.rodata))
        *(.rodata*)
        . = ALIGN(4);
    } >FLASH

    .init_array ALIGN(4): {
        __init_array_start = .;
        KEEP(*(.init_array))
        __init_array_end = .;
    } >FLASH

    .fini_array ALIGN(4): {
        __fini_array_start = .;
        KEEP(*(.fini_array))
        __fini_array_end = .;
    } >FLASH
}

SECTIONS {
    __stacktop = ORIGIN(SRAM) + LENGTH(SRAM);
    __data_load = LOADADDR(.data);
    . = ORIGIN(SRAM);

    .data ALIGN(4) : {
        __data_start = .;
        *(.data)
        *(.data*)
        . = ALIGN(4);
        __data_end = .;
    } >SRAM AT >FLASH

    .bss ALIGN(4) (NOLOAD) : {
        __bss_start = .;
        *(.bss)
        *(.bss*)
        . = ALIGN(4);
        __bss_end = .;
        *(.noinit)
        *(.noinit*)
    } >SRAM

    . = ALIGN(4);
    __heap_start = .;
}

我的问题是在copy_data() 函数中,为什么我们需要将__data_load 的地址分配给指针*src?是__data_load = LOADADDR(.data);__data_start 相同。 copy_data() 函数在程序中做了什么?提前致谢。

【问题讨论】:

  • 它设置.data 段。通常,微控制器中的变量最终会根据它们的类型而分为不同的段,see this。关于如何自己编写一个MCU的整个启动代码,你可以查看this,但对于初学者来说这不是任务,你可能需要了解MCU的基本汇编程序。
  • 顺便说一句,这看起来像 C++ 而不是 C。void fill_heap(unsigned fill=0x55555555) 不是有效的 C。while (true); 也不是,因为从不包含 stdbool.h。 call_init_array 看起来它正在为具有静态存储持续时间的 C++ 对象执行构造函数。只有糟糕的 (P)C++ 程序员才会为 MCU 编写这样的代码:main_app(); // call destructors for static instances call_fini_array(); // stop while (true); 我很确定这段代码不是由专业人士编写的。
  • 使用 C(++) 引导 C(++) 是个坏主意。
  • 用您的链接描述文件进行实验,到目前为止,您似乎是正确的 __data_load 和 __data_start 都在闪存中。这可能不是您想要的。有很多更简单的方法可以做到这一点(无需使用相同语言引导语言,无需引导程序的引导程序)。使用工具本身来简单地查看它们产生了什么。
  • 从 C 开始作为您的第一个引导程序和链接器脚本,然后有一天如果您仍然觉得需要,尝试 C++。

标签: c++ arm startup


【解决方案1】:

链接器脚本指示链接器将数据放在闪存中,但链接代码就像数据在 ram 中一样。然后在启动代码中,数据从加载数据的地址(闪存)复制到数据应该是的地址(RAM)。

【讨论】:

    【解决方案2】:

    copy_data() 将从起始地址__data_load 读取的内存复制到从__data_start__data_end 的地址范围内

    因此复制的总大小为__data_end - __data_start

    当然,您已经在__data_load 获得了可用的数据。该程序将其从 FLASH 复制到 SRAM 中,可以根据需要对其进行读写。

    【讨论】:

    • 数据在闪存中“加载”,但无法轻易更改。所以它必须先复制到 ram 才能变得可写。注意:数据,不是rodata。
    • No &gt;SRAM AT &gt;FLASH 表示它的虚拟地址是 SRAM 但加载地址(图像中的实际位置)是 FLASH
    • __stacktop = ORIGIN(SRAM) + LENGTH(SRAM); __data_load = LOADADDR(.data); . = ORIGIN(SRAM); 表示 SRAM 或闪存中的地址?谁能告诉 SRAM AT FLASH 的地址是什么?因为内存地址如下:MEMORY { FLASH(rx) : ORIGIN = 0x08000000, LENGTH = 32K SRAM(rwx) : ORIGIN = 0x20000000, LENGTH = 4K }
    • __stacktopSRAM。而&gt;SRAM AT &gt;FLASHSRAM中的一个虚拟地址,映射到FLASH中的一个地址……所以是FLASH
    【解决方案3】:

    问题

    我们在嵌入式代码中遇到了问题。存储选项是非易失性但不可修改(闪存/ROM 等)或易失性可修改存储。 'data' 是初始化的非零任意值,可以修改(相对于constrodata)。这个怎么安排?

    数据的副本放在闪存或 ROM 中。然后将该数据复制到 RAM 中进行读取和写入。


    我的问题是在 copy_data() 函数中,为什么我们需要将 __data_load 的地址分配给指针 *src?是 __data_load = LOADADDR(.data);与 __data_start 相同。 copy_data() 函数在程序中做了什么?

    copy_data()是上述问题的解决方案。它从闪存(加载位置)获取内存并将其复制到 RAM。虚拟寻址也存在类似的二分法。在启用 MMU 之前,您需要将物理地址和虚拟地址内容安排为相同的位置。链接器文档通常将运行/RAM 位置称为“VADDR”。

    使用操作系统或某些 ROM 引导加载程序,您可以从磁盘/MMC(NAND 闪存)加载到 RAM 并能够绕过copy_data()。仅当您的代码将直接从非易失性设备运行时才需要它。将整个映像从闪存复制到 RAM 通常会更快、更简单。这当然取决于资源。从 RAM 读取访问通常比闪存快。同样,这取决于您的系统。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-06-26
      • 2021-08-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-10
      相关资源
      最近更新 更多