手写简易操作系统(五)--获得物理内存容量
前情提要
上一章中我们进入了保护模式,并且跳转到了32位模式下执行。这一章较为简单,我们来获取物理内存的实际容量。
一、获得内存容量的方式
在Linux中有多种方法获取内存容量,如果一种方法失败,就会试用其他方法。其本质上是通过调用BIOS中断0x15实现的。分别是三个子功能,子功能号要放在寄存器EAX或AX中。
EAX=0xE820:遍历主机上全部内存。最大支持2^64Byte
AX=0xE801:分别检测低15MB和16MB~4GB的内存,最大支持2^32Byte。
AH=0x88:最多检测出64MB内存,实际内存超过此容量也按照64MB返回。
若三种方法都失败了只能将机器挂起,停止运行。
二、利用子功能号0xE820
BIOS中断 0x15的子功能0xE820能够获取系统的内存布局,由于系统内存各部分的类型属性不同,BIOS就按照类型属性来划分这片系统内存,所以这种查询呈迭代式,每次BIOS只返回一种类型的内存信息,直到将所有内存类型返回完毕。
内存信息的内容是用地址范围描述符来描述的,用于存储这种描述符的结构称之为地址范围描述符(Address Range Descriptor Structure,ARDS),格式见下表
其中Type为1则表示这段内存可以被操作系统使用,Type为2则表示这段内存不能给操作系统使用(因为这个地址可能是硬件端口,系统ROM,某种设备的内存映射到了这部分什么的),其他的都未定义。
正常情况下,不会出现较大的内存区域不可用的情况,除非安装的物理内存极其小。这意味着,在所有返回的ARDS结构里,此值最大的内存块一定是操作系统可使用的部分,即主板上配置的物理内存容量。
此中断子功能参数见下表
三、利用子功能号0xE801
此方法虽然简单,但功能也不强大,最大只能识别4GB内存,不过这对咱们32位地址总线足够了。稍微有点不便的是此方法检测到的内存是分别存放到两组寄存器中的。低于15MB的内存以1KB为单位大小来记录,单位数量在寄存器AX和CX中记录,其中AX和CX的值是一样的。16MB~4GB是以64KB为单位大小来记录的,单位数量在寄存器BX和DX中记录,其中BX和DX的值是一样的。
为什么区分16MB以上即以下呢?其实这只是为了兼容,80286拥有24位地址线,其寻址空间是16MB。当时有一些ISA设备要用到地址15MB以上的内存作为缓冲区,也就是此缓冲区为1MB大小,所以硬件系统就把这部分内存保留下来,操作系统不可以用此段内存空间。现在这些设备我们几乎不会接触到,但是这个问题还是保留下来了。我们当然在实际操作时要无视这个空间。
四、利用子功能号0x88
该方法使用最简单,但功能也最简单,简单到只能识别最大64MB的内存。即使内存容量大于64MB,也只会显示63MB。此中断只会显示1MB之上的内存,不包括这1MB。
五、检测代码
将程序修改为
; os/src/boot/loader.s
%include "boot.inc"
section loader vstart=LOADER_BASE_ADDR ; 程序开始的地址jmp loader_startLOADER_STACK_TOP equ LOADER_BASE_ADDR ; 栈顶地址;构建gdt及其内部的描述符
GDT_BASE: dd 0x00000000 dd 0x00000000CODE_DESC: dd 0x0000FFFF dd DESC_CODE_HIGH4DATA_STACK_DESC: dd 0x0000FFFFdd DESC_DATA_HIGH4VIDEO_DESC: dd 0x80000007 ; limit=(0xbffff-0xb8000)/4k=0x7dd DESC_VIDEO_HIGH4 ; 此时dpl为0GDT_SIZE equ $ - GDT_BASE
GDT_LIMIT equ GDT_SIZE - 1
times 60 dq 0 ; 此处预留60个描述符的slot
SELECTOR_CODE equ (0x0001<<3) + TI_GDT + RPL0 ; 第一个选择子
SELECTOR_DATA equ (0x0002<<3) + TI_GDT + RPL0 ; 第二个选择子
SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL0 ; 第三个选择子; 以下是定义gdt的指针,前2字节是gdt界限,后4字节是gdt起始地址
gdt_ptr dw GDT_LIMIT dd GDT_BASEtotal_mem_bytes dd 0 ; 保存内存容量,以字节为单位
ards_buf times 244 db 0 ; 人工对齐:total_mem_bytes4字节+gdt_ptr6字节+ards_buf244字节+ards_nr2,共256字节
ards_nr dw 0 ; 用于记录ards结构体数量loader_start:mov byte [gs:160],'L'mov byte [gs:161],0x0Fmov byte [gs:162],'O'mov byte [gs:163],0x0Fmov byte [gs:164],'A'mov byte [gs:165],0x0F mov byte [gs:166],'D'mov byte [gs:167],0x0Fmov byte [gs:168],'E'mov byte [gs:169],0x0Fmov byte [gs:170],'R'mov byte [gs:171],0x0F; 获取内存容量,int 15, ax = E820h
.get_total_mem_bytes:xor ebx, ebx ;第一次调用时,ebx值要为0mov edx, 0x534d4150 ;edx只赋值一次,循环体中不会改变mov di, ards_buf ;ards结构缓冲区
.e820_mem_get_loop: ;循环获取每个ARDS内存范围描述结构mov eax, 0x0000e820 ;执行int 0x15后,eax值变为0x534d4150,所以每次执行int前都要更新为子功能号。mov ecx, 20 ;ARDS地址范围描述符结构大小是20字节int 0x15jc .failed_so_try_e801 ;若cf位为1则有错误发生,尝试0xe801子功能add di, cx ;使di增加20字节指向缓冲区中新的ARDS结构位置inc word [ards_nr] ;记录ARDS数量cmp ebx, 0 ;若ebx为0且cf不为1,这说明ards全部返回,当前已是最后一个jnz .e820_mem_get_loop;在所有ards结构中,找出(base_add_low + length_low)的最大值,即内存的容量。mov cx, [ards_nr] ;遍历每一个ARDS结构体,循环次数是ARDS的数量mov ebx, ards_buf xor edx, edx ;edx为最大的内存容量,在此先清0
.find_max_mem_area: ;无须判断type是否为1,最大的内存块一定是可被使用mov eax, [ebx] ;base_add_lowadd eax, [ebx+8] ;length_lowadd ebx, 20 ;指向缓冲区中下一个ARDS结构cmp edx, eax ;冒泡排序,找出最大,edx寄存器始终是最大的内存容量jge .next_ardsmov edx, eax ;edx为总内存大小
.next_ards:loop .find_max_mem_areajmp .mem_get_ok; 获取内存容量,int 15, ax = E801h
.failed_so_try_e801:mov ax,0xe801int 0x15jc .failed_so_try88 ;若当前e801方法失败,就尝试0x88方法; 先算出低15M的内存,ax和cx中是以KB为单位的内存数量,将其转换为以byte为单位mov cx,0x400 ;cx和ax值一样,cx用做乘数mul cx shl edx,16and eax,0x0000FFFFor edx,eaxadd edx, 0x100000 ;ax只是15MB,故要加1MBmov esi,edx ;先把低15MB的内存容量存入esi寄存器备份; 再将16MB以上的内存转换为byte为单位,寄存器bx和dx中是以64KB为单位的内存数量xor eax,eaxmov ax,bx mov ecx, 0x10000 ;0x10000十进制为64KBmul ecx ;32位乘法,默认的被乘数是eax,积为64位,高32位存入edx,低32位存入eax.add esi,eax ;由于此方法只能测出4G以内的内存,故32位eax足够了,edx肯定为0,只加eax便可mov edx,esi ;edx为总内存大小jmp .mem_get_ok; 获取内存容量,int 15, ah = 0x88
.failed_so_try88: ;int 15后,ax存入的是以kb为单位的内存容量mov ah, 0x88int 0x15jc .error_hltand eax,0x0000FFFF;16位乘法,被乘数是ax,积为32位.积的高16位在dx中,积的低16位在ax中mov cx, 0x400 ;0x400等于1024,将ax中的内存容量换为以byte为单位mul cxshl edx, 16 ;把dx移到高16位or edx, eax ;把积的低16位组合到edx,为32位的积add edx,0x100000 ;0x88子功能只会返回1MB以上的内存,故实际内存大小要加上1MBjmp .mem_get_ok;将内存换为byte单位后存入total_mem_bytes处。
.mem_get_ok:mov [total_mem_bytes], edx ; 打开A20地址线
.open_A20:in al,0x92or al,0000_0010Bout 0x92,al; 加载gdt描述符
.load_gdt:lgdt [gdt_ptr]; 修改cr0标志寄存器的PE位
.change_cr0_PE:mov eax, cr0or eax, 0x00000001mov cr0, eax.jmp_bit_32jmp SELECTOR_CODE:p_mode_start ; 刷新流水线,避免分支预测的影响; 远跳将导致之前做的预测失效,从而起到了刷新的作用。.error_hlt: ;出错则挂起hlt; 下面就是保护模式下的程序了
[bits 32]
p_mode_start:mov ax, SELECTOR_DATAmov ds, axmov es, axmov ss, axmov esp,LOADER_STACK_TOPmov ax, SELECTOR_VIDEOmov gs, axmov byte [gs:320], 'M'mov byte [gs:321],0x0Fmov byte [gs:322], 'A'mov byte [gs:323],0x0Fmov byte [gs:324], 'I'mov byte [gs:325],0x0Fmov byte [gs:326], 'N'mov byte [gs:327],0x0Fjmp $
可以看到仿真结果
可以看到,检测到的内存就是32MB
结束语
这节我们讲述了如何检测物理内存的大小,下节课我们对内存进行处理,内存现在是一个线性的空间,谁想去哪儿就去哪儿,这并不利于我们管理,而且会导致内存的碎皮化问题,下节我们讲内存的分段与分页。
ps:上节说不知道为啥程序不运行了,最后发现是loader导入时导入的扇区数太少了,这节也是不知道为啥不执行了,后面发现是dd指令在将准备好的程序放入硬盘时只放了1024字节,但是程序有一千一百多字节,所以没放下。。。。
相关文章:

手写简易操作系统(五)--获得物理内存容量
前情提要 上一章中我们进入了保护模式,并且跳转到了32位模式下执行。这一章较为简单,我们来获取物理内存的实际容量。 一、获得内存容量的方式 在Linux中有多种方法获取内存容量,如果一种方法失败,就会试用其他方法。其本质上是…...

机器学习之DeepSequence软件使用学习3-预测突变效应
import theano import numpy as np import sys import pandas as pd import scipy #scipy 模块是 Python 中用于科学计算和数据分析的重要模块之一。它包含了许多高级的数学函数和工具,包括数值积分、优化、线性代数、统计等。 from scipy.stats import spearmanr #…...

Linux文件与文件系统的压缩
文章目录 Linux文件与文件系统的压缩Linux系统常见的压缩命令gzip,zcat/zmore/zless/zgrepbzip2,bzcat/bzmore/bzless/bzgreppxz,xzcat/xzmore/xzless/xzgrepgzip,bzip2,xz压缩时间对比打包命令:tar打包命令…...
ubuntu 中进入python 编辑如何退出到命令行
文章目录 在Python解释器(交互式命令行)中,你可以使用 exit()函数或 CtrlD(在Unix/Linux/macOS上)或 CtrlZ然后输入 Enter(在Windows上)来退出Python解释器并返回到命令行。 以下是具体的步骤&a…...

