开发一个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…...
工业安全零事故的智能守护者:一体化AI智能安防平台
前言: 通过AI视觉技术,为船厂提供全面的安全监控解决方案,涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面,能够实现对应负责人反馈机制,并最终实现数据的统计报表。提升船厂…...
2025年能源电力系统与流体力学国际会议 (EPSFD 2025)
2025年能源电力系统与流体力学国际会议(EPSFD 2025)将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会,EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...
聊聊 Pulsar:Producer 源码解析
一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台,以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中,Producer(生产者) 是连接客户端应用与消息队列的第一步。生产者…...
关于 WASM:1. WASM 基础原理
一、WASM 简介 1.1 WebAssembly 是什么? WebAssembly(WASM) 是一种能在现代浏览器中高效运行的二进制指令格式,它不是传统的编程语言,而是一种 低级字节码格式,可由高级语言(如 C、C、Rust&am…...
并发编程 - go版
1.并发编程基础概念 进程和线程 A. 进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位。B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。C.一个进程可以创建和撤销多个线程;同一个进程中…...
Linux nano命令的基本使用
参考资料 GNU nanoを使いこなすnano基础 目录 一. 简介二. 文件打开2.1 普通方式打开文件2.2 只读方式打开文件 三. 文件查看3.1 打开文件时,显示行号3.2 翻页查看 四. 文件编辑4.1 Ctrl K 复制 和 Ctrl U 粘贴4.2 Alt/Esc U 撤回 五. 文件保存与退出5.1 Ctrl …...
Caliper 配置文件解析:fisco-bcos.json
config.yaml 文件 config.yaml 是 Caliper 的主配置文件,通常包含以下内容: test:name: fisco-bcos-test # 测试名称description: Performance test of FISCO-BCOS # 测试描述workers:type: local # 工作进程类型number: 5 # 工作进程数量monitor:type: - docker- pro…...
MFE(微前端) Module Federation:Webpack.config.js文件中每个属性的含义解释
以Module Federation 插件详为例,Webpack.config.js它可能的配置和含义如下: 前言 Module Federation 的Webpack.config.js核心配置包括: name filename(定义应用标识) remotes(引用远程模块࿰…...
《Docker》架构
文章目录 架构模式单机架构应用数据分离架构应用服务器集群架构读写分离/主从分离架构冷热分离架构垂直分库架构微服务架构容器编排架构什么是容器,docker,镜像,k8s 架构模式 单机架构 单机架构其实就是应用服务器和单机服务器都部署在同一…...
【UE5 C++】通过文件对话框获取选择文件的路径
目录 效果 步骤 源码 效果 步骤 1. 在“xxx.Build.cs”中添加需要使用的模块 ,这里主要使用“DesktopPlatform”模块 2. 添加后闭UE编辑器,右键点击 .uproject 文件,选择 "Generate Visual Studio project files",重…...
