手写简易操作系统(五)--获得物理内存容量

前情提要

上一章中我们进入了保护模式,并且跳转到了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),格式见下表

image-20240312204933393

其中Type为1则表示这段内存可以被操作系统使用,Type为2则表示这段内存不能给操作系统使用(因为这个地址可能是硬件端口,系统ROM,某种设备的内存映射到了这部分什么的),其他的都未定义。

正常情况下,不会出现较大的内存区域不可用的情况,除非安装的物理内存极其小。这意味着,在所有返回的ARDS结构里,此值最大的内存块一定是操作系统可使用的部分,即主板上配置的物理内存容量。

此中断子功能参数见下表

请添加图片描述

三、利用子功能号0xE801

此方法虽然简单,但功能也不强大,最大只能识别4GB内存,不过这对咱们32位地址总线足够了。稍微有点不便的是此方法检测到的内存是分别存放到两组寄存器中的。低于15MB的内存以1KB为单位大小来记录,单位数量在寄存器AX和CX中记录,其中AX和CX的值是一样的。16MB~4GB是以64KB为单位大小来记录的,单位数量在寄存器BX和DX中记录,其中BX和DX的值是一样的。

image-20240312211154917

为什么区分16MB以上即以下呢?其实这只是为了兼容,80286拥有24位地址线,其寻址空间是16MB。当时有一些ISA设备要用到地址15MB以上的内存作为缓冲区,也就是此缓冲区为1MB大小,所以硬件系统就把这部分内存保留下来,操作系统不可以用此段内存空间。现在这些设备我们几乎不会接触到,但是这个问题还是保留下来了。我们当然在实际操作时要无视这个空间。

四、利用子功能号0x88

该方法使用最简单,但功能也最简单,简单到只能识别最大64MB的内存。即使内存容量大于64MB,也只会显示63MB。此中断只会显示1MB之上的内存,不包括这1MB。

image-20240312211611141

五、检测代码

将程序修改为

; os/src/boot/loader.s
%include "boot.inc" 
section loader vstart=LOADER_BASE_ADDR ; 程序开始的地址

jmp loader_start

LOADER_STACK_TOP equ LOADER_BASE_ADDR ; 栈顶地址

;构建gdt及其内部的描述符
GDT_BASE:  dd    0x00000000 
	       dd    0x00000000

CODE_DESC: dd    0x0000FFFF 
	       dd    DESC_CODE_HIGH4

DATA_STACK_DESC:  dd    0x0000FFFF
		          dd    DESC_DATA_HIGH4

VIDEO_DESC: dd    0x80000007	       ; limit=(0xbffff-0xb8000)/4k=0x7
	        dd    DESC_VIDEO_HIGH4     ; 此时dpl为0

GDT_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_BASE

total_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],0x0F
    mov byte [gs:162],'O'
    mov byte [gs:163],0x0F
    mov byte [gs:164],'A'
    mov byte [gs:165],0x0F   
    mov byte [gs:166],'D'
    mov byte [gs:167],0x0F
    mov byte [gs:168],'E'
    mov byte [gs:169],0x0F
    mov byte [gs:170],'R'
    mov byte [gs:171],0x0F

; 获取内存容量,int 15, ax = E820h
.get_total_mem_bytes:
    xor ebx, ebx              ;第一次调用时,ebx值要为0
    mov 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 0x15
    jc .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_low
    add eax, [ebx+8]	      ;length_low
    add ebx, 20		          ;指向缓冲区中下一个ARDS结构
    cmp edx, eax		      ;冒泡排序,找出最大,edx寄存器始终是最大的内存容量
    jge .next_ards
    mov edx, eax		      ;edx为总内存大小
.next_ards:
    loop .find_max_mem_area
    jmp .mem_get_ok

; 获取内存容量,int 15, ax = E801h
.failed_so_try_e801:
    mov ax,0xe801
    int 0x15
    jc .failed_so_try88       ;若当前e801方法失败,就尝试0x88方法

    ; 先算出低15M的内存,ax和cx中是以KB为单位的内存数量,将其转换为以byte为单位
    mov cx,0x400	          ;cx和ax值一样,cx用做乘数
    mul cx 
    shl edx,16
    and eax,0x0000FFFF
    or edx,eax
    add edx, 0x100000         ;ax只是15MB,故要加1MB
    mov esi,edx	              ;先把低15MB的内存容量存入esi寄存器备份

    ; 再将16MB以上的内存转换为byte为单位,寄存器bx和dx中是以64KB为单位的内存数量
    xor eax,eax
    mov ax,bx		
    mov ecx, 0x10000	      ;0x10000十进制为64KB
    mul 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, 0x88
    int 0x15
    jc  .error_hlt
    and eax,0x0000FFFF
      
    ;16位乘法,被乘数是ax,积为32位.积的高16位在dx中,积的低16位在ax中
    mov cx, 0x400      ;0x400等于1024,将ax中的内存容量换为以byte为单位
    mul cx
    shl edx, 16	       ;把dx移到高16位
    or  edx, eax	   ;把积的低16位组合到edx,为32位的积
    add edx,0x100000   ;0x88子功能只会返回1MB以上的内存,故实际内存大小要加上1MB
    jmp .mem_get_ok

