4

羽夏看Linux内核——中断与分页相关入门知识 - 寂静的羽夏

 1 year ago
source link: https://www.cnblogs.com/wingsummer/p/16564726.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

羽夏看Linux内核——中断与分页相关入门知识

  此系列是本人一个字一个字码出来的,包括示例和实验截图。如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我

你如果是从中间插过来看的,请仔细阅读 羽夏看Linux系统内核——简述 ,方便学习本教程。

  中断通常是由CPU外部的输入输出设备(硬件)所触发的,供外部设备通知CPU有事情需要处理,因此又叫中断请求,英文为Interrupt Request。中断请求的目的是希望CPU暂时停止执行当前正在执行的程序,转去执行中断请求所对应的中断处理例程,中断处理程序由哪有IDT表决定。
  80x86有两条中断请求线:非屏蔽中断线,NMI,全称NonMaskable Interrupt和可屏蔽中断线,INTR,全称Interrupt Require

不可屏蔽中断

  什么是不可屏蔽中断CPUEFLAG之中有一个位,它是IF位。如果它被置0。如果有可屏蔽中断告诉CPU有中断来了,你能先执行我的代码呢?可是IF位是0,对不起,我听不见。左耳朵进,右耳朵出。反之,我会处理。常见的不可屏蔽中断有电脑长按关机、键盘输入等等。当非可屏蔽中断产生时,CPU在执行完当前指令后会里面进入中断处理程序,非可屏蔽中断不受那个位的影响,一旦发生,CPU必须处理。为了方便观看,给个EFLAG图解:

2520882-20220206203708663-1407159662.jpg

  那么CPU是如何处理我们的不可屏蔽中断呢?我们先来看如下表格:

(IDT表)中断号 NMI 说明
0x2 不可屏蔽中断 80x86 中固定为 0x2

  如果处理不可屏蔽中断,CPU会调用2号中断。涉及的IDT表和中断门的知识如果忘却请查看前面的教程。

可屏蔽中断

  什么是可屏蔽中断,我就不赘述了。在硬件级,可屏蔽中断是由一块专门的芯片来管理的,通常称为中断控制器。它负责分配中断资源和管理各个中断源发出的中断请求.为了便于标识各个中断请求,中断管理器通常用IRQ,全称为Interrupt Request,后面加上数字来表示不同的中断。
  那么CPU是如何处理我们的可屏蔽中断呢?我们先来看如下表格:

(IDT表)中断号 IRQ 说明
0x30 IRQ0 时钟中断
0x31-0x3F IRQ1-IRQ15 其他硬件设备的中断

  如果自己的程序执行时不希望CPU去处理这些中断,可以用CLI指令清空EFLAG寄存器中的IF位,用STI指令设置EFLAG寄存器中的IF位。
  硬件中断与IDT表中的对应关系并非固定不变的,可以参考白皮书的Chapter 10 Advanced Programmable Interrupt Controller(APIC)进行了解。

  异常通常是CPU在执行指令时检测到的某些错误,比如除0、访问无效页等。中断与异常之间有一些相似之处,但它们是不一样的:中断来自于外部设备,是中断源(比如键盘)发起的,CPU是被动的;而异常来自于CPU本身,是CPU主动产生的。INT N虽然被称为“软件中断”,但其本质是异常,EFLAGIF位对INT N是无效。

  无论是由硬件设备触发的中断请求还是由CPU产生的异常,处理程序都在IDT表。常见的异常处理程序如下表所示:

错误类型 (IDT表)中断号
页错误 0xE
段错误 0xD
除零错误 0x0
双重错误 0x8

控制寄存器

  控制寄存器用于控制和确定CPU的操作模式。控制寄存器有Cr0Cr1Cr2Cr3Cr4Cr1被保留了,Cr3用于页目录表基址,其他的将继续详细讲解。

  Cr0是一个十分重要的寄存器,可以说它是总开关的集合体。如下图所示:

o_211023151018_4-5.png

  PE位是启用保护模式(Protection Enable)标志。若PE = 1是开启保护模式,反之为实地址模式。这个标志仅开启段级保护,而并没有启用分页机制。若要启用分页机制,那么PEPG标志都要置位。
  PG位是启用分页机制。在开启这个标志之前必须已经或者同时开启PE标志。PG = 0PE = 0,处理器工作在实地址模式下。PG = 0PE = 1,处理器工作在没有开启分页机制的保护模式下。PG = 1PE = 0,在PE没有开启的情况下无法开启PGPG = 1PE = 1,处理器工作在开启了分页机制的保护模式下。
  WP位对于Intel 80486或以上的CPU,是写保护(Write Proctect)标志。当设置该标志时,处理器会禁止超级用户程序(例如特权级0的程序)向用户级只读页面执行写操作;当CPL < 3的时候,如果WP = 0可以读写任意用户级物理页,只要线性地址有效。如果WP = 1可以读取任意用户级物理页,但对于只读的物理页,则不能写。

  当CPU访问某个无效页面时,会产生缺页异常,此时,CPU会将引起异常的线性地址存放在CR2中,如下图所示:

