在这一章中引入了ld链接脚本如何指示程序的入口地址,如何指导.text、.data、.bss、.stack以及heap在内存中的管理。以及如何在start.S启动文件中,将位于flash中的数据搬移到sram中。并实现了一个简单的动态内存申请功能。
一、ld链接脚本
链接脚本用于指定链接器如何将程序的各个部分(如代码段、数据段、栈、堆等)安排到内存中的特定位置。它可以精确的控制程序如何被加载到内存中,尤其是在资源有限的嵌入式设备上。
定义内存区域。首先定义程序将使用的内存区域。每个区域都可以指定起始地址、大小和访问权限。通常会定义FLASH和RAM两个区域。其中ENTRY( _start )即在指示程序入口地址。
ENTRY( _start ) MEMORY{ RAM (xrw) : ORIGIN = 0x80000000, LENGTH = 32K FLASH (rx) : ORIGIN = 0x20000000, LENGTH = 128K }
设置段(sections)。链接脚本中会指定如何将代码和数据等分配到内存中定义的区域。每个段(如.text、.data、.bss)都可以指定存放位置和对齐方式。
SECTIONS { .text : { PROVIDE(_text_start = .); *(.text .text.*) PROVIDE(_text_end = .); } >FLASH AT>FLASH .rodata : { PROVIDE(_rodata_start = .); *(.rodata .rodata.*) PROVIDE(_rodata_end = .); } >FLASH AT>FLASH .dalign : { . = ALIGN(4); PROVIDE(_data_vma = .); } >RAM AT>FLASH .dlalign : { . = ALIGN(4); PROVIDE(_data_lma = .); } >FLASH AT>FLASH .data : { PROVIDE(_data_start = .); *(.data .data.*) *(.sdata .sdata.*) . = ALIGN(4); PROVIDE(_data_end = .); } >RAM AT>FLASH .bss : { . = ALIGN(4); PROVIDE(_start_bss = .); *(.bss .bss.*) *(.sbss .sbss.*) . = ALIGN(4); PROVIDE(_end_bss = .); } >RAM AT>FLASH .stack ORIGIN(RAM) + LENGTH(RAM) - __stack_size : { PROVIDE( _heap_end = . ); . = ALIGN(4); PROVIDE(_start_stack = .); . = . + __stack_size; PROVIDE(_end_stack = .); } >RAM PROVIDE(_memory_start = ORIGIN(RAM)); PROVIDE(_memory_end = ORIGIN(RAM) + LENGTH(RAM)); PROVIDE(_heap_start = _end_bss); PROVIDE(_heap_size = _start_stack - _heap_start); }
.text段用于存储程序代码,放在flash中。
.rodata段用于存储静态变量或常量,放在flash中。
.data段用于存储已初始化的全局变量,放在RAM中。
.bss段存储未初始化的全局变量,放在RAM中。
符号和重定位。 链接脚本可以用PROVIDE来定义符号,其值是当前内存位置。
对齐方式和内存填充。链接脚本可以通过ALIGN来确保内存段的对齐,以优化内存访问效率和避免内存碎片。 ALIGN(4)表示地址会被调整为4字节对齐。
加载地址和运行地址。 加载地址是程序在内存中被加载的位置。运行地址是程序在运行时的实际地址。例如,存储时会被加载到物理FLASH中但在运行时会被加载到内存RAM区域中。
.bss : { . = ALIGN(4); PROVIDE(_start_bss = .); *(.bss .bss.*) *(.sbss .sbss.*) . = ALIGN(4); PROVIDE(_end_bss = .); } >RAM AT>FLASH
二 、mem.S
在mem.S中指示出了,ld链接文件里定义的不同区域的起始和结束地址。包括代码段(.text)、数据段(.data)、只读数据段(.rodata)、bss段、堆区和栈区。这些符号通过.word 指令存储了对应的地址,用于程序在运行时访问和管理内存区域,确保内存的正确初始化和使用。
.section .rodata
.global TEXT_START
TEXT_START: .word _text_start
.global TEXT_END
TEXT_END: .word _text_end
.global DATA_START
DATA_START: .word _data_start
.globaL DATA_END
DATA_END: .word _data_end
.global RODATA_START
RODATA_START: .word _rodata_start
.global RODATA_END
RODATA_END: .word _rodata_end
.global BSS_START
BSS_START: .word _start_bss
.global BSS_END
BSS_END: .word _end_bss
.global HEAP_START
HEAP_START: .word _heap_start
.global HEAP_SIZE
HEAP_SIZE: .word _heap_size
.global STACK_START
STACK_START: .word _start_stack
.global STACK_END
STACK_END: .word _end_stack
三、动态内存分配的实现
随后使用分配出来的堆区,按照一定字节划分成若干page,供用户程序申请使用。
#include "os.h"
extern ptr_t TEXT_START;
extern ptr_t TEXT_END;
extern ptr_t DATA_START;
extern ptr_t DATA_END;
extern ptr_t RODATA_START;
extern ptr_t RODATA_END;
extern ptr_t BSS_START;
extern ptr_t BSS_END;
extern ptr_t HEAP_START;
extern ptr_t HEAP_SIZE;
extern ptr_t STACK_START;
extern ptr_t STACK_END;
/*
* _alloc_start points to the actual start address of heap pool
* _alloc_end points to the actual end address of heap pool
* _num_pages holds the actual max number of pages we can allocate.
*/
static uint32_t _alloc_start = 0;
static uint32_t _alloc_end = 0;
static uint32_t _num_pages = 0;
#define PAGE_SIZE 4096
#define PAGE_MASK (~(PAGE_SIZE-1))
#define PAGE_RESERVED 1
#define PAGE_TAKEN (uint8_t)(1<<0)
#define PAGE_LAST (uint8_t)(1<<1)
/*
* Page Descriptor
* flags:
* - bit 0: flag if this page is taken(allocated)
* - bit 1: flag if this page is the last page of the memory block allocated
*/
struct page{
uint8_t flags;
};
static inline void _clear_page(struct page *page){
page->flags = 0;
}
static inline int _is_page_free(struct page *page){
return !(page->flags & PAGE_TAKEN);
}
static inline void _set_page_flag(struct page *page,uint8_t flag)
{
page->flags |= flag;
}
static inline int _is_page_last(struct page *page){
return page->flags & PAGE_LAST;
}
/*
* align the address to the border of page(4K).
*/
static inline ptr_t _align_page(ptr_t addr)
{
/*
* Add PAGE_SIZE - 1 to the address. This increases the address
* to the next page boundary if it's not already aligned.
* Then perform a bitwise AND operation with PAGE_MASK to
* "rounds down" the address to the nearest page boundary.
*/
return (addr + PAGE_SIZE - 1) & PAGE_MASK;
}
void page_init()
{
/*
* We reserved PAGE_RESERVED pages to hold the Page structures.
* Sub more 1 to avoid overlapping heap and stack.
*/
_num_pages = (HEAP_SIZE / PAGE_SIZE) - PAGE_RESERVED - 1;
printf("HEAP_START = %x, HEAP_SIZE = %x, num of pages = %d\n",HEAP_START,HEAP_SIZE,_num_pages);
struct page *page = (struct page *)HEAP_START;
for (int i=0; i<_num_pages; i++){
_clear_page(page);
page++;
}
_alloc_start = _align_page(HEAP_START + PAGE_RESERVED * PAGE_SIZE);
_alloc_end = _alloc_start + _num_pages * PAGE_SIZE;
printf("TEXT: 0x%x -> 0x%x\n", TEXT_START, TEXT_END);
printf("RODATA: 0x%x -> 0x%x\n", RODATA_START, RODATA_END);
printf("DATA: 0x%x -> 0x%x\n", DATA_START, DATA_END);
printf("BSS: 0x%x -> 0x%x\n", BSS_START, BSS_END);
printf("HEAP: 0x%x -> 0x%x\n", _alloc_start, _alloc_end);
printf("STACK: 0x%x -> 0x%x\n", STACK_START, STACK_END);
}
/*
* Allocate a memory block which is composed of contiguous physical pages
* - npages: the number of PAGE_SIZE pages to allocate
*/
void *page_alloc(int npages){
if(npages <= 0 || _num_pages < npages){
return NULL;
}
/* Note we are searching the page descriptor bitmaps. */
int found = 0;
struct page *page_i = (struct page *)HEAP_START;
for (int i = 0; i < (_num_pages - npages); i++){
if(_is_page_free(page_i)){
found=1;
struct page *page_j = page_i + 1;
for(int j=i+1; j<(i + npages); j++){
if(!_is_page_free(page_j)){
found = 0;
break;
}
page_j++;
}
if(found){
struct page *page_k = page_i;
for(int k = i; k < (i + npages); k++){
_set_page_flag(page_k, PAGE_TAKEN);
page_k++;
}
page_k--;
_set_page_flag(page_k,PAGE_LAST);
return (void *)(_alloc_start + i * PAGE_SIZE);
}
}
page_i++;
}
return NULL;
}
/*
* Free the memory block
* - p: start address of the memory block
*/
void page_free(void *p)
{
/*
* Assert (TBD) if p is invalid
*/
if(!p || (ptr_t)p >= _alloc_end){
return;
}
/* get the first page descriptor of this memory block */
struct page *page = (struct page *)HEAP_START;
page += ((ptr_t)p - _alloc_start)/ PAGE_SIZE;
/* loop and clear all the page descriptors of the memory block */
while(!_is_page_free(page)){
if(_is_page_last(page)){
_clear_page(page);
break;
}else{
_clear_page(page);
page++;
}
}
}
void page_test()
{
void *p = page_alloc(2);
printf("p = %p\n", p);
//page_free(p);
void *p2 = page_alloc(1);
printf("p2 = %p\n", p2);
page_free(p2);
void *p3 = page_alloc(4);
printf("p3 = %p\n", p3);
}