Linux深入理解内存管理36

Linux深入理解内存管理36(基于Linux6.6)---临时页表映射过程介绍

一、概述

为了降低启动代码的复杂性,进入linux内核的时候,MMU使关闭的。如果关闭MMU,意味着不能利用高速缓存的性能,那么内核使如何要打开MMU并使能数据高速缓存呢?

  • 在关闭MMU的情况下,处理器访问的地址都是物理地址。当MMU打开后,处理器访问地址就变成虚拟地址
  • 目前的处理器都是多级流水行架构,处理器会提前预取多条指令到流水线中。当打开MMU后,处理器之前预取的指令就会以虚拟地址来访问,到MMU查找对应的物理地址。

因此,为了保证处理器在开启MMU后,能完成从物理地址到虚拟地址的平滑过渡,首先会创建VA和PA的相等映射,也就是恒等映射(identity mapping)。 建立恒等映射是小范围的,占用的空间通常是内核映像的大小,也就是几兆字节。

/*
 * Setup the initial page tables. We only setup the barest amount which is
 * required to get the kernel running. The following sections are required:
 *   - identity mapping to enable the MMU (low address, TTBR0)
 *   - first few MB of the kernel linear mapping to jump to once the MMU has
 *     been enabled
 */
__create_page_tables:
    mov    x28, lr                                             #把LR的值存放到X28

这里会建立两种section,分别完成identity mapping和kernel image mapping

	/*
	 * Invalidate the idmap and swapper page tables to avoid potential
	 * dirty cache lines being evicted.
	 */
	adrp	x0, idmap_pg_dir                                   
#加载idmap_pg_dir到x0
	adrp	x1, swapper_pg_dir + SWAPPER_DIR_SIZE              
#加载swapper_pg_dir到x1
	bl	__inval_cache_range									   
#使页表对应的高速缓存无效,为后面建立内核空间页表映射

这个主要是将identity mapping和kernel image mapping空间的页表对应的高速缓存无效掉,其主要的实现在arch/arm64/kernel/vmlinux.lds.S链接文件中

#arch/arm64/kernel/vmlinux.lds.S
	. = ALIGN(PAGE_SIZE);
	idmap_pg_dir = .;
	. += IDMAP_DIR_SIZE;
	swapper_pg_dir = .;
	. += SWAPPER_DIR_SIZE;

调用__inval_cache_range函数来使idmap_pg_dir和swapper_pg_dir + SWAPPER_DIR_SIZE区间内页表i对应的高速缓存无效i,也就使先清空idmap_pg_dir对应大小为IDMAP_DIR_SIZE和swapper_pg_dir对应大小为SWAPPER_DIR_SIZE页表的高速缓存。

  • idmap_pg_dir是identity mapping使用的页表,也就是物理地址和虚拟地址是相等的,主要是解决打开MMU后,从物理地址转换成虚拟地址,防止MMU开启后,无法获取页表
  • swapper_pg_dir是kernel image mapping初始阶段使用的页表,swapper_pg_dirLinux内核编译后,kernel image是需要进行映射的,包括text,data等各种。请注意,这里的内存是一段连续内存。也就是说页表(PGD/PUD/PMD)都是连在一起的,地址相差PAGE_SIZE(4k)
	/*
	 * Clear the idmap and swapper page tables.
	 */
	adrp	x0, idmap_pg_dir
	adrp	x6, swapper_pg_dir + SWAPPER_DIR_SIZE
1:	stp	xzr, xzr, [x0], #16
	stp	xzr, xzr, [x0], #16
	stp	xzr, xzr, [x0], #16
	stp	xzr, xzr, [x0], #16
	cmp	x0, x6
	b.lo	1b

这段话的含义很明白了,将idmap_pg_dir赋值给x0,将swapper_pg_dir + SWAPPER_DIR_SIZE赋值给x6,然后将这段空间的页表的内容设置为0。

    mov    x7, SWAPPER_MM_MMUFLAGS