o_211023151024_4-6.png

  Cr4的结构如下图所示:

2520882-20220305153025181-197890540.png

  VME用于虚拟8086模式。PAE用于确认是哪个分页,PAE = 1,是2-9-9-12分页,PAE = 010-10-12分页。PSE是大页是否开启的总开关,如果置0,就算PDE中设置了大页你也得是普通的页。至于分页到底是什么,将会在下一篇进行讲解。

  有些结构的位我并没有详细介绍,详情请查看白皮书的控制寄存器的篇章,如下图所示:

o_211023152443_4-8.png

  如果对分页不了解看不懂没关系,接下来将会介绍相关知识。

  在讲解分页基础之前,我们先大体了解CPU是如何在保护模式下访问数据的,如下图所示:

o_2110091504151-5.png

  比如我们执行mov eax,ds:[0x12345678]这句汇编指令的时候,0x12345678这个线性地址会传递给CPU,先查询TLB缓存有没有,有的话直接取出来返回;如果没有,经过MMU(内存管理单元)处理得到物理地址,通过固定的分页模式直接找到,取出数据返回。
  前面的教程讲解了段的机制,接下来将介绍页的机制。CPU为了方便管理物理内存,按照页的方式进行管理内存。可用的所有内存可以类比为一本书,而所有的内存被分为这本书的一个页。对于32位来说,它有10-10-12分页和2-9-9-12分页。其中10-10-12分页最为简单,故拿其作为详细讲解,作为分页讲解的基础。
  我们都了解一个进程都有4GB的虚拟地址空间,它们并不是真正的地址,而是个索引。它通过某种方式进行转换,从而指向真正的物理地址,示意图如下所示:

o_2110030854141-1.png

  而虚拟地址也被称作线性地址。举个例子,比如某个进程里面我想读取一个0x12345678,它就是线性地址,通过一些转换,找到了对应的物理地址0x10101010,如下图所示:

o_2110030909421-2.png

  每个进程都有一个CR3,准确的说是都一个CR3的值。CR3本身是个寄存器,一核一套。CR3里面放的是一个真正的物理地址,指向一个物理页,一共4096字节,如下图所示:

o_2110030909461-3.png

  对于10-10-12分页来说,线性地址对应的物理地址是有对应关系的,它被分成了三个部分,每个部分都有它具体的含义。线性地址分配的结构如下图所示:

o_2110030909491-4.png

  第一个部分指的是PDEPDT的索引,第二部分是PTEPTT的索引,第三个部分是在PTE指向的物理页的偏移。PDT被称为页目录表,PTT被称为页表。PDEPTE分别是它们的成员,大小为4个字节。我们接下来将详细介绍每一个部分是咋用的。

10-10-12 分页整体结构

  通过实验我们了解了它们的结构,接下来将详细介绍了。根据实验结果的体验,可以给出如下图:

o_2110150653221-6.png

  分页并不是由操作系统决定的,而是由CPU决定的。只是操作系统遵守了CPU的约定来实现的。物理页是什么?物理页是操作系统对可用的物理内存的抽象,按照4KB的大小进行管理(Intel是按照这个值做的,别的CPU就不清楚了),和真实硬件层面上的内存有一层的映射关系,这个不是保护模式的范畴,故不介绍。

PDE 与 PTE

  前面我们简单了解PDEPTE,接下来将学习它们的属性结构,结构如下:

o_2110150700591-7.png

  表示PDE或者PTE是否有效,如果有效为1,反之为0

  如果R/W = 0,表示是只读的,反之为可读可写。

  如果U/S = 0,则为特权用户(super user),即非3环权限。反之,则为普通用户,即为3环权限。

  这个位只对PDE有意义。如果PS == 1,则PDE直接指向物理页,不再指向PTE,低22位是页内偏移。它的大小为4MB,俗称“大页”。

  是否被访问,即是否被读或者写过,如果被访问过则置1

  脏位,指示是否被写过。若没有被写过为0,被写过为1

注意,下面的三个位的讲解将涉及 TLB 和控制寄存器相关知识,为了保证文章的完整性,故先介绍。之后将会详细讲解。

  表示是否为全局页。它的作用是什么呢?举个例子,操作系统的进程的高2G映射基本不变,如果Cr3改了,TLB刷新重建高2G以上很浪费。所以PDEPTE中有个G位,如果为1,刷新TLB时将不会刷新它指向的页。

  当PWT = 1,写缓存的时候也要将数据写入内存中。

  当PCD = 1时,禁止某个页写入缓存,直接写内存。比如,做页表用的页,已经存储在TLB中了,可能不需要再缓存了。

  • PTE可以没有物理页,且只能对应一个物理页。
  • 多个PTE也可以指向同一个物理页。
  • PDEPTE重合的属性共同决定着最终物理页的属性。比如P位,如果有一个是0,那么最终的物理页就是无效的。但是PDEPTE它们的属性的影响范围是不一样的。数值上:物理页的属性 = PDE属性 & PTE属性。

