当前位置: 首页 > news >正文

开发一个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上的操作系统&#xff08;一&#xff09;—— 环境搭建_riscv开发环境_Patarw_Li的博客-CSDN博客 开发一个RISC-V上的操作系统&#xff08;二…...

区块链:可验证随机函数

本篇主要介绍可验证随机函数的定义及其在区块链上的作用。 1 可验证随机函数 1.1 定义 可验证随机函数(Verifiable Random Function&#xff0c;VRF)本质上还是一类具有验证功能的伪随机函数。对于一个特定的输入 m m m以及输入者的私钥 S K SK SK&#xff0c;VRF会输出一个随…...

Flask中flask-session

Flask中flask-session Flask-Session是一个为Flask应用程序开发的工具&#xff0c;允许您轻松处理服务器端会话。会话是存储和追踪用户特定数据的方式。例如&#xff0c;当用户登录到应用程序时&#xff0c;他们的状态&#xff08;即登录状态&#xff09;可以保存在会话中&…...

react-Native init初始化项目报错”TypeError: cli.init is not a function“

文章目录 一、问题&#xff1a;二、解决&#xff1a; 一、问题&#xff1a; 在react-native init appDemo 创建项目时&#xff0c;报错TypeError: cli.init is not a function。 二、解决&#xff1a; 产生这个问题的原因是&#xff1a;使用这种方式创建工程&#xff0c;rea…...

【gitlib】linux系统rpm安装gitlib最新版本

目录 下载gitlib安装包 安装需要的依赖 设置开机启动 安装邮件服务器并设置开机启动 rpm执行安装gitlib 修改gitlib.rb文件的属性 修改完毕后执行更新配置 查看gitlib运行 查看gitlib初始化root密码 gitlib入口访问地址 下载gitlib安装包 Index of /gitlab-ce/yum/el7/…...

iOS开发-检查版本更新与强制更新控制

iOS开发-检查版本更新与强制更新控制。 在开发中经常遇到需要检查版本&#xff0c;检查版本及请求appstoreLookUrl查看版本号与当前的版本号进行比对&#xff0c;看是否需要更新。强制更新控制&#xff0c;是将获取到当前版本号传给服务端&#xff0c;服务端判断当前的版本是否…...

自动化运维工具——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详解

论文&#xff1a;https://arxiv.org/pdf/2112.10070.pdf 代码&#xff1a;https://github.com/ljynlp/W2NER 文章目录 W2NER介绍模型架构解码 源码介绍数据输入格式模型代码 参考资料 W2NER 介绍 W2NER模型&#xff0c;将NER任务转化预测word-word&#xff08;备注&#xff…...

ElementUI tabs标签页样式改造美化

今天针对ElementUI的Tabs标签页进行了样式修改&#xff0c;更改为如下图所属的样子。 在线运行地址&#xff1a;JSRUN项目-ElementUI tabs标签页样式改造 大家如果有需要可以拿来修改使用&#xff0c;下面我也简单的贴上代码&#xff0c;代码没有注释&#xff0c;很抱歉&#x…...

出海周报|Temu在美状告shein、ChatGPT安卓版上线、小红书回应闪退

工程机械产业“出海”成绩喜人&#xff0c;山东相关企业全国最多Temu在美状告shein&#xff0c;跨境电商战事升级TikTok将在美国推出电子商务计划&#xff0c;售卖中国商品高德即将上线国际图服务&#xff0c;初期即可覆盖全球超200个国家和地区ChatGPT安卓版正式上线&#xff…...

2023年7月26日 单例模式

单例模式 饿汉模式 package com.wz.cinema.platform.server.util;public class DataManager {/*** 单例模式&#xff1a;整个类在运行中只会有一个实例* 既然是在运行中只有一个实例&#xff0c;那么就必须* 考虑多线程环境** 单例模式分为懒汉模式和饿汉模式* 饿汉模式本身就是…...

[ 容器 ] 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有时不稳定?该如何解决?

由于不同品牌路由器的性能差异&#xff0c;无法完美兼容最新的无线网卡技术&#xff0c;在连接网络时&#xff08;特别是网络负载较大的情况下&#xff09;&#xff0c;可能会出现Wi-Fi信号断开、无法网络无法访问、延迟突然变大的情况&#xff1b;可尝试下面方法进行调整。 1…...

【业务功能篇59】Springboot + Spring Security 权限管理 【下篇】

UserDetails接口定义了以下方法&#xff1a; getAuthorities(): 返回用户被授予的权限集合。这个方法返回的是一个集合类型&#xff0c;其中每个元素都是一个GrantedAuthority对象&#xff0c;表示用户被授予的权限。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 查看本地暂存的代码。 清除本地暂存的代码修改&#xff1a; git stash clear git回退代码仓库版本_git回退到之前的版本会影响本地代码嘛_zhangphil的博客-CSDN博客git回退代码版本_git回退到之前的版本会影…...

消防应急照明设置要求在炼钢车间电气室的应用

摘 要:文章以GB51309—2018《消防应急照明和疏散指示系统技术标准》为设计依据&#xff0c;结合某炼钢车间转炉项目的设计过程&#xff0c;在炼钢车间电气室的疏散照明和备用照明的设计思路、原则和方法等方面进行阐述。通过选择合理的消防应急疏散照明控制系统及灯具供配电方案…...

element 表单验证 深层验证绑定

直接上代码 :prop 和prop 都可以&#xff0c;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…...

CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型

CVPR 2025 | MIMO&#xff1a;支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题&#xff1a;MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者&#xff1a;Yanyuan Chen, Dexuan Xu, Yu Hu…...

Docker 运行 Kafka 带 SASL 认证教程

Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明&#xff1a;server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...

【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力

引言&#xff1a; 在人工智能快速发展的浪潮中&#xff0c;快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型&#xff08;LLM&#xff09;。该模型代表着该领域的重大突破&#xff0c;通过独特方式融合思考与非思考…...

python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)

更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...

Module Federation 和 Native Federation 的比较

前言 Module Federation 是 Webpack 5 引入的微前端架构方案&#xff0c;允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...

Robots.txt 文件

什么是robots.txt&#xff1f; robots.txt 是一个位于网站根目录下的文本文件&#xff08;如&#xff1a;https://example.com/robots.txt&#xff09;&#xff0c;它用于指导网络爬虫&#xff08;如搜索引擎的蜘蛛程序&#xff09;如何抓取该网站的内容。这个文件遵循 Robots…...

相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)

【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...

QT3D学习笔记——圆台、圆锥

类名作用Qt3DWindow3D渲染窗口容器QEntity场景中的实体&#xff08;对象或容器&#xff09;QCamera控制观察视角QPointLight点光源QConeMesh圆锥几何网格QTransform控制实体的位置/旋转/缩放QPhongMaterialPhong光照材质&#xff08;定义颜色、反光等&#xff09;QFirstPersonC…...

tauri项目,如何在rust端读取电脑环境变量

如果想在前端通过调用来获取环境变量的值&#xff0c;可以通过标准的依赖&#xff1a; std::env::var(name).ok() 想在前端通过调用来获取&#xff0c;可以写一个command函数&#xff1a; #[tauri::command] pub fn get_env_var(name: String) -> Result<String, Stri…...

uniapp 实现腾讯云IM群文件上传下载功能

UniApp 集成腾讯云IM实现群文件上传下载功能全攻略 一、功能背景与技术选型 在团队协作场景中&#xff0c;群文件共享是核心需求之一。本文将介绍如何基于腾讯云IMCOS&#xff0c;在uniapp中实现&#xff1a; 群内文件上传/下载文件元数据管理下载进度追踪跨平台文件预览 二…...