2024.3.12 C++
1.思维导图 2.自己封装一个矩形类(Rect),拥有私有属性:宽度(width)、高度(height),定义公有成员函数: 初始化函数:void init(int w, int h)更改宽度的函数:set_w(int w)更改高度的函数:set_h(int h) 输出该矩形的周长和面积函数:void show() #include <iostream…...

飞塔防火墙开局百篇——002.FortiGate上网配置——透明模式配置(Transparent)
透明模式配置 开启透明模式创建策略 在不改变现有网络拓扑前提下,将防火墙NGFW以透明模式部署到网络中,放在路由器和交换机之间,防火墙为透明模式,对内网网段192.168.1.0/24的上网进行4~7层的安全防护。 登陆FortiGate防火墙界面&…...
代码随想录算法训练营第52天|300.最长递增子序列 674.最长连续递增序列 718.最长重复子数组
300.最长递增子序列 这道题还挺简单的,咱们设置dp[i]表示到第i个数字时的递增子序列的最长的值,那么dp[i]就要遍历从0到i-1的数,也就是看看当前这个数字是否比前面的数字大,如果大的话就看看现在的子序列长度是否会长于前面那个数…...
分享一些开源的游戏仓库
1.CnC_Remastered_Collection 红色警戒95版本 https://github.com/electronicarts/CnC_Remastered_Collection gitee仓库分流:https://gitee.com/loswdarmy/CnC_Remastered_Collection 2.Far-Cry-1-Source-Full 孤岛惊魂1 https://github.com/StrongPC123/Far-Cry-…...