PAE 分页

  PAE分页是啥,其实他就是2-9-9-12分页的英文缩写。为什么要有2-9-9-12分页,其实还是物理页不够用了,需要扩展。但想要足够的物理页,位数在那里,你想大也大不了。那么我就需要扩展物理页地址的位数,于是乎2-9-9-12分页诞生了,它整体分页的结构如下:

o_211021142614_3-1.png

  与10-10-12分页不同的地方就是,多了一层名为页目录指针表的东西,英文缩写为PDPTT。每个PDEPTE被扩展为8个字节,物理地址描述的位数扩展为24位,故可以描述更多的物理页,但个数减半,变成了512个。下面详细查看它们的结构。
  首先看PDPTT的结构。由2-9-9-122可知第一部分由两位二进制组成,那么最多有4种结果。也就是为什么有五个成员,它的结构如下图所示:

o_211021143418_3-2.png

  然后是PDE,既然学过了10-10-12分页,直接看下面的结构示意图吧:

o_211021143428_3-4.png
o_211021143425_3-3.png

  然后是PTE,同理不多说了:

o_211021143950_3-6.png

  我们之前做一道作业题目知道,我写的shellcode写到一个页上,它并没有执行权限,但它不是代码,仍然可以被我执行。为了弥补这个漏洞,Intel给我们补了一个硬件层面上的漏洞,它是一个位,处于PDEPTE的最高位,如下图所示:

o_211021143432_3-5.png

  如果最高位是1,说明被保护。如果这个是数据,且这个X位被置为1,则会被报出异常不能执行。反之,和正常的10-10-12分页没什么两样。
  一个进程的线性地址仍是4GB的线性空间,有再多的物理页有啥用呢?在10-10-12分页下,假设进程一启动,就把所有的物理页都挂上,且没有任何交换。那么只能启动一个;如果在2-9-9-12分页下,同样的情况,它可以启动4个进程。这个就是2-9-9-12分页的意义。

  CPU通过页的方式对物理内存进行了,需要通过某种运算方式才能访问真正的内存。但是,如果频繁访问某一个线性地址,每次都得通过推算到真正的物理地址然后进行读写操作,是不是挺浪费效率的?Intel就考虑到性能的问题,提供了TLB这一个机制,提供缓存提高读写效率。
  TLB的全称为Translation Lookaside Buffer,它的结构如下:

  对于TLB,给出如下说明:
  1. ATTR(属性):如果是2-9-9-12分页,属性是PDPEPDEPTE三个属性共同决定的。如果是10-10-12分页就是PDEPTE共同决定。
  2. 不同的CPU这个表的大小不一样。
  3. 只要Cr3变了,TLB立马刷新,一核一套TLB
  如果Cr3改了,TLB刷新重建高2G以上很浪费。所以PDEPTE中有个G标志位,如果G位为1刷新TLB时将不会刷新PDE/PTEG位为1的页,当TLB满了,根据统计信息将不常用的地址废弃,最近最常用的保留。
  TLB有不同的种类,用于不同的缓存目的,它在X86体系里的实际应用最早是从Intel486CPU开始的,在X86体系的CPU里边,一般都设有如下4组TLB
  第一组:缓存一般页表(4K字节页面)的指令页表缓存:Instruction-TLB
  第二组:缓存一般页表(4K字节页面)的数据页表缓存:Data-TLB
  第三组:缓存大尺寸页表(2M/4M字节页面)的指令页表缓存:Instruction-TLB
  第四组:缓存大尺寸页表(2M/4M字节页面)的数据页表缓存:Data-TLB

CPU缓存

  CPU缓存是位于CPU与物理内存之间的临时存储器,它的容量比内存小的多但是交换速度却比内存要快得多。它可以做的很大,但不是TLB,它们有很大的不同。TLB存的是线性地址与物理地址的对应关系,CPU缓存存的是物理地址与内容对应关系。
  更多的细节请参考白皮书的Chapter 11 Memory Cache Control,本篇教程主要是针对内核安全层面,就不再赘述了。

PWT 与 PCD

  PWT全称为Page Write ThroughPWT = 1时,写Cache的时候也要将数据写入内存中。
  PCD全称为Page Cache DisablePCD = 1时,禁止某个页写入缓存,直接写内存。比如,做页表用的页,已经存储在TLB中了,可能不需要再缓存了。

  羽夏看Linux内核——内核加载


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK