渝中网站建设,七米网站建设推广优化,重庆ssc做号网站,全球访问量top100网站目录
往期文章传送门
一、内存管理简介
二、Linker Script 链接脚本
三、动态分配内存
四、测试 往期文章传送门
开发一个RISC-V上的操作系统#xff08;一#xff09;—— 环境搭建_riscv开发环境_Patarw_Li的博客-CSDN博客
开发一个RISC-V上的操作系统#xff08;二…目录
往期文章传送门
一、内存管理简介
二、Linker Script 链接脚本
三、动态分配内存
四、测试 往期文章传送门
开发一个RISC-V上的操作系统一—— 环境搭建_riscv开发环境_Patarw_Li的博客-CSDN博客
开发一个RISC-V上的操作系统二—— 系统引导程序Bootloader_Patarw_Li的博客-CSDN博客
开发一个RISC-V上的操作系统三—— 串口驱动程序UART_Patarw_Li的博客-CSDN博客 本节的代码在仓库的02_MEM_M目录下仓库链接riscv_os: 一个RISC-V上的简易操作系统
本文代码的运行调试会在前面开发的RISC-V处理器上进行仓库链接cpu_prj: 一个基于RISC-V指令集的CPU实现
一、内存管理简介
操作系统将一整块内存划分了几个区域每个区域用来做不同的事情 其中
栈区stack存放函数形参和局部变量由编译器自动分配和释放。堆区heap动态分配区域由程序员申请后使用如使用malloc函数需要手动释放否则会造成内存泄漏。全局/静态存储区存放全局变量和静态变量包括静态全局变量和静态局部变量初始化后的全局变量和静态局部变量放在.data段未初始化的放在.bss段。常量区存放常量如一些const修饰的符号字符串等等并且常量区的内存是只读的位于.rodata段。代码区存放程序二进制代码存在于.text段。 而我们所做的操作系统同样也要将系统内存划分为这些区域 那么要如何管理划分这些区域呢这就要用到Linker Script连接脚本了下面介绍一下Linker Script连接脚本的使用方法。
二、Linker Script 链接脚本
官方文档链接Scripts (LD)
一般来说程序从.c到可执行文件会经过三个步骤编译、汇编、链接。编译步骤会将.c文件编译成.s汇编文件.s汇编文件再由汇编操作生成.o目标文件最后再由链接器将所有的.o文件链接起来生成可执行文件 而每一个链接操作都是由链接脚本Linker Script所控制的按照官方的话来说链接脚本用来描述 input file 中的每个section应该如何被映射到 output file 中并且控制 output file中的内存布局。我们可以自己编写链接脚本也可以使用默认的链接脚本如果要使用自己编写的链接脚本则需要在编译时使用 -T 参数来指定。 下面是链接脚本的一些基础语法更详细的描述建议去看官方文档 下面看看本项目中的链接脚本是如何编写的位置在02_MEM_M下的os.ld文件
OUTPUT_ARCH( riscv )ENTRY( _start )MEMORY
{rom (wxari) : ORIGIN 0x00000000, LENGTH 16Kram (wxa!ri) : ORIGIN 0x00004000, LENGTH 8K
}SECTIONS
{.text : {PROVIDE(_text_start .);*(.text .text.*)PROVIDE(_text_end .);. ALIGN(4);} rom.rodata : {PROVIDE(_rodata_start .);*(.rodata .rodata.*)PROVIDE(_rodata_end .);. ALIGN(4);} rom.data : {PROVIDE(_data_start .);*(.sdata .sdata.*)*(.data .data.*)PROVIDE(_data_end .);. ALIGN(4);} ram.bss :{PROVIDE(_bss_start .);*(.sbss .sbss.*)*(.bss .bss.*)*(COMMON)PROVIDE(_bss_end .);. ALIGN(4);} ram/* stack */PROVIDE(_stack_start _bss_end);PROVIDE(_stack_end _stack_start 512);/* heap */PROVIDE(_heap_start _stack_end);PROVIDE(_heap_size _memory_end - _heap_start);PROVIDE(_memory_start ORIGIN(ram));PROVIDE(_memory_end ORIGIN(ram) LENGTH(ram));
}
其中
OUTPUT_ARCH( riscv ) 指出输出文件所适用的体系架构。ENTRY( _start ) 设置入口点为我们在start.S中定义的_start符号。MEMORY 指定内存各个部分的起始地址和大小这里和前面设计的RISC-V处理器上的rom和ram大小对应。SECTIONS 指定分区的布局.text和.rodata段放在rom中因为都是只读数据.data、.bss段放在ram中接下来是栈区大小为512B然后是堆区。
添加了各个区块的管理后在启动文件start.S里面的内容也需要作出一些更新
#include inc/platform.h.global _start.text
_start:# Set all bytes in the BSS section to zero.la a0, _bss_startla a1, _bss_endbgeu a0, a1, 2f
1:sw zero, (a0)addi a0, a0, 4bltu a0, a1, 1b
2:la sp, _stack_end # set the initial stack pointerj start_kernel # jump to kernel其中 1: 下方的代码是用来初始化.bss段的数据因为.bss段都是未经过初始化的符号所以我们需要将.bss段内容清0。
有了链接脚本我们开发的操作系统程序在编译链接后的各个分区都会被分配到指定的位置上极大地方便了之后的开发。
三、动态分配内存
既然我们已经完成了静态内存的分配接下来我们要实现内存的动态分配也就是如何去动态地分配堆区的空间下面会基于page来实现动态内存分配和C语言里面的malloc和free函数一样这里也实现了page_alloc和page_free函数用来实现page的分配和释放 并且采用数组的方式来管理页内存前面红蓝区域为页索引用来标明对应页是否已经被分配、是否是最后一页_alloc_start为页内存开始分配的起始地址 页索引的数据结构定义如下在page.c中使用flags的两个位作为标志一个是页是否被使用标志PAGE_TAKEN一个是是否为最后一页标志PAGE_LAST 下面是page.c的全部代码
#include inc/os.h/** Following global vars are defined in mem.S*/
extern uint32_t TEXT_START;
extern uint32_t TEXT_END;
extern uint32_t RODATA_START;
extern uint32_t RODATA_END;
extern uint32_t DATA_START;
extern uint32_t DATA_END;
extern uint32_t BSS_START;
extern uint32_t BSS_END;
extern uint32_t HEAP_START;
extern uint32_t HEAP_SIZE;/** _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 128
#define PAGE_ORDER 7#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*/
typedef struct Page {uint8_t flags;
} Page;static inline void _clear(Page *page)
{page-flags 0;
}static inline uint8_t _is_free(Page *page)
{if (page-flags PAGE_TAKEN) {return 0;} else {return 1;}
}static inline void _set_flag(Page *page, uint8_t flags)
{page-flags | flags;
}static inline uint8_t _is_last(Page *page)
{if (page-flags PAGE_LAST) {return 1;} else {return 0;}
}/** align the address to the border of page(128)*/
static inline uint32_t _align_page(uint32_t address)
{uint32_t order (1 PAGE_ORDER) - 1;return (address order) (~order);
}void page_init()
{ /** one page for page struct, max can index 128 page*/_num_pages (HEAP_SIZE / PAGE_SIZE) - 1;printf(HEAP_START%x, HEAP_SIZE%x, num of pages %d\n, HEAP_START, HEAP_SIZE, _num_pages);Page *page (Page *)HEAP_START;for (int i 0; i _num_pages; i) {_clear(page);page;}_alloc_start _align_page(HEAP_START 1 * 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);
}/** 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)
{/* Note we are searching the page descriptor bitmaps. */uint8_t found 0;Page *page_i (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.*/Page *page_j page_i 1;for (int j i 1; j (i npages); j) {if (!_is_free(page_j)) {found 0;break;}page_j;}/** found equal 1 means get a memory block which is enough for us,* then return the address of the first page of this memory block.*/if (found) {Page *page_k page_i;for (int k i; k (i npages); k) {_set_flag(page_k, PAGE_TAKEN);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 */Page *page (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;}}
}
四、测试
还是和之前一样我们在kernal.c文件里面调用页分配和释放函数来测试
void start_kernel(void){printf(Hello World!\n);page_init();void *p page_alloc(2);printf(p 0x%x\n, p);//page_free(p);void *p2 page_alloc(7);printf(p2 0x%x\n, p2);page_free(p2);void *p3 page_alloc(4);printf(p3 0x%x\n, p3);while(1){}; // stop here!
}
打印的信息会通过串口工具来接收所以要准备好串口调试工具在RISC-V处理器仓库的serial_utils目录下: 怎么编译和烧录操作系统程序可以参考我的这篇文章开发一个RISC-V上的操作系统一—— 环境搭建_riscv开发环境_Patarw_Li的博客-CSDN博客
烧录完成后打开串口调试工具开启串口然后按下复位键即可看到如下现象 可以看到每个段的地址起始都被打印出来了并且最下面打印了start_kernal函数里面的测试结果结果和理想的结果一样至此RISC-V上的操作系统的内存管理部分结束
遇到问题欢迎加群 892873718 交流~