Blog

身体虚弱导致易怒易躁

Question #

由于疾病、睡眠不足等原因导致身体虚弱时,为什么人会变得易怒易躁?

Answer #

位于大脑边缘系统的“小脑扁桃体”负责产生愤怒等情绪。身体虚弱时,前额皮质功能下降,无法控制对遵循本能的大脑边缘系统。

From #

获取内存视图

Contents #

给出一个物理地址并不能准确地定位到内存空间,内存空间只是映射物理地址空间中的一个子集,物理地址空间中可能有空洞,有 ROM,有内存,有显存,有 I/O 寄存器,所以获取内存有多大没用,关键是要获取哪些物理地址空间是可以读写的内存。

由于 PC 机上电后由 BIOS 执行硬件初始化,中断向量表是 BIOS 设置的,所以执行中断自然执行 BIOS 服务。这个中断服务是 int 15h,但是它需要一些参数,就是在执行 int 15h 之前,对特定寄存器设置一些值,代码如下。

_getmemmap:
  xor ebx,ebx ;ebx设为0
  mov edi,E80MAP_ADR ;edi设为存放输出结果的1MB内的物理内存地址
loop:
  mov eax,0e820h ;eax必须为0e820h
  mov ecx,20 ;输出结果数据项的大小为20字节:8字节内存基地址,8字节内存长度,4字节内存类型
  mov edx,0534d4150h ;edx必须为0534d4150h
  int 15h ;执行中断
  jc error ;如果flags寄存器的C位置1,则表示出错
  add edi,20;更新下一次输出结果的地址
  cmp ebx,0 ;如ebx为0,则表示循环迭代结束
  jne loop  ;还有结果项,继续迭代
  ret
error:;出错处理

上面的代码是在迭代中执行中断,每次中断都输出一个 20 字节大小数据项,最后会形成一个该数据项(结构体)的数组,可以用 C 语言结构表示,如下。

#define RAM_USABLE 1 //可用内存
#define RAM_RESERV 2 //保留内存不可使用
#define RAM_ACPIREC 3 //ACPI表相关的
#define RAM_ACPINVS 4 //ACPI NVS空间
#define RAM_AREACON 5 //包含坏内存
typedef struct s_e820{
    u64_t saddr;    /* 内存开始地址 */
    u64_t lsize;    /* 内存大小 */
    u32_t type;    /* 内存类型 */
}e820map_t;

From #

07 | Cache与内存:程序放在哪儿?

...

开启Cache

Question #

x86机器上如何开启Cache?

Answer #

目前 Cache 已经成为了现代计算机的标配,但是 x86 CPU 上默认是关闭 Cache 的,需要在 CPU 初始化时将其开启。

在 x86 CPU 上开启 Cache 非常简单,只需要将 CR0 寄存器中 CD、NW 位同时清 0 即可。CD=1 时表示 Cache 关闭,NW=1 时 CPU 不维护内存数据一致性。所以 CD=0、NW=0 的组合才是开启 Cache 的正确方法。

开启 Cache 只需要用四行汇编代码,代码如下:

mov eax, cr0
;开启 CACHE
btr eax,29 ;CR0.NW=0
btr eax,30  ;CR0.CD=0
mov cr0, eax

From #

07 | Cache与内存:程序放在哪儿?

Cache的MESI协议

MESI协议的四种基本状态

MESI协议的四种基本状态 #

MESI 协议定义了 4 种基本状态:M、E、S、I,即修改(Modified)、独占(Exclusive)、共享(Shared)和无效(Invalid)。下面我结合示意图,给你解释一下这四种状态。

1.M 修改(Modified):当前 Cache 的内容有效,数据已经被修改而且与内存中的数据不一致,数据只在当前 Cache 里存在。比如说,内存里面 X=5,而 CPU 核心 1 的 Cache 中 X=2,Cache 与内存不一致,CPU 核心 2 中没有 X。

  1. E 独占(Exclusive):当前 Cache 中的内容有效,数据与内存中的数据一致,数据只在当前 Cache 里存在;类似 RAM 里面 X=5,同样 CPU 核心 1 的 Cache 中 X=5(Cache 和内存中的数据一致),CPU 核心 2 中没有 X。
  1. S 共享(Shared):当前 Cache 中的内容有效,Cache 中的数据与内存中的数据一致,数据在多个 CPU 核心中的 Cache 里面存在。例如在 CPU 核心 1、 CPU 核心 2 里面 Cache 中的 X=5,而内存中也是 X=5 保持一致。
  1. 无效(Invalid):当前 Cache 无效。前面三幅图 Cache 中没有数据的那些,都属于这个情况。

最后还要说一下 Cache 硬件,它会监控所有 CPU 上 Cache 的操作,根据相应的操作使得 Cache 里的数据行在上面这些状态之间切换。Cache 硬件通过这些状态的变化,就能安全地控制各 Cache 间、各 Cache 与内存之间的数据一致性了。

...

长模式下的4KB分页

Question #

长模式下的4KB分页

Answer #