Java详解:单列 | 双列集合 | Collections类
○ 前言: 在开发实践中,我们需要一些能够动态增长长度的容器来保存我们的数据,java中为了解决数据存储单一的情况,java中就提供了不同结构的集合类,可以让我们根据不同的场景进行数据存储的选择,如Java中提…...
Centos7 使用docker来部署mondb
参考官方手册: https://www.mongodb.com/docs/manual/tutorial/install-mongodb-community-with-docker/#std-label-docker-mongodb-community-install 使用脚本快速安装docker curl -fsSL https://get.docker.com -o get-docker.sh | bash get-docker.sh使用 Doc…...
Java SE入门及基础(35)
接口 1. 概念 在软件工程中,软件与软件的交互很重要,这就需要一个约定。每个程序员都应该能够编写实现这样的约定。接口就是对约定的描述。 In the Java programming language, an interface is a reference type, similar to a class, that can con…...

基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的常见车型识别系统(Python+PySide6界面+训练代码)
摘要:本文深入探讨了如何应用深度学习技术开发一个先进的常见车型识别系统。该系统核心采用最新的YOLOv8算法,并与早期的YOLOv7、YOLOv6、YOLOv5等版本进行性能比较,主要评估指标包括mAP和F1 Score等。详细解析了YOLOv8的工作机制,…...
Sqoop 学习
参考视频 大数据Sqoop教程丨从零开始讲解大数据业务及数据采集和迁移需求_哔哩哔哩_bilibili 介绍 Sqoop是Hadoop生态体系和RDBMS(关系型数据库)体系之间传送数据的一种工具 Hadop生态系统:HDFS,Hbase,Hive等 RDBMS包…...

Ollama 只安装 Ollama,本地快速部署谷歌开源大模型Gemma(基于Ollama)
参考:本地快速部署谷歌开源大模型Gemma(基于Ollama) - 知乎 确保系统更新: Bash sudo apt update && sudo apt upgrade 需要先下载Ollama,版本要求0.1.26及以上 运行curl -fsSL https://ollama.com/install.sh | sh 监听 Ollama API 接…...

一条 sql 语句可能导致的表锁和行锁以及死锁检测
锁 MDL 当对一个表做增删改查操作的时候,加 MDL 读锁;当要对表做结构变更操作的时候,加 MDL 写锁 ALTER TABLE tbl_name NOWAIT add column ... ALTER TABLE tbl_name WAIT N add column ... …...

