Linux mem 1.3 分页寻址(Paging)机制详解

1. X86手册定义

在x86架构下有两种地址转换模式。Intel® 64 and IA-32 architectures software developer’s manual / Volume 3 / Chapter 4:

  • 段寻址(segmentation),将逻辑地址(logical addresses)翻译成线性地址(linear addresses)。
  • 页寻址(Paging),将线性地址(linear addresses)翻译成物理地址(physical address),同时检查检查对地址的访问权限(access rights)和cache类型(memory type)。

Linux下只使用页寻址模式。

1.1 paging modes

在这里插入图片描述

x86支持以上的四种paging模式:

  • 32-bit paging。32位模式,线性地址宽度32bit,物理地址宽度40bit,pagesize支持4k/4M。
  • PAE paging。32位模式,线性地址宽度32bit,物理宽度最大52bit,pagesize支持4k/2M,支持可执行属性的配置。
  • 4-level paging。64位模式,线性地址宽度48bit(需要4级mmu表映射:pgd→pud→pmd→pte),物理宽度最大52bit,pagesize支持4k/2M/1G,支持可执行属性的配置,支持PCIDs和protection key的。
  • 5-level paging。64位模式,线性地址宽度52bit(需要5级mmu表映射:pgd→p4d→pud→pmd→pte),物理宽度最大52bit,pagesize支持4k/2M/1G,支持可执行属性的配置,支持PCIDs和protection key的。

重点属性:

  • execute-disable access rights(可执行权限):可以防止软件从其他可读的页面中获取指令。
  • PCIDs(process-context identifiers)(进程上下文标识符):在4级分页和5级分页模式,软件可以启用一种功能,逻辑处理器可利用该功能为多个线性地址空间缓存信息。 当软件在不同的线性地址空间之间切换时,处理器可以保留缓存的信息。
  • protection keys(保护键):对于4级分页和5级分页,每个线性地址都与一个保护键关联。软件可以使用保护密钥权限寄存器禁用对与该保护密钥相关联的线性地址的某些访问权限。

paging相关的全局寄存器概述:

regbitnamedescript描述
CR0-PGenables paging使能分页功能
CR3--pgd physical addressmmu pgd表的起始物理地址
-----
CR4-PAEpaging modes64bit模式下物理地址宽度32bit/40bit
IA32_EFER-LMEpaging modes32bit/64bit模式
CR4-LA57paging modes64bit模式下线性地址宽度48bit/57bit
---Modifiers-
CR016WPwrite protected只读地址对特权模式写操作的保护
CR44PSEpage size extend32bit模式的页尺寸扩展
CR47PGEpage global enable全局页面
CR417PCIDEprocess-context identifiers进程上下文标识符
CR420SMEPsupervisor-mode fetch protect特权模式fetch用户模式指令保护
CR421SMAPsupervisor-mode access protect特权模式存取用户模式数据保护
CR422PKEprotection keys enable保护键使能
CR423CETcontrol-flow enforcement technology控制流实施和影子堆栈
CR424PKSprotection keys supervisor特权模式保护键使能
IA32_EFER11NXEexecute-disable配置禁止可执行权限

相关属性配置寄存器的含义:

  • CR0.WP允许保护页面免受特权模式(supervisor-mode)写操作。如果CR0.WP = 0,则允许对具有只读访问权限的线性地址进行特权模式写访问;如果CR0.WP = 1,则不是。(无论CR0.WP的值如何,都不允许对具有只读访问权限的线性地址进行用户模式(user-mode)写访问。)第4.6节解释了如何确定访问权限,包括特权模式(supervisor-mode)和用户模式(user-mode)访问的定义。
  • CR4.PSE启用4 MB页面进行32位分页。如果CR4.PSE = 0,则32位分页只能使用4 KB页面;如果CR4.PSE = 1,则32位分页可以同时使用4 KB页面和4 MB页面。有关更多信息,请参见第4.3节。(与CR4.PSE的值无关,PAE分页,4级分页和5级分页可以使用多个页面大小。)
  • CR4.PGE启用全局页面。如果CR4.PGE = 0,则不会在地址空间之间共享任何转换;如果CR4.PGE = 1,则可以在地址空间之间共享指定的转换。有关更多信息,请参见第4.10.2.4节。
  • CR4.PCIDE启用4级分页和5级分页的进程上下文标识符(PCID)。 PCID允许逻辑处理器为多个线性地址空间缓存信息。有关更多信息,请参见第4.10.1节。
  • CR4.SMEP允许保护页面免受特权模式指令的访问。如果CR4.SMEP = 1,则在特权模式下运行的软件无法从用户模式下可访问的线性地址中获取指令。第4.6节说明了如何确定访问权限,包括主管模式访问和用户模式可访问性的定义。
  • CR4.SMAP允许保护页面免受特权模式的数据访问。如果CR4.SMAP = 1,则在特权模式下运行的软件无法访问在用户模式下可以访问的线性地址处的数据。软件可以通过设置EFLAGS.AC来覆盖此保护。第4.6节说明了如何确定访问权限,包括主管模式访问和用户模式可访问性的定义。
  • CR4.PKECR4.PKS允许基于保护密钥指定访问权限。 4级分页和5级分页将每个线性地址与一个保护密钥相关联。当CR4.PKE = 1时,PKRU寄存器为每个保护锁指定是否可以读取或写入带有该保护锁的用户模式线性地址。当CR4.PKS = 1时,IA32_PKRS MSR对特权模式线性地址执行相同的操作。有关更多信息,请参见第4.6节。
  • CR4.CET支持控制流实施技术,包括影子堆栈功能。如果CR4.CET = 1,则某些内存访问被标识为影子堆栈访问,某些线性地址转换为影子堆栈页面。第4.6节说明了如何确定这些访问权限和页面的访问权限。 (仅当还设置了CR0.WP时,处理器才允许设置CR4.CET。)
  • IA32_EFER.NXE为PAE分页/4级分页5级分页模式下启用执行禁用访问权限。如果IA32_EFER.NXE = 1,则可以防止从指定的线性地址进行指令提取(即使允许从该地址读取数据)。第4.6节说明了如何确定访问权限。 (IA32_EFER.NXE对于32位分页无效。要使用此功能限制从可读页中提取指令的软件,必须使用PAE分页,4级分页或5级分页。)

4种分页模式都使用层次化(hierarchical)的分页结构(paging structures)来进行地址转化:
在这里插入图片描述

分页结构(paging structures)中重要的标志:

  • P flag(bit 0)。如果遇到标记为“不存在”的分页结构条目(因为其P标志位0)被清除或保留位被置位,则会发生这种情况。在这种情况下,线性地址没有任何转换。访问该地址会导致页面错误异常(请参见第4.7节)。
  • PS(page size) flag(bit 7)。如果线性地址中剩余的位数超过12位,请查询当前页面结构条目的位7(PS-页面大小)。如果该位为0,则该条目引用另一个分页结构(paging structure);否则为0。如果该位为1,则该条目映射一个页面(page),这种页就是huge page大小为1G/2M/4M。如果线性地址中仅剩余12位,则当前的页面结构条目将始终映射页面(bit 7用于其他目的),这种就是普通的page大小为4k。

1.2 4-LEVEL PAGING5-LEVEL PAGING模式

在64bit模式下,每一级分页结构(paging structure)条目(entry)的大小为8字节,一个page 4k最多容纳的entry数量为512(2^9)个,所以每一级分页结构提供的寻址长度为9bit。最后一级页帧(page frame)的寻址长度为12bit(4k)。

对应64bit模式下的两种分页模式(paging modes):

  • 1、4-LEVEL PAGING。线性地址为48bit (9+9+9+9+12),物理地址为52bit。在linux下的分页结构为:pgd→pud→pmd→pte→page(4k)
  • 2、5-LEVEL PAGING。线性地址为57bit (9+9+9+9+9+12),物理地址为52bit。在linux下的分页结构为:pgd→p4d→pud→pmd→pte→page(4k)

1.2.1 4-LEVEL PAGING

4-LEVEL PAGING下还支持4K/2M/1G几种page模式,下面是其分页结构(paging structure)的层次图:

  • 1、4-LEVEL PAGING 4k-page (pgd→pud→pmd→pte→page(4k))
    在这里插入图片描述

  • 2、4-LEVEL PAGING 2M-page (pgd→pud→pmd→page(2M))
    在这里插入图片描述

  • 3、4-LEVEL PAGING 1G-page (pgd→pud→page(1G))
    在这里插入图片描述

在上述模式下,CR3和分页结构条目(paging structure entry)的格式总览:
在这里插入图片描述

下面小节,阐述格式的详细含义。

1.2.2 CR3 format

两种分页模式都使用使用CR3内容定位的内存中分页结构的层次结构转换线性地址,CR3的内容用于定位第一个分页结构。 对于4级分页,这是PML4表,对于5级分页,它是PML5表。在Linux下都称为PGD(page global directory)。

  • CR4.PCIDE = 0 时的CR3格式
Bit Position(s)ContentsDescript
2:0Ignored-
3(PWT) Page-level write-through; indirectly determines the memory type used to access the PML4 table during linearaddress translation (see Section 4.9.2)page级的write-through属性
4(PCD) Page-level cache disable; indirectly determines the memory type used to access the PML4 table during linear-address translation (see Section 4.9.2)page级的cache disable属性
11:5Ignored-
M–1:12Physical address of the 4-KByte aligned PML4 table or PML5 table used for linear-address translation14k对齐的PML4/PML5物理地址
63:MReserved (must be 0)M为物理地址,最大为52bit
  • CR4.PCIDE = 1 时的CR3格式