该分页方式下,64 位虚拟地址被分为 6 个位段,分别是:保留位段,顶级页目录索引、页目录指针索引、页目录索引、页表索引、页内偏移,顶级页目录、页目录指针、页目录、页表各占有 4KB 大小,其中各有 512 个条目,每个条目 8 字节 64 位大小,如下图所示。 上面图中 CR3 已经变成 64 位的 CPU 的寄存器,它指向一个顶级页目录,里面的顶级页目项指向页目录指针,依次类推。需要注意的是,虚拟地址 48 到 63 这 16 位是根据第 47 位来决定的,47 位为 1,它们就为 1,反之为 0,这是因为 x86 CPU 并没有实现全 64 位的地址总线,而是只实现了 48 位,但是 CPU 的寄存器却是 64 位的。这种最高有效位填充的方式,即使后面扩展 CPU 的地址总线也不会有任何影响,下面我们去看看当前分页模式下的 CR3、顶级页目录项、页目录指针项、页目录项、页表项的格式,我画了一张图帮你理解。 由上图可知,长模式下的 4KB 分页下,由一个顶层目录、二级中间层目录和一层页表组成了 64 位地址翻译过程。顶级页目录项指向页目录指针页,页目录指针项指向页目录页,页目录项指向页表页,页表项指向一个 4KB 大小的物理页,各级页目录项中和页表项中依然存在各种属性位,这在图中已经说明。其中的 XD 位,可以控制代码页面是否能够运行。

From #

06 | 虚幻与真实:程序中的地址如何转换?

保护模式下的4KB分页

...

sub:分页机制(32位)

Content #

32 位虚拟地址被分为三个位段:页目录索引、页表索引、页内偏移,只有一级页目录,其中包含 1024 个条目 ,每个条目指向一个页表,每个页表中有 1024 个条目。其中一个条目就指向一个物理页,每个物理页 4KB。这正好是 4GB 地址空间。 上图中 CR3 就是 CPU 的一个 32 位的寄存器,MMU 就是根据这个寄存器找到页目录的。 CR3寄存器数据格式 页目录表项格式 页表项格式

页目录项、页表项都是 4 字节 32 位,1024 个项正好是 4KB(一个页),因此它们的地址始终是 4KB 对齐的,所以低 12 位才可以另作它用,形成了页面的相关属性,如是否存在、是否可读可写、是用户页还是内核页、是否已写入、是否已访问等。

From #

06 | 虚幻与真实:程序中的地址如何转换?

开启MMU

Question #

如何开启 MMU?

Answer #

要使用分页模式就必先开启 MMU,但是开启 MMU 的前提是 CPU 进入保护模式或者长模式,开启 CPU 这两种模式的方法,我们在前面第五节课已经讲过了,下面我们就来开启 MMU,步骤如下:

  1. 使 CPU 进入保护模式或者长模式。
  2. 准备好页表数据,这包含顶级页目录,中间层页目录,页表,假定我们已经编写了代码,在物理内存中生成了这些数据。
  3. 把顶级页目录的物理内存地址赋值给 CR3 寄存器。
mov eax, PAGE_TLB_BADR ;页表物理地址
mov cr3, eax
  1. 设置 CPU 的 CR0 的 PE 位为 1,这样就开启了 MMU。
;开启 保护模式和分页模式
mov eax, cr0
bts eax, 0    ;CR0.PE =1
bts eax, 31   ;CR0.P = 1
mov cr0, eax

From #

06 | 虚幻与真实:程序中的地址如何转换?

切换到长模式必须要开启分页

Question #

切换到长模式必须要开启分页,如何开启?

Answer #

切换到长模式必须要开启分页,想想看,长模式下已经不对段基址和段长度进行检查了,那么内存地址空间就得不到保护了。而长模式下内存地址空间的保护交给了 MMU,MMU 依赖页表对地址进行转换,页表有特定的格式存放在内存中,其地址由 CPU 的 CR3 寄存器指向,这在后面讲 MMU 的那节课会专门讲。

mov eax, cr4
bts eax, 5   ;CR4.PAE = 1
mov cr4, eax ;开启 PAE
mov eax, PAGE_TLB_BADR ;页表物理地址
mov cr3, eax

From #

05 | CPU工作模式:执行程序的三种模式

长模式中断门描述符格式

Question #

长模式中断门描述符格式是怎样的?

Answer #

保护模式下为了实现对中断进行权限检查,实现了中断门描述符,在中断门描述符中存放了对应的段选择子和其段内偏移,还有 DPL 权限,如果权限检查通过,则用对应的段选择子和其段内偏移装载 CS:EIP 寄存器。

如果你还记得中断门描述符,就会发现其中的段内偏移只有 32 位,但是长模式支持 64 位内存寻址,所以要对中断门描述符进行修改和扩展,下面我们就来看看长模式下的中断门描述符的格式,如下图所示。

结合上图,我们可以看出长模式下中断门描述符的格式变化。

首先为了支持 64 位寻址中断门描述符在原有基础上增加 8 字节,用于存放目标段偏移的高 32 位值。其次,目标代码段选择子对应的代码段描述符必须是 64 位的代码段。最后其中的 IST 是 64 位 TSS 中的 IST 指针,因为我们不使用这个特性,所以不作详细介绍。

长模式也同样在内存中有一个中断门描述符表,只不过表中的条目(如上图所示)是 16 字节大小,最多支持 256 个中断源,对中断的响应和相关权限的检查和保护模式一样,这里不再赘述。

From #

05 | CPU工作模式:执行程序的三种模式

保护模式下中断门描述符格式

长模式段描述符

Question #

长模式段描述符的格式是怎样的?

Answer #

长模式依然具备保护模式绝大多数特性,如特权级和权限检查。相同的部分就不再重述了,这里只会说明长模式和保护模式下的差异。

下面我们来看看长模式下段描述的格式,如下图所示。 在长模式下,CPU 不再对段基址和段长度进行检查,只对 DPL 进行相关的检查,这个检查流程和保护模式下一样。

当描述符中的 L=1,D/B=0 时,就是 64 位代码段,DPL 还是 0~3 的特权级。然后有多个段描述在内存中形成一个全局段描述符表,同样由 CPU 的 GDTR 寄存器指向。

From #

05 | CPU工作模式:执行程序的三种模式

保护模式段描述符