目录
🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂 捐赠一下吧 🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂
THP 使用手册
本节基于丰富的实例案例来介绍 THP 的使用,实践案例已经在 BiscuitOS 适配,开发者可以参考部署流程在 BiscuitOS 直接实践案例:
匿名共享映射: 直接分配一个 ShmemHugepage 大页
在启用透明大页的系统中,如果对 ShmemHugepage 的策略是 always 时,进程通过匿名共享方式分配 2MiB 虚拟内存时,只要访问 2MiB 虚拟内存任意位置是,系统直接分配一个 ShmemHugepage 物理页与 2MiB 虚拟内存建立页表映射关系。为了更好实践 ShmemHugepages,内核必须启用 CONFIG_TRANSPARENT_HUGEPAGE 宏和 CONFIG_TRANSPARENT_HUGEPAGE_ALWAYS 宏。本节用于介绍应用程序分配 ShmemHugepage 的方法, BiscuitOS 提供了使用案例,其在 BiscuitOS 上部署逻辑如下:
BiscuitOS-THP-ShmemHugepage-anonymous-default Gitee Source Code
案例源码通过一个应用程序进行展示,应用程序首先在 29 行调用 mmap() 函数,通过匿名共享映射的方式分配了 BISCUITOS_MAP_SIZE 的虚拟内存,通过宏定义可以知道虚拟内存大小为 8MiB,那么接下来在 44-47 行分别按 2MiB 的偏移对虚拟内存进行访问,可以看到每个 2MiB 的虚拟区域都被访问了一次。为了调试方便进程在 50 行处调用 sleep(-1) 保持不退出. 至此程序的功能完结,案例代码很精简,另外当系统启动之后需要设置 THP 的 ShmemHugepage 策略为 always, 那么接下来通过下面的命令在 BiscuitOS 上进行实践:
可以看到当系统启动之后,向 “/sys/kernel/mm/transparent_hugepage/shmem_enabled” 写入 always 时,那么 THP 的 ShmemHugepage 策略就是直接分配. 接着后台运行应用程序 BiscuitOS-THP-ShmemHugepage-anonymous-default,此时查看 ShmemHugepage 的使用情况,可以看出此时进程占用了 4 个 ShmemHugepage 大页,总共 8MiB 物理内存,这与源码访问 8MiB 虚拟内存对应. 至此实践完毕符合预期.
文件共享映射: 直接分配一个 ShmemHugepage 大页
在启用透明大页的系统中,如果挂载了一个带 “huge=always” 参数的 tmpfs 文件系统时,进程在该 tmpfs 文件系统下通过文件共享映射方式分配 2MiB 虚拟内存时,只要访问 2MiB 虚拟内存任意位置是,系统直接分配一个 ShmemHugepage 物理页与 2MiB 虚拟内存建立页表映射关系。为了更好实践 ShmemHugepages,内核必须启用 CONFIG_TRANSPARENT_HUGEPAGE 宏和 CONFIG_TRANSPARENT_HUGEPAGE_ALWAYS 宏。本节用于介绍应用程序分配 ShmemHugepage 的方法, BiscuitOS 提供了使用案例,其在 BiscuitOS 上部署逻辑如下:
BiscuitOS-THP-ShmemHugepage-file-shared-default Gitee Source Code
案例源码通过一个应用程序进行展示,应用程序首先在 31 行通过 open() 函数以可读可写的方式打开文件,如果文件 BiscuitOS 不存在,那么创建 BiscuitOS 文件,程序接着在 38 行调用 write() 函数向 BiscuitOS 文件后端写入数据。然后程序在 41 行调用 mmap() 函数,通过文件共享映射的方式分配了 BISCUITOS_MAP_SIZE 的虚拟内存,通过宏定义可以知道虚拟内存大小为 8MiB,该虚拟内存用于映射 BiscuitOS 文件。那么接下来在 57 行对虚拟内存进行访问。为了调试方便进程在 60 行处调用 sleep(-1) 保持不退出. 至此程序的功能完结,案例代码很精简,另外当系统启动之后需要挂载一个带 “huge=always” 参数的 tmpfs 文件系统,并在文件系统中打开该文件, 那么接下来通过下面的命令在 BiscuitOS 上进行实践:
可以看到当系统启动之后,挂载一个 huge tmpfs 之后在后台运行应用程序 BiscuitOS-THP-ShmemHugepage-file-shared-default,此时查看 ShmemHugepage 的使用情况,可以看出此时进程占用了 1 个 ShmemHugepage 大页,总共 2MiB 物理内存,这与源码访问 2MiB 虚拟内存对应. 至此实践完毕符合预期.
文件私有映射: 直接分配一个 ShmemHugepage 大页
在启用透明大页的系统中,如果挂载了一个带 “huge=always” 参数的 tmpfs 文件系统时,进程在该 tmpfs 文件系统下通过文件私有映射方式分配 2MiB 虚拟内存时,只要访问 2MiB 虚拟内存任意位置是,系统直接分配一个 ShmemHugepage 物理页与 2MiB 虚拟内存建立页表映射关系。为了更好实践 ShmemHugepages,内核必须启用 CONFIG_TRANSPARENT_HUGEPAGE 宏和 CONFIG_TRANSPARENT_HUGEPAGE_ALWAYS 宏。本节用于介绍应用程序分配 ShmemHugepage 的方法, BiscuitOS 提供了使用案例,其在 BiscuitOS 上部署逻辑如下:
BiscuitOS-THP-ShmemHugepage-file-private-default Gitee Source Code
案例源码通过一个应用程序进行展示,应用程序首先在 31 行通过 open() 函数以可读可写的方式打开文件,如果文件 BiscuitOS 不存在,那么创建 BiscuitOS 文件,程序接着在 38 行调用 write() 函数向 BiscuitOS 文件后端写入数据。然后程序在 41 行调用 mmap() 函数,通过文件私有映射的方式分配了 BISCUITOS_MAP_SIZE 的虚拟内存,通过宏定义可以知道虚拟内存大小为 8MiB,该虚拟内存用于映射 BiscuitOS 文件。那么接下来在 57 行对虚拟内存进行访问。为了调试方便进程在 60 行处调用 sleep(-1) 保持不退出. 至此程序的功能完结,案例代码很精简,另外当系统启动之后需要挂载一个带 “huge=always” 参数的 tmpfs 文件系统,并在文件系统中打开该文件, 那么接下来通过下面的命令在 BiscuitOS 上进行实践:
可以看到当系统启动之后,挂载一个 huge tmpfs 之后在后台运行应用程序 BiscuitOS-THP-ShmemHugepage-file-private-default,此时查看 ShmemHugepage 的使用情况,可以看出此时进程占用了 1 个 ShmemHugepage 大页,总共 2MiB 物理内存,这与源码访问 2MiB 虚拟内存对应. 至此实践完毕符合预期.
匿名私有映射: 直接分配一个 AnonHugepage 大页
在启用透明大页的系统中,如果对 THP 的策略是 always 时,并且系统物理内存大于 512MiB,那么进程通过匿名私有方式分配 2MiB 虚拟内存时,只要访问 2MiB 虚拟内存任意位置是,系统直接分配一个 AnonHugepage 物理页与 2MiB 虚拟内存建立页表映射关系。为了更好实践 AnonHugepages,内核必须启用 CONFIG_TRANSPARENT_HUGEPAGE 宏和 CONFIG_TRANSPARENT_HUGEPAGE_ALWAYS 宏, 并且系统物理内存大于 512MiB。本节用于介绍应用程序分配 AnonHugepage 的方法, BiscuitOS 提供了使用案例,其在 BiscuitOS 上部署逻辑如下:
案例源码通过一个应用程序进行展示,应用程序首先在 29 行调用 mmap() 函数,通过匿名私有映射的方式分配了 BISCUITOS_MAP_SIZE 的虚拟内存,通过宏定义可以知道虚拟内存大小为 8MiB,那么接下来在 44-47 行分别按 2MiB 的偏移对虚拟内存进行访问,可以看到每个 2MiB 的虚拟区域都被访问了一次。为了调试方便进程在 50 行处调用 sleep(-1) 保持不退出. 至此程序的功能完结,案例代码很精简,另外当系统启动之后需要设置 THP 的策略为 always, 那么接下来通过下面的命令在 BiscuitOS 上进行实践:
可以看到当系统启动之后,向 “/sys/kernel/mm/transparent_hugepage/enabled” 写入 always 时,那么 THP 的策略就是直接分配. 接着后台运行应用程序 BiscuitOS-THP-AnonHugepage-default,此时查看 AnonHugepage 的使用情况,可以看出此时进程占用了 4 个 AnonHugepage 大页,总共 8MiB 物理内存,这与源码访问 8MiB 虚拟内存对应. 至此实践完毕符合预期.
匿名私有映射: 合并 4K-Page 为 AnonHugepage 大页
在启用透明大页的系统中,如果对 THP 的策略是 always 时,并且系统物理内存大于 512MiB,那么进程通过匿名私有方式分配虚拟内存,并与 4KiB 物理页建立页表,只要同时满足两个条件,那么内核会将 4KiB 物理页合并成一个 AnonHugePage 2MiB 的大页: 第一个条件是虚拟内存的长度不小于 2MiB; 第二个条件是虚拟内存中已经有 50% 的虚拟内存与 4KiB 物理页建立页表。只要满足这两个条件,THP 机制立即将虚拟内存映射到一个 2MiB 的 AnonHugepage 大页上。为了更好实践 AnonHugepages,内核必须启用 CONFIG_TRANSPARENT_HUGEPAGE 宏和 CONFIG_TRANSPARENT_HUGEPAGE_ALWAYS 宏, 并且系统物理内存大于 512MiB。本节用于介绍应用程序分配的 4KiB 物理页合并成 AnonHugepage 的方法, BiscuitOS 提供了使用案例,其在 BiscuitOS 上部署逻辑如下:
案例源码通过一个应用程序进行展示,程序在 26-36 行定义了函数 BiscuitOS_alloc() 函数,该函数用于从进程的地址空间 vaddr 处分配长度为 size 的虚拟内存,如果参数 populate 为 1,那么函数会为分配的虚拟内存映射实际的 4KiB 物理页. 接着进入 main() 函数,在 42 行处分配一段虚拟内存,其范围是 [0x6000000000, 0x6000080000), 并为这段虚拟内存直接分配物理内存与之建立页表,此时可以获得的区间大小为一个大页的 25%,物理内存也为一个大页的 25%。接着 44 行分配第二段虚拟内存,其范围是 [0x6000080000, 0x6000100000), 但没有为其分配物理内存,另外由于新分配的虚拟内存区域与上一块分配的虚拟内存区域相邻,那么系统会将其合并成一块更大的虚拟区域,其范围是 [0x6000000000, 0x6000100000), 新的虚拟区域其长度为一个大页的 50%,物理内存为一个大页的 25%. 程序接着在 46 行分配一段虚拟内存,其范围是 [0x6000100000, 0x6000140000), 并为这段虚拟内存分配物理内存,另外由于新分配的区域与上一块区域相邻,那么系统将两段虚拟内存区域再次合并成一块更大的区域,此时新区域的范围是 [0x6000000000, 0x6000140000), 新的虚拟区域其长度为一个大页的 62.5%,物理内存为一个大页的 37.5%. 程序继续在 48 行分配一段虚拟内存,其范围是 [0x6000140000, 0x6000180000), 并为这段虚拟内存分配物理内存,另外由于新分配的区域与上一块区域相邻,那么系统将两段虚拟内存区域再次合并成一块更大的区域,此时新区域的范围是 [0x6000000000, 0x6000180000), 新的虚拟区域其长度为一个大页的 75%,物理内存为一个大页的 50%, 此时已经满足合并物理内存不小于 2MiB 大页的 50% 条件,但虚拟内存长度没有达到 2MiB。程序接着在 52 行行分配一段虚拟内存,其范围是 [0x6000180000, 0x6000200000), 并没有为这段虚拟内存分配物理内存,另外由于新分配的区域与上一块区域相邻,那么系统将两段虚拟内存区域再次合并成一块更大的区域,此时新区域的范围是 [0x6000000000, 0x6000200000), 新的虚拟区域其长度为一个大页的 100%,物理内存为一个大页的 50%, 此时已经满足合并的两个条件,系统将 2MiB 区域使用的 4KiB 物理页合并成一个 2MiB 的 AnonHugepage 大页. 至此程序的功能完结,案例代码很精简,另外当系统启动之后需要设置 THP 的策略为 always, 那么接下来通过下面的命令在 BiscuitOS 上进行实践:
可以看到当系统启动之后,向 “/sys/kernel/mm/transparent_hugepage/enabled” 写入 always 时,那么 THP 的策略就是满足条件直接分配. 接着后台运行应用程序 BiscuitOS-THP-AnonHugepage-merge-default,此时查看 AnonHugepage 的使用情况,可以看出此时进程占用了 1 个 AnonHugepage 大页,总共 2MiB 物理内存,这与源码访问 2MiB 虚拟内存对应. 至此实践完毕符合预期.
匿名私有映射: Madvise 4K-Page 为 AnonHugepage 大页
在启用透明大页的系统中,如果对 THP 的策略是 madvise 时,那么进程通过匿名私有方式分配虚拟内存,可以通过调用 madvise() 函数与 MADV_HUGEPAGE 参数配合将指定的虚拟内存对应的 4KiB 物理页合并为 AnonHugepage 大页。madvise() 作用的位置不同导致的策略不同,如果虚拟内存已经映射了 4KiB 物理页,那么调用 madvise() 函数会将对应的 4KiB 页合并成一个 AnonHugepage 大页; 如果虚拟内存没有映射任何 4KiB 物理页,那么调用 madvise() 函数时不会分配任何 AnonHugepage 大页,只有访问 madvise() 指定虚拟区域的任一个位置,那么系统直接为虚拟区域分配一个 AnonHugepage 大页. 为了更好实践 AnonHugepages,内核必须启用 CONFIG_TRANSPARENT_HUGEPAGE 宏和 CONFIG_TRANSPARENT_HUGEPAGE_ALWAYS 宏。本节用于介绍应用程序通过 madvise() 函数将分配的 4KiB 物理页合并成 AnonHugepage 的方法, BiscuitOS 提供了使用案例,其在 BiscuitOS 上部署逻辑如下:
BiscuitOS-THP-AnonHugepage-madvise-default Gitee Source Code
案例源码通过一个应用程序进行展示,程序在 29-39 行通过 mmap() 函数以匿名私有的方式分配了 8MiB 的虚拟内存,由于使用了 MAP_POPULATE 标志,系统同时分配了 8MiB 的物理内存并与虚拟内存建立页表。接着在 45 行调用 madvise() 函数结合 MADV_HUGEPAGE 参数建议将 [0x0x6000000000, 0x6000200000) 区域使用 AnonHugepage 大页,由于 madvise() 函数之前该区域全部由 4KiB 的物理页构成,那么当调用 madvise() 函数时就会直接触发 4KiB 物理页 Merge 到 AnonHugepage。程序 48-50 行的作用是对剩余的 3 段 2MiB 虚拟内存进行访问,其对应的物理内存由 4KiB 构成. 至此程序的功能完结,案例代码很精简,另外当系统启动之后需要设置 THP 的策略为 always, 那么接下来通过下面的命令在 BiscuitOS 上进行实践:
可以看到当系统启动之后,向 “/sys/kernel/mm/transparent_hugepage/enabled” 写入 madvise 时,那么 THP 的策略就是建议指定区域使用 AnonHugepage. 接着后台运行应用程序 BiscuitOS-THP-AnonHugepage-madvise-default,此时查看 AnonHugepage 的使用情况,可以看出此时进程占用了 1 个 AnonHugepage 大页,总共 8MiB 物理内存,这与源码访问 8MiB 虚拟内存对应. 至此实践完毕符合预期.