经过前面的几篇介绍,已经搭建了基本的界面,和实现对应的键盘操作功能,接下来我们开始实现各具体的功能.本文先介绍Memory的相关知识,阐述内存空间的概念,然后介绍如何访问内存空间,并在XU中如何实现memory dump 部分。

1. 内存介绍


    谈到内存,相信大部分读者第一个想到是那根在机箱里插在主板上的绿色条子(当然偶尔也能碰到红色或蓝色:)


bios内存插槽 bios内存条_dos


  图1 常见内存条


这里先明确一下概念,内存,即内存储器,英文Memory,(所以港台同胞称其为“记忆体”)。作用是暂时存放CPU中运算数据,以及硬盘等外部存储的交换数据。其形态多是被标准化成条状,因此往往我们把它直接用等价成内存条来看了。但实际上存在一点已少盖全,比如智能手机中也是要有内存的,只是形态各异。基本功用是一致的。当然本文涉及的主要还是电脑 上用到内存,即使这样,形态也有差异的。


   内存涉及到的专有名词技术术语挺多的,本文仅是把相关概念用导图梳理一下,详情网络上大把资源不再赘述。


bios内存插槽 bios内存条_pc_02


 图2 内存相关概念导图


1.1 内存工作原理


    现在市面上通用的内存大都属于DRAM ,Dynamic Random Access Memory的衍生产品,如我们主流DDR内存, 是利用电容存储电荷的能力来当作存储器,通过电晶体开关来决定是输出还是写入,如果电荷多就是1,少就是0; 使用电容来做内存体积可以做到很小,但是因为电容有漏电的问题,因此必须周期性地对电容进行充电,否则内容将会消失,这个充电过程叫刷新,因为体积小容量高,成本优势等,DDR内存已经延续至4代,当然也有专门用于显存的gddr5冒出来了。






bios内存插槽 bios内存条_dos_03



图3 DRAM存储原理


1.2 内存相关参数获取


    不同的主板支持的内存会有所差异,这取决于芯片组的支持范围,对于不同厂商的内存,其关键参数CL,BL等,是要根据不同平台来调整的,BIOS在初始化时,有一个重要的动作就是设置相关北桥(或内存控制器)参数。而这些参数该填多少,怎么填,一般都是交给芯片厂商提供的MRC(memory referance code)来实现的,对于板级厂商一般无需修改,但如果想突破个别限制,有时还是需要改一下的。这里还涉及到如何获取所插入的内存条的参数,前面到图中,仔细找找,能看到一个SPD的词汇,这个就是固化在内存条模组上的资料,BIOS通过I2C协议(smbus)获取其内容,从而知道如何设置对应的内存控制器了。见下图内存条组成




bios内存插槽 bios内存条_bios内存插槽_04


图4 内存条组成


SPD General Standard,可以到JEDEC官网下载,用来参考一下。这里不再赘述。




1.3 内存演进


内存作为计算机系统中宝贵的资源,一直得到业界长期关注推进, 由最开始的几M到目前的几十G的容量,甚至更大。早期的DIP式,到现在的模组化的内存条,经过了一系列的进化。


我们只聚焦今天的王者DRAM,有最早的SIMM 30 和FPM 平分天下,到EDO,经典的SDRAM, RDRAM的贵族崛起, 再到DDR独霸天下的今天(DDR3,DDR4),只能感慨性能之差异不可同日而语。如下图所示内存进化史。


 

bios内存插槽 bios内存条_dos_05


图5 内存发展简图


2 内存空间


 对于CPU来讲,内存就是他的柜子,因为出身不同,不同家族的CPU,其可配置的柜子是不一样的,这里就撤出一个概念,CPU有多大的活动空间来翻柜子,即CPU的寻址空间。对于X86来讲,有内存寻址和IO寻址, 本文先聚焦内存寻址空间,简称内存空间,内存空间与内存是两个概念,内存是柜子,是可以放具体东西的,是实物。而内存空间是指CPU有多大的活动范围来翻箱子,是抽象的,不能直接放东西。





bios内存插槽 bios内存条_dos_06



图6 内存空间VS内存


2.1内存空间占用情况


因为历史原因,X86的内存使用分布不是连续的,表1是较典型的PC内存使用布局


表1 Physical memory layout of the PC


