概念
内存对齐
Why
比如这么一种处理器,它每次读写内存的时候都从某个8倍数的地址开始,一次读出或写入8个字节的数据,假如软件能保证double类型的数据都从8倍数地址开始,那么读或写一个double类型数据就只需要一次内存操作。否则,我们就可能需要两次内存操作才能完成这个动作,因为数据或许恰好横跨在两个符合对齐要求的8字节内存块上。
How
- 按照结构体中,最宽的数据成员分配
- 整个结构体的总大小为最宽基本类型成员大小的整数倍!。——永远成立!
- char int --> 8 Bytes, char char int --> 8 Bytes, char int char --> 12Bytes, char double --> 16Bytes
GDT
https://www.cnblogs.com/bajdcc/p/8972946.html
GDT是一个结构体数组,每一个元素大小为8Bytes,保存着对应段的基址,段大小以及访问权限。
段描述符表是段描述符的一个数组,描述符表的长度可变,最多可以包含 8192 个 8 字节描述符。有两种描述符表:全局描述符表 GDT(Global descriptor table);局部描述符表 LDT(Local descriptor table)。
描述符表存储在由操作系统维护着的特殊数据结构中,并且由处理器的内存管理硬件来引用。这些特殊结构应该保存在仅由操作系统软件访问的受保护的内存区域中,以防止应用程序修改其中的地址转换信息。虚拟地址空间被分割成大小相等的两半。一半由 GDT 来映射变换到线性地址,另一半则由 LDT 来映射。整个虚拟地址空间共含有 2^14 个段:一半空间(即 2^13 个段)是由 GDT 映射的全局虚拟地址空间,另一半是由 LDT 映射的局部虚拟地址空间。通过指定一个描述符表(GDT 或 LDT)以及表中描述符号,我 们就可以定位一个描述符。
当发生任务切换时,LDT 会更换成新任务的 LDT,但是 GDT 并不会改变。因此,GDT 所映射的一半虚拟地址空间是系统中所有任务共有的,但是 LDT 所映射的另一半则在任务切换时被改变。系统中所有 任务共享的段由 GDT 来映射。这样的段通常包括含有操作系统的段以及所有任务各自的包含 LDT 的特殊段。
Snapshot
虚拟地址到真正的物理地址需要走的路
基本知识
**Bochs:**小型模拟器
Linux v0.11文件系统照搬MINIX文件系统,现在主流的linux文件系统为Ext3
0.1 AS86汇编器
语句可以是只包含空格、制表符和换行符的空行,也可以是赋值语句(或定义语句)、伪操作符语句和机器指令语句。
伪操作符语句(汇编指示符)是汇编器使用的指示符,它通常并不会产生任何代码。它由伪操作码和0个或多个操作数组成。每个操作码都由一个点字符’.'开始。
标号是由一个标识符后跟一个冒号’:'组成。在编译过程中,当汇编器遇到一个标号,那么当前位置计数器的值就会赋值给这个标号。
因此一条汇编语句通常由标号(可选)、指令助记符(指令名)和操作数三个字段组成,标号位于一条指令的第一个字段。它代表其所在位置的地址,通常指明一个跳转指令的目标位置。最后还可以跟随用注释符开始的注释部分
0.2 汇编
rep ;相当于 while(cx --) movw 如果 cx不为零,则执行后面的语句,并且把cx减一
movw ; 从内存[si]处移动cx个字到[di]处
0.3 BIOS中断
INT 0x13
0x02
功能描述:读扇区
入口参数:AH=02H
AL=扇区数
CH=柱面
CL=扇区
DH=磁头
DL=驱动器,00H~7FH:软盘;80H~0FFH:硬盘
ES:BX=缓冲区的地址
0.4 间接寻址
物理地址一般由段寄存器和偏移量构成,段寄存器一般有代码段寄存器(CS) , 数据段寄存器(DS) , 堆栈段寄存器(SS) ,都可以使用,偏移量可以放在BX , BP , SI , DI。
我有一个数30 , 要把这个数放在内存中 , 然后在之后要采用寄存器间接寻址找到它 , 我们可以这样做 :
MOV AX, 30
MOV SI, 2000H ;那个2000H是我们自己定义的哟
MOV [SI], AX ; 这样子会把我们AX寄存器中30的值存放在[SI]这个地址里.
[SI] 表示的是物理地址 , 计算的方式就是DS<<4 +SI ; 用的段寄存器是DS
DS:si ES:di
配合使用
0.5 C内嵌汇编
内嵌汇编指令格式如下:
指令部分:输出部分:输入部分:损坏部分
__asm__ __volatile__(
"汇编代码 \n"
"汇编代码 \n"
:"=r"(c变量名) //第一个冒号表示从汇编里输出到c语言的变量, =号表示在汇编里只能改变C变量的值,而不能取它的值. +号表示可以取变量值,也可改变变量的值. r表示在汇编里用一个寄存器代替c变量
:"r"(c变量名) //第二个冒号表示汇编里只能取c变量的值, 不能再有"=","+"号
//输入的变量的寄存器只能使用一次, 如果多次使用此输入的值,则应放到一个固定的寄存器上面(R0-R12)
:"r0", "r1" //第三个冒号表示告诉编译器不要把r0, r1寄存器分配给%0, %1等
);
// __volatile__ 告诉编译器不要优化下面的汇编代码, 可用可不用
限制性字符(部分)
通用寄存器 “a” 将输入变量放入eax
这里有一个问题:假设eax已经被使用,那怎么办?
其实很简单:因为GCC 知道eax 已经被使用,它在这段汇编代码
的起始处插入一条语句pushl %eax,将eax 内容保存到堆栈,然
后在这段代码结束处再增加一条语句popl %eax,恢复eax的内容
“b” 将输入变量放入ebx
“c” 将输入变量放入ecx
“d” 将输入变量放入edx
“s” 将输入变量放入esi
“d” 将输入变量放入edi
“q” 将输入变量放入eax,ebx,ecx,edx中的一个
“r” 将输入变量放入通用寄存器,也就是eax,ebx,ecx,
edx,esi,edi中的一个
“A” 把eax和edx合成一个64 位的寄存器(use long longs)
内存 “m” 内存变量
“o” 操作数为内存变量,但是其寻址方式是偏移量类型,
也即是基址寻址,或者是基址加