ComputerSystems
ComputerSystems
目录
寻址分析
汇编代码能直接操作地址,当然只能操作CPU寻址范围内的地址 像GPU中的非共享地址则无法直接操作,但获取可以依赖编写共享内存的部分的汇编代码来间接编辑或访问
寻址范围(8086 CPU)
几种存储单元
8086处理器 (x86鼻祖):16位处理器
寻址空间:一共20跟寻址线,2^20=1MB的空间 (按道理16位寄存器的寻址范围是2^16=64KB,这里居然超了,被拓展多了4根线。当然段寄存器和IP寄存器都依旧是16位的)
存储单元之间的数据传输关系(mov)
内部 寻址范围
寄存器组
8086cpu内部寄存器结构图 (注意如果是32/64位寄存器,有的会多一个前缀,例如ip->rip,sp->rsp)
- 通用寄存器 (General Register)
- 数据寄存器 (Data Register)
- AH|AL、AX累加器
- BH|BL、BX基址寄存器
- CH|CL、CX计数寄存器
- DH|DL、DX数据寄存器
- 变址/索引寄存器 (Index Register)
- SI、源变址寄存器 (Source Index)
- DI、目的变址寄存器 (Destination Index)
- 指针寄存器 (Pointer Register)
- BP、基址指针 (Base Pointer)
- SP、堆栈指针 (Stack Pointer)
- 数据寄存器 (Data Register)
- 控制寄存器 (Control Register)
- IP、指令指针 (Index Pointer,而不是Internet Protocol)
- FLAGS、标志寄存器 (Flags)
- 段寄存器 (Segment Register)
- CS代码段寄存器 (Code Segment)
- DS数据段寄存器 (Data Segment)
- ES扩展段寄存器 (Extended Segment)
- SS堆栈段寄存器 (Stack Segment)
通用寄存器 (8086)
8086 16位寄存器:有8个16位通用寄存器,其中4个可以拆成2个8位寄存器
通用寄存器 (64位)
64位通用寄存器:可以看到有很多的历史残留问题,导致命名不统一
8个字节 | 前4字节 | 前2字节 | 前1字节 | 含义 | 地址特征 |
---|---|---|---|---|---|
%rax | %eax | %ax | %al | 返回值 | r?x,e?x,?x,?l【rule1】 |
%rbx | %ebx | %bx | %bl | 被调用者保存 | r?x,e?x,?x,?l【rule1】 |
%rcx | %ecx | %cx | %cl | 第4个参数 | r?x,e?x,?x,?l【rule1】 |
%rdx | %edx | %dx | %dl | 第3个参数 | r?x,e?x,?x,?l【rule1】 |
%rsi | %esi | %si | %sil | 第2个参数 | r?i,e?i,?i,?l【rule2】 |
%rdi | %edi | %di | %dil | 第1个参数 | r?i,e?i,?i,?l【rule2】 |
%rbp | %ebp | %bp | %bpl | 被调用者保存 | r?p,e?p,?p,?l【rule3】 |
%rsp | %esp | %sp | %spl | 栈指针(stack pointer) | r?p,e?p,?p,?l【rule3】 |
—— | —— | —— | —— | —— | ——(前后8行分割线) |
%r8 | %r8d | %r8w | %r8b | 第5个参数 | r?p,r?d,r?w,r?b【rule4】 |
%r9 | %r9d | %r9w | %r9b | 第6个参数 | r?p,r?d,r?w,r?b【rule4】 |
%r10 | %r10d | %r10w | %r10b | 调用者保存 | r?p,r?d,r?w,r?b【rule4】 |
%r11 | %r11d | %r11w | %r11b | 调用者保存 | r?p,r?d,r?w,r?b【rule4】 |
%r12 | %r12d | %r12w | %r12b | 被调用者保存 | r?p,r?d,r?w,r?b【rule4】 |
%r13 | %r13d | %r13w | %r13b | 被调用者保存 | r?p,r?d,r?w,r?b【rule4】 |
%r14 | %r14d | %r14w | %r14b | 被调用者保存 | r?p,r?d,r?w,r?b【rule4】 |
%r15 | %r15d | %r15w | %r15b | 被调用者保存 | r?p,r?d,r?w,r?b【rule4】 |
图表记法:(该死的,这命名好混乱,记不注,用时得查)
有含义的部分:(除了特别的几个,其他的就不记了,需要用再查,命名很混乱)
- 8位的前8个寄存器:根据英文缩写来记
- 16位部分:abcd,x和l。例如:高8位ah-high,低8位是al-low,结合起来是ax
- 32位部分:前缀e:extended扩展
- 64位部分:前缀r:row行,r8~r15。_dwb分别表示_/double/word/byte,分别是四字/双字/字/字节
新命名均r开头,后8个寄存器均r+数字开头
前四个寄存器均x/l结尾,前一个位为a/b/c/d
所有16个寄存器的低位部分都可以作为自己、字(16位)、双字(32位)、四字(64位)数字来访问
那么当复制和生成1、2、4字节时,对于高位的处理有两条规则(生成8字节时:没有剩余字节)
- 生成1、2字节时:保留前面剩下的
- 生成4字节时:高位的4字节置为0(该规则是作为从
IA32
到x86-64
扩展的部分而使用的)
其他寄存器
标志寄存器(属于控制寄存器)
调试汇编时输入r可以看到末尾的标志寄存器状态。大小写分别表示1和0
eflags 0x00000082: id vip vif ac vm rf nt IOPL=0 of df if tf SF zf af pf cf
标志 | 单词 | 含义 |
---|---|---|
CF | Carry Flag | 进位 |
PF | Even Flag | 偶数 |
AF | ||
ZF | Zero Flag | 零标志 |
SF | Sign Flag | 符号位,负数位 |
TF | ||
IF | ||
DF | ||
OF | Overflow Flag | 溢出位 |
要结合Jcc
(条件转移指令)来使用
jmp是直接跳转,而jcc是符合条件才会跳转
外部 寻址范围
寻址范围 - 全部
并不是显卡、BIOS等全部都映射到内存空间,只是将CPU寻址范围内的部分地址分配给其他设备
寻址范围 | (共1MB) | 分配给 |
---|---|---|
0x00000~0x9FFFF | 64*11 = 640KB | 内存 DRAM |
0xAFFFF~0xBFFFF | 64*02 = 128KB | 显卡(包括文字模式、图像模式在内的显示部分) |
0xCFFFF~0xFFFFF | 64*04 = 256KB | 假装内存的BIOS(ROM芯片、显卡、硬盘等的BIOS) |
寻址范围 - 内存 (IBM PC 5150)
IBM PC 5150中的ROM BIOS构成(历史残留)
内存范围 | (共640KB) | 分配给 |
---|---|---|
0x0000~0x???? (最开头) | 未知 | BIOS产生的中断向量表以及BIOS的数据 |
(中间) | 未知 | 其他 |
0x7c00~0x7FFF (最末端) | 1KB | 1024B大小的BootLoader |
开机时DRAM内存空间的变化
(1) CPU收到RST信号,IP被强制定位到ROM,从ROM取指令和执行
(2) BIOS程序执行过程中,产生一个
中断向量表
的东西,DRAM被消耗了一部分(3) BIOS执行完POST后,开始在外部存储设备中找
BootLoader
,并加载到内存里 而因为BootLoader将系统拉起来后内存就可以被释放了,所以它加载的位置在末端其中IBM PC 5150中支持的最小内存是32KB (0x0000~0x7FFF,8086则是64KB)
其中BootLoader的Boot扇区也就是
MBR扇区是512B
,栈/数据是512B
,共计1KB也就是BootLoader的位置在:32KB-1KB+1=
0x7c00
寻址范围 - 内存 (8086)
从IBM PC 5150的32KB,后来内存扩展到1MB到现在的数GB,这个0x07c00
的位置也没变
内存范围 | (共640KB) | 分配给 |
---|---|---|
0x00000~0x07BFF (最开头) | 未知 | BIOS产生的中断向量表以及BIOS的数据 |
0x07C00~0x07DFF (非最末端) | 512B | 1024B大小的BootLoader |
0x07E00~0x07FFF (非最末端) | 512B | 1024B大小的BootLoader |
0x08000~0x9FFFF (最末端) | 未知 | 其他 |
- 查看内存中cpu寻址的范围
- 和下面显卡的查看方式不同,内存在设备管理器中居然是不显示的!
- 内存可能很大 (4/8/16/32GB),但系统只给它分配几百MB的地址空间
寻址范围 - 显卡
这部分其实本质上就是内存,当时的显卡是集成显卡,没有独显。显然这部分的本质就是内存,可以当内存使用
显存范围 | (共128KB) | 分配给 |
---|---|---|
0xA0000~0xAFFFF | 64KB | EGA/VGA/XGA/XVGA 彩色图形 |
0xB0000~0xB7FFF | 32KB | Mono text video buffer 黑白文本 |
0xB8000~0xBFFFF | 32KB | CGA/EGA + chroma text video buffer 彩色文本 |
查看显存中cpu寻址的范围
- Win+X > 设备管理器 > 显示适配器 > 选择你的显卡并双击 > 资源菜单 > 资源设置中可以看到分配给内存的显存范围
- 显卡显存可能很大 (4/8/16/24GB),但系统只给它分配几百MB的地址空间 我的电脑:显存大小24GB,专用8GB,共享16GB。分配的显存大小?? =64MB+64KB??(应该是我算漏了,少了)
显存分配原理
- 看起来比较小,但可以作为地址映射出去
- 例如使用256MB来映射4GB的显存,显卡也存在自己的汇编指令(可以切换映射到地址空间的显存等。话说Shared是吗)
寻址方法
寄存器寻址方法(8086分段机制)
8086分段机制:解决16位寄存器无法访问全部1MB地址空间的问题,而且程序重定位也变得简单(ROM-BIOS或操作系统可以进行调度)
内存偏移
- 代码段和数据段是分别的两段连续的内存(冯诺依曼架构)
- CS寄存器:代码段的开头。CS指定代码段基准地址、IP指定代码段偏移地址
- DS寄存器:数据段的开头。DS指定数据段基准地址、增加相应的偏移地址来访问数据
- CPU的IP存储的不是绝对物理地址,而是相对于CS的偏移。CPU访问通过
段地址: 偏移地址
的模式来
段地址:偏移地址(8086中16位寄存器如何存储20位的寻址范围 )
寻址问题
- 8086 CPU 20位,总寻址范围:2^20=1MB
- 8086寄存器仅16位,能存储地址的数量:2^16=64KB
- 即寄存器不能存储CPU寻址范围内的所有地址?——此时需要
段地址:偏移地址
的寻址方式
解决方法
两个16位寄存器
- 段寄存器:ds,存物理地址 16字节对齐:每个段的起始地址,都必须是16的倍数
- 偏移地址寄存器:ax,存逻辑地址
寻址范围分段
- 8086的1MB空间切分时
- 最少可分16个段,每段64KB(1MB = 16x64KB = (2^4)x64KB)
- 最多可分64K个段,每段16B
- 实际使用过程中灵活性很大,可以分为16、32、64、128......65536个段
使用
逻辑地址 = 段地址:偏移地址 = 段地址左移4位+便宜地址 = 实际物理地址
扩展思考:一个物理地址可能由多少个逻辑地址来表示?
举例:设物理地址0x00010 (17),此时两种
段数 逻辑地址 16 0x0000::0x0010 32 0x0000::0x0010 ...... 0x0000::0x0010 32768 0x0000::0x0010 65536 0x0001::0x0000 举例:设物理地址0xFFFFF,此时则比较麻烦,16-4+1=12种
代码段 - 汇编地址
PC是如何指导程序的位置呢?
汇编地址:
- 编译时 汇编代码通过nasm编译成bin文件时,nasm会把汇编.asm当成一整个代码段, 里面的每一条指令都会有一个相对于代码头部的偏移地址,这个偏移地址就是汇编地址
- 加载时 bin在虚拟机或真机上运行时,ROM-BIOS程序将Start.bin加载到内存。具体加载到内存的什么位置,视程序大小和内存闲置空间而定自动分配,分配好后该这段程序有了一个起始物理地址,该地址给CS (代码段寄存器)
- 执行时 CPU按
CS:IP
(代码段寄存器:指令指针) 的逻辑地址去取指令
堆栈段 vs 数据段、代码段
栈段与代码段类似的
- 代码段:CS (代码段寄存器) 保存代码段的基址,IP (指令指针) 保存相对于基址的偏移
- 数据段:DS (数据段寄存器) 保存数据段的基址,ax等 (数据寄存器) 表示相对于基址的偏移
- 栈段:SS (堆栈段寄存器) 保存代码段的基址,SP (堆栈指针) 保存相对于基址的偏移
区别
- 内容不同
- IP指向当前在运行的指令
- SP也叫栈顶指针,指向栈顶
- 方向不同
- 栈的走向跟数据段或代码段不一样
- 数据段和代码段:从内存低处向高处进行。例如代码从开始0x7c00,下条可能为0x7c02
- 栈段:从内存高处向低处进行。push操作让sp减少,pop操作让sp增大
访问方式
原理
之前运行起来是在512字节的启动扇区写入,我们写的是BootLoader程序
- CPU与统一编址设备之间使用
mov
存入寄存器并访问 - CPU与独立编址设置之间使用
in/out
指令来访问,也是通过端口来访问的。端口本质是寄存器的代号
设备端口原理
- 显卡、硬盘上都有自己的寄存器,8~16位不等
- CPU读写不同的端口,实际上是在读写设备的寄存器
- ICH (I/O Controler Hub)
使用汇编读取硬盘(寻址范围外)
计算机主硬盘分配了8个端口,0x1f0~1f7
从硬盘访问数据的方式
- CHS (Cylinders Heads Sectors,柱面磁头扇区):需要磁头、柱面和扇区的信息。但这种方式太过繁琐,而且像固态硬盘根本没有这些信息的也不适合
- LBA (Logical Block Addressing,逻辑块寻址):主流方式,比较快
虚拟内存
虚拟地址空间 | 补充说明 |
---|---|
内核虚拟内存 | 【顶部区域】不允许应用程序读写和调用,必须通过调用内核来执行这些操作 |
用户栈 (往下增长) | 运行时创建 【动态大小】每次调用函数栈增长,函数返回时栈会收缩 |
↕ | |
共享库的内存映射区域 (往上增长) | 【动态大小】存放像C标准库和数学库这样共享库代码和数据的地方 |
↑ | |
运行时堆 (往上增长) | 运行时由malloc创建 【动态大小】调用malloc和free这样的C标准库函数时可动态扩展和收缩 |
读/写数据 | 从hello可执行文件加载进来的程序代码和数据 |
只读的代码和数据 | 【开始区域】从hello可执行文件加载进来的程序代码和数据 |