Linux从头学02:x86中内存【段寻址】方式的来龙去脉
扫描二维码
随时随地手机看文章
-
什么是代码段?
-
什么是数据段?
-
数据的类型和长度
-
寻址范围
-
栈
-
实模式和保护模式
-
Linux 中的分段策略
为了利用性能越来越强悍的计算机,操作系统的也是在逐步变得膨胀和复杂。
什么是代码段?
在上一篇文章:Linux 从头学 01:CPU 是如何执行一条指令的? 中,已经提到过,在处理器的内部,执行每一条指令码时,CPU是非常机械、非常单纯地从 CS:IP 这2个寄存器计算得到转换后的物理地址,从这个物理地址所指向的内存地址处,读取一定长度的指令,然后交给逻辑运算单元(Arithmetic Logic Unit, ALU)去执行。
物理地址的计算方式是:CS * 16 IP。当CPU读取一条指令后,根据指令操作码它能够自动知道这条指令一共需要读取多少个字节。
mov bx, 3344H
当执行第一条指令时,CS = 2000H,IP = 0000H,经过地址转换之后的物理地址是:2000H * 16 0000 = 20000H(乘以 16 也就表示十六进制的数左移 1 位):
当第一条指令码B8 22 11这3个字节被读取之后,IP 寄存器中的内容自动增加3`,从而指向下一条指令:
当第二条指令码BB 44 33这3个字节被读取之后,IP寄存器中的内容又增加3,变为0006H。
CS: 段寄存器,其中的值左移 1 位之后,得到的值就表示代码段在内存中的首地址,或者称作基地址;IP: 指令指针寄存器,表示一条指令的地址,距离基地址的偏移量,也就是说,IP 寄存器是用来帮助 CPU 记住:哪些指令已经被处理过了,下一个要被处理的指令是哪一个;
什么是数据段?
作为一个有意义的程序,仅仅只有指令是不够的,还必须操作数据。
CPU对内存中数据段的访问方式,与访问代码段是类似的,也是通过一个基地址,再加上一个偏移量来得到数据段中的某个物理地址。
数据的类型和长度
但是,在操作数据段中每一个数据,有一个比较重要的概念需要时刻铭记:数据的类型是什么,这个数据在内存中占据的字节数是多少。
假设30000H是数据段的基地址(也就意味着DS寄存器中的内容是3000H),那么30000H地址处的数据大小是多少:11H?2211H?还是44332211H?
那么可以说ptr指针指向的数值是11H。
就可以说ptr指针指向的数值就是44332211H(假设是小端格式)。
PS: 之前我曾说过,文章的主要目的是学习 Linux 操作系统,但是为了学习一些相对底层的内容,在开始阶段必须抛开操作系统的外衣,进入到硬件最近的地方去看。在汇编语言中,CPU是通过指令码中的相关寄存器来判断操作数据的长度。但是该怎么看呢?还是要借助一些原始的手段和工具,那么汇编代码无疑就是最好的、也是唯一的手段;
不过,涉及到的汇编代码都是最简单的,仅仅是为了说明原理;
其中的 [0] 表示内存的数据段中偏移地址是0的位置。
因为指令码中的al寄存器是8位,因此CPU就只读取30000H处的一个字节11,放到al寄存器中。(此时ax寄存器的高8位,也就是ah中的值保持不变)
寻址范围
从以上内容可以总结得出:
注意:这里的段寄存器左移1位,是指十六进制的左移,相当于是乘以 16,因此段的基地址都是16的倍数。
- 代码段和数据段都是通过 【基地址 偏移地址】的方式进行寻址;
- 基地址都放在各自的段寄存器中,CPU 会自动把段寄存器的值,左移 1 位之后,作为段的基地址;
- 偏移地址决定了段中的每一个具体的地址,最大偏移地址是 16 个 bit1,也即是 64KB 的空间;
我们来计算最后一个段的空间。
栈
栈也是数据空间的一种,只不过它的操作方式有些特殊而已。
push(入栈): 往栈空间中放入一个数据;注意:这里的数据是固定 2 个字节,也就是一个字。
pop(出栈): 从栈空间中弹出一个数据;
栈空间的基地址是1000:0000,SS:SP执行的地址空间是栈顶,此时栈顶中的元素是44。
push as
栈顶指针寄存器SP中的值首先减 2,变成000A:
然后,再把寄存器ax中的值1234H放入SS:SP指向的内存单元处:
出栈的操作顺序是相反的:
首先把SS:SP指向的内存单元中的数据1234H放入寄存器bx中,然后把栈顶指针寄存器SP中的值加 2,变成000C:
以上描述的是 8086 处理器中对栈操作的执行过程。
满:是指栈顶指针指向的那个空间中,是一个有效的数据。当一个新数据入栈时,栈顶指针先指向下一个空的位置,然后 把数据放入这个位置;空:是指栈顶指针指向的那个空间中,是一个无效的数据。当一个新数据入栈时,先把数据放入这个位置,然后栈顶指针指向下一个空的位置;
递增:是指在数据入栈时,栈顶指针向高地址方向增长;
递减:是指在数据入栈时,栈顶指针向低地址方向递减;
实模式和保护模式
从以上对内存的寻址方式中可以看出:只要在可寻址的范围内,我们写的程序是可以对内存中任意一个位置的数据进行操作的。
有些书籍中会提到 IA-32A 这个概念,IA-32 是英特尔 Architecture 32-bit简称,即英特尔32位体系架构,也是在386中首先采用。从386以后引入的保护模式下,地址线变成了32根,最大寻址空间可以达到4GB。虽然引进了保护模式,但是也存在实模式,即向前兼容。电脑开机后处于实模式,BIOS 加载主引导记录以及进行一些寄存器的设置之后就进入保护模式。
Linux 中的分段策略
上面描述的分段机制是 x86 处理器中所提供的一种内存寻址机制,这仅仅是一种机制而已。
因此,如何利用x86提供的分段机制是操作系统需要操心的问题。
这是Linux2.6版本中四个主要的段描述符,这里先不用管段描述符是什么,它们最终都是用来描述内存中的一块空间而已。
------ End ------





