第 8 章 内存管理
对内存进一步的管理,实现动态的分配和释放。
- 自动管理内存 - 栈(stack)(自动变量,生命周期为函数调用和返回)
- 静态内存 - 全局变量/静态变量(放在ELF文件加载进内存的数据段里 .data .odata等)
- 动态管理内存 - 堆(heap)(malloc free 程序员自己申请和释放)
- .text:已编译程序的机器代码。
- .rodate:只读数据,比如printf语句中的格式串和switch的跳转表。
- .data:已经初始化的全局变量和静态C变量。
- .bss:未初始化的全局变量和静态C变量,以及所有被初始化为0的全局或静态变量,仅是占位符,不占据任何实际磁盘空间。区分初始化和非初始化是为了空间效率。
- .symtab:一个符号表,它存放在程序中定义和引用的函数和全局变量静态变量的信息,不包括局部变量。
多个C文件 只是被编译成可重定向文件,每个.o文件代码和数据的地址都是从0开始。 通过链接器脚本获得位置信息,生成最后的可执行文件。
ENTRY(_start)指明程序从_start指向的那条指令开始运行
OUTPUT_ARCH("riscv")指定输出的文件使用RISCV体系结构,具体的32or64通过调用gcc的编译选项时规定。
规定一块名为ram的内存 起始地址为0x80000000,长度为128M,权限为可写可执行可分配 可读 进行初始化。”!“是什么意思没搞清楚
PROVIDE(_text_start = .);即表示 把当前地址赋值给_test_start这个符号 “.”表示当前地址
.text:那一段代码表示 将所有输入文件(可重定位文件)里的.text、.text.*都放在这一段内存里(ram)组成输出elf文件中的.text段。而且定义了这个段的起始地址_text_start和结束地址_text_end
注意.data段首先有一个. = ALIGN(4096) 是因为页大小为4K,使地址对齐。
通过PROVIDE可以定义很多地址符号:例如内存起始地址_memory_start = ram的起始地址
内存终止地址_memort_end = ram起始地址加上ram的长度。
链接器脚本里定义的符号就可以直接用在汇编代码里了。
实现 Page 级别的内存分配和释放
数据结构设计
在堆里面预留一部分内存用来存储页分配信息。注意索引部分内存也要进行4K对齐。
索引数据结构就是一个8位的flags ,里面第0位表示该页是否被分配,第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;
};
Page 分配和释放接口设计
页分配函数中首先设置标志位found
然后从HEAP_START位置开始搜索第一块没有被分配的页的索引page_i
以page_i开始搜索npages个索引看是否都是没有被分配的页
如果存在符合要求的,那么将最后一页的索引设置为最后一页,并返回page_i所指向页的物理地址
否则,返回第一步。寻找下一块没有被分配的页。
void *page_alloc(int npages)
{
/* 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_free(page_i)) {
found = 1;
/*
* meet a free page, continue to check if following
* (npages - 1) pages are also unallocated.
*/
struct Page *page_j = page_i;
for (int j = i; j < (i + npages); j++) {
if (!_is_free(page_j)) {
found = 0;
break;
}
page_j++;
}
/*
* get a memory block which is good enough for us,
* take housekeeping, then return the actual start
* address of the first page of this memory block
*/
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;
}
/*
* Free the memory block
* - p: start address of the memory block
*/
void page_free(void *p)
{
/*
* Assert (TBD) if p is invalid
*/
if (!p || (uint32_t)p >= _alloc_end) {
return;
}
/* get the first page descriptor of this memory block */
struct Page *page = (struct Page *)HEAP_START;
page += ((uint32_t)p - _alloc_start)/ PAGE_SIZE;
/* loop and clear all the page descriptors of the memory block */
while (!_is_free(page)) {
if (_is_last(page)) {
_clear(page);
break;
} else {
_clear(page);
page++;;
}
}
}
页释放函数,若指针为空或者指针地址超出页范围,直接返回
否则,修改该段被分配的页表的索引值,注意最后一页要修改is_last索引值。