;将内存换为byte单位后存入total_mem_bytes处。
.mem_get_ok:
    mov [total_mem_bytes], edx	 

; 打开A20地址线
.open_A20:
    in   al,0x92
    or   al,0000_0010B
    out  0x92,al

; 加载gdt描述符
.load_gdt:
    lgdt [gdt_ptr]

; 修改cr0标志寄存器的PE位
.change_cr0_PE:
    mov  eax, cr0
    or   eax, 0x00000001
    mov  cr0, eax

.jmp_bit_32
    jmp  SELECTOR_CODE:p_mode_start ; 刷新流水线,避免分支预测的影响
					                ; 远跳将导致之前做的预测失效,从而起到了刷新的作用。

.error_hlt:		      ;出错则挂起
    hlt

; 下面就是保护模式下的程序了
[bits 32]
p_mode_start:
    mov ax, SELECTOR_DATA
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov esp,LOADER_STACK_TOP
    mov ax, SELECTOR_VIDEO
    mov gs, ax

    mov byte [gs:320], 'M'
    mov byte [gs:321],0x0F
    mov byte [gs:322], 'A'
    mov byte [gs:323],0x0F
    mov byte [gs:324], 'I'
    mov byte [gs:325],0x0F
    mov byte [gs:326], 'N'
    mov byte [gs:327],0x0F

    jmp $

可以看到仿真结果

image-20240312221237373

可以看到,检测到的内存就是32MB

结束语

这节我们讲述了如何检测物理内存的大小,下节课我们对内存进行处理,内存现在是一个线性的空间,谁想去哪儿就去哪儿,这并不利于我们管理,而且会导致内存的碎皮化问题,下节我们讲内存的分段与分页。

ps:上节说不知道为啥程序不运行了,最后发现是loader导入时导入的扇区数太少了,这节也是不知道为啥不执行了,后面发现是dd指令在将准备好的程序放入硬盘时只放了1024字节,但是程序有一千一百多字节,所以没放下。。。。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/451750.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

考研数学|汤家凤《1800》vs 张宇《1000》,怎么选?

汤家凤的1800题和张宇的1000题都是备考数学考研的热门选择&#xff0c;但究竟哪个更适合备考呢&#xff1f;下面分享一些见解。 首先&#xff0c;让我们来看看传统习题册存在的一些问题。虽然传统习题册通常会覆盖考试的各个知识点和题型&#xff0c;但其中一些问题在于它们可…

JDBC连接MysqL

import java.sql.*;public class Demo {public static void main(String[] args) throws ClassNotFoundException, SQLException {//1.注册驱动&#xff0c;加载驱动&#xff1b;Class.forName("com.mysql.jdbc.Driver");//2.获得连接,返回connection类型的对象&…

汤唯短发造型:保留经典和适合自己的风格,也许才是最重要的

汤唯短发造型&#xff1a;保留经典和适合自己的风格&#xff0c;也许才是最重要的 汤唯短发造型登上Vogue四月刊封面&#xff0c;引发网友热议。#李秘书讲写作#说说是怎么回事&#xff1f; 这次Vogue四月刊的封面大片&#xff0c;汤唯以一头短发亮相&#xff0c;身穿五颜六色的…

钉钉平台“智”领宠物界,开启萌宠智能新时代!

在当前数字化转型的浪潮中&#xff0c;钉钉用便捷的数字化解决方案推动了宠物业界的智能升级。一家宠物用品公司采用无雀科技数字化管理系统&#xff0c;与钉钉平台结合&#xff0c;解决了小型企业普遍存在的财务管理不清晰、业务流程不规范、客户信息核对繁琐等痛点问题。 针对…

一周学会Django5 Python Web开发-Django5内置模板引擎-模板继承

锋哥原创的Python Web开发 Django5视频教程&#xff1a; 2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~共计34条视频&#xff0c;包括&#xff1a;2024版 Django5 Python we…

Linux中文件的权限

我们首先需要明白&#xff0c;权限 用户角色 文件的权限属性 一、拥有者、所属组和other&#xff08;用户角色&#xff09; 以文件file1为例 第一个箭头所指处即是文件的拥有者&#xff0c;拥有者为zz 第二个箭头所指处即使文件的所属组&#xff0c;所属组为zz 除去拥有者…

嵌入式系统工程师错题总结

笔者来介绍一下嵌入式系统工程师考试的一些易错题目 题目介绍  流水线指令计算公式&#xff1a;一条指令总时间max&#xff08;单个指令执行时间&#xff09;*&#xff08;指令数-1&#xff09;  平均故障间隔时间  ICMP协议&#xff1a;传送通信问题相关的消息。 …

电脑打开应用慢

电脑打开什么应用都很慢 我的电脑是i73060&#xff0c;但是打开应用很慢 解决办法&#xff1a; 在注册表[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\CI\Policy]将"VerifiedAndReputablePolicyState"的值设置为0&#xff0c;如果没有这一项&#xff0c;就…

SPI、Spring SPI、SpringFactoriesLoader

一、SPI技术 SPI全名Service Provider interface&#xff0c;翻译过来就是“服务提供接口”&#xff0c;再说简单就是提供某一个服务的接口&#xff0c; 提供给服务开发者或者服务生产商来进行实现。 Java SPI 是JDK内置的一种动态加载扩展点的实现。 这个机制在一般的业务代…

【数据集】2023自动驾驶开源数据集-学习笔记

文章目录 1. 自动驾驶有哪些公开数据集2. 预测相关的数据集有哪些 1. 自动驾驶有哪些公开数据集 waymo open dataset 适应任务: 域适应&#xff0c;2D追踪&#xff0c;2D检测&#xff0c;3D追踪&#xff0c;3D检测&#xff0c;实时2D检测&#xff0c;实时3D检测&#xff0c;交互…

深入解析Java内存模型

一、背景 并发编程本质问题是&#xff1a;CPU、内存以及IO三者之间的速度差异。CPU速度快于内存、内存访问速度又远远快于IO&#xff0c;根据木桶理论&#xff0c;程序性能取决于最慢的操作&#xff0c;即IO操作。这样会出现CPU和内存交互时&#xff0c;CPU性能无法被充分利用…

使用命令行查看同一局域网内所有ip地址

由于学科实践课程提供的局域网IP扫描软件在本机上运行时&#xff0c;无法扫描出树莓派&#xff08;可能和防火墙设置有关&#xff1f;&#xff09;&#xff0c;所以记录一种通过命令行查看同一局域网下设备IP地址的方法&#xff0c;以手机热点下查找树莓派IP为例。 Step1&#…

Hive面经

hive原理 Hive 内部表和外部表的区别Hive 有索引吗运维如何对 Hive 进行调度ORC、Parquet 等列式存储的优点数据建模用的哪些模型&#xff1f;1. 星型模型2. 雪花模型3. 星座模型 为什么要对数据仓库分层&#xff1f;使用过 Hive 解析 JSON 串吗sort by 和 order by 的区别数据…

读书笔记之《机器与人》:AI如何重构工作方式和流程?

《机器与人: 埃森哲论新人工智能》作者是【美】保罗•多尔蒂和詹姆斯•威尔逊 &#xff0c;原作名: Human Machine: Reimagining Work in the Age of AI&#xff0c;2018年出版。 保罗•多尔蒂&#xff08;PAUL DAUGHERTYH&#xff09;&#xff1a;埃森哲首席技术官和创新官、…

策略迭代和价值迭代

策略迭代价值迭代 策略迭代&#xff08;Policy Iteration&#xff09;基本步骤例子&#xff1a;公主的营救 价值迭代&#xff08;Value Iteration&#xff09;基本步骤例子&#xff1a;公主的营救 策略迭代与价值迭代的区别实现方式目标收敛速度与其他技术的交互 策略迭代&…

浅谈Redis 的 保护模式(protected-mode)

今天在一台服务器上面部署了redis,发现始终无法用工具远程连接,项目里面是正常的,就是工具不行,防火墙也关闭了.折腾了一会才突然想起来,是不是触发了保护模式. 什么时候触发保护模式protected-mode: 同时满足以下两个: 1.bind未指定ip 2.未配置密码 解决方案: 编辑redis…

Room+ViewModel+LiveData

Room框架支持的LiveData会自动监听数据库的变化&#xff0c;当数据库发生变化的时候&#xff0c;会调用onChanged函数更新UI 1.MainActivity package com.tiger.room2;import android.os.AsyncTask; import android.os.Bundle; import android.util.Log; import android.vie…

红帽认证RHCE好考吗?多长时间能考下来?报名费多少一门?哪些人适合考红帽认证?

一、红帽认证等级 红帽认证考试有三个等级&#xff0c;分别是RHCSA&#xff08;红帽认证系统管理员&#xff09;&#xff0c;RHCE&#xff08;红帽认证工程师&#xff09;&#xff0c;RHCA&#xff08;红帽认证架构师&#xff09;。RHCA是最高级别的认证。 二、RHCE考试 1、考…

一款好用的AI工具——边界AICHAT(二)

目录 3.11、AI智能在线抠图3.12、AI智能图片增强放大3.13、AI图片擦除3.14、AI图片理解3.15、音频视频网页理解模型3.16、角色扮演3.17、AI文档理解对话3.18、公文写作模式3.19、插件库3.20、AI思维导图3.21、PPT一键生成3.22、音视频生成PPT 本篇博文接上一篇博文 一款好用的…

Singularity(四)| 自定义容器

Singularity&#xff08;四&#xff09;| 自定义容器 4.1 Singularity Definition 文件 对于可复制的、高质量的容器&#xff0c;我们应该使用定义文件&#xff08;Definition File&#xff09;构建 Singularity 容器 。使用定义文件的方式可以在纯文本文件中描述容器的配置和…