SWAPPER_MM_MMUFLAGS宏描述了段映射的属性,它实现在arch/arm64/include/asm/kernel-pgtable.h头文件中

#arch/arm64/include/asm/kernel-pgtable.h
#define SWAPPER_MM_MMUFLAGS	(PMD_ATTRINDX(MT_NORMAL) | SWAPPER_PMD_FLAGS)
#define SWAPPER_PMD_FLAGS	(PMD_TYPE_SECT | PMD_SECT_AF | PMD_SECT_S)

其中定义了内存属性为普通内存MT_NORMAL,其属性含义如下:

  • PMD_TYPE_SECT表示一个块映射。
  • PMD_SECT_AF设置为块映射的访问权限,该Bit用来表示该页表是否第一次是使用,当程序访问对应的page或者section的时候,就会使用该entry,如果从来没有被访问过,那么其值等于0,否者等于1)。该bit主要被操作系统用来跟踪一个page是否被使用过(最近是否被访问),当该page首次被创建的时候,AF等于0,当代码第一次访问该page的时候,会产生MMU fault,这时候,异常处理函数应该设定AF等于1,从而阻止下一次访问该page的时候产生MMU Fault。
  • PMD_SECT_S表示块映射的共享属性。
		/*
	 * Create the identity mapping.
	 */
	adrp	x0, idmap_pg_dir                                                     -----(1)
	adrp	x3, __idmap_text_start		                                         -----(2)

#ifndef CONFIG_ARM64_VA_BITS_48                                                  -----(3)
#define EXTRA_SHIFT	(PGDIR_SHIFT + PAGE_SHIFT - 3)                               -----(4)
#define EXTRA_PTRS	(1 << (48 - EXTRA_SHIFT))                                    -----(5)

	/*
	 * If VA_BITS < 48, it may be too small to allow for an ID mapping to be
	 * created that covers system RAM if that is located sufficiently high
	 * in the physical address space. So for the ID map, use an extended
	 * virtual range in that case, by configuring an additional translation
	 * level.
	 * First, we have to verify our assumption that the current value of
	 * VA_BITS was chosen such that all translation levels are fully
	 * utilised, and that lowering T0SZ will always result in an additional
	 * translation level to be configured.
	 */
#if VA_BITS != EXTRA_SHIFT                                                      -----(6)
#error "Mismatch between VA_BITS and page size/number of translation levels"
#endif

	/*
	 * Calculate the maximum allowed value for TCR_EL1.T0SZ so that the
	 * entire ID map region can be mapped. As T0SZ == (64 - #bits used),
	 * this number conveniently equals the number of leading zeroes in
	 * the physical address of __idmap_text_end.
	 */
	adrp	x5, __idmap_text_end                                               -----(7)
	clz	x5, x5                                                                 
	cmp	x5, TCR_T0SZ(VA_BITS)	// default T0SZ small enough?                  -----(8)
	b.ge	1f			// .. then skip additional level                       

	adr_l	x6, idmap_t0sz                                                     -----(9)
	str	x5, [x6]
	dmb	sy
	dc	ivac, x6		// Invalidate potentially stale cache line

	create_table_entry x0, x3, EXTRA_SHIFT, EXTRA_PTRS, x5, x6                 -----(10)
1:
#endif

	create_pgd_entry x0, x3, x5, x6                                             -----(11)
	mov	x5, x3				// __pa(__idmap_text_start)
	adr_l	x6, __idmap_text_end		// __pa(__idmap_text_end)
	create_block_map x0, x7, x3, x5, x6                                         -----(12)
  1. 将加载idmap_pg_dir的物理地址到x0寄存器,idmap_pg_dir使恒等映射的页表,其定义在vmlinux.lds.S链接文件中定义了。
	. = ALIGN(PAGE_SIZE);
	idmap_pg_dir = .;
	. += IDMAP_DIR_SIZE;
	swapper_pg_dir = .;

