如何计算堆的大小,只有算出可用空间才能对其管理。
ENTRY
功能:用于设置入口点,即程序中执行的第一条指令 symbol 参数是一个符号的名称
OUTPUT_ARCH
功能:指定输出文件所适用的计算机体系架构
为什么用 riscv64-unknown-elf-gcc,但是编译出来的文件是 32 位程序? riscv64 是 host 是 64 位系统,编译 target 是由 gcc 的参数决定
MEMORY
功能:用于描述目标机器上内存区域的位置,大小和相关
1 2 3 4 5 6 7 MEMORY { rom(rx):ORIGIN = 0 , LENGTH = 256 K ram(!rx):org = 0 x40000000, l = 4 M }
TODO:括号里的 rx 含义是?
SECTION
功能:告诉链接器如何将 input sections 映射到 output sections,以及如何将 output sections 放置到内存中。
1 2 3 4 5 6 7 8 SECTION { .=0x0000 ; .text :{*(.text) } .=0x8000000 ; .data :{*(.data) } .bss :{*(.bss) } }>ram
PROVIDE
功能:
可以在 Linker Script 中定义符号(Symbols)
每个符号包括一个名字(name) 和一个对应的地址值(address)
在代码中可以访问这些符号,等同于访问一个地址。
1 2 3 4 5 6 7 8 9 10 11 12 .bss :{ PROVIDE(_bss_start = .); /* 当前地址赋值给符号_bss_start */ *(.sbss .sbss.*) *(.bss .bss.*) *(COMMON) PROVIDE(_bss_end = .); } >ram PROVIDE(_memory_start = ORIGIN(ram)); PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram)); PROVIDE(_heap_start = _bss_end); /* 堆空间就是接在了bss段之后,所以堆开始地址就是bss结束地址 */ PROVIDE(_heap_size = _memory_end - _heap_start); /* 计算堆大小 */
.global
表示全局变量,.word
表示定义变量,下面的代码就是定义一些全局变量,方便在 C 代码中使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 .section .rodata.global HEAP_STARTHEAP_START: .word _heap_start.global HEAP_SIZEHEAP_SIZE: .word _heap_size.global TEXT_STARTTEXT_START: .word _text_start.global TEXT_ENDTEXT_END: .word _text_end.global DATA_STARTDATA_START: .word _data_start.global DATA_ENDDATA_END: .word _data_end.global RODATA_STARTRODATA_START: .word _rodata_start.global RODATA_ENDRODATA_END: .word _rodata_end.global BSS_START BSS_START: .word _bss_start.global BSS_END BSS_END: .word _bss_end
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 extern uint32_t TEXT_START;extern uint32_t TEXT_END;extern uint32_t DATA_START;extern uint32_t DATA_END;extern uint32_t RODATA_START;extern uint32_t RODATA_END;extern uint32_t BSS_START;extern uint32_t BSS_END;extern uint32_t HEAP_START;extern uint32_t HEAP_SIZE;#define PAGE_SIZE 4096 static uint32_t _num_pages = _num_pages = (HEAP_SIZE / PAGE_SIZE) - 8 ;
实现 Page 级别的内存分配与释放
日常使用的操作系统,都是以字节为单位分配空间,但是为了教学方便,RVOS 是以 Page 为单位分配内存。
数据结构设计
数组方式管理 将内存模拟为一个连续的数组,数组的前部预留 8 个 Page 来管理其余的内存。目前考虑管理的状态有:
这 Page 是否被使用了
这个 Page 是不是最后一块分配的内存,方便我们释放内存时找到最后一块分配的内存
我们可以使用一个 8 bit 的flag
来记录这些信息,flag bit[0]
表示是否已使用,flag bit[1]
表示是否是最后一个分配的页面。
1 2 3 4 5 6 7 8 9 struct Page { uint8_t flags; };
也就是每一个 Page 都由一个 8 bit 的结构体struct Page
管理,我们总共分配了 8 个 Page 用来管理,一个 Page 占 4K,那么我们可以一个管理$8 \times 4096 = 32768$个 Page。那就刚好可以管理$32768 \times 4096 = 134217728 \text{bit}$内存=128M。
Page 分配与释放接口设计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 void *page_alloc (int npages) { int found = 0 ; struct Page *page_i = (struct Page *)HEAP_START; for (int i = 0 ; i < (_num_pages - npages); i++) { if (_is_free(page_i)) { found = 1 ; struct Page *page_j = page_i; for (int j = i; j < (i + npages); j++) { if (!_is_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_flag(page_k, PAGE_TAKEN); page_k++; } page_k--; _set_flag(page_k, PAGE_LAST); return (void *)(_alloc_start + i * PAGE_SIZE); } } page_i++; } return NULL ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 void page_free (void *p) { if (!p || (uint32_t )p >= _alloc_end) { return ; } struct Page *page = (struct Page *)HEAP_START; page += ((uint32_t )p - _alloc_start)/ PAGE_SIZE; while (!_is_free(page)) { if (_is_last(page)) { _clear(page); break ; } else { _clear(page); page++;; } } }
评论