prometheus 原理(架构,promql表达式,描点原理)
大家好,我是蓝胖子,提到监控指标,不得不说prometheus,今天这篇文章我会对prometheus 的架构设计,promql表达式原理和监控图表的绘图原理进行详细的解释。来让大家对prometheus的理解更加深刻。 架构设计 先来看看&am…...
Linux的目录结构(介绍主要的)
/:根目录,文件系统的起点,包含了所有目录和文件 /bin:存放基本的可执行命令,如ls,cp,rm /lib:主要存放动态链接库 /opt:供第三方软件安装的目录,通常将软件…...

推房子游戏c++
这段代码是一个推箱子游戏的实现。游戏中有一个地图,地图上有墙壁、人、箱子和目标位置。玩家通过键盘输入WASD或方向键来控制人物的移动,目标是将所有的箱子推到相应的目标位置上。 代码中的dt数组表示地图,每个位置上的字符表示对应的元素…...

docker学习入门篇
1、docker简介 docker官网: www.docker.com dockerhub官网: hub.docker.com docker文档官网:docs.docker.com Docker是基于Go语言实现的云开源项目。 Docker的主要目标是:Build, Ship and Run Any App, Anywhere(构建&…...
【Spring Boot 3】动态注入和移除Bean
【Spring Boot 3】动态注入和移除Bean 背景介绍开发环境开发步骤及源码工程目录结构总结动态注入Bean的方法动态移除Bean的方法注意事项背景 软件开发是一门实践性科学,对大多数人来说,学习一种新技术不是一开始就去深究其原理,而是先从做出一个可工作的DEMO入手。但在我个…...
Android Wi-Fi 连接失败日志分析
1. Android wifi 关键日志总结 (1) Wi-Fi 断开 (CTRL-EVENT-DISCONNECTED reason3) 日志相关部分: 06-05 10:48:40.987 943 943 I wpa_supplicant: wlan0: CTRL-EVENT-DISCONNECTED bssid44:9b:c1:57:a8:90 reason3 locally_generated1解析: CTR…...

相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了: 这一篇我们开始讲: 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下: 一、场景操作步骤 操作步…...
前端倒计时误差!
提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...
【Linux】C语言执行shell指令
在C语言中执行Shell指令 在C语言中,有几种方法可以执行Shell指令: 1. 使用system()函数 这是最简单的方法,包含在stdlib.h头文件中: #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...

DIY|Mac 搭建 ESP-IDF 开发环境及编译小智 AI
前一阵子在百度 AI 开发者大会上,看到基于小智 AI DIY 玩具的演示,感觉有点意思,想着自己也来试试。 如果只是想烧录现成的固件,乐鑫官方除了提供了 Windows 版本的 Flash 下载工具 之外,还提供了基于网页版的 ESP LA…...

华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建
华为云FlexusDeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色,华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型,能助力我们轻松驾驭 DeepSeek-V3/R1,本文中将分享如何…...

图表类系列各种样式PPT模版分享
图标图表系列PPT模版,柱状图PPT模版,线状图PPT模版,折线图PPT模版,饼状图PPT模版,雷达图PPT模版,树状图PPT模版 图表类系列各种样式PPT模版分享:图表系列PPT模板https://pan.quark.cn/s/20d40aa…...

C++ Visual Studio 2017厂商给的源码没有.sln文件 易兆微芯片下载工具加开机动画下载。
1.先用Visual Studio 2017打开Yichip YC31xx loader.vcxproj,再用Visual Studio 2022打开。再保侟就有.sln文件了。 易兆微芯片下载工具加开机动画下载 ExtraDownloadFile1Info.\logo.bin|0|0|10D2000|0 MFC应用兼容CMD 在BOOL CYichipYC31xxloaderDlg::OnIni…...

基于 TAPD 进行项目管理
起因 自己写了个小工具,仓库用的Github。之前在用markdown进行需求管理,现在随着功能的增加,感觉有点难以管理了,所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD,需要提供一个企业名新建一个项目&#…...
Java编程之桥接模式
定义 桥接模式(Bridge Pattern)属于结构型设计模式,它的核心意图是将抽象部分与实现部分分离,使它们可以独立地变化。这种模式通过组合关系来替代继承关系,从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...