开发一个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…...

WIZnet W5500-EVB-Pico 静态IP配置教程(二)
W5500是一款高性价比的 以太网芯片,其全球独一无二的全硬件TCP、IP协议栈专利技术,解决了嵌入式以太网的接入问题,简单易用,安全稳定,是物联网设备的首选解决方案。WIZnet提供完善的配套资料以及实时周到的技术支持服务…...

R语言无法调用stats.dll的问题解决方案[补充]
写在前面 在去年10月份,出过一起关于R语言无法调用stats.dll的问题解决方案,今天(你看到后是昨天)不知道为什么,安装包,一直安装不了,真的是炸裂了。后面再次把R与Rstuido升级。说实话,我是真不…...

无线蓝牙耳机有什么推荐?怎么选择适合自己的耳机?七款蓝牙耳机分享
随着信息技术的不断发展,蓝牙耳机的不断发展也是必然的,可以说蓝牙耳机在大部分人们的生活中是不可缺少的一部分。那么我们该怎么去挑选出适合我们自己的需求的“蓝”朋友呢? 第一款:南卡小音舱lite2蓝牙耳机 推荐指数ÿ…...

【数据分析专栏之Python篇】四、pandas介绍
前言 在上一篇中我们安装和使用了Numpy。本期我们来学习使用 核心数据分析支持库 Pandas。 一、pandas概述 1.1 pandas 简介 Pandas 是 Python 的 核心数据分析支持库,提供了快速、灵活、明确的数据结构,旨在简单、直观地处理关系型、标记型数据。 …...

《算法竞赛·快冲300题》每日一题:“最小生成树”
《算法竞赛快冲300题》将于2024年出版,是《算法竞赛》的辅助练习册。 所有题目放在自建的OJ New Online Judge。 用C/C、Java、Python三种语言给出代码,以中低档题为主,适合入门、进阶。 文章目录 题目描述题解C代码Java代码Python代码 “ 最…...

mysql的主键选择
一.没有定义主键有什么问题 如果定义了主键,那么InnoDB会使用主键作为聚簇索引如果没有定义主键,那么会使用第一非空的唯一索引(NOT NULL and UNIQUE INDEX)作为聚簇索引如果既没有主键也找不到合适的非空索引,那么In…...

Eureka 学习笔记1:服务端实例缓存
版本 awsVersion ‘1.11.277’ 缓存类型registryConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>AbstractInstanceRegistry成员变量readWriteCacheMapLoadingCacheResponseCacheImpl成员变量readOnlyCacheMapConcurrentMap<Key, Value>…...

vue : 无法加载文件 C:\Users\86182\AppData\Roaming\npm\vue.ps1,因为在此系统上禁止运行脚本。
windows11: PS E:\VueProjects> vue vue : 无法加载文件 C:\Users\86182\AppData\Roaming\npm\vue.ps1,因为在此系统上禁止运行脚本。有关详细信息,请参阅 https:/ go.microsoft.com/fwlink/?LinkID135170 中的 about_Execution_Policie…...

FLinkCDC读取MySQl时间戳时区相关问题解决汇总
FlinkCDC时间问题timestamp等https://blog.csdn.net/qq_30529079/article/details/127809317 FLinkCDC读取MySQl中的日期问题https://blog.csdn.net/YPeiQi/article/details/130265653 关于flink1.11 flink sql使用cdc时区差8小时问题https://blog.csdn.net/weixin_44762298/…...

第三篇-Tesla P40+CentOS7+CUDA 11.7 部署实践
硬件环境 系统:CentOS-7 CPU: 14C28T 显卡:Tesla P40 24G 准备安装 驱动: 515 CUDA: 11.7 cuDNN: 8.9.2.26 安装依赖 yum clean all yum update yum install -y gcc gcc-c pciutils kernel-devel-$(uname -r) kernel-headers-$(uname -r)查看GPU信息…...