这里分配给idmap_pg_dir的u页面大小为IDMAP_DIR_SIZE,而IDMAP_DIR_SIZE实现在arch/arm64/include/asm/kernel-pgtable.h头文件中,通常大小使3个连续的4K页面。

#define IDMAP_DIR_SIZE		(IDMAP_PGTABLE_LEVELS * PAGE_SIZE)
#define IDMAP_PGTABLE_LEVELS (ARM64_HW_PGTABLE_LEVELS(PHYS_MASK_SHIFT) - 1)
#define PHYS_MASK_SHIFT (CONFIG_ARM64_PA_BITS)

这里的CONFIG_ARM64_PA_BITS配置的是48. 这里的含义是,计算采用section mapping的话,需要几个页来存放table。ARM64_HW_PGTABLE_LEVELS,很关键,根据配置的物理地址线的宽度计算需要的页面数,

#define ARM64_HW_PGTABLE_LEVELS(va_bits) (((va_bits) - 4) / (PAGE_SHIFT - 3))

完成的公司转换成为

((((va_bits) - PAGE_SHIFT) + (PAGE_SHIFT - 3) - 1) / (PAGE_SHIFT - 3))

结合vmlinux.lds,上面的公式就是: ((48-12)+(12-3)-1) / (12-3) = (36+9-1)/9 = 44/9 = 4,最终IDMAP_DIR_SIZE为3个页面,即PGD/PUD/PMD页表,每一级页表占据一个页面。

  • 2.主要是将__idmap_text_start放到x3中,idmap是kernel image中的一个段,其位于 arch/arm64/kernel/vmlinux.lds.S中,定义如下,即定义了一个以__idmap_text_start开始,__idmap_text_end结束的段,该段会被放在vmlinux的代码段中。
        . = ALIGN(SZ_4K);                               \
        VMLINUX_SYMBOL(__idmap_text_start) = .;         \
        *(.idmap.text)                                  \
        VMLINUX_SYMBOL(__idmap_text_end) = .;

除了开机启动时,打开MMU外,内核里还有很对场景需要恒等映射的,如唤醒处理其的函数cpu_do_resume。

  • 3.如果没有定义CONFIG_ARM64_VA_BITS_48,表示虚拟地址的宽度配置项,就需要使用create_table_entry。主要是解决要标识映射的物理地址超出VA_BITS覆盖范围的问题。此时基本上创建identity mapping是没有什么大问题的,但是,如果物理内存的地址位于非常高的位置,那么在进行identity mapping就有问题了,因为有可能你配置的VA_BITS不够大,超出了虚拟地址的范围。这时候,就需要扩展virtual address range了。当然,如果配置了48bits的VA_BITS就不存在这样的问题了,因为ARMv8最大支持的VA BITS就是48个,根本不可能扩展了。
  • 4.在虚拟地址地址不是48 bit,而系统内存的物理地址又放到了非常非常高的位置,这时候,为了完成identity mapping,我们必须要扩展虚拟地址,那么扩展多少呢?扩展到48个bit。扩展之后,增加了一个EXTRA的level,地址映射关系是EXTRA—>PGD—>……,其中EXTRA_SHIFT等于(PGDIR_SHIFT + PAGE_SHIFT - 3)。
  • 5.扩展之后,地址映射多个一个level,称之EXTRA level,该level的Translation table中有多少个entry呢?EXTRA_PTRS给出了答案。
  • 6.linux kernel中,对地址映射是有要求的,即要求PGD是满的。例如:48 bit的虚拟地址,4k的page size,对应的映射关系是PGD(9-bit)+PUD(9-bit)+PMD(9-bit)+PTE(9-bit)+page offset(12-bit),对于42bit的虚拟地址,64k的page size,对应的映射关系是PGD(13-bit)+ PTE(13-bit)+ page offset(16-bit)。这两种例子有一个共同的特点就是PGD中的entry数目都是满的,也就是说索引到PGD的bit数目都是PAGE_SIZE-3。如果不满足这个关系,linux kernel会认为你的配置是有问题的。注意:这是内核的要求,实际上ARM64的硬件没有这么要求
     
  • 7.正因为正确的配置下,PGD都是满的,因此扩展之后EXTRA_SHIFT一定是等于VA_BITS的,否则一定是你的配置有问题。我们延续上一个实例来说明如何扩展虚拟地址的bit数目。对于42bit的虚拟地址,64k的page size,扩展之后,虚拟地址是48个bit,地址映射关系是EXTRA(6-bit)+ PGD(13-bit)+ PTE(13-bit)+ page offset(16-bit)。
     
  • 8.x5保存了__idmap_text_end的物理地址,之所以这么做是因为需要确定identity mapping的最高的物理地址,计算该物理地址的前导0有多少个,从而可以判断该地址是否是位于物理地址空间中比较高的位置。零计数指令CLZ,指令用于计算最高符号位与第一个1之间的0的个数宏定义TCR_T0SZ可以计算给定虚拟地址数目下,前导0的个数。如果虚拟地址是48的话,那么前导0是16个。如果当前物理地址的前导0的个数(x5的值)还有小于当前配置虚拟地址的前导0的个数,那么就需要扩展。比较__idmap_text_end是否超过了VM_BITS所能达到的地址范围,如果没有,则跳转到标签1出,即11处,如果有,则跳转到9。
  • 9.创建extra translation table的entry。具体传递的参数如下:
     
  • (1)x0:页表地址idmap_pg_dir。
  • (2)x3:准备映射的虚拟地址。
  • (3)EXTRA_SHIFT:正常建立最高level mapping的时候, shift是PGDIR_SHIFT,但是,由于物理地址位置太高,需要额外的映射,因此这里需要再加上一个level的mapping,因此shift需要PGDIR_SHIFT + (PAGE_SHIFT - 3)。
  • (4)EXTRA_PTRS:增加了一个level的Translation table,需要确定这个增加level的Translation table中包含的描述符的数目,EXTRA_PTRS给出了这个参数。
  • 10.create_pgd_entry,建立各个中间level的table描述符。
  • 11.还将__idmap_text_start放到x5寄存器中,__idmap_text_end放到x6寄存器中。
  • 创建最后一个level translation table的entry。该entry可能是page descriptor,也可能是block descriptor,具体传递的参数如下:
  • (1)x0:指向最后一个level的translation table。
  • (2)x7:要创建映射的memory attribute。
  • (3)x3:物理地址。
  • (4)x5:虚拟地址的起始地址(其实和x3一样)。
  • (5)x6:虚拟地址的结束地址。

二、create_table_entry

这个宏定义主要是用来创建一个中间level的translation table中的描述符。如果用linux的术语,就是创建PGD、PUD或者PMD的描述符。如果用ARM64术语,就是创建L0、L1或者L2的描述符。具体创建哪一个level的Translation table descriptor是由tbl参数指定的,tbl指向了该translation table的内存。

	.macro	create_table_entry, tbl, virt, shift, ptrs, tmp1, tmp2
	lsr	\tmp1, \virt, #\shift
	and	\tmp1, \tmp1, #\ptrs - 1	// table index
	add	\tmp2, \tbl, #PAGE_SIZE
	orr	\tmp2, \tmp2, #PMD_TYPE_TABLE	// address of next table and entry type
	str	\tmp2, [\tbl, \tmp1, lsl #3]
	add	\tbl, \tbl, #PAGE_SIZE		// next level table page
	.endm

create_table_entry宏自带6个参数,部分参数说明如下:

  • tbl:指向了该translation table的内存,决定了具体创建哪一个level的Translation table descriptor
  • virt:要创建地址映射的那个虚拟地址
  • shift表示这一级页表的在虚拟地址中的偏移
  • ptr表示这一级页表是几位的
  • tmp1和tmp2是临时变量

其处理流程如下:

  1. tmp1中保存virt地址对应在Translation table中的entry index
  2. 初始阶段的页表(PGD/PUD/PMD/PTE)都是排列在一起的,每一个占用一个page。也就是说,如果create_table_entry当前操作的是PGD,那么tmp2这时候保存了下一个level的页表,也就是PUD了。
  3. 光有下一级translation table的地址不行,还要告知该描述符是否有效(bit 0),该描述符的类型是哪一种类型(bit 1)。对于中间level的页表,该描述符不可能是block entry,只能是table type的描述符,因此该描述符的最低两位是0b11。
  4. #define PMD_TYPE_TABLE (_AT(pmdval_t, 3) << 0)
  5. 把页表项内容放到指定的页表项当中,之所以有“lsl #3”操作,是因为一个描述符占据8个Byte
  6. 结束的时候tbl会加上一个PAGE_SIZE,也就是tbl变成了下一级页表的地址

三、create_pgd_entry

这个宏的作用并不仅仅是创建pgd,实际上该函数不仅仅创建PGD中的描述符,如果需要下一级的translation table,例如PUD、PMD,也需要同时建立,最终的要求是能够完成所有中间level的translation table的建立(其实每个table中都是只建立了一个描述符),仅仅留下PTE,由其他代码来完成。

	.macro	create_pgd_entry, tbl, virt, tmp1, tmp2
	create_table_entry \tbl, \virt, PGDIR_SHIFT, PTRS_PER_PGD, \tmp1, \tmp2
#if SWAPPER_PGTABLE_LEVELS > 3
	create_table_entry \tbl, \virt, PUD_SHIFT, PTRS_PER_PUD, \tmp1, \tmp2
#endif
#if SWAPPER_PGTABLE_LEVELS > 2
	create_table_entry \tbl, \virt, SWAPPER_TABLE_SHIFT, PTRS_PER_PTE, \tmp1, \tmp2
#endif
	.endm

create_pgd_entry宏自带4个参数,部分参数说明如下:

  • tbl:tbl是pgd translation table的地址
  • virt: 具体要创建哪一个地址的描述符由virt指定
  • tmp1和tmp2是临时变量

其处理流程如下:

  1. 这里通过create_table_entry调用该函数在PGD中为虚拟地址virt创建一个table type的描述符。
  2. SWAPPER_PGTABLE_LEVELS其实定义了swapper进程地址空间的页表的级数,可能3,也可能是2,具体中间的Translation table有多少个level是和配置相关的,如果是section mapping,那么中间level包括PGD和PUD就OK了,PMD是最后一个level。如果是page mapping,那么需要PGD、PUD和PMD这三个中间level,PTE是最后一个level。当然,如果整个page level是3或者2的时候,也有可能不存在PUD或者PMD这个level。
  3. 当SWAPPER_PGTABLE_LEVELS > 3的时候,需要创建PUD这一级的Translation table。
  4. 当SWAPPER_PGTABLE_LEVELS > 2的时候,需要创建PMD这一级的Translation table。
  • 当虚拟地址是48个bit,4k page size,这时候page level等于4,映射关系是PGD(L0)—>PUD(L1)—>PMD(L2)—>Page table(L3)—>page。

img

但是如果采用了section mapping,映射关系是PGD(L0)—>PUD(L1)—>PMD(L2)—>section。在create_pgd_entry函数中将创建PGD和PUD这两个中间level 。

img

四、create_block_map

该函数就是在tbl指定的Translation table中建立block descriptor以便完成address mapping,以2M为大小进行映射。具体mapping的内容是将start 到 end这一段VA mapping到phys开始的PA上去,代码如下。

	.macro	create_block_map, tbl, flags, phys, start, end
	lsr	\phys, \phys, #SWAPPER_BLOCK_SHIFT
	lsr	\start, \start, #SWAPPER_BLOCK_SHIFT
	and	\start, \start, #PTRS_PER_PTE - 1	// table index
	orr	\phys, \flags, \phys, lsl #SWAPPER_BLOCK_SHIFT	// table entry
	lsr	\end, \end, #SWAPPER_BLOCK_SHIFT
	and	\end, \end, #PTRS_PER_PTE - 1		// table end index
9999:	str	\phys, [\tbl, \start, lsl #3]		// store the entry
	add	\start, \start, #1			// next entry
	add	\phys, \phys, #SWAPPER_BLOCK_SIZE		// next block
	cmp	\start, \end
	b.ls	9999b
	.endm

create_pgd_entry宏自带5个参数,部分参数说明如下

  • tbl:tbl是pgd translation table的地址
  • flags:表示当前页表项指示的是block还是page
  • phys: 要映射的物理地址的起始地址
  • start和end分表表示物理地址要映射到的虚拟地址的开始和结束

其处理流程如下:

  • 前6行当中,phys和flag计算得到页表项的内容,通过start得到页表的index开始,通过end得到页表的计数。
  • 循环映射从phys开始的地址映射到start—end的区域

五、如何创建页表

负责创建映射关系的函数是create_page_tables。create_page_tables函数负责identity mapping和kernel image mapping。前文提到identity mapping主要是打开MMU的过度阶段,因此对于identity mapping不需要映射整个kernel,只需要映射操作MMU代码相关的部分。首先我们在回头看看create_page_tables的流程。

  • 对identity mapping和kernel mapping对应的区域invalid cache操作
  • identity和swapper的页表区域清零操作
  • 对identity区域进行映射,映射的过程就是调用create_pgd_entry和create_block_map的过程
  • 对kernel区域进行映射,映射的过程就是调用create_pgd_entry和create_block_map的过程

5.1、Identity区域进行映射

 create_page_tables创建了一个恒等映射,把idmap.text段的虚拟地址映射到相同的物理地址上,这个映射的页表在idmap_pg_dir页表中,映射的起始地址为idmap_text_start,结束地址为idmap_text_end。

5.2、创建内核映像的页表

	/*
	 * Map the kernel image (starting with PHYS_OFFSET).
	 */
	adrp	x0, swapper_pg_dir
	mov_q	x5, KIMAGE_VADDR + TEXT_OFFSET	// compile time __va(_text)
	add	x5, x5, x23			// add KASLR displacement
	create_pgd_entry x0, x5, x3, x6
	adrp	x6, _end			// runtime __pa(_end)
	adrp	x3, _text			// runtime __pa(_text)
	sub	x6, x6, x3			// _end - _text
	add	x6, x6, x5			// runtime __va(_end)
	create_block_map x0, x7, x3, x5, x6

	/*
	 * Since the page tables have been populated with non-cacheable
	 * accesses (MMU disabled), invalidate the idmap and swapper page
	 * tables again to remove any speculatively loaded cache lines.
	 */
	adrp	x0, idmap_pg_dir
	adrp	x1, swapper_pg_dir + SWAPPER_DIR_SIZE
	dmb	sy
	bl	__inval_cache_range
  • 加载swapper_pg_dir页表的基地址到x0寄存器。
  • 加载内核映像即将要映射的虚拟地址为KIMAGE_VADDR + TEXT_OFFSET,即__va(_text)。
  • 内核映像的虚拟地址要加上KASLR,x23寄存器存放了_PHYS_OFFSET的值。
  • 内核映像的起始地址为_text,结束地址为_end,计算内环境映像借宿的虚拟地址。
  • 调用create_block_map建立页表映射。
  1. x0:tbl是pgd translation table的地址。
  2. x7:表示当前页表项指示的是block还是page。
  3. x3: 要映射的物理地址的起始地址。
  4. x5和x6分表表示物理地址要映射到的虚拟地址的开始和结束。
  • 使恒等映射页表idmap_pg_dir和内核态页表swapper_pg_dir对应的高速缓存无效,虽然目前还没有使能MMU,但是数据可能被提前取到高速缓存中,因此要清对应的高速缓存并使它无效。

所以函数会创建两个映射:

  • 一段是identity mapping,其实就是把地址等于物理地址的那些虚拟地址mapping到物理地址上去,打开MMU相关的代码需要这样的mapping。
  • kernel image mapping,内核代码欢快的执行当然需要将kernel running需要的地址(kernel txt、rodata、data、bss等等)进行映射了。

具体的映射情况可以参考下图:

六、开启MMU

ENTRY(__enable_mmu)
	mrs	x1, ID_AA64MMFR0_EL1
	ubfx	x2, x1, #ID_AA64MMFR0_TGRAN_SHIFT, 4
	cmp	x2, #ID_AA64MMFR0_TGRAN_SUPPORTED
	b.ne	__no_granule_support
	update_early_cpu_boot_status 0, x1, x2
	adrp	x1, idmap_pg_dir
	adrp	x2, swapper_pg_dir
	msr	ttbr0_el1, x1			// load TTBR0
	msr	ttbr1_el1, x2			// load TTBR1
	isb
	msr	sctlr_el1, x0
	isb
	/*
	 * Invalidate the local I-cache so that any instructions fetched
	 * speculatively from the PoC are discarded, since they may have
	 * been dynamically patched at the PoU.
	 */
	ic	iallu
	dsb	nsh
	isb
	ret
ENDPROC(__enable_mmu)

__enable_mmu函数传递一个参数

  • x0:表示SCTLR_EL1的值

因为ARM64处理器有两个页表的地址寄存器,一个使TTBR0,另外一个使TTBR1,当虚拟地址的第63位为0时,选择TTBR0指向的页表;当虚拟地址的第63位为1时,,选择TTBR1指向的页表。

由于ARM64的Soc的处理器物理地址的起始地址是从0开始,物理内存大小不可能太大,所以在做恒等映射的时候,我们采用TTBR0来映射低256TB大小的地址空间,而把内核映像映射到内核空间时,采用TTBR1。该函数通过msr ttbr0_el1, x1和msr ttbr1_el1, x2来完成配置。

七、总结

7.1、临时页表映射的实现过程

临时页表映射的实现通常依赖于操作系统的内存管理子系统,并通过调用特定的内存映射接口(如 mmap()vmalloc() 或直接操作页表)来完成。

1、内存区域的选择

临时映射的首要步骤是选择或分配一个适当的虚拟地址空间来映射物理内存。这个地址空间通常由操作系统或硬件提供,确保不会与其他进程或内核空间的映射冲突。

2、创建或更新页表
  • 页表项的创建:当需要映射虚拟地址到物理地址时,操作系统会检查当前进程的页表。如果没有现成的映射,操作系统将创建或更新页表项,将虚拟页映射到实际物理页。
  • 临时页表:为避免破坏现有的映射或减少不必要的内存开销,操作系统可能会创建临时的页表,来管理短期的映射需求。操作系统会将虚拟地址映射到临时页表的物理地址上。
3、执行映射操作

一旦页表创建或更新,操作系统会更新 MMU(内存管理单元)的映射信息,使得处理器能够正确地访问新的映射。

  • 映射到物理内存:在某些情况下,操作系统会直接修改页表项,将虚拟地址映射到某个物理地址。这种方式通常用于 I/O 映射。
  • 虚拟页映射:操作系统会为临时页表映射分配虚拟地址,并通过修改页表项,将这些虚拟地址与物理内存的地址相对应。
4、访问与清理
  • 访问控制:临时映射通常有一个时间限制,操作系统在此期间会限制进程对映射内存的访问权限。可能会设置某些保护措施,如只读、只写、执行等标志,以确保映射的内存区域按照预期进行操作。
  • 解除映射:当不再需要临时映射时,操作系统会清理相应的页表项,解除映射,并回收内存。这通常涉及修改页表,确保虚拟地址不再指向物理地址。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值