在《[一文读懂 HugePages的原理] 》一文中介绍了 HugePages(大内存页)的原理和使用,现在我们来分析一下 Linux 内核是怎么实现 HugePages 分配的。
本文使用 Linux 内核 2.6.23 版本
在内核初始化时,会调用 hugetlb_init
函数对 HugePages 分配器进行初始化,其实现如下:
1static int __init hugetlb_init(void)
2{
3 unsigned long i;
4
5 // 1. 初始化空闲大内存页链表 hugepage_freelists,
6 // 内核使用 hugepage_freelists 链表把空闲的大内存页连接起来,
7 // 为了分析简单,我们可以把 MAX_NUMNODES 当成 1
8 for (i = 0; i < MAX_NUMNODES; ++i)
9 INIT_LIST_HEAD(&hugepage_freelists[i]);
10
11 // 2. max_huge_pages 为系统能够使用的大页内存的数量,
12 // 由系统启动项 hugepages 指定,
13 // 这里主要申请大内存页, 并且保存到 hugepage_freelists 链表中.
14 for (i = 0; i < max_huge_pages; ++i) {
15 if (!alloc_fresh_huge_page())
16 break;
17 }
18
19 max_huge_pages = free_huge_pages = nr_huge_pages = i;
20
21 return 0;
22}
hugetlb_init
函数主要完成两个工作:
hugepage_freelists
,这个链表保存了系统中能够使用的大内存。hugepage_freelists
链表中。我们再来分析下 alloc_fresh_huge_page
函数是怎么申请大内存页的,其实现如下:
1static int alloc_fresh_huge_page(void)
2{
3 static int prev_nid;
4 struct page *page;
5 int nid;
6 ...
7 // 1. 申请一个大的物理内存页...
8 page = alloc_pages_node(nid, htlb_alloc_mask|__GFP_COMP|__GFP_NOWARN,
9 HUGETLB_PAGE_ORDER);
10
11 if (page) {
12 // 2. 设置释放大内存页的回调函数为 free_huge_page
13 set_compound_page_dtor(page, free_huge_page);
14 ...
15 // 3. put_page 函数将会调用上面设置的 free_huge_page 函数把内存页放入到缓存队列中
16 put_page(page);
17
18 return 1;
19 }
20
21 return 0;
22}
所以,alloc_fresh_huge_page
函数主要完成三个工作:
alloc_pages_node
函数申请一个大内存页(2MB)。free_huge_page
,当释放大内存页时,将会调用这个函数进行释放操作。put_page
函数释放大内存页,其将会调用 free_huge_page
函数进行相关操作。那么,我们来看看 free_huge_page
函数是怎么释放大内存页的,其实现如下:
1static void free_huge_page(struct page *page)
2{
3 ...
4 enqueue_huge_page(page); // 把大内存页放置到空闲大内存页链表中
5 ...
6}
free_huge_page
函数主要调用 enqueue_huge_page
函数把大内存页添加到空闲大内存页链表中,其实现如下:
1static void enqueue_huge_page(struct page *page)
2{
3 int nid = page_to_nid(page); // 我们假设这里一定返回 0
4
5 // 把大内存页添加到空闲链表 hugepage_freelists 中
6 list_add(&page->lru, &hugepage_freelists[nid]);
7
8 // 增加计数器
9 free_huge_pages++;
10 free_huge_pages_node[nid]++;
11}
从上面的实现可知,enqueue_huge_page
函数只是简单的把大内存页添加到空闲链表 hugepage_freelists
中,并且增加计数器。
假如我们设置了系统能够使用的大内存页为 100 个,那么空闲大内存页链表 hugepage_freelists
的结构如下图所示:
所以,HugePages 分配器初始化的调用链为:
1hugetlb_init()
2 |
3 +——> alloc_fresh_huge_page()
4 |
5 |——> alloc_pages_node()
6 |——> set_compound_page_dtor()
7 +——> put_page()
8 |
9 +——> free_huge_page()
10 |
11 +——> enqueue_huge_page()
为系统准备好空闲的大内存页后,现在来了解下怎样分配大内存页。在《[一文读懂 HugePages的原理] 》一文中介绍过,要申请大内存页,必须使用 mmap
系统调用把虚拟内存映射到 hugetlbfs
文件系统中的文件中。
免去繁琐的文件系统挂载过程,我们主要来看看当使用 mmap
系统调用把虚拟内存映射到 hugetlbfs
文件系统的文件时会发生什么事情。
每个文件描述符对象都有个 mmap
的方法,此方法会在调用 mmap
函数映射到文件时被触发,我们来看看 hugetlbfs
文件的 mmap
方法所对应的真实函数,如下:
1const struct file_operations hugetlbfs_file_operations = {
2 .mmap = hugetlbfs_file_mmap,
3 .fsync = simple_sync_file,
4 .get_unmapped_area = hugetlb_get_unmapped_area,
5};
从上面的代码可以发现,hugetlbfs
文件的 mmap
方法被设置为 hugetlbfs_file_mmap
函数。所以当调用 mmap
函数映射 hugetlbfs
文件时,将会调用 hugetlbfs_file_mmap
函数来处理。
而 hugetlbfs_file_mmap
函数最主要的工作就是把虚拟内存分区对象的 vm_flags
字段添加 VM_HUGETLB
标志位,如下代码:
1static int
2hugetlbfs_file_mmap(struct file *file, struct vm_area_struct *vma)
3{
4 ...
5 vma->vm_flags |= VM_HUGETLB | VM_RESERVED; // 为虚拟内存分区添加 VM_HUGETLB 标志位
6 ...
7 return ret;
8}
为虚拟内存分区对象设置 VM_HUGETLB
标志位的作用是:当对虚拟内存分区进行物理内存映射时,会进行特殊的处理,下面将会介绍。
使用 mmap
函数映射到 hugetlbfs
文件后,会返回一个虚拟内存地址。当对这个虚拟内存地址进行访问(读写)时,由于此虚拟内存地址还没有与物理内存地址进行映射,将会触发 缺页异常
,内核会调用 do_page_fault
函数对 缺页异常
进行修复。
我们来看看整个流程,如下图所示:
所以,最终会调用 do_page_fault
函数对 缺页异常
进行修复操作,我们来看看 do_page_fault
做了什么工作,实现如下:
1asmlinkage void
2__kprobes do_page_fault(struct pt_regs *regs, unsigned long error_code)
3{
4 ...
5 struct mm_struct *mm;
6 struct vm_area_struct *vma;
7 unsigned long address;
8 ...
9
10 mm = tsk->mm; // 1. 获取当前进程对应的内存管理对象
11 address = read_cr2(); // 2. 获取触发缺页异常的虚拟内存地址
12
13 ...
14 vma = find_vma(mm, address); // 3. 通过虚拟内存地址获取对应的虚拟内存分区对象
15 ...
16
17 // 4. 调用 handle_mm_fault 函数对异常进行修复
18 fault = handle_mm_fault(mm, vma, address, write);
19 ...
20
21 return;
22}
上面代码对 do_page_fault
进行了精简,精简后主要完成4个工作:
read_cr2
获取触发缺页异常的虚拟内存地址。缺页异常
的虚拟内存地址获取对应的虚拟内存分区对象。handle_mm_fault
函数对 缺页异常
进行修复。我们继续来看看 handle_mm_fault
函数的实现,代码如下:
1int handle_mm_fault(struct mm_struct *mm, struct vm_area_struct *vma,
2 unsigned long address, int write_access)
3{
4 ...
5 if (unlikely(is_vm_hugetlb_page(vma))) // 虚拟内存分区是否需要使用 HugePages
6 return hugetlb_fault(mm, vma, address, write_access); // 如果使用 HugePages, 就调用 hugetlb_fault 进行处理
7 ...
8}
对 handle_mm_fault
函数进行精简后,逻辑就非常清晰。如果虚拟内存分区使用 HugePages,那么就调用 hugetlb_fault
函数进行处理(由于我们分析使用 HugePages 的情况,所以刚好进入这个分支)。
hugetlb_fault
函数主要对进程的页表进行填充,所以我们先来回顾一下 HugePages 对应的页表结构,如下图:
从上图可以看出,使用 HugePages 后,页中间目录
直接指向物理内存页。所以,hugetlb_fault
函数主要就是对 页中间目录项
进行填充。实现如下:
1int hugetlb_fault(struct mm_struct *mm, struct vm_area_struct *vma,
2 unsigned long address, int write_access)
3{
4 pte_t *ptep;
5 pte_t entry;
6 int ret;
7
8 ptep = huge_pte_alloc(mm, address); // 1. 找到虚拟内存地址对应的页中间目录项
9 ...
10 entry = *ptep;
11
12 if (pte_none(entry)) { // 如果页中间目录项还没进行映射
13 // 2. 那么调用 hugetlb_no_page 函数进行映射操作
14 ret = hugetlb_no_page(mm, vma, address, ptep, write_access);
15 ...
16 return ret;
17 }
18 ...
19}
对 hugetlb_fault
函数进行精简后,主要完成两个工作:
缺页异常
的虚拟内存地址找到其对应的 页中间目录项
。hugetlb_no_page
函数对 页中间目录项
进行映射操作。我们再来看看 hugetlb_no_page
函数怎么对 页中间目录项
进行填充:
1static int
2hugetlb_no_page(struct mm_struct *mm, struct vm_area_struct *vma,
3 unsigned long address, pte_t *ptep, int write_access)
4{
5 ...
6 page = find_lock_page(mapping, idx);
7 if (!page) {
8 ...
9 // 1. 从空闲大内存页链表 hugepage_freelists 中申请一个大内存页
10 page = alloc_huge_page(vma, address);
11 ...
12 }
13 ...
14 // 2. 通过大内存页的物理地址生成页中间目录项的值
15 new_pte = make_huge_pte(vma, page, ((vma->vm_flags & VM_WRITE)
16 && (vma->vm_flags & VM_SHARED)));
17
18 // 3. 设置页中间目录项的值为上面生成的值
19 set_huge_pte_at(mm, address, ptep, new_pte);
20 ...
21 return ret;
22}
通过对 hugetlb_no_page
函数进行精简后,主要完成3个工作:
alloc_huge_page
函数从空闲大内存页链表 hugepage_freelists
中申请一个大内存页。至此,HugePages 的映射过程已经完成。
还有个问题,就是 CPU 怎么知道
页中间表项
指向的是页表
还是大内存页
呢?这是因为
页中间表项
有个PSE
的标志位,如果将其设置为1,那么就表明其指向大内存页
,否则就指向页表
。
本文介绍了 HugePages 实现的整个流程,当然本文也只是介绍了申请内存的流程,释放内存的流程并没有分析,如果有兴趣的话可以自己查阅源码。
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/ys-sAVbe8bmv21FK2n9PlQ
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。