linear address range

real-mode address range

memory type

use

0- 3FF

0000:0000-0000:03FF

RAM

real-mode interrupt vector table (IVT)

400- 4FF

0040:0000-0040:00FF

BIOS data area (BDA)

500- 9FBFF

0050:0000-9000:FBFF

free conventional memory (below 1 meg)

9FC00- 9FFFF

9000:FC00-9000:FFFF

extended BIOS data area (EBDA)

A0000- BFFFF

A000:0000-B000:FFFF

video RAM

VGA framebuffers

C0000- C7FFF

C000:0000-C000:7FFF

ROM

video BIOS (32K is typical size)

C8000- EFFFF

C800:0000-E000:FFFF

NOTHING

 

F0000- FFFFF

F000:0000-F000:FFFF

ROM

motherboard BIOS (64K is typical size)

100000- FEBFFFFF

 

RAM

free extended memory (1 meg and above)

FEC00000- FFFFFFFF  

 

various

motherboard BIOS, PnP NVRAM, ACPI, etc. 


Expanded Memory VS Extended Memory,扩充内存与扩展内存,这个容易混淆,扩充内存技术,主要是通过在常规内存开辟一个窗口映射到其他存储设备上,从而实现扩容的目的,因限制较多,所以现在基本已经不用,扩展内存,主要指1M以上的内存扩容,常规的0~640KB是DOS使用的常规内存,640KB~1MB是UMA,即 Upper Memory Area, 1MB~1MB+65519B 即为HMA,就是High Memory Area,这里为什么是1MB+655219B是因实模式下逻辑寻址最高寻址地址,FFFF:FFFF -> 10FFEF H = 1MB + 65519B, HMA的大小就是65536-16=65520 Bytes。 





bios内存插槽 bios内存条_bios_07



图7 扩展内存,HMA,UMA


2.2获取内存布局信息


知道内存的大概布局和相关概念,那我们如何获取内存布局信息呢? 这个一般是操作系统或BootLoader考虑的,要和BIOS或EFI交互信息,从而知道哪段能用哪段不能用,通常我们会引述一个东西E280,这个是啥?目前最好的PC侦测内存的方式。用BIOS提供的中断INT 0x15, EAX=0xE820 命令,E820就是这么来的,实际上,这个命令返回的是一个地址,以便下次调用时继续使用,直到返回0为止。表示所有的内存使用情况反馈完毕。使用E820的汇编参考代码如下

; use the INT 0x15, eax= 0xE820 BIOS function to get a memory map
    
    ; inputs: es:di -> destination buffer for 24 byte entries
    
    ; outputs: bp = entry count, trashes all registers except esi
    
    do_e820:
    
    	xor ebx, ebx		; ebx must be 0 to start
    
    	xor bp, bp		; keep an entry count in bp
    
    	mov edx, 0x0534D4150	; Place "SMAP" into edx
    
    	mov eax, 0xe820
    
    	mov [es:di + 20], dword 1	; force a valid ACPI 3.X entry
    
    	mov ecx, 24		; ask for 24 bytes
    
    	int 0x15
    
    	jc short .failed	; carry set on first call means "unsupported function"
    
    	mov edx, 0x0534D4150	; Some BIOSes apparently trash this register?
    
    	cmp eax, edx		; on success, eax must have been reset to "SMAP"
    
    	jne short .failed
    
    	test ebx, ebx		; ebx = 0 implies list is only 1 entry long (worthless)
    
    	je short .failed
    
    	jmp short .jmpin
    
    .e820lp:
    
    	mov eax, 0xe820		; eax, ecx get trashed on every int 0x15 call
    
    	mov [es:di + 20], dword 1	; force a valid ACPI 3.X entry
    
    	mov ecx, 24		; ask for 24 bytes again
    
    	int 0x15
    
    	jc short .e820f		; carry set means "end of list already reached"
    
    	mov edx, 0x0534D4150	; repair potentially trashed register
    
    .jmpin:
    
    	jcxz .skipent		; skip any 0 length entries
    
    	cmp cl, 20		; got a 24 byte ACPI 3.X response?
    
    	jbe short .notext
    
    	test byte [es:di + 20], 1	; if so: is the "ignore this data" bit clear?
    
    	je short .skipent
    
    .notext:
    
    	mov ecx, [es:di + 8]	; get lower uint32_t of memory region length
    
    	or ecx, [es:di + 12]	; "or" it with upper uint32_t to test for zero
    
    	jz .skipent		; if length uint64_t is 0, skip entry
    
    	inc bp			; got a good entry: ++count, move to next storage spot
    
    	add di, 24
    
    .skipent:
    
    	test ebx, ebx		; if ebx resets to 0, list is complete
    
    	jne short .e820lp
    
    .e820f:
    
    	mov [mmap_ent], bp	; store the entry count
    
    	clc			; there is "jc" on end of list to this point, so the carry must be cleared
    
    	ret
    
    .failed:
    
    	stc			; "function unsupported" error exit
    
    	ret

