动手学操作系统(六、获取物理内存容量)

动手学操作系统(六、获取物理内存容量)

在上一节中,我们介绍了保护模式和实模式的区别,保护模式的最大特点是“大”,“大”是指寻址空间大,在进入保护模式之后,我们还将要接触虚拟内存、内存管理等,但这些和内存有关的概念都是建立在物理内存之上的,所以无论理论上的概念有多高大上,最终还是要落实到物理内存上才行,所以这一节我们来学习如何获取物理内存容量。

文章目录

  • 动手学操作系统(六、获取物理内存容量)
    • 1. 利用BIOS中断0x15子功能0xe820获取内存
    • 2. 利用BIOS中断0x15子功能0xe801获取内存
    • 3. 利用BIOS中断0x15子功能0x88获取内存
    • 4. 代码
    • Reference

1. 利用BIOS中断0x15子功能0xe820获取内存

在Linux中有许多中方式来获取内存,如果一种方式失败,那么会使用别的方式来获取,如果所有方式都失败了,就会导致无法获取内存信息,后续程序无法加载,只好将机器挂起停止运行。

BIOS中断提供了丰富的功能,具体要调用的功能,需要在寄存器AXEAX中指定,如下:

  • EAX=0xE820 :遍历主机上全部内存
  • AX=0xE801:分别检测15MB和16MB~4GB的内存,最大支持4GB
  • AH=0x88:最多检测出64MB内存,实际内存超过此容量也按照64MB返回。

其中中断0x15的子功能0xE820的强大之处在于其返回内存的信息比较丰富,包括多个属性字段,所以需要一种格式结构来组织这些数据,内存信息的内容是用地址范围描述符来进行描述,用于存储这种描述符的结构称之为地址范围描述符(Address Range Descriptor Structure, ARDS)

Image

BIOS中断只是一段函数例程,现在介绍一下BIOS中断0x150xe820子功能需要哪些参数

Image
Image

调用步骤如下:

  1. 填写好“调用前输入”中列出的寄存器
  2. 执行中断调用int 0x15
  3. CF位为0的情况下,“返回后输出”中对应的寄存器便会有对应的结果

2. 利用BIOS中断0x15子功能0xe801获取内存

另一种获取内存容量的方法是BIOS0x15中断的子功能0xE801,这个方法虽然简单,但是这个方法只能最大检测4GB内存,且有点不方便的是此方法检测的内存是分别存放在两组寄存器中。

Image

调用步骤如下:

  1. AX寄存器写入0xE801
  2. 执行中断调用int 0x15
  3. CF位为0的情况下,“返回后输出”中对应的寄存器便会有对应的结果

3. 利用BIOS中断0x15子功能0x88获取内存

最后一种获取内存的方法同样是BIOS0x15中断,子功能号0x88,该方法使用最简单,但功能也最简单,简单到只能识别最大64MB的内存。

Image

调用步骤如下:

  1. AX寄存器写入0x88
  2. 执行中断调用int 0x15
  3. CF位为0的情况下,“返回后输出”中对应的寄存器便会有对应的结果

4. 代码

目录结构如下:

.
├── bin
│   ├── loader.bin
│   └── mbr.bin
├── bochs_out.log
├── command_compile.sh
├── command_run.sh
├── src
│   └── boot
│       ├── lib
│       │   └── boot.inc
│       ├── loader.S
│       └── mbr.S
└── test

5 directories, 8 files

修改Loader.S

; ~/d2los/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 $

使用上一节中编写的command_compile.shcommand_run.sh进行编译、运行

command_compile.sh

nasm -I src/boot/lib -o bin/mbr.bin src/boot/mbr.S
nasm -I src/boot/lib -o bin/loader.bin src/boot/loader.S
dd if=./bin/mbr.bin of=/home/sjh/bochs/bin/hardisk60MB.img bs=512 count=1 seek=0 conv=notrunc
dd if=./bin/loader.bin of=/home/sjh/bochs/bin/hardisk60MB.img bs=512 count=2 seek=1 conv=notrunc

command_run.sh

~/bochs/bin/bochs -f ~/bochs/bin/bochsrc.disk 

编译

cd ~/d2los
sh command_compile.sh
sh command_run.sh

结果如下:

Image

在运行的过程中依次按下6->c->ctrl+c->r

可以看到检测的内存是33554432

