开发一个RISC-V上的操作系统(四)—— 内存管理
目录
往期文章传送门
一、内存管理简介
二、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 交流~
相关文章:
开发一个RISC-V上的操作系统(四)—— 内存管理
目录 往期文章传送门 一、内存管理简介 二、Linker Script 链接脚本 三、动态分配内存 四、测试 往期文章传送门 开发一个RISC-V上的操作系统(一)—— 环境搭建_riscv开发环境_Patarw_Li的博客-CSDN博客 开发一个RISC-V上的操作系统(二…...
区块链:可验证随机函数
本篇主要介绍可验证随机函数的定义及其在区块链上的作用。 1 可验证随机函数 1.1 定义 可验证随机函数(Verifiable Random Function,VRF)本质上还是一类具有验证功能的伪随机函数。对于一个特定的输入 m m m以及输入者的私钥 S K SK SK,VRF会输出一个随…...
Flask中flask-session
Flask中flask-session Flask-Session是一个为Flask应用程序开发的工具,允许您轻松处理服务器端会话。会话是存储和追踪用户特定数据的方式。例如,当用户登录到应用程序时,他们的状态(即登录状态)可以保存在会话中&…...
react-Native init初始化项目报错”TypeError: cli.init is not a function“
文章目录 一、问题:二、解决: 一、问题: 在react-native init appDemo 创建项目时,报错TypeError: cli.init is not a function。 二、解决: 产生这个问题的原因是:使用这种方式创建工程,rea…...
【gitlib】linux系统rpm安装gitlib最新版本
目录 下载gitlib安装包 安装需要的依赖 设置开机启动 安装邮件服务器并设置开机启动 rpm执行安装gitlib 修改gitlib.rb文件的属性 修改完毕后执行更新配置 查看gitlib运行 查看gitlib初始化root密码 gitlib入口访问地址 下载gitlib安装包 Index of /gitlab-ce/yum/el7/…...
iOS开发-检查版本更新与强制更新控制
iOS开发-检查版本更新与强制更新控制。 在开发中经常遇到需要检查版本,检查版本及请求appstoreLookUrl查看版本号与当前的版本号进行比对,看是否需要更新。强制更新控制,是将获取到当前版本号传给服务端,服务端判断当前的版本是否…...
自动化运维工具——Ansible
自动化运维工具——Ansible 一、Ansible概述二、ansible 环境安装部署1.管理端安装 ansible2.ansible 目录结构3.配置主机清单4.配置密钥对验证 三、ansible 命令行模块1.command 模块2.shell 模块3.cron 模块4.user 模块5.group 模块6.copy 模块7.file 模块8.hostname 模块9&a…...
W2NER详解
论文:https://arxiv.org/pdf/2112.10070.pdf 代码:https://github.com/ljynlp/W2NER 文章目录 W2NER介绍模型架构解码 源码介绍数据输入格式模型代码 参考资料 W2NER 介绍 W2NER模型,将NER任务转化预测word-word(备注ÿ…...
ElementUI tabs标签页样式改造美化
今天针对ElementUI的Tabs标签页进行了样式修改,更改为如下图所属的样子。 在线运行地址:JSRUN项目-ElementUI tabs标签页样式改造 大家如果有需要可以拿来修改使用,下面我也简单的贴上代码,代码没有注释,很抱歉&#x…...
出海周报|Temu在美状告shein、ChatGPT安卓版上线、小红书回应闪退
工程机械产业“出海”成绩喜人,山东相关企业全国最多Temu在美状告shein,跨境电商战事升级TikTok将在美国推出电子商务计划,售卖中国商品高德即将上线国际图服务,初期即可覆盖全球超200个国家和地区ChatGPT安卓版正式上线ÿ…...
2023年7月26日 单例模式
单例模式 饿汉模式 package com.wz.cinema.platform.server.util;public class DataManager {/*** 单例模式:整个类在运行中只会有一个实例* 既然是在运行中只有一个实例,那么就必须* 考虑多线程环境** 单例模式分为懒汉模式和饿汉模式* 饿汉模式本身就是…...
[ 容器 ] Docker 安全及日志管理
目录 Docker 容器与虚拟机的区别Docker 存在的安全问题Docker 架构缺陷与安全机制Docker 安全基线标准容器相关的常用安全配置方法限制流量流向镜像安全避免Docker 容器中信息泄露DockerClient 端与 DockerDaemon 的通信安全 容器的安全性问题的根源在于容器和宿主机共享内核。…...
游游的排列构造
示例1 输入 5 2 输出 3 1 5 2 4 示例2 输入 5 3 输出 2 1 4 3 5 #include<bits/stdc.h> using namespace std; typedef long long ll; const int N1e55; int n,k; int main(){scanf("%d%d",&n,&k);int xn-k1;int yn-k;int f1;for(int i1;i&l…...
拯救者Y9000K无线Wi-Fi有时不稳定?该如何解决?
由于不同品牌路由器的性能差异,无法完美兼容最新的无线网卡技术,在连接网络时(特别是网络负载较大的情况下),可能会出现Wi-Fi信号断开、无法网络无法访问、延迟突然变大的情况;可尝试下面方法进行调整。 1…...
【业务功能篇59】Springboot + Spring Security 权限管理 【下篇】
UserDetails接口定义了以下方法: getAuthorities(): 返回用户被授予的权限集合。这个方法返回的是一个集合类型,其中每个元素都是一个GrantedAuthority对象,表示用户被授予的权限。getPassword(): 返回用户的密码。这个方法返回的是一个字符…...
性能优化 - 前端性能监控和性能指标计算方式
性能优化 - 前端性能监控和性能指标计算方式 前言一. 性能指标介绍1.1 单一指标介绍1.2 指标计算① Redirect(重定向耗时)② AppCache(应用程序缓存的DNS解析)③ DNS(DNS解析耗时)④ TCP(TCP连接耗时)⑤ TTFB(请求响应耗时)⑥ Trans(内容传输耗时)⑦ DOM(DOM解析耗时) 1.3 FP(f…...
git stash clear清空本地暂存代码
git stash clear清空本地暂存代码 git stash 或者 git stash list 查看本地暂存的代码。 清除本地暂存的代码修改: git stash clear git回退代码仓库版本_git回退到之前的版本会影响本地代码嘛_zhangphil的博客-CSDN博客git回退代码版本_git回退到之前的版本会影…...
消防应急照明设置要求在炼钢车间电气室的应用
摘 要:文章以GB51309—2018《消防应急照明和疏散指示系统技术标准》为设计依据,结合某炼钢车间转炉项目的设计过程,在炼钢车间电气室的疏散照明和备用照明的设计思路、原则和方法等方面进行阐述。通过选择合理的消防应急疏散照明控制系统及灯具供配电方案…...
element 表单验证 深层验证绑定
直接上代码 :prop 和prop 都可以,vue2和vue3或者是element、elementplus都可以用 <template><div class"page page-table"><section class"page-query-form"><breadcrumb :hasLine"false" /></section&g…...
brew 换镜像网站
在国内,使用brew极慢. 因为它需要访问国外的一些服务器. 解决方法是使用国内的镜像站. 如果是首次安装: curl https://raw.githubusercontent.com/Homebrew/install/master/install.sh > install-brew.sh 然后,在下载的文件中, 修改BREW_REPO为: BREW_REPO"https…...
Ubuntu系统下交叉编译openssl
一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机:Ubuntu 20.04.6 LTSHost:ARM32位交叉编译器:arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...
RocketMQ延迟消息机制
两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数,对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后…...
06 Deep learning神经网络编程基础 激活函数 --吴恩达
深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...
vue3+vite项目中使用.env文件环境变量方法
vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量,这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...
初学 pytest 记录
安装 pip install pytest用例可以是函数也可以是类中的方法 def test_func():print()class TestAdd: # def __init__(self): 在 pytest 中不可以使用__init__方法 # self.cc 12345 pytest.mark.api def test_str(self):res add(1, 2)assert res 12def test_int(self):r…...
免费PDF转图片工具
免费PDF转图片工具 一款简单易用的PDF转图片工具,可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件,也不需要在线上传文件,保护您的隐私。 工具截图 主要特点 🚀 快速转换:本地转换,无需等待上…...
【 java 虚拟机知识 第一篇 】
目录 1.内存模型 1.1.JVM内存模型的介绍 1.2.堆和栈的区别 1.3.栈的存储细节 1.4.堆的部分 1.5.程序计数器的作用 1.6.方法区的内容 1.7.字符串池 1.8.引用类型 1.9.内存泄漏与内存溢出 1.10.会出现内存溢出的结构 1.内存模型 1.1.JVM内存模型的介绍 内存模型主要分…...
Ubuntu Cursor升级成v1.0
0. 当前版本低 使用当前 Cursor v0.50时 GitHub Copilot Chat 打不开,快捷键也不好用,当看到 Cursor 升级后,还是蛮高兴的 1. 下载 Cursor 下载地址:https://www.cursor.com/cn/downloads 点击下载 Linux (x64) ,…...
wpf在image控件上快速显示内存图像
wpf在image控件上快速显示内存图像https://www.cnblogs.com/haodafeng/p/10431387.html 如果你在寻找能够快速在image控件刷新大图像(比如分辨率3000*3000的图像)的办法,尤其是想把内存中的裸数据(只有图像的数据,不包…...
热烈祝贺埃文科技正式加入可信数据空间发展联盟
2025年4月29日,在福州举办的第八届数字中国建设峰会“可信数据空间分论坛”上,可信数据空间发展联盟正式宣告成立。国家数据局党组书记、局长刘烈宏出席并致辞,强调该联盟是推进全国一体化数据市场建设的关键抓手。 郑州埃文科技有限公司&am…...