如果只是获取内存大小,方法较多,比如通过CMOS的30,31 Byte,比如通过SMBios,或者INT 0x15的其他功能,AH=E801, AH=E881, 因为不是所有的主板都提供这些功能,这里不展开介绍。参考代码XU有实现E820的功能。读者可自行查阅。


2.3内存映射机制


前面已经解释内存和内存空间的区别,那么内存空间除了内存还有什么呢? 比如上面图例中的BIOS区域,也是可以通过内存空间访问地,他不算是传统意义上的内存,而是广义内存中的非失忆存储设备,一般是SPI接口的Flash(早期也有FWH的),还有些PCI设备可以通过内存访问其寄存器的,这里就涉及到各概念memory mapping。 


PCI设备寄存器map到内存空间,  Why? 1. X86的IO空间只有64K, 扣除保留的空间,实际能使用的很有限。2.CPU的IO指令存取数据很慢,用Memory方式可以很快。因此,对于需要高速、大空间的PCI设备都改成内存空间上访问其寄存器,PCI本身支持内存存取,方便转移,因此现在高速的PCI设备一般占用内存空间。


读者可能会奇怪PCI与内存之间没线路连接,怎么可以在内存空间看到PCI设备呢?这个关键在于桥片了,(南北桥时代看北桥),实际上就是内存控制器,他做了些工作,将特定的内存位置转由PCI总线传送出去,所以可以使用内存的方式操作PCI设备。





bios内存插槽 bios内存条_软件_08



图8 内存映射机制


当然,不是说有的PCI设备都需要内存映射,主要取决于厂商的设计,简单的PCI设备无需,高级的复杂的比如RAID卡等需要memory mapping。



3.内存的读写操作


   前面已经将内存的基础知识做了一下回顾,下面解释CPU对于内存怎样访问读写。


3.1内存地址空间访问模式


内存地址空间的访问模式与CPU的工作模式对应的, X86因为历史原因,一直保留着实模式real mode,这时的寻址范围1 MB以下,主要是其地址线范围所限,通过段地址+偏移地址来访问内存的指定位置。


286及以后的CPU支持保护模式,286的地址上线是16MB,而之后的是4G或更大。保护模式用到了段选择子,虽然还是通过CPU的段寄存器操作,但其定义已经完全变了。


另外一种叫big real mode的模式,实际上等价于在实模式实开启了A20,使得内存线性地址与实际的物理地址一一对应,这个对于调试,内存内容比较会直观些。


3.2内存访问指令


内存的访问指令,汇编比较直接,读:mov 通用寄存器,[内存地址]  写:mov 「内存地址],通用寄存器;C语言中,用指针操作会方便些。当然我们在C中定义的变量,对其修改时其实也是最终转换成mov指令的。只是这部分工作由编译器汇编器代劳了。


3.3XU中内存访问的实现


最后,简单介绍一下XU中对内存dump功能的实现方式,前面已经说过C语言中可以通过指针来操作内存,读或写,XU中就是通过指针操作实现的,不过拥有了一个union联合来方便按Byte,Word,Dword来访问显示数据,定义如下:


union point_tag {
    
        unsigned char *pb;
    
        unsigned short *pw;
    
        unsigned long *pd;
    
        unsigned long  d;
    
    } pmem;

这样访问的是同一个地址,按小边对齐,可以访问数据的长度因为union里面的不同成员定义类型而定,这样方便后续切换操作,所访问的内存地址仍然是同一个。至于相关信息的显示内容,就不再赘述,请各位看官参考源代码吧,基本都是体力劳动了。