Bit Position(s)ContentsDescript
11:0PCID (see Section 4.10.1)-
M–1:12Physical address of the 4-KByte aligned PML4 table or PML5 table used for linear-address translation14k对齐的PML4/PML5物理地址
63:MReserved (must be 0)M为物理地址,最大为52bit

1.2.3 PML5/PGD entry format

x86下的PML5对应linux下的PGD(page global directory):

Bit Position(s)ContentsDescript
0 (P)Present; must be 1 to reference a PML4 table为1指向一个PML4 table
1 (R/W)Read/write; if 0, writes may not be allowed to the 256-TByte region controlled by this entry (see Section 4.6)256T区域的写权限控制
2 (U/S)User/supervisor; if 0, user-mode accesses are not allowed to the 256-TByte region controlled by this entry (see Section 4.6)256T区域的用户模式访问允许
3 (PWT)Page-level write-through; indirectly determines the memory type used to access the PML4 table referenced by this entry (see Section 4.9.2)page级的write-through属性
4 (PCD)Page-level cache disable; indirectly determines the memory type used to access the PML4 table referenced by this entry (see Section 4.9.2)page级的cache disable属性
5 (A)Accessed; indicates whether this entry has been used for linear-address translation (see Section 4.8)指示当前entry是否用过做地址转换
6Ignored-
7 (PS)Reserved (must be 0)-
11:8Ignored-
M–1:12Physical address of 4-KByte aligned PML4 table referenced by this entry4k对齐的PML4物理地址
51:MReserved (must be 0)-
62:52Ignored-
63 (XD)If IA32_EFER.NXE = 1, execute-disable (if 1, instruction fetches are not allowed from the 256-TByte region controlled by this entry; see Section 4.6); otherwise, reserved (must be 0)如果IA32_EFER.NXE = 1,256T区域的可执行权限disable配置

1.2.4 PML4/P4D entry format

x86下的PML4对应linux下的P4D(page four directory):

Bit Position(s)ContentsDescript
0 (P)Present; must be 1 to reference a page-directory-pointer table为1指向一个PDPT
1 (R/W)Read/write; if 0, writes may not be allowed to the 512-GByte region controlled by this entry (see Section 4.6)512G区域的写权限控制
2 (U/S)User/supervisor; if 0, user-mode accesses are not allowed to the 512-GByte region controlled by this entry (see Section 4.6)512G区域的用户模式访问允许
3 (PWT)Page-level write-through; indirectly determines the memory type used to access the page-directory-pointer table referenced by this entry (see Section 4.9.2)page级的write-through属性
4 (PCD)Page-level cache disable; indirectly determines the memory type used to access the page-directory-pointer table referenced by this entry (see Section 4.9.2)page级的cache disable属性
5 (A)Accessed; indicates whether this entry has been used for linear-address translation (see Section 4.8)指示当前entry是否用过做地址转换
6Ignored-
7 (PS)Reserved (must be 0)-
11:8Ignored-
M–1:12Physical address of 4-KByte aligned page-directory-pointer table referenced by this entry4k对齐的PDPT物理地址
51:MReserved (must be 0)-
62:52Ignored-
63 (XD)If IA32_EFER.NXE = 1, execute-disable (if 1, instruction fetches are not allowed from the 512-GByte region controlled by this entry; see Section 4.6); otherwise, reserved (must be 0)如果IA32_EFER.NXE = 1,512G区域的可执行权限disable配置

1.2.5 PDPT/PUD entry format (1-GByte Page)

x86下的PDPT(Page-Directory-Pointer-Table)对应linux下的PUD(page upper directory):

Bit Position(s)ContentsDescript
0 (P)Present; must be 1 to map a 1-GByte page映射1G page必须设置为1
1 (R/W)Read/write; if 0, writes may not be allowed to the 1-GByte page referenced by this entry (see Section 4.6)1G page的写权限控制
2 (U/S)User/supervisor; if 0, user-mode accesses are not allowed to the 1-GByte page referenced by this entry (see Section 4.6)1G page的用户模式访问允许
3 (PWT)Page-level write-through; indirectly determines the memory type used to access the 1-GByte page referenced by this entry (see Section 4.9.2)page级的write-through属性
4 (PCD)Page-level cache disable; indirectly determines the memory type used to access the 1-GByte page referenced by this entry (see Section 4.9.2)page级的cache disable属性
5 (A)Accessed; indicates whether software has accessed the 1-GByte page referenced by this entry (see Section 4.8)指示软件是否访问过当前entry对应的1G page区域
6 (D)Dirty; indicates whether software has written to the 1-GByte page referenced by this entry (see Section 4.8)指示软件是否写入过当前entry对应的1G page区域
7 (PS)Page size; must be 1 (otherwise, this entry references a page directory; see Table 4-17)必须置1
8 (G)Global; if CR4.PGE = 1, determines whether the translation is global (see Section 4.10); ignored otherwise如果CR4.PGE = 1,定义地址转换是否是全局的
11:9Ignored-
12 (PAT)Indirectly determines the memory type used to access the 1-GByte page referenced by this entry (see Section 4.9.2)1间接决定1G page的memory type
29:13Reserved (must be 0)-
(M–1):30Physical address of the 1-GByte page referenced by this entry1G对齐的page物理地址
51:MReserved (must be 0)-
58:52Ignored-
62:59Protection key if CR4.PKE = 1 or CR4.PKS = 1, this may control the page’s access rights (see Section 4.6.2); otherwise, it is not used to control access rights.Protection key,如果CR4.PKE = 1 or CR4.PKS = 1,控制page的访问权限
63 (XD)If IA32_EFER.NXE = 1, execute-disable (if 1, instruction fetches are not allowed from the 1-GByte page controlled by this entry; see Section 4.6); otherwise, reserved (must be 0)如果IA32_EFER.NXE = 1,1G page的可执行权限disable配置

1.2.6 PDPT/PUD entry format (Page Directory)

x86下的PDPT(Page-Directory-Pointer-Table)对应linux下的PUD(page upper directory):

Bit Position(s)ContentsDescript
0 (P)Present; must be 1 to reference a page directory为1指向一个PD
1 (R/W)Read/write; if 0, writes may not be allowed to the 1-GByte region controlled by this entry (see Section 4.6)1G区域的写权限控制
2 (U/S)User/supervisor; if 0, user-mode accesses are not allowed to the 1-GByte region controlled by this entry (see Section 4.6)1G区域的用户模式访问允许
3 (PWT)Page-level write-through; indirectly determines the memory type used to access the page directory referenced by this entry (see Section 4.9.2)page级的write-through属性
4 (PCD)Page-level cache disable; indirectly determines the memory type used to access the page directory referenced by this entry (see Section 4.9.2)page级的cache disable属性
5 (A)Accessed; indicates whether this entry has been used for linear-address translation (see Section 4.8)指示当前entry是否用过做地址转换
6Ignored-
7 (PS)Page size; must be 0 (otherwise, this entry maps a 1-GByte page; see Table 4-16)必须置0
11:8Ignored-
(M–1):12Physical address of 4-KByte aligned page directory referenced by this entry4k对齐的PD物理地址
51:MReserved (must be 0)-
62:52Ignored-
63 (XD)If IA32_EFER.NXE = 1, execute-disable (if 1, instruction fetches are not allowed from the 1-GByte region controlled by this entry; see Section 4.6); otherwise, reserved (must be 0)如果IA32_EFER.NXE = 1,1G区域的可执行权限disable配置

1.2.7 PD/PMD entry format (2-MByte Page)

x86下的PD(Page-Directory)对应linux下的PMD(page middle directory):

Bit Position(s)ContentsDescript
0 (P)Present; must be 1 to map a 2-MByte page映射2M page必须设置为1
1 (R/W)Read/write; if 0, writes may not be allowed to the 2-MByte page referenced by this entry (see Section 4.6)2M page的写权限控制
2 (U/S)User/supervisor; if 0, user-mode accesses are not allowed to the 2-MByte page referenced by this entry (see Section 4.6)2M page的用户模式访问允许
3 (PWT)Page-level write-through; indirectly determines the memory type used to access the 2-MByte page referenced by this entry (see Section 4.9.2)page级的write-through属性
4 (PCD)Page-level cache disable; indirectly determines the memory type used to access the 2-MByte page referenced by this entry (see Section 4.9.2)page级的cache disable属性
5 (A)Accessed; indicates whether software has accessed the 2-MByte page referenced by this entry (see Section 4.8)指示软件是否访问过当前entry对应的2M page区域
6 (D)Dirty; indicates whether software has written to the 2-MByte page referenced by this entry (see Section 4.8)指示软件是否写入过当前entry对应的2M page区域
7 (PS)Page size; must be 1 (otherwise, this entry references a page table; see Table 4-19)必须置1
8 (G)Global; if CR4.PGE = 1, determines whether the translation is global (see Section 4.10); ignored otherwise如果CR4.PGE = 1,定义地址转换是否是全局的
11:9Ignored-
12(PAT) Indirectly determines the memory type used to access the 2-MByte page referenced by this entry (see Section 4.9.2)间接决定2M page的memory type
20:13Reserved (must be 0)-
(M–1):21Physical address of the 2-MByte page referenced by this entry2M对齐的page物理地址
51:MReserved (must be 0)-
58:52Ignored-
62:59Protection key if CR4.PKE = 1 or CR4.PKS = 1, this may control the page’s access rights (see Section 4.6.2); otherwise, it is not used to control access rights.Protection key,如果CR4.PKE = 1 or CR4.PKS = 1,控制page的访问权限
63 (XD)If IA32_EFER.NXE = 1, execute-disable (if 1, instruction fetches are not allowed from the 2-MByte page controlled by this entry; see Section 4.6); otherwise, reserved (must be 0)如果IA32_EFER.NXE = 1,2M page的可执行权限disable配置

1.2.8 PD/PMD entry format (Page Directory)

x86下的PD(Page-Directory)对应linux下的PMD(page middle directory):

Bit Position(s)ContentsDescript
0 (P)Present; must be 1 to reference a page table为1指向一个PT
1 (R/W)Read/write; if 0, writes may not be allowed to the 2-MByte region controlled by this entry (see Section 4.6)2M区域的写权限控制
2 (U/S)User/supervisor; if 0, user-mode accesses are not allowed to the 2-MByte region controlled by this entry (see Section 4.6)2M区域的用户模式访问允许
3 (PWT)Page-level write-through; indirectly determines the memory type used to access the page table referenced by this entry (see Section 4.9.2)page级的write-through属性
4 (PCD)Page-level cache disable; indirectly determines the memory type used to access the page table referenced by this entry (see Section 4.9.2)page级的cache disable属性
5 (A)Accessed; indicates whether this entry has been used for linear-address translation (see Section 4.8)指示当前entry是否用过做地址转换
6Ignored-
7 (PS)Page size; must be 0 (otherwise, this entry maps a 2-MByte page; see Table 4-18)必须置0
11:8Ignored-
(M–1):12Physical address of 4-KByte aligned page table referenced by this entry2M对齐的PD物理地址
51:MReserved (must be 0)-
62:52Ignored-
63 (XD)If IA32_EFER.NXE = 1, execute-disable (if 1, instruction fetches are not allowed from the 2-MByte region controlled by this entry; see Section 4.6); otherwise, reserved (must be 0)如果IA32_EFER.NXE = 1,2M区域的可执行权限disable配置

1.2.9 PTE format (4-KByte Page)

在x86和linux下都称为PTE(Page-Table Entry):

Bit Position(s)ContentsDescript
0 (P)Present; must be 1 to map a 4-KByte page映射4k page必须设置为1
1 (R/W)Read/write; if 0, writes may not be allowed to the 4-KByte page referenced by this entry (see Section 4.6)4k page的写权限控制
2 (U/S)User/supervisor; if 0, user-mode accesses are not allowed to the 4-KByte page referenced by this entry (see Section 4.6)4k page的用户模式访问允许
3 (PWT)Page-level write-through; indirectly determines the memory type used to access the 4-KByte page referenced by this entry (see Section 4.9.2)page级的write-through属性
4 (PCD)Page-level cache disable; indirectly determines the memory type used to access the 4-KByte page referenced by this entry (see Section 4.9.2)page级的cache disable属性
5 (A)Accessed; indicates whether software has accessed the 4-KByte page referenced by this entry (see Section 4.8)指示软件是否访问过当前entry对应的4k page区域
6 (D)Dirty; indicates whether software has written to the 4-KByte page referenced by this entry (see Section 4.8)指示软件是否写入过当前entry对应的4k page区域
7 (PAT)Indirectly determines the memory type used to access the 4-KByte page referenced by this entry (see Section 4.9.2)间接决定4k page的memory type
8 (G)Global; if CR4.PGE = 1, determines whether the translation is global (see Section 4.10); ignored otherwise如果CR4.PGE = 1,定义地址转换是否是全局的
11:9Ignored-
(M–1):12Physical address of the 4-KByte page referenced by this entry4k对齐的page物理地址
51:MReserved (must be 0)-
58:52Ignored-
62:59Protection key if CR4.PKE = 1 or CR4.PKS = 1, this may control the page’s access rights (see Section 4.6.2); otherwise, it is not used to control access rights.Protection key,如果CR4.PKE = 1 or CR4.PKS = 1,控制page的访问权限
63 (XD)If IA32_EFER.NXE = 1, execute-disable (if 1, instruction fetches are not allowed from the 4-KByte page controlled by this entry; see Section 4.6); otherwise, reserved (must be 0)如果IA32_EFER.NXE = 1,4k page的可执行权限disable配置

1.3 Access Right

1.3.1 访问模式

1、访问模式

对线性地址的每次访问分为特权模式访问(supervisor-mode access)和用户模式访问(user-mode access)。对于所有指令获取(instruction fetches)和大多数数据访问(data accesses),此区别由当前特权级别(CPL)确定:

  • CPL < 3,时进行的访问是特权模式访问(supervisor-mode access)
  • CPL = 3,时进行的访问是用户模式访问(user-mode access)

段选择子中最低2bit叫做RPL。作为段选择子的时候,cs和ss比较特殊,它们的RPL代表着当前进程的特权级,因此,二者的RPL又叫CPL。
在这里插入图片描述

隐式访问:一些操作隐式访问具有线性地址的系统数据结构;无论CPL如何,对这些数据结构的最终访问都是特权模式访问。这种访问的示例包括以下内容:访问全局描述符表(GDT)或本地描述符表(LDT)以加载段描述符;提供中断或异常时访问中断描述符表(IDT);并作为任务切换或CPL更改的一部分访问任务状态段(TSS)。无论CPL如何,所有这些访问都称为隐式特权模式访问。 CPL<3 时进行的其他访问称为显式特权模式访问。

2、地址模式:

如控制线性地址转换的页面结构条目所指定的那样。如果在至少一个分页结构项中,U/S标志(bit2)为0,则该地址为特权模式地址(supervisor-mode address)。否则,该地址是用户模式地址(user-mode address)。

3、影子堆栈:

启用控制流实施技术(CET)的影子堆栈功能后,对线性地址的某些访问将被视为影子堆栈访问(请参阅英特尔®64和IA-32体系结构软件开发人员的第18.2节“影子堆栈”)。手册,第1卷)。与普通数据访问一样,每个影子堆栈访问都被定义为用户访问或特权访问。通常,如果CPL = 3,则影子堆栈访问是用户访问;如果CPL ❤️,则是特权访问。WRUSS指令是一个例外。尽管只能在CPL = 0时执行,但是处理器会将其影子堆栈访问视为用户访问。

影子堆栈访问仅允许访问影子堆栈地址。如果线性地址的转换符合以下条件,则线性地址是影子堆栈地址:

  • (1)在映射包含线性地址的分页结构条目中,R/W标志(bit1)为0,D脏标志(bit6)为1;
  • (2)在控制线性地址转换的所有其他分页结构条目中,R/W标志为1。

1.3.2 访问权限(access rights)

权限配置寄存器的含义:

  • CR0.WP允许保护页面免受特权模式(supervisor-mode)写操作。如果CR0.WP = 0,则允许对具有只读访问权限的线性地址进行特权模式写访问;如果CR0.WP = 1,则不是。(无论CR0.WP的值如何,都不允许对具有只读访问权限的线性地址进行用户模式(user-mode)写访问。)第4.6节解释了如何确定访问权限,包括特权模式(supervisor-mode)和用户模式(user-mode)访问的定义。
  • CR4.SMEP允许保护页面免受特权模式指令的访问。如果CR4.SMEP = 1,则在特权模式下运行的软件无法从用户模式下可访问的线性地址中获取指令。第4.6节说明了如何确定访问权限,包括主管模式访问和用户模式可访问性的定义。
  • CR4.SMAP允许保护页面免受特权模式的数据访问。如果CR4.SMAP = 1,则在特权模式下运行的软件无法访问在用户模式下可以访问的线性地址处的数据。软件可以通过设置EFLAGS.AC来覆盖此保护。第4.6节说明了如何确定访问权限,包括主管模式访问和用户模式可访问性的定义。
  • CR4.PKECR4.PKS允许基于保护密钥指定访问权限。 4级分页和5级分页将每个线性地址与一个保护密钥相关联。当CR4.PKE = 1时,PKRU寄存器为每个保护锁指定是否可以读取或写入带有该保护锁的用户模式线性地址。当CR4.PKS = 1时,IA32_PKRS MSR对特权模式线性地址执行相同的操作。有关更多信息,请参见第4.6节。
  • IA32_EFER.NXE为PAE分页/4级分页5级分页模式下启用执行禁用访问权限。如果IA32_EFER.NXE = 1,则可以防止从指定的线性地址进行指令提取(即使允许从该地址读取数据)。第4.6节说明了如何确定访问权限。 (IA32_EFER.NXE对于32位分页无效。要使用此功能限制从可读页中提取指令的软件,必须使用PAE分页,4级分页或5级分页。)

1、特权模式下的数据访问(supervisor-mode data access):

CPU ModeAcessU/S targetR/WCR0.WPCR4.SMAPEFLAGS.ACexplicit/implicitprotection keyAccess rights
supervisor-mode(cpl<3)data reads0 (supervisor-mode address)-----read permittedread
1 (user-mode address)--0--read permittedread
--11explicitread permittedread
--10--not read
--1-implicit-not read
data writes0 (supervisor-mode address)-0---write permittedwrite
1 (W)1---write permittedwrite
0 ®1----not write
1 (user-mode address)-00--write permittedwrite
-011explicitwrite permittedwrite
-010--not write
-01-implicit-not write
1 (W)10--write permittedwrite
0 ®1----not write
1 (W)111explicitwrite permittedwrite
1 (W)110--not write
1 (W)11-implicit-not write

注:表格里的向上箭头表示同上的意思。

2、特权模式下的指令预取(supervisor-mode instruction fetches):

CPU ModeAcessU/S targetCR4.SMEPIA32_EFER.NXEXDAccess rights
supervisor-mode(cpl<3)instruction fetches0 (supervisor-mode address)-0-fetch
-10fetch
-11not fetch
1 (user-mode address)00-fetch
010fetch
011not fetch
1--not fetch

3、特权模式下的影子堆栈访问(supervisor-mode shadow-stack accesses):

超级用户模式影子堆栈访问仅允许访问超级用户模式影子堆栈地址(请参见上文)。

4、用户模式下的数据访问(user-mode data access)

CPU ModeAcessU/S targetR/Wprotection keyAccess rights
user-mode(cpl=3)data reads0 (supervisor-mode address)--not read
1 (user-mode address)-read permittedread
data writes0 (supervisor-mode address)--not write
1 (user-mode address)1 (W)write permittedwrite

5、用户模式下的指令预取(user-modeinstruction fetches)

CPU ModeAcessU/S targetIA32_EFER.NXEXDAccess rights
user-mode(cpl=3)instruction fetches0 (supervisor-mode address)--not fetch
1 (user-mode address)0-fetch
10fetch
11not fetch

6、用户模式下的影子堆栈访问(user-mode shadow-stack accesses):

在enclave模式之外进行的用户模式阴影堆栈访问仅允许对用户模式阴影堆栈地址进行访问(见上文)。在enclave模式下进行的用户模式阴影堆栈访问被视为普通数据访问(见上文)。

处理器可以在TLB分页结构缓存(paging-structure caches)中缓存来自分页结构条目的信息(请参阅第4.10节)。 这些结构可能包括有关访问权限的信息。 处理器可以基于TLB和分页结构缓存而不是基于内存中的分页结构来强制执行访问权限。

这个事实意味着,如果软件修改了分页结构条目以更改访问权限,则处理器可能不会将该更改用于对受影响的线性地址的后续访问(请参阅第4.10.4.3节)。 有关软件如何确保处理器使用修改后的访问权限的信息,请参见第4.10.4.2节。

1.3.2 Protection Keys

1、protection key (index)

4级分页和5级分页将4位保护密钥与每个线性地址相关联(该保护密钥位于映射包含线性地址的页面的分页结构项的位62:59中;请参见第4.5节)。

PTE中使用4bit来表示protection key,就是一个index(最大为2^4即16),使用这个index在PKRU或者IA32_PKRS MSR中查询当前的读写权限。

2、PKRU/IA32_PKRS MSR

PKRU寄存器和IA32_PKRS MSR寄存器(MSR的位63:32被保留,并且必须
为零)的格式:

for each i (0 ≤ i ≤ 15){
    PKRU[2i] is the access-disable bit for protection key i (ADi); 
    PKRU[2i+1] is the write-disable bit for protection key i (WDi). 
}

软件可以使用ECX = 0的RDPKRU和WRPKRU指令来读取和写入PKRU。 此外,PKRU寄存器处于XSAVE管理状态,因此可以通过XSAVE功能集中的指令进行读写。请参阅英特尔®64和IA-32体系结构软件的第13章“使用XSAVE功能集管理状态”。 开发人员手册,第1卷,有关XSAVE功能集的更多信息。

软件可以使用RDMSR和WRMSR指令来读取和写入IA32_PKRS MSR。 使用WRMSR写入IA32_PKRS MSR不会序列化。 IA32_PKRS MSR不受XSAVE管理。

3、CR4.PKE/CR4.PKS

以下两个保护密钥功能根据其保护密钥来控制对线性地址的访问:

regfunction
CR4.PKE = 1则PKRU寄存器针对每个保护密钥确定是否可以读取或写入带有该保护密钥的用户模式地址(user-mode addresses)。
CR4.PKS = 1则IA32_PKRS MSR(MSR索引6E1H)为每个保护密钥确定是否可以读取或写入具有该保护密钥的超级用户模式地址(supervisor-mode addresses)。

4、access rights

线性地址的保护键仅控制对地址的数据访问(data accesses)。它不影响从地址指令获取(instructions fetches)。

用户模式地址(user-mode address)的配置:

i (protection key index)U/S targetCPU ModeCR4.PKEPKRU.ADiPKRU.WDiCR0.WPAccess rights
PTE(bits 62:59)1 (user-mode address)-0---reads writes
-11--not access
user-mode(cpl=3)101-not writes
supervisor-mode(cpl<3)1011not writes
1010reads writes

特权模式地址(supervisor-mode address)的配置:

i (protection key index)U/S targetCPU ModeCR4.PKSIA32_PKRS MSR.ADiIA32_PKRS MSR.WDiCR0.WPAccess rights
PTE(bits 62:59)0 (supervisor-mode address)supervisor-mode(cpl<3)0---reads writes
11--not access
1011not writes
1010reads writes

1.4 PAGE-FAULT EXCEPTIONS

使用线性地址的访问可能会导致页面错误异常(#PF;exception 14)。出于以下两个原因之一,访问线性地址可能会导致页面错误异常:

  • (1)线性地址没有转换;
  • (2)线性地址有转换,但其访问权限不允许访问。

如第4.3节,第4.4.2节和第4.5节所述,如果线性地址的转换过程将分页结构项中的P标志(位0)设置为0 或者将保留位设置为1,则该线性地址没有转换。如果存在线性地址的转换,则其访问权限按照4.6节中的规定确定。

启用英特尔®软件保护扩展(英特尔®SGX)后,处理器可能会由于与分页无关的原因而传递异常14。请参阅第37章“安全区访问控制和数据结构”中的第37.3节“访问控制要求”和第37.20节“安全区页面缓存映射(EPCM)”。这种异常称为SGX引发的页面错误。处理器使用错误代码将SGX引发的页面错误与普通页面错误区分开。

以下是page-fault exception的错误码:
在这里插入图片描述

err codebitvaldescript
P00故障是由于页面不存在引起的。分页结构条目中的P标志为0。
P01故障是由其他页面级保护冲突引起的。
W/R10导致故障的访问是读取。
W/R11导致故障的访问是写操作。
U/S20特权模式访问导致了故障。
U/S21用户模式访问导致故障。
RSVD30该错误不是由保留位冲突引起的。
RSVD31该故障是由某些寻呼结构条目中的保留位设置为1引起的。分页结构条目中保留的位保留用于将来的功能。
I/D40该错误不是由指令提取引起的。
I/D41故障是由指令提取引起的。
PK50故障不是由保护键引起的。
PK51发生了保护密钥冲突。
SS60故障不是由影子堆栈访问引起的。
SS61故障是由影子堆栈访问引起的。
SGX150故障与SGX无关。
SGX151该故障是由于违反SGX特定的访问控制要求引起的。

1.5 ACCESSED AND DIRTY FLAGS

提供了两个标志给内存管理软件使用,用来管理page和paging structures进出物理内存:

flagsbitdescriptevent
A Accessed5对于线性地址转换期间使用的任何分页结构条目,bit5是已访问标志。每当处理器使用分页结构条目作为线性地址转换的一部分时,它都会在该条目中设置访问标志(如果尚未设置)。
D Dirty6对于映射页面的分页结构项(与引用另一个分页结构相对),bit6是脏标志。只要有对线性地址的写操作,处理器就会在分页结构条目中设置脏标志(如果尚未设置),该脏标志标识线性地址的最终物理地址(PTE或分页结构条目,其中PS标志为1)。

注:如果一个逻辑处理器上的软件写入页面,而另一逻辑处理器上的软件同时清除映射该页面的页面结构条目中的R / W标志,则在某些处理器上执行可能会导致该条目的脏标志被设置(由于(由于更新了第二逻辑处理器上的条目),并且清除了条目的R / W标志。在支持控制流实施技术(CET)的处理器上永远不会发生这种情况。具体来说,支持CET的处理器将永远不会在R / W标志被清除的页面结构条目中设置脏标志。

当页面或分页结构最初加载到物理内存中时,内存管理软件可能会清除这些标志。这些标志是“粘滞的(sticky)”,这意味着一旦置位,处理器就不会清除它们。只有软件可以清除它们。

处理器可以在TLB和分页结构缓存中缓存来自分页结构条目的信息(请参阅第4.10节)。这个事实意味着,如果软件将访问标志或脏标志从1更改为0,则在后续访问中,处理器可能不会使用受影响的线性地址来设置存储器中的相应位(请参阅第4.10.4.3节)。有关如何确保软件按需更新这些位的信息,请参见第4.10.4.2节。

注:处理器用来设置这些标志的访问可能会或可能不会暴露给处理器的自修改代码检测逻辑。如果处理器正在从用于分页结构的同一存储区中执行代码,则这些标志的设置可能会或可能不会立即更改正在执行的代码流。

1.6 MEMORY TYPING

内存访问的内存类型是指用于该访问的缓存类型。 第11章“内存缓存控制”提供了有关Intel-64和IA-32体系结构中的内存类型的许多详细信息。 本节介绍分页如何有助于确定内存类型。

分页促成内存类型的方式取决于处理器是否支持页面属性表(PAT;请参见第11.12节)。1第4.9.1节和第4.9.2节说明了分页如何根据PAT是否为内存类型做出了贡献 支持的。

1.6.1 PAT is Not Supported

注:支持4级分页或5级分页的所有处理器均支持PAT。 因此,本节仅适用于32位分页和PAE分页。

如果不支持PAT,则分页会与11.5.2.1节中的表11-6中指定的存储器类型范围寄存器(MTRR)一起促进存储器类型。
对于对物理地址的任何访问,该表将MTRR为该物理地址指定的内存类型与PCD值和PWT值组合在一起。 后两个值确定如下:

Access TargetPCD PWT come from
PDE with 32-bit pagingCR3
PDE with PAE pagingrelevant PDPTE
PTErelevant PDE
physical addressrelevant PTE or PDE

对于PAE分页,在加载PDPTE时使用UC存储器类型(请参见第4.4.1节)。

1.6.2 PAT is Supported

如果支持PAT,则分页结合PAT和11.5.2.2节中的表11-7中指定的存储器类型范围寄存器(MTRR)来确定存储器类型(memory typing)。

PAT是一个64bit的MSR寄存器(IA32_PAT; MSR index 277H),由8个8bit的条目组成(每个条目组成的bit为:8i+7:8i)。

对于对物理地址的任何访问,该表将MTRR为该物理地址指定的内存类型与从PAT中选择的内存类型进行组合。 11.12.3节中的表11-11指定了如何从PAT中选择一种存储器类型。具体来说,它来自PAT的条目i,其中i定义如下:

Access Targetcond 1iPAT PCD PWT come from
top paging structure(PML4/PML5 )CR4.PCIDE = 10-
top paging structure(PML4/PML5 )CR4.PCIDE = 02*PCD+PWTCR3
PDE with PAE paging-2*PCD+PWTPDPTE
paging-structure entry X whose address is in another paging-structure entry Y-2*PCD+PWTY
physical address-4PAT+2PCD+PWTPTE (if the translation uses a 4-KByte page), the relevant PDE (if the translation uses a 2-MByte page or a 4-MByte page), or the relevant PDPTE (if the translation uses a 1-GByte page)

对于PAE分页,在加载PDPTE时使用WB存储器类型(请参见第4.4.1节)。

1.6.3 Caching

处理器可以在TLB和分页结构缓存中缓存来自分页结构条目的信息(请参阅第4.10节)。 这些结构可能包括有关内存类型的信息。 处理器可以使用来自TLB和分页结构缓存的内存类型信息,而不是使用内存中的分页结构。

这个事实意味着,如果软件修改了分页结构条目以更改存储器类型位,则处理器可能不会将该更改用于使用该条目的后续转换或访问受影响的线性地址。 有关软件如何确保处理器使用修改的内存类型的信息,请参见第4.10.4.2节。

1.7 CACHING TRANSLATION INFORMATION

Intel-64和IA-32架构可以通过缓存处理器中分页结构中的数据来加速地址转换过程。因为处理器不能确保其缓存的数据始终与内存中的结构保持一致,所以对于软件开发人员而言,了解处理器如何以及何时可以缓存此类数据非常重要。他们还应该了解软件可以采取什么措施来删除可能不一致的缓存数据,以及何时删除。本部分向软件开发人员提供有关处理器相关操作的信息。

第4.10.1节介绍了过程上下文标识符(PCID),逻辑处理器可使用该标识符来区分为不同线性地址空间缓存的信息。

第4.10.2节和第4.10.3节分别描述了处理器如何在转换后备缓冲区(TLB)和分页结构缓存中缓存信息。

第4.10.4节说明了软件如何通过使TLB和分页结构缓存的某些部分无效来删除不一致的缓存信息。第4.10.5节介绍了多处理器系统的特殊注意事项。

1.7.1 Process-Context Identifiers (PCIDs)

进程上下文标识符(PCID)是一种工具,逻辑处理器可通过该工具缓存多个线性地址空间的信息。当软件切换到具有不同PCID的不同线性地址空间时(例如,通过加载CR3;有关详细信息,请参阅第4.10.4.1节),处理器可以保留缓存的信息。

PCID是12位标识符。通过设置CR4的PCIDE标志(位17)来启用非零PCID。如果CR4.PCIDE = 0,则当前PCID始终为000H;否则,当前PCID是CR3的位11:0的值。并非所有处理器都允许将CR4.PCIDE设置为1。有关如何确定是否允许的信息,请参见第4.1.4节。

处理器确保CR4.PCIDE仅在IA-32e模式下可以为1(因此,32位分页和PAE分页仅使用PCID 000H)。另外,仅当CR3 [11:0] = 000H时,软件才能将CR4.PCIDE从0更改为1。这些要求是通过对MOV CR指令的以下限制来强制执行的:

•如果将CR4.PCIDE从0更改为1,并且IA32_EFER.LMA = 0或CR3 [11:0]≠000H,则MOV到CR4会导致通用保护异常(#GP)。
•如果在CR4.PCIDE = 1时将CR0.PG清除为0,则MOV to CR0会导致一般保护异常。

当逻辑处理器在TLB(4.10.2节)和分页结构缓存(4.10.3节)中创建条目时,它将这些条目与当前PCID关联。当使用TLB和分页结构高速缓存中的条目转换线性地址时,逻辑处理器仅使用与当前PCID相关联的那些条目(有关异常,请参见4.10.2.4节)。

如果CR4.PCIDE = 0,则逻辑处理器不会为000H以外的任何PCID缓存信息。这是因为(1)如果CR4.PCIDE = 0,则逻辑处理器会将任何新缓存的信息与当前PCID 000H关联; (2)如果到CR4的MOV清除了CR4.PCIDE,则所有缓存的信息都将失效(请参阅第4.10.4.1节)。

注意:在没有处理器允许将CR4.PCIDE设置为1时产生的本手册修订版中,第4.10节讨论了在不参考PCID的情况下缓存转换信息的情况。虽然本节现在在其缓存规范中引用了PCID,但是此文档更改无意暗示对不允许将CR4.PCIDE设置为1的处理器行为的任何更改。

1.7.2 Translation Lookaside Buffers (TLBs)

处理器可以将关于线性地址的转换的信息缓存在转换后备缓冲器(TLB)中。 通常,TLB包含将页码映射到页框的条目。 这些术语在第4.10.2.1节中定义。 第4.10.2.2节描述了如何在TLB中缓存信息,第4.10.2.3节提供了TLB使用的详细信息。 第4.10.2.4节介绍了全局页面功能,该功能允许软件指示某些翻译在缓存到TLB中时应接受特殊处理。

  • Global Pages

当CR4中的PGE标志(位7)为1时,Intel-64和IA-32架构也允许全局页面。 如果映射页面的分页结构条目(PTE或PS标志为1的分页结构条目)中的G标志(位8)为1,则使用该分页为线性地址缓存的任何TLB条目 结构输入被认为是全局的。

因为G标志仅在映射页面的分页结构条目中使用,并且由于此类条目中的信息未缓存在分页结构缓存中,所以全局页面功能不会影响分页结构缓存的行为。

即使TLB条目与不同于当前PCID的PCID关联,逻辑处理器也可以使用全局TLB条目来转换线性地址。

1.7.3 Paging-Structure Caches

除了TLB之外,处理器还可以在内存中缓存有关分页结构的其他信息。

2. 代码解析

2.1 Paging的创建

在page_fault的处理函数__handle_mm_fault()中,有详细的paging创建的详细过程。大概的原理还是根据映射层次结构(pgd→p4d→pud→pmd→pte)逐个创建,paging structure的空间通过__get_free_pages()获取page来进行构建。

static int __handle_mm_fault(struct vm_area_struct *vma, unsigned long address,
		unsigned int flags)
{
	struct vm_fault vmf = {
		.vma = vma,
		.address = address & PAGE_MASK,
		.flags = flags,
		.pgoff = linear_page_index(vma, address),
		.gfp_mask = __get_fault_gfp_mask(vma),
	};
	unsigned int dirty = flags & FAULT_FLAG_WRITE;
	struct mm_struct *mm = vma->vm_mm;
	pgd_t *pgd;
	p4d_t *p4d;
	int ret;

	pgd = pgd_offset(mm, address);
	p4d = p4d_alloc(mm, pgd, address);
	if (!p4d)
		return VM_FAULT_OOM;

	vmf.pud = pud_alloc(mm, p4d, address);
	if (!vmf.pud)
		return VM_FAULT_OOM;
	if (pud_none(*vmf.pud) && transparent_hugepage_enabled(vma)) {
		ret = create_huge_pud(&vmf);
		if (!(ret & VM_FAULT_FALLBACK))
			return ret;
	} else {
		pud_t orig_pud = *vmf.pud;

		barrier();
		if (pud_trans_huge(orig_pud) || pud_devmap(orig_pud)) {

			/* NUMA case for anonymous PUDs would go here */

			if (dirty && !pud_write(orig_pud)) {
				ret = wp_huge_pud(&vmf, orig_pud);
				if (!(ret & VM_FAULT_FALLBACK))
					return ret;
			} else {
				huge_pud_set_accessed(&vmf, orig_pud);
				return 0;
			}
		}
	}

	vmf.pmd = pmd_alloc(mm, vmf.pud, address);
	if (!vmf.pmd)
		return VM_FAULT_OOM;
	if (pmd_none(*vmf.pmd) && transparent_hugepage_enabled(vma)) {
		ret = create_huge_pmd(&vmf);
		if (!(ret & VM_FAULT_FALLBACK))
			return ret;
	} else {
		pmd_t orig_pmd = *vmf.pmd;

		barrier();
		if (unlikely(is_swap_pmd(orig_pmd))) {
			VM_BUG_ON(thp_migration_supported() &&
					  !is_pmd_migration_entry(orig_pmd));
			if (is_pmd_migration_entry(orig_pmd))
				pmd_migration_entry_wait(mm, vmf.pmd);
			return 0;
		}
		if (pmd_trans_huge(orig_pmd) || pmd_devmap(orig_pmd)) {
			if (pmd_protnone(orig_pmd) && vma_is_accessible(vma))
				return do_huge_pmd_numa_page(&vmf, orig_pmd);

			if (dirty && !pmd_write(orig_pmd)) {
				ret = wp_huge_pmd(&vmf, orig_pmd);
				if (!(ret & VM_FAULT_FALLBACK))
					return ret;
			} else {
				huge_pmd_set_accessed(&vmf, orig_pmd);
				return 0;
			}
		}
	}

	return handle_pte_fault(&vmf);
}

2.2 Paging查询

lookup_address()函数根据地址来查询最后一级指向线性地址的paging structure地址。level则返回了page类型,比如4k page的PTE、2M page的PMD、1G page的PUD。

pte_t *lookup_address(unsigned long address, unsigned int *level)
{
        return lookup_address_in_pgd(pgd_offset_k(address), address, level);
}

↓

pte_t *lookup_address_in_pgd(pgd_t *pgd, unsigned long address,
			     unsigned int *level)
{
	p4d_t *p4d;
	pud_t *pud;
	pmd_t *pmd;

	*level = PG_LEVEL_NONE;

	if (pgd_none(*pgd))
		return NULL;

	p4d = p4d_offset(pgd, address);
	if (p4d_none(*p4d))
		return NULL;

	*level = PG_LEVEL_512G;
	if (p4d_large(*p4d) || !p4d_present(*p4d))
		return (pte_t *)p4d;

	pud = pud_offset(p4d, address);
	if (pud_none(*pud))
		return NULL;

	*level = PG_LEVEL_1G;
	if (pud_large(*pud) || !pud_present(*pud))
		return (pte_t *)pud;

	pmd = pmd_offset(pud, address);
	if (pmd_none(*pmd))
		return NULL;

	*level = PG_LEVEL_2M;
	if (pmd_large(*pmd) || !pmd_present(*pmd))
		return (pte_t *)pmd;

	*level = PG_LEVEL_4K;

	return pte_offset_kernel(pmd, address);
}

2.3 Paging属性设置(R/W/X)

用户态地址通常使用mprotect()函数来改变一段内存的属性(R/W/X)。

2.3.1 protection标志的转换

在函数调用的过程中,protection标志经历了两次转换:

  • 1、prot*vma->vm_flags

第一次是把mprotect()函数中的prot参数转换成vm_flag,即把PROT_*开头的标志转换成VM_*开头的标志。

mprotect() → do_mprotect_pkey() → calc_vm_prot_bits():

static inline unsigned long
calc_vm_prot_bits(unsigned long prot, unsigned long pkey)
{
	return _calc_vm_trans(prot, PROT_READ,  VM_READ ) |
	       _calc_vm_trans(prot, PROT_WRITE, VM_WRITE) |
	       _calc_vm_trans(prot, PROT_EXEC,  VM_EXEC) |
	       arch_calc_vm_prot_bits(prot, pkey);
}

转换公式为:

PROT_READ → VM_READ
PROT_WRITE → VM_WRITE
PROT_EXEC → VM_EXEC
pkey → VM_PKEY_BIT0/1/2/3
  • 2、vma->vm_flagsvma->vm_page_prot

第二次是把通用的vma->vm_flags转换成具体cpu架构手册描述的paging structures中的格式:

mprotect() → do_mprotect_pkey() → mprotect_fixup() → vma_set_page_prot() → vm_pgprot_modify() → vm_get_page_prot():

pgprot_t vm_get_page_prot(unsigned long vm_flags)
{
	/* (1) 根据protection_map[]查表来把vm_flags转换成pgprot */
	pgprot_t ret = __pgprot(pgprot_val(protection_map[vm_flags &
				(VM_READ|VM_WRITE|VM_EXEC|VM_SHARED)]) |
			/* (2) 把vm的protection key转换成架构的protection key */
			pgprot_val(arch_vm_get_page_prot(vm_flags)));

	return arch_filter_pgprot(ret);
}

关于protection_map[]的定义:

/* description of effects of mapping type and prot in current implementation.
 * this is due to the limited x86 page protection hardware.  The expected
 * behavior is in parens:
 *
 * map_type	prot
 *		PROT_NONE	PROT_READ	PROT_WRITE	PROT_EXEC
 * MAP_SHARED	r: (no) no	r: (yes) yes	r: (no) yes	r: (no) yes
 *		w: (no) no	w: (no) no	w: (yes) yes	w: (no) no
 *		x: (no) no	x: (no) yes	x: (no) yes	x: (yes) yes
 *
 * MAP_PRIVATE	r: (no) no	r: (yes) yes	r: (no) yes	r: (no) yes
 *		w: (no) no	w: (no) no	w: (copy) copy	w: (no) no
 *		x: (no) no	x: (no) yes	x: (no) yes	x: (yes) yes
 */
pgprot_t protection_map[16] __ro_after_init = {
	__P000, __P001, __P010, __P011, __P100, __P101, __P110, __P111,
	__S000, __S001, __S010, __S011, __S100, __S101, __S110, __S111
};

#define VM_READ		0x00000001	/* currently active flags */
#define VM_WRITE	0x00000002
#define VM_EXEC		0x00000004
#define VM_SHARED	0x00000008

protection_map[]的index就是VM_*几个flag的组合,例如:

__P000的index = 0000,
__P001的index = 0001,		// VM_READ
__P111的index = 0111,		// VM_READ|VM_WRITE|VM_EXEC
__S000的index = 1000,		// VM_SHARED
__S111的index = 1111,		// VM_SHARED|VM_READ|VM_WRITE|VM_EXEC

__P000的定义在不同cpu架构下是不一样的,就是具体paging structures中的格式。以x86_64为例:

#define __P000	PAGE_NONE
#define __P001	PAGE_READONLY
#define __P010	PAGE_COPY
#define __P011	PAGE_COPY
#define __P100	PAGE_READONLY_EXEC
#define __P101	PAGE_READONLY_EXEC
#define __P110	PAGE_COPY_EXEC
#define __P111	PAGE_COPY_EXEC

#define __S000	PAGE_NONE
#define __S001	PAGE_READONLY
#define __S010	PAGE_SHARED
#define __S011	PAGE_SHARED
#define __S100	PAGE_READONLY_EXEC
#define __S101	PAGE_READONLY_EXEC
#define __S110	PAGE_SHARED_EXEC
#define __S111	PAGE_SHARED_EXEC

↓

#define PAGE_NONE	__pgprot(_PAGE_PROTNONE | _PAGE_ACCESSED)
#define PAGE_SHARED	__pgprot(_PAGE_PRESENT | _PAGE_RW | _PAGE_USER | \
				 _PAGE_ACCESSED | _PAGE_NX)

#define PAGE_SHARED_EXEC	__pgprot(_PAGE_PRESENT | _PAGE_RW |	\
					 _PAGE_USER | _PAGE_ACCESSED)
#define PAGE_COPY_NOEXEC	__pgprot(_PAGE_PRESENT | _PAGE_USER |	\
					 _PAGE_ACCESSED | _PAGE_NX)
#define PAGE_COPY_EXEC		__pgprot(_PAGE_PRESENT | _PAGE_USER |	\
					 _PAGE_ACCESSED)
#define PAGE_COPY		PAGE_COPY_NOEXEC
#define PAGE_READONLY		__pgprot(_PAGE_PRESENT | _PAGE_USER |	\
					 _PAGE_ACCESSED | _PAGE_NX)
#define PAGE_READONLY_EXEC	__pgprot(_PAGE_PRESENT | _PAGE_USER |	\
					 _PAGE_ACCESSED)

↓

// 和x86手册中的定义一一对应
#define _PAGE_BIT_PRESENT	0	/* is present */
#define _PAGE_BIT_RW		1	/* writeable */
#define _PAGE_BIT_USER		2	/* userspace addressable */
#define _PAGE_BIT_PWT		3	/* page write through */
#define _PAGE_BIT_PCD		4	/* page cache disabled */
#define _PAGE_BIT_ACCESSED	5	/* was accessed (raised by CPU) */
#define _PAGE_BIT_DIRTY		6	/* was written to (raised by CPU) */
#define _PAGE_BIT_PSE		7	/* 4 MB (or 2MB) page */
#define _PAGE_BIT_PAT		7	/* on 4KB pages */
#define _PAGE_BIT_GLOBAL	8	/* Global TLB entry PPro+ */
#define _PAGE_BIT_SOFTW1	9	/* available for programmer */
#define _PAGE_BIT_SOFTW2	10	/* " */
#define _PAGE_BIT_SOFTW3	11	/* " */
#define _PAGE_BIT_PAT_LARGE	12	/* On 2MB or 1GB pages */
#define _PAGE_BIT_SOFTW4	58	/* available for programmer */
#define _PAGE_BIT_PKEY_BIT0	59	/* Protection Keys, bit 1/4 */
#define _PAGE_BIT_PKEY_BIT1	60	/* Protection Keys, bit 2/4 */
#define _PAGE_BIT_PKEY_BIT2	61	/* Protection Keys, bit 3/4 */
#define _PAGE_BIT_PKEY_BIT3	62	/* Protection Keys, bit 4/4 */
#define _PAGE_BIT_NX		63	/* No execute: only valid after cpuid check */

arch_vm_get_page_prot()也实现了protection key的格式转换:

#define arch_vm_get_page_prot(vm_flags)	__pgprot(	\
		((vm_flags) & VM_PKEY_BIT0 ? _PAGE_PKEY_BIT0 : 0) |	\
		((vm_flags) & VM_PKEY_BIT1 ? _PAGE_PKEY_BIT1 : 0) |	\
		((vm_flags) & VM_PKEY_BIT2 ? _PAGE_PKEY_BIT2 : 0) |	\
		((vm_flags) & VM_PKEY_BIT3 ? _PAGE_PKEY_BIT3 : 0))

↓

#define _PAGE_PKEY_BIT0	(_AT(pteval_t, 1) << _PAGE_BIT_PKEY_BIT0)
#define _PAGE_PKEY_BIT1	(_AT(pteval_t, 1) << _PAGE_BIT_PKEY_BIT1)
#define _PAGE_PKEY_BIT2	(_AT(pteval_t, 1) << _PAGE_BIT_PKEY_BIT2)
#define _PAGE_PKEY_BIT3	(_AT(pteval_t, 1) << _PAGE_BIT_PKEY_BIT3)

2.3.2 mprotect()

SYSCALL_DEFINE3(mprotect, unsigned long, start, size_t, len,
		unsigned long, prot)
{
	return do_mprotect_pkey(start, len, prot, -1);
}

↓

static int do_mprotect_pkey(unsigned long start, size_t len,
		unsigned long prot, int pkey)
{
	unsigned long nstart, end, tmp, reqprot;
	struct vm_area_struct *vma, *prev;
	int error = -EINVAL;
	const int grows = prot & (PROT_GROWSDOWN|PROT_GROWSUP);
	const bool rier = (current->personality & READ_IMPLIES_EXEC) &&
				(prot & PROT_READ);

	prot &= ~(PROT_GROWSDOWN|PROT_GROWSUP);
	if (grows == (PROT_GROWSDOWN|PROT_GROWSUP)) /* can't be both */
		return -EINVAL;

	if (start & ~PAGE_MASK)
		return -EINVAL;
	if (!len)
		return 0;
	len = PAGE_ALIGN(len);
	end = start + len;
	if (end <= start)
		return -ENOMEM;
	if (!arch_validate_prot(prot))
		return -EINVAL;

	/* (1) 配置给vma的protection属性 */
	reqprot = prot;

	if (down_write_killable(&current->mm->mmap_sem))
		return -EINTR;

	/*
	 * If userspace did not allocate the pkey, do not let
	 * them use it here.
	 */
	error = -EINVAL;
	if ((pkey != -1) && !mm_pkey_is_allocated(current->mm, pkey))
		goto out;

	/* (2) 找到第一个可能的的vma */
	vma = find_vma(current->mm, start);
	error = -ENOMEM;
	if (!vma)
		goto out;
	prev = vma->vm_prev;
	if (unlikely(grows & PROT_GROWSDOWN)) {
		if (vma->vm_start >= end)
			goto out;
		start = vma->vm_start;
		error = -EINVAL;
		if (!(vma->vm_flags & VM_GROWSDOWN))
			goto out;
	} else {
		if (vma->vm_start > start)
			goto out;
		if (unlikely(grows & PROT_GROWSUP)) {
			end = vma->vm_end;
			error = -EINVAL;
			if (!(vma->vm_flags & VM_GROWSUP))
				goto out;
		}
	}
	if (start > vma->vm_start)
		prev = vma;

	/* (3) 逐个更新vma的protection属性 */
	for (nstart = start ; ; ) {
		unsigned long mask_off_old_flags;
		unsigned long newflags;
		int new_vma_pkey;

		/* Here we know that vma->vm_start <= nstart < vma->vm_end. */

		/* Does the application expect PROT_READ to imply PROT_EXEC */
		if (rier && (vma->vm_flags & VM_MAYEXEC))
			prot |= PROT_EXEC;

		/*
		 * Each mprotect() call explicitly passes r/w/x permissions.
		 * If a permission is not passed to mprotect(), it must be
		 * cleared from the VMA.
		 */
		mask_off_old_flags = VM_READ | VM_WRITE | VM_EXEC |
					ARCH_VM_PKEY_FLAGS;

		/* (3.1) 根据prot得到新的`protection keys` */
		new_vma_pkey = arch_override_mprotect_pkey(vma, prot, pkey);
		/* (3.2) 标志的格式转换,把`PROT_*`转换成`VM_*`:
				PROT_READ → VM_READ
				PROT_WRITE → VM_WRITE
				PROT_EXEC → VM_EXEC
				pkey → VM_PKEY_BIT0/1/2/3
		 */
		newflags = calc_vm_prot_bits(prot, new_vma_pkey);
		/* (3.3) 新的flags,与上旧vm_flags中的protection部分 */
		newflags |= (vma->vm_flags & ~mask_off_old_flags);

		/* newflags >> 4 shift VM_MAY% in place of VM_% */
		/* (3.4) "newflags >> 4"得到了"VM_MAY%"
				"VM_MAY%"表明了当前vma可以被配置的能力,如果配置了不支持的属性则返回出错
				 但是上面mask_off_old_flags没有覆盖"VM_MAY%",所以这一步为空操作
		 */
		if ((newflags & ~(newflags >> 4)) & (VM_READ | VM_WRITE | VM_EXEC)) {
			error = -EACCES;
			goto out;
		}

		error = security_file_mprotect(vma, reqprot, prot);
		if (error)
			goto out;

		tmp = vma->vm_end;
		if (tmp > end)
			tmp = end;
		/* (3.5) 对vma配置新的属性 */
		error = mprotect_fixup(vma, &prev, nstart, tmp, newflags);
		if (error)
			goto out;
		nstart = tmp;

		if (nstart < prev->vm_end)
			nstart = prev->vm_end;
		if (nstart >= end)
			goto out;

		vma = prev->vm_next;
		if (!vma || vma->vm_start != nstart) {
			error = -ENOMEM;
			goto out;
		}
		prot = reqprot;
	}
out:
	up_write(&current->mm->mmap_sem);
	return error;
}

↓

int
mprotect_fixup(struct vm_area_struct *vma, struct vm_area_struct **pprev,
	unsigned long start, unsigned long end, unsigned long newflags)
{
	struct mm_struct *mm = vma->vm_mm;
	unsigned long oldflags = vma->vm_flags;
	long nrpages = (end - start) >> PAGE_SHIFT;
	unsigned long charged = 0;
	pgoff_t pgoff;
	int error;
	int dirty_accountable = 0;

	if (newflags == oldflags) {
		*pprev = vma;
		return 0;
	}

	/*
	 * Do PROT_NONE PFN permission checks here when we can still
	 * bail out without undoing a lot of state. This is a rather
	 * uncommon case, so doesn't need to be very optimized.
	 */
	/* (3.5.1) 如果需要对PFN权限进行检查 */
	if (arch_has_pfn_modify_check() &&
	    (vma->vm_flags & (VM_PFNMAP|VM_MIXEDMAP)) &&
	    (newflags & (VM_READ|VM_WRITE|VM_EXEC)) == 0) {
		error = prot_none_walk(vma, start, end, newflags);
		if (error)
			return error;
	}

	/*
	 * If we make a private mapping writable we increase our commit;
	 * but (without finer accounting) cannot reduce our commit if we
	 * make it unwritable again. hugetlb mapping were accounted for
	 * even if read-only so there is no need to account for them here
	 */
	/* (3.5.2) 对写属性的一些处理 */
	if (newflags & VM_WRITE) {
		/* Check space limits when area turns into data. */
		if (!may_expand_vm(mm, newflags, nrpages) &&
				may_expand_vm(mm, oldflags, nrpages))
			return -ENOMEM;
		if (!(oldflags & (VM_ACCOUNT|VM_WRITE|VM_HUGETLB|
						VM_SHARED|VM_NORESERVE))) {
			charged = nrpages;
			if (security_vm_enough_memory_mm(mm, charged))
				return -ENOMEM;
			newflags |= VM_ACCOUNT;
		}
	}

	/*
	 * First try to merge with previous and/or next vma.
	 */
	/* (3.5.3) 如果属性一致,尝试合并vma */
	pgoff = vma->vm_pgoff + ((start - vma->vm_start) >> PAGE_SHIFT);
	*pprev = vma_merge(mm, *pprev, start, end, newflags,
			   vma->anon_vma, vma->vm_file, pgoff, vma_policy(vma),
			   vma->vm_userfaultfd_ctx);
	if (*pprev) {
		vma = *pprev;
		VM_WARN_ON((vma->vm_flags ^ newflags) & ~VM_SOFTDIRTY);
		goto success;
	}

	*pprev = vma;

	/* (3.5.4) 如果属性不一致,需要分割vma */
	if (start != vma->vm_start) {
		error = split_vma(mm, vma, start, 1);
		if (error)
			goto fail;
	}

	if (end != vma->vm_end) {
		error = split_vma(mm, vma, end, 0);
		if (error)
			goto fail;
	}

success:
	/*
	 * vm_flags and vm_page_prot are protected by the mmap_sem
	 * held in write mode.
	 */
	/* (3.5.5) 设置新的vma->vm_flags */
	vma->vm_flags = newflags;
	/* (3.5.6) 计算是否需要write notify */
	dirty_accountable = vma_wants_writenotify(vma, vma->vm_page_prot);
	/* (3.5.7) 根据新的vma->vm_flags计算vma->vm_page_prot */
	vma_set_page_prot(vma);

	/* (3.5.8) 更改vma页面属性 */
	change_protection(vma, start, end, vma->vm_page_prot,
			  dirty_accountable, 0);

	/*
	 * Private VM_LOCKED VMA becoming writable: trigger COW to avoid major
	 * fault on access.
	 */
	/* (3.5.9) 私有VM_LOCKED VMA变为可写:触发COW以避免访问时的重大故障。 */
	if ((oldflags & (VM_WRITE | VM_SHARED | VM_LOCKED)) == VM_LOCKED &&
			(newflags & VM_WRITE)) {
		populate_vma_page_range(vma, start, end, NULL);
	}

	vm_stat_account(mm, oldflags, -nrpages);
	vm_stat_account(mm, newflags, nrpages);
	perf_event_mmap(vma);
	return 0;

fail:
	vm_unacct_memory(charged);
	return error;
}

|→

/*
 * Some shared mappigns will want the pages marked read-only
 * to track write events. If so, we'll downgrade vm_page_prot
 * to the private version (using protection_map[] without the
 * VM_SHARED bit).
 * 一些共享页面希望把page标记成只读以便用来追踪写入事件,如果是这样,我们将vm_page_prot降级为私有版本(使用没有VM_SHARED位的 protection_map[])。
 */
int vma_wants_writenotify(struct vm_area_struct *vma, pgprot_t vm_page_prot)
{
	vm_flags_t vm_flags = vma->vm_flags;
	const struct vm_operations_struct *vm_ops = vma->vm_ops;

	/* If it was private or non-writable, the write bit is already clear */
	/* (3.5.6.1) 必须是可写的共享页面  */
	if ((vm_flags & (VM_WRITE|VM_SHARED)) != ((VM_WRITE|VM_SHARED)))
		return 0;

	/* The backer wishes to know when pages are first written to? */
	/* (3.5.6.2) 有把page恢复成可写的函数 */
	if (vm_ops && (vm_ops->page_mkwrite || vm_ops->pfn_mkwrite))
		return 1;

	/* The open routine did something to the protections that pgprot_modify
	 * won't preserve? */
	/* (3.5.6.3) prot不能发生改变 */
	if (pgprot_val(vm_page_prot) !=
	    pgprot_val(vm_pgprot_modify(vm_page_prot, vm_flags)))
		return 0;

	/* Do we need to track softdirty? */
	/* (3.5.6.4)  */
	if (IS_ENABLED(CONFIG_MEM_SOFT_DIRTY) && !(vm_flags & VM_SOFTDIRTY))
		return 1;

	/* Specialty mapping? */
	/* (3.5.6.5)  */
	if (vm_flags & VM_PFNMAP)
		return 0;

	/* Can the mapping track the dirty pages? */
	/* (3.5.6.6)  */
	return vma->vm_file && vma->vm_file->f_mapping &&
		mapping_cap_account_dirty(vma->vm_file->f_mapping);
}

|→

/* Update vma->vm_page_prot to reflect vma->vm_flags. */
void vma_set_page_prot(struct vm_area_struct *vma)
{
	unsigned long vm_flags = vma->vm_flags;
	pgprot_t vm_page_prot;

	/* (3.5.7.1) 根据vma->vm_flags计算vma->vm_page_prot */
	vm_page_prot = vm_pgprot_modify(vma->vm_page_prot, vm_flags);

	/* (3.5.7.2) 如果需要开启writenotify功能,则对vm_page_prot降级成私有 */
	if (vma_wants_writenotify(vma, vm_page_prot)) {
		vm_flags &= ~VM_SHARED;
		vm_page_prot = vm_pgprot_modify(vm_page_prot, vm_flags);
	}
	/* remove_protection_ptes reads vma->vm_page_prot without mmap_sem */
	/* (3.5.7.3) 更新vma->vm_page_prot */
	WRITE_ONCE(vma->vm_page_prot, vm_page_prot);
}

|→ change_protection_range() → change_protection_range() → change_p4d_range() → change_pud_range() → change_pmd_range() → change_pte_range() → pte_modify()

2.4 writenotify

这个问题是这样引入的:

通过mmap()可以把文件内容直接映射到进程空间,大概有两种相关的映射类型:

MAP_SHARED对映射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。
MAP_PRIVATE 对映射区域的写入操作会产生一个映射文件的复制,即私人的“写入时复制”(copy on write)对此区域作的任何修改都不会写回原来的文件内容。

面对MAP_SHARED的共享映射区域,在修改完对应内存的内容以后PTE变脏(DirtyPTE),需要文件缓存同步系统能感知到,即标记对应的page结构为脏页(DirtyPage),在合适时机把改动内容刷回到文件存储当中。

其实就是PTE中的Dirty标志D,怎么能通知到Page的Dirty改动。系统使用了以下的技巧来实现这个机制:

  • 1、如果是共享可写页面(VM_WRITE|VM_SHARED),将其降级为私有只读页面:
mmap() → do_mmap() → mmap_region() → vma_set_page_prot()

void vma_set_page_prot(struct vm_area_struct *vma)
{
	unsigned long vm_flags = vma->vm_flags;
	pgprot_t vm_page_prot;

	vm_page_prot = vm_pgprot_modify(vma->vm_page_prot, vm_flags);
	/* (1) 共享可写页面需要writenotify功能,需要降级处理 */
	if (vma_wants_writenotify(vma, vm_page_prot)) {
		vm_flags &= ~VM_SHARED;
		vm_page_prot = vm_pgprot_modify(vm_page_prot, vm_flags);
	}
	/* remove_protection_ptes reads vma->vm_page_prot without mmap_sem */
	WRITE_ONCE(vma->vm_page_prot, vm_page_prot);
}
  • 2、对降级页面进行写操作,会触发异常。在异常处理中,将页面设置可写,并设置page dirty:
__handle_mm_fault() → handle_pte_fault() → do_shared_fault() → do_page_mkwrite() → vmf->vma->vm_ops->page_mkwrite():

int filemap_page_mkwrite(struct vm_fault *vmf)
{
	struct page *page = vmf->page;
	struct inode *inode = file_inode(vmf->vma->vm_file);
	int ret = VM_FAULT_LOCKED;

	sb_start_pagefault(inode->i_sb);
	vma_file_update_time(vmf->vma);
	lock_page(page);
	if (page->mapping != inode->i_mapping) {
		unlock_page(page);
		ret = VM_FAULT_NOPAGE;
		goto out;
	}
	/*
	 * We mark the page dirty already here so that when freeze is in
	 * progress, we are guaranteed that writeback during freezing will
	 * see the dirty page and writeprotect it again.
	 */
	/* 设置page为dirty */
	set_page_dirty(page);
	wait_for_stable_page(page);
out:
	sb_end_pagefault(inode->i_sb);
	return ret;
}
__handle_mm_fault() → handle_pte_fault() → do_shared_fault() → finish_fault() → alloc_set_pte() → maybe_mkwrite():

static inline pte_t maybe_mkwrite(pte_t pte, struct vm_area_struct *vma)
{
	/* 恢复页面为可写状态 */
	if (likely(vma->vm_flags & VM_WRITE))
		pte = pte_mkwrite(pte);
	return pte;
}
  • 3、在系统回刷完dirty page以后,重新将页面降级成只读:
do_writepages() → ext4_writepages() → mpage_prepare_extent_to_map() → mpage_process_page_bufs() → mpage_submit_page() → clear_page_dirty_for_io() → page_mkclean() → page_mkclean_file() → page_mkclean_one() → pte_wrprotect()

static inline pte_t pte_wrprotect(pte_t pte)
{
	return pte_clear_flags(pte, _PAGE_RW);
}

2.5 mm切换

在进程切换时,页表映射也需要切换到新的地址空间,即新进程的mm->pgd需要加载进cr3寄存器。

schedule() → __schedule() → context_switch() → switch_mm_irqs_off():

void switch_mm_irqs_off(struct mm_struct *prev, struct mm_struct *next,
			struct task_struct *tsk)
{

	load_new_mm_cr3(next->pgd, new_asid, true);

}

参考资料:

1、Intel® 64 and IA-32 architectures software developer’s manual
2、linux如何感知通过mmap进行的文件修改
3、Linux文件系统
4、Linux分页机制之概述
5、页式存储管理
6、linux中的分页机制
7、mmap和msync相关的一个问题

posted @ 2020-11-02 17:15  pwl999  阅读(435)  评论(0编辑  收藏  举报