33554432 byte ÷ 1024 ÷ 1024 = 32 MB 33554432 \text{byte} \div 1024 \div 1024 = 32\text{MB} 33554432byte÷1024÷1024=32MB

Reference

[1]《一个64位操作系统的设计与实现》
[2]《操作系统真象还原》

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

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

相关文章

迅狐跨境商城系统|全平台兼容|前端采用uni-app跨端框架,后端采用ThinkPHP5框架

高效实现全平台兼容的迅狐跨境商城系统 迅狐跨境商城系统是一款专为跨境电商企业设计的全平台兼容系统。其前端采用uni-app跨端框架&#xff0c;后端采用ThinkPHP5框架&#xff0c;旨在实现高效的开发和运营管理。 1. 全平台兼容的前端设计 迅狐跨境商城系统的前端采用uni-a…

C# WinForm —— 34 ToolStrip 工具栏 介绍

1. 简介 工具栏 ToolStrip&#xff0c;一般紧贴在菜单栏下面 2. 属性 属性解释(Name)控件ID&#xff0c;在代码里引用的时候会用到Enabled控件是否启用Dock定义要绑定到容器的控件边框&#xff0c;默认是topAnchor定义某个控件绑定到的容器的边缘。当控件锚定到某个边缘时&a…

基于MCGS的双容水箱液位控制系统设计【MCGS+MATLAB+研华工控机】

摘 要 液位控制技术在众多工业领域中扮演着至关重要的角色。无论是化工、制药、食品加工还是水处理行业&#xff0c;对液位进行精确控制都是保证生产流程稳定、产品质量可靠的关键环节。因此基于实验平台设计了液位自动控制系统。首先&#xff0c;根据实际液位的控制需求&…

NHANES数据库及应用

NHANES数据库使用 NHANES - National Health and Nutrition Examination Survey Homepage (cdc.gov) 保姆级NHANES数据库使用教程 - 哔哩哔哩 (bilibili.com) 该数据库所涉及的参与者的死亡状况 &#xff1a;Data Access - National Death Index (cdc.gov) TyG对CVD的影响研…

简单的基于Transformer的滚动轴承故障诊断(Pytorch)

递归神经网络在很长一段时间内是序列转换任务的主导模型&#xff0c;其固有的序列本质阻碍了并行计算。因此&#xff0c;在2017年&#xff0c;谷歌的研究人员提出了一种新的用于序列转换任务的模型架构Transformer&#xff0c;它完全基于注意力机制建立输入与输出之间的全局依赖…

【JAVA】javadoc,如何生成标准的JAVA API文档

目录 1.什么是JAVA DOC 2.标签 3.命令 1.什么是JAVA DOC 当我们写完JAVA代码&#xff0c;别人要调用我们的代码的时候要是没有API文档是很痛苦的&#xff0c;只能跟进源码去一个个的看&#xff0c;一个个方法的猜&#xff0c;并且JAVA本来就不是一个重复造轮子的游戏&#…

javaWeb项目-ssm+vue网上租车系统功能介绍

本项目源码&#xff1a;java-基于ssmvue的网上租车系统源码说明文档资料资源-CSDN文库 项目关键技术 开发工具&#xff1a;IDEA 、Eclipse 编程语言: Java 数据库: MySQL5.7 框架&#xff1a;ssm、Springboot 前端&#xff1a;Vue、ElementUI 关键技术&#xff1a;springboot、…

Python基础教程(十五):面向对象编程

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; &#x1f49d;&#x1f49…

Diffusers代码学习-SDXL

Stable Diffusion XL&#xff08;SDXL&#xff09;是一个强大的文本到图像生成模型&#xff0c;它以三种关键方式迭代以前的Stable Differsion模型&#xff1a; 1. UNet大3倍&#xff0c;SDXL将第二个文本编码器&#xff08;OpenCLIP-ViT-bigG/14&#xff09;与原始文本编码器…

Kubernetes 基础架构最佳实践:从架构设计到平台自动化

本文探讨了如何将DigitalOcean Kubernetes (DOKS)应用于生产环境&#xff0c;并提供实现生产准备&#xff08;production readiness&#xff09;的指导。 规划您的基础架构 Kubernetes 基础架构的规划至关重要&#xff0c;因为它为稳定且可扩展的应用部署平台奠定了基础。通过适…

Coze+Discord:打造你的免费AI助手(教您如何免费使用GPT-4o/Gemini等最新最强的大模型/Discord如何正确连接Coze)

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 文章内容 📒📝 准备Discord📝 准备Coze🔌 连接💡 测试效果⚓️ 相关链接 ⚓️📖 介绍 📖 你是否想免费使用GPT-4o/Gemini等最新最强的大模型,但又不想花费高昂的费用?本文将教你如何通过Coze搭建Bot,并将其转发…

计算机网络(2) 网络层:IP服务模型

一.Internet Protocol在TCP/IP四层模型中的作用 第三层网络层负责数据包从哪里来到哪里去的问题。传输层的数据段提交给网络层后&#xff0c;网络层负责添加IP段&#xff0c;包含数据包源地址与目的地址。将添加IP段的数据包交由数据链路层添加链路头形成最终在各节点传输中所需…

YASKAWA机器人HW1171921-B电缆维修

安川机器人作为现代工业自动化的重要设备&#xff0c;其稳定运行对于生产线的连续性和效率至关重要。然而&#xff0c;随着使用时间的增长&#xff0c;可能会出现各种YASKAWA机器人本体线缆故障&#xff0c;如断线、短路、接触不良等。 一、安川工业机器人电缆维修前的准备 在进…

Java | Leetcode Java题解之第147题对链表进行插入排序

题目&#xff1a; 题解&#xff1a; class Solution {public ListNode insertionSortList(ListNode head) {if (head null) {return head;}ListNode dummyHead new ListNode(0);dummyHead.next head;ListNode lastSorted head, curr head.next;while (curr ! null) {if (…

糖尿病患者血糖控制困难,4个辅助降糖方法分享。

对于糖尿病患者来讲&#xff0c;血糖控制极为困难&#xff0c;稍不留意就会致使忽高忽低的情况出现&#xff0c;今天我来教你 4 个办法来辅助降糖。 第一&#xff0c;在饮食上可多进食全谷类食物&#xff0c;中医认为谷类食物是脾胃的主食。经常吃这类食物不但能够给脾胃提供充…

流媒体传输协议HTTP-FLV、WebSocket-FLV、HTTP-TS 和 WebSocket-WS的详细介绍、应用场景及对比

一、前言 HTTP-FLV、WS-FLV、HTTP-TS 和 WS-TS 是针对 FLV 和 TS 格式视频流的不同传输方式。它们通过不同的协议实现视频流的传输&#xff0c;以满足不同的应用场景和需求。接下来我们对这些流媒体传输协议进行剖析。 二、传输协议 1、HTTP-FLV 介绍&#xff1a;基于 HTTP…

word怎么单页横向设置(页码不连续版)

打开word&#xff0c;将光标放在第一页的最后位置。 然后点击布局下的分隔符&#xff0c;选择下一页。 将光标放在第二页的开头&#xff0c;点击布局下的纸张方向&#xff0c;选择横向即可。 效果展示。 PS&#xff1a;如果那一页夹在两页中间&#xff0c;那么在…

Github 2024-06-13 Go开源项目日报Top10

根据Github Trendings的统计,今日(2024-06-13统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Go项目10TypeScript项目1Shell项目1多存储文件列表/WebDAV程序 创建周期:1265 天开发语言:Go协议类型:GNU Affero General Public License v…

TIA博途Wincc与S7-1500 (V2.9) 或S7-1200 (V4.5) 及更高版本固件PLC通信失败的原因汇总

TIA博途Wincc与S7-1500 (V2.9) 或S7-1200 (V4.5) 及更高版本固件PLC通信失败的原因汇总 从TIA Portal V17开始,对于S7-1500PLC新增了V2.9的固件,S7-1200新增了V4.5的固件,PLC侧默认激活了“仅支持PG/PC和HMI的安全通信”, 注意事项1 如果PLC侧激活了“仅支持PG/PC和HMI的安…

Golang | Leetcode Golang题解之第148题排序链表

题目&#xff1a; 题解&#xff1a; func merge(head1, head2 *ListNode) *ListNode {dummyHead : &ListNode{}temp, temp1, temp2 : dummyHead, head1, head2for temp1 ! nil && temp2 ! nil {if temp1.Val < temp2.Val {temp.Next temp1temp1 temp1.Next} …