~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~

TOMOYO Linux Cross Reference
Linux/Documentation/translations/zh_CN/mm/hugetlbfs_reserv.rst

Version: ~ [ linux-6.12-rc7 ] ~ [ linux-6.11.7 ] ~ [ linux-6.10.14 ] ~ [ linux-6.9.12 ] ~ [ linux-6.8.12 ] ~ [ linux-6.7.12 ] ~ [ linux-6.6.60 ] ~ [ linux-6.5.13 ] ~ [ linux-6.4.16 ] ~ [ linux-6.3.13 ] ~ [ linux-6.2.16 ] ~ [ linux-6.1.116 ] ~ [ linux-6.0.19 ] ~ [ linux-5.19.17 ] ~ [ linux-5.18.19 ] ~ [ linux-5.17.15 ] ~ [ linux-5.16.20 ] ~ [ linux-5.15.171 ] ~ [ linux-5.14.21 ] ~ [ linux-5.13.19 ] ~ [ linux-5.12.19 ] ~ [ linux-5.11.22 ] ~ [ linux-5.10.229 ] ~ [ linux-5.9.16 ] ~ [ linux-5.8.18 ] ~ [ linux-5.7.19 ] ~ [ linux-5.6.19 ] ~ [ linux-5.5.19 ] ~ [ linux-5.4.285 ] ~ [ linux-5.3.18 ] ~ [ linux-5.2.21 ] ~ [ linux-5.1.21 ] ~ [ linux-5.0.21 ] ~ [ linux-4.20.17 ] ~ [ linux-4.19.323 ] ~ [ linux-4.18.20 ] ~ [ linux-4.17.19 ] ~ [ linux-4.16.18 ] ~ [ linux-4.15.18 ] ~ [ linux-4.14.336 ] ~ [ linux-4.13.16 ] ~ [ linux-4.12.14 ] ~ [ linux-4.11.12 ] ~ [ linux-4.10.17 ] ~ [ linux-4.9.337 ] ~ [ linux-4.4.302 ] ~ [ linux-3.10.108 ] ~ [ linux-2.6.32.71 ] ~ [ linux-2.6.0 ] ~ [ linux-2.4.37.11 ] ~ [ unix-v6-master ] ~ [ ccs-tools-1.8.12 ] ~ [ policy-sample ] ~
Architecture: ~ [ i386 ] ~ [ alpha ] ~ [ m68k ] ~ [ mips ] ~ [ ppc ] ~ [ sparc ] ~ [ sparc64 ] ~

Diff markup

Differences between /Documentation/translations/zh_CN/mm/hugetlbfs_reserv.rst (Architecture i386) and /Documentation/translations/zh_CN/mm/hugetlbfs_reserv.rst (Architecture ppc)


  1 .. include:: ../disclaimer-zh_CN.rst                1 .. include:: ../disclaimer-zh_CN.rst
  2                                                     2 
  3 :Original: Documentation/mm/hugetlbfs_reserv.r      3 :Original: Documentation/mm/hugetlbfs_reserv.rst
  4                                                     4 
  5 :翻译:                                            5 :翻译:
  6                                                     6 
  7  司延腾 Yanteng Si <siyanteng@loongson.cn>        7  司延腾 Yanteng Si <siyanteng@loongson.cn>
  8                                                     8 
  9 :校译:                                            9 :校译:
 10                                                    10 
 11 ==============                                     11 ==============
 12 Hugetlbfs 预留                                   12 Hugetlbfs 预留
 13 ==============                                     13 ==============
 14                                                    14 
 15 概述                                             15 概述
 16 ====                                               16 ====
 17                                                    17 
 18 Documentation/admin-guide/mm/hugetlbpage.rst       18 Documentation/admin-guide/mm/hugetlbpage.rst
 19 中描述的巨页通常是预先分配给应     19 中描述的巨页通常是预先分配给应用程序使用的 。如果VMA指
 20 示要使用巨页,这些巨页会在缺页     20 示要使用巨页,这些巨页会在缺页异常时被实例化到任务的地址空间。如果在缺页异常
 21 时没有巨页存在,任务就会被发送     21 时没有巨页存在,任务就会被发送一个SIGBUS,并经常不高兴地死去。在加入巨页支
 22 持后不久,人们决定,在mmap()时检     22 持后不久,人们决定,在mmap()时检测巨页的短缺情况会更好。这个想法是,如果
 23 没有足够的巨页来覆盖映射,mmap()     23 没有足够的巨页来覆盖映射,mmap()将失败。这首先是在mmap()时在代码中做一个
 24 简单的检查,以确定是否有足够的     24 简单的检查,以确定是否有足够的空闲巨页来覆盖映射。就像内核中的大多数东西一
 25 样,代码随着时间的推移而不断发     25 样,代码随着时间的推移而不断发展。然而,基本的想法是在mmap()时 “预留”
 26 巨页,以确保巨页可以用于该映射     26 巨页,以确保巨页可以用于该映射中的缺页异常。下面的描述试图描述在v4.10内核
 27 中是如何进行巨页预留处理的。         27 中是如何进行巨页预留处理的。
 28                                                    28 
 29                                                    29 
 30 读者                                             30 读者
 31 ====                                               31 ====
 32 这个描述主要是针对正在修改hugetlb     32 这个描述主要是针对正在修改hugetlbfs代码的内核开发者。
 33                                                    33 
 34                                                    34 
 35 数据结构                                       35 数据结构
 36 ========                                           36 ========
 37                                                    37 
 38 resv_huge_pages                                    38 resv_huge_pages
 39         这是一个全局的(per-hstate)     39         这是一个全局的(per-hstate)预留的巨页的计数。预留的巨页只对预留它们的任
 40         务可用。因此,一般可用的     40         务可用。因此,一般可用的巨页的数量被计算为(``free_huge_pages - resv_huge_pages``)。
 41 Reserve Map                                        41 Reserve Map
 42         预留映射由以下结构体描述::     42         预留映射由以下结构体描述::
 43                                                    43 
 44                 struct resv_map {                  44                 struct resv_map {
 45                         struct kref refs;          45                         struct kref refs;
 46                         spinlock_t lock;           46                         spinlock_t lock;
 47                         struct list_head regio     47                         struct list_head regions;
 48                         long adds_in_progress;     48                         long adds_in_progress;
 49                         struct list_head regio     49                         struct list_head region_cache;
 50                         long region_cache_coun     50                         long region_cache_count;
 51                 };                                 51                 };
 52                                                    52 
 53         系统中每个巨页映射都有一     53         系统中每个巨页映射都有一个预留映射。resv_map中的regions列表描述了映射中的
 54         区域。一个区域被描述为::        54         区域。一个区域被描述为::
 55                                                    55 
 56                 struct file_region {               56                 struct file_region {
 57                         struct list_head link;     57                         struct list_head link;
 58                         long from;                 58                         long from;
 59                         long to;                   59                         long to;
 60                 };                                 60                 };
 61                                                    61 
 62         file_region结构体的 ‘from’ 和     62         file_region结构体的 ‘from’ 和 ‘to’ 字段是进入映射的巨页索引。根据映射的类型,在
 63         reserv_map 中的一个区域可能表     63         reserv_map 中的一个区域可能表示该范围存在预留,或预留不存在。
 64 Flags for MAP_PRIVATE Reservations                 64 Flags for MAP_PRIVATE Reservations
 65         这些被存储在预留的映射指     65         这些被存储在预留的映射指针的底部。
 66                                                    66 
 67         ``#define HPAGE_RESV_OWNER    (1UL <<      67         ``#define HPAGE_RESV_OWNER    (1UL << 0)``
 68                 表示该任务是与该映射     68                 表示该任务是与该映射相关的预留的所有者。
 69         ``#define HPAGE_RESV_UNMAPPED (1UL <<      69         ``#define HPAGE_RESV_UNMAPPED (1UL << 1)``
 70                 表示最初映射此范围(     70                 表示最初映射此范围(并创建储备)的任务由于COW失败而从该任务(子任务)中取消映
 71                 射了一个页面。              71                 射了一个页面。
 72 Page Flags                                         72 Page Flags
 73         PagePrivate页面标志是用来指示     73         PagePrivate页面标志是用来指示在释放巨页时必须恢复巨页的预留。更多细节将在
 74         “释放巨页” 一节中讨论。      74         “释放巨页” 一节中讨论。
 75                                                    75 
 76                                                    76 
 77 预留映射位置(私有或共享)            77 预留映射位置(私有或共享)
 78 ==========================                         78 ==========================
 79                                                    79 
 80 一个巨页映射或段要么是私有的,     80 一个巨页映射或段要么是私有的,要么是共享的。如果是私有的,它通常只对一个地址空间
 81 (任务)可用。如果是共享的,它     81 (任务)可用。如果是共享的,它可以被映射到多个地址空间(任务)。对于这两种类型的映射,
 82 预留映射的位置和语义是明显不同     82 预留映射的位置和语义是明显不同的。位置的差异是:
 83                                                    83 
 84 - 对于私有映射,预留映射挂在VMA     84 - 对于私有映射,预留映射挂在VMA结构体上。具体来说,就是vma->vm_private_data。这个保
 85   留映射是在创建映射(mmap(MAP_PRIVA     85   留映射是在创建映射(mmap(MAP_PRIVATE))时创建的。
 86 - 对于共享映射,预留映射挂在inode     86 - 对于共享映射,预留映射挂在inode上。具体来说,就是inode->i_mapping->private_data。
 87   由于共享映射总是由hugetlbfs文件     87   由于共享映射总是由hugetlbfs文件系统中的文件支持,hugetlbfs代码确保每个节点包含一个预
 88   留映射。因此,预留映射在创建     88   留映射。因此,预留映射在创建节点时被分配。
 89                                                    89 
 90                                                    90 
 91 创建预留                                       91 创建预留
 92 ========                                           92 ========
 93 当创建一个巨大的有页面支持的共     93 当创建一个巨大的有页面支持的共享内存段(shmget(SHM_HUGETLB))或通过mmap(MAP_HUGETLB)
 94 创建一个映射时,就会创建预留。     94 创建一个映射时,就会创建预留。这些操作会导致对函数hugetlb_reserve_pages()的调用::
 95                                                    95 
 96         int hugetlb_reserve_pages(struct inode     96         int hugetlb_reserve_pages(struct inode *inode,
 97                                   long from, l     97                                   long from, long to,
 98                                   struct vm_ar     98                                   struct vm_area_struct *vma,
 99                                   vm_flags_t v     99                                   vm_flags_t vm_flags)
100                                                   100 
101 hugetlb_reserve_pages()做的第一件事是    101 hugetlb_reserve_pages()做的第一件事是检查在调用shmget()或mmap()时是否指定了NORESERVE
102 标志。如果指定了NORESERVE,那么这    102 标志。如果指定了NORESERVE,那么这个函数立即返回,因为不需要预留。
103                                                   103 
104 参数'from'和'to'是映射或基础文件的    104 参数'from'和'to'是映射或基础文件的巨页索引。对于shmget(),'from'总是0,'to'对应于段/映射
105 的长度。对于mmap(),offset参数可以    105 的长度。对于mmap(),offset参数可以用来指定进入底层文件的偏移量。在这种情况下,'from'和'to'
106 参数已经被这个偏移量所调整。        106 参数已经被这个偏移量所调整。
107                                                   107 
108 PRIVATE和SHARED映射之间的一个很大的    108 PRIVATE和SHARED映射之间的一个很大的区别是预留在预留映射中的表示方式。
109                                                   109 
110 - 对于共享映射,预留映射中的条    110 - 对于共享映射,预留映射中的条目表示对应页面的预留存在或曾经存在。当预留被消耗时,预留映射不被
111   修改。                                       111   修改。
112 - 对于私有映射,预留映射中没有    112 - 对于私有映射,预留映射中没有条目表示相应页面存在预留。随着预留被消耗,条目被添加到预留映射中。
113   因此,预留映射也可用于确定哪    113   因此,预留映射也可用于确定哪些预留已被消耗。
114                                                   114 
115 对于私有映射,hugetlb_reserve_pages()    115 对于私有映射,hugetlb_reserve_pages()创建预留映射并将其挂在VMA结构体上。此外,
116 HPAGE_RESV_OWNER标志被设置,以表明该    116 HPAGE_RESV_OWNER标志被设置,以表明该VMA拥有预留。
117                                                   117 
118 预留映射被查阅以确定当前映射/段    118 预留映射被查阅以确定当前映射/段需要多少巨页预留。对于私有映射,这始终是一个值(to - from)。
119 然而,对于共享映射来说,一些预    119 然而,对于共享映射来说,一些预留可能已经存在于(to - from)的范围内。关于如何实现这一点的细节,
120 请参见 :ref:`预留映射的修改 <resv_ma    120 请参见 :ref:`预留映射的修改 <resv_map_modifications>` 一节。
121                                                   121 
122 该映射可能与一个子池(subpool)相    122 该映射可能与一个子池(subpool)相关联。如果是这样,将查询子池以确保有足够的空间用于映射。子池
123 有可能已经预留了可用于映射的预    123 有可能已经预留了可用于映射的预留空间。更多细节请参见 :ref: `子池预留 <sub_pool_resv>`
124 一节。                                         124 一节。
125                                                   125 
126 在咨询了预留映射和子池之后,就    126 在咨询了预留映射和子池之后,就知道了需要的新预留数量。hugetlb_acct_memory()函数被调用以检查
127 并获取所要求的预留数量。hugetlb_ac    127 并获取所要求的预留数量。hugetlb_acct_memory()调用到可能分配和调整剩余页数的函数。然而,在这
128 些函数中,代码只是检查以确保有    128 些函数中,代码只是检查以确保有足够的空闲的巨页来容纳预留。如果有的话,全局预留计数resv_huge_pages
129 会被调整,如下所示::                     129 会被调整,如下所示::
130                                                   130 
131         if (resv_needed <= (resv_huge_pages -     131         if (resv_needed <= (resv_huge_pages - free_huge_pages))
132                 resv_huge_pages += resv_needed    132                 resv_huge_pages += resv_needed;
133                                                   133 
134 注意,在检查和调整这些计数器时    134 注意,在检查和调整这些计数器时,全局锁hugetlb_lock会被预留。
135                                                   135 
136 如果有足够的空闲的巨页,并且全    136 如果有足够的空闲的巨页,并且全局计数resv_huge_pages被调整,那么与映射相关的预留映射被修改以
137 反映预留。在共享映射的情况下,    137 反映预留。在共享映射的情况下,将存在一个file_region,包括'from'-'to'范围。对于私有映射,
138 不对预留映射进行修改,因为没有    138 不对预留映射进行修改,因为没有条目表示存在预留。
139                                                   139 
140 如果hugetlb_reserve_pages()成功,全局    140 如果hugetlb_reserve_pages()成功,全局预留数和与映射相关的预留映射将根据需要被修改,以确保
141 在'from'-'to'范围内存在预留。            141 在'from'-'to'范围内存在预留。
142                                                   142 
143 消耗预留/分配一个巨页                   143 消耗预留/分配一个巨页
144 ===========================                       144 ===========================
145                                                   145 
146 当与预留相关的巨页在相应的映射    146 当与预留相关的巨页在相应的映射中被分配和实例化时,预留就被消耗了。该分配是在函数alloc_hugetlb_folio()
147 中进行的::                                    147 中进行的::
148                                                   148 
149         struct folio *alloc_hugetlb_folio(stru    149         struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma,
150                                      unsigned     150                                      unsigned long addr, int avoid_reserve)
151                                                   151 
152 alloc_hugetlb_folio被传递给一个VMA指针    152 alloc_hugetlb_folio被传递给一个VMA指针和一个虚拟地址,因此它可以查阅预留映射以确定是否存在预留。
153 此外,alloc_hugetlb_folio需要一个参数    153 此外,alloc_hugetlb_folio需要一个参数avoid_reserve,该参数表示即使看起来已经为指定的地址预留了
154 预留,也不应该使用预留。avoid_rese    154 预留,也不应该使用预留。avoid_reserve参数最常被用于写时拷贝和页面迁移的情况下,即现有页面的额
155 外拷贝被分配。                             155 外拷贝被分配。
156                                                   156 
157                                                   157 
158 调用辅助函数vma_needs_reservation()来    158 调用辅助函数vma_needs_reservation()来确定是否存在对映射(vma)中地址的预留。关于这个函数的详
159 细内容,请参见 :ref:`预留映射帮助    159 细内容,请参见 :ref:`预留映射帮助函数 <resv_map_helpers>` 一节。从
160 vma_needs_reservation()返回的值通常为0    160 vma_needs_reservation()返回的值通常为0或1。如果该地址存在预留,则为0,如果不存在预留,则为1。
161 如果不存在预留,并且有一个与映    161 如果不存在预留,并且有一个与映射相关联的子池,则查询子池以确定它是否包含预留。如果子池包含预留,
162 则可将其中一个用于该分配。然而    162 则可将其中一个用于该分配。然而,在任何情况下,avoid_reserve参数都会优先考虑为分配使用预留。在
163 确定预留是否存在并可用于分配后    163 确定预留是否存在并可用于分配后,调用dequeue_huge_page_vma()函数。这个函数需要两个与预留有关
164 的参数:                                      164 的参数:
165                                                   165 
166 - avoid_reserve,这是传递给alloc_hugetlb    166 - avoid_reserve,这是传递给alloc_hugetlb_folio()的同一个值/参数。
167 - chg,尽管这个参数的类型是long,    167 - chg,尽管这个参数的类型是long,但只有0或1的值被传递给dequeue_huge_page_vma。如果该值为0,
168   则表明存在预留(关于可能的问    168   则表明存在预留(关于可能的问题,请参见 “预留和内存策略” 一节)。如果值
169   为1,则表示不存在预留,如果可    169   为1,则表示不存在预留,如果可能的话,必须从全局空闲池中取出该页。
170                                                   170 
171 与VMA的内存策略相关的空闲列表被    171 与VMA的内存策略相关的空闲列表被搜索到一个空闲页。如果找到了一个页面,当该页面从空闲列表中移除时,
172 free_huge_pages的值被递减。如果有一    172 free_huge_pages的值被递减。如果有一个与该页相关的预留,将进行以下调整::
173                                                   173 
174         SetPagePrivate(page);   /* 表示分    174         SetPagePrivate(page);   /* 表示分配这个页面消耗了一个预留,
175                                  * 如果遇    175                                  * 如果遇到错误,以至于必须释放这个页面,预留将被
176                                  * 恢复。 *    176                                  * 恢复。 */
177         resv_huge_pages--;      /* 减少全    177         resv_huge_pages--;      /* 减少全局预留计数 */
178                                                   178 
179 注意,如果找不到满足VMA内存策略    179 注意,如果找不到满足VMA内存策略的巨页,将尝试使用伙伴分配器分配一个。这就带来了超出预留范围
180 的剩余巨页和超额分配的问题。即    180 的剩余巨页和超额分配的问题。即使分配了一个多余的页面,也会进行与上面一样的基于预留的调整:
181 SetPagePrivate(page) 和 resv_huge_pages--.       181 SetPagePrivate(page) 和 resv_huge_pages--.
182                                                   182 
183 在获得一个新的巨页后,(folio)->_hug    183 在获得一个新的巨页后,(folio)->_hugetlb_subpool被设置为与该页面相关的子池的值,如果它存在的话。当页
184 面被释放时,这将被用于子池的计    184 面被释放时,这将被用于子池的计数。
185                                                   185 
186 然后调用函数vma_commit_reservation(),    186 然后调用函数vma_commit_reservation(),根据预留的消耗情况调整预留映射。一般来说,这涉及
187 到确保页面在区域映射的file_region    187 到确保页面在区域映射的file_region结构体中被表示。对于预留存在的共享映射,预留映射中的条目
188 已经存在,所以不做任何改变。然    188 已经存在,所以不做任何改变。然而,如果共享映射中没有预留,或者这是一个私有映射,则必须创建一
189 个新的条目。                                189 个新的条目。
190                                                   190 
191 注意,如果找不到满足VMA内存策略    191 注意,如果找不到满足VMA内存策略的巨页,将尝试使用伙伴分配器分配一个。这就带来了超出预留范围
192 的剩余巨页和过度分配的问题。即    192 的剩余巨页和过度分配的问题。即使分配了一个多余的页面,也会进行与上面一样的基于预留的调整。
193 SetPagePrivate(page)和resv_huge_pages-。        193 SetPagePrivate(page)和resv_huge_pages-。
194                                                   194 
195 在获得一个新的巨页后,(page)->priva    195 在获得一个新的巨页后,(page)->private被设置为与该页面相关的子池的值,如果它存在的话。当页
196 面被释放时,这将被用于子池的计    196 面被释放时,这将被用于子池的计数。
197                                                   197 
198 然后调用函数vma_commit_reservation(),    198 然后调用函数vma_commit_reservation(),根据预留的消耗情况调整预留映射。一般来说,这涉及
199 到确保页面在区域映射的file_region    199 到确保页面在区域映射的file_region结构体中被表示。对于预留存在的共享映射,预留映射中的条目
200 已经存在,所以不做任何改变。然    200 已经存在,所以不做任何改变。然而,如果共享映射中没有预留,或者这是一个私有映射,则必须创建
201 一个新的条目。                             201 一个新的条目。
202                                                   202 
203 在alloc_hugetlb_folio()开始调用vma_needs_    203 在alloc_hugetlb_folio()开始调用vma_needs_reservation()和页面分配后调用
204 vma_commit_reservation()之间,预留映射    204 vma_commit_reservation()之间,预留映射有可能被改变。如果hugetlb_reserve_pages在共
205 享映射中为同一页面被调用,这将    205 享映射中为同一页面被调用,这将是可能的。在这种情况下,预留计数和子池空闲页计数会有一个偏差。
206 这种罕见的情况可以通过比较vma_nee    206 这种罕见的情况可以通过比较vma_needs_reservation和vma_commit_reservation的返回值来
207 识别。如果检测到这种竞争,子池    207 识别。如果检测到这种竞争,子池和全局预留计数将被调整以进行补偿。关于这些函数的更多信息,请
208 参见 :ref:`预留映射帮助函数 <resv_ma    208 参见 :ref:`预留映射帮助函数 <resv_map_helpers>` 一节。
209                                                   209 
210                                                   210 
211 实例化巨页                                   211 实例化巨页
212 ==========                                        212 ==========
213                                                   213 
214 在巨页分配之后,页面通常被添加    214 在巨页分配之后,页面通常被添加到分配任务的页表中。在此之前,共享映射中的页面被添加到页面缓
215 存中,私有映射中的页面被添加到    215 存中,私有映射中的页面被添加到匿名反向映射中。在这两种情况下,PagePrivate标志被清除。因此,
216 当一个已经实例化的巨页被释放时    216 当一个已经实例化的巨页被释放时,不会对全局预留计数(resv_huge_pages)进行调整。
217                                                   217 
218                                                   218 
219 释放巨页                                      219 释放巨页
220 ========                                          220 ========
221                                                   221 
222 巨页释放是由函数free_huge_folio()执    222 巨页释放是由函数free_huge_folio()执行的。这个函数是hugetlbfs复合页的析构器。因此,它只传
223 递一个指向页面结构体的指针。当    223 递一个指向页面结构体的指针。当一个巨页被释放时,可能需要进行预留计算。如果该页与包含保
224 留的子池相关联,或者该页在错误    224 留的子池相关联,或者该页在错误路径上被释放,必须恢复全局预留计数,就会出现这种情况。
225                                                   225 
226 page->private字段指向与该页相关的任    226 page->private字段指向与该页相关的任何子池。如果PagePrivate标志被设置,它表明全局预留计数
227 应该被调整(关于如何设置这些标    227 应该被调整(关于如何设置这些标志的信息,请参见
228 :ref: `消耗预留/分配一个巨页 <consum    228 :ref: `消耗预留/分配一个巨页 <consume_resv>` )。
229                                                   229 
230                                                   230 
231 该函数首先调用hugepage_subpool_put_page    231 该函数首先调用hugepage_subpool_put_pages()来处理该页。如果这个函数返回一个0的值(不等于
232 传递的1的值),它表明预留与子池    232 传递的1的值),它表明预留与子池相关联,这个新释放的页面必须被用来保持子池预留的数量超过最小值。
233 因此,在这种情况下,全局resv_huge_    233 因此,在这种情况下,全局resv_huge_pages计数器被递增。
234                                                   234 
235 如果页面中设置了PagePrivate标志,    235 如果页面中设置了PagePrivate标志,那么全局resv_huge_pages计数器将永远被递增。
236                                                   236 
237 子池预留                                      237 子池预留
238 ========                                          238 ========
239                                                   239 
240 有一个结构体hstate与每个巨页尺寸    240 有一个结构体hstate与每个巨页尺寸相关联。hstate跟踪所有指定大小的巨页。一个子池代表一
241 个hstate中的页面子集,它与一个已    241 个hstate中的页面子集,它与一个已挂载的hugetlbfs文件系统相关
242                                                   242 
243 当一个hugetlbfs文件系统被挂载时,    243 当一个hugetlbfs文件系统被挂载时,可以指定min_size选项,它表示文件系统所需的最小的巨页数量。
244 如果指定了这个选项,与min_size相    244 如果指定了这个选项,与min_size相对应的巨页的数量将被预留给文件系统使用。这个数字在结构体
245 hugepage_subpool的min_hpages字段中被跟    245 hugepage_subpool的min_hpages字段中被跟踪。在挂载时,hugetlb_acct_memory(min_hpages)
246 被调用以预留指定数量的巨页。如    246 被调用以预留指定数量的巨页。如果它们不能被预留,挂载就会失败。
247                                                   247 
248 当从子池中获取或释放页面时,会    248 当从子池中获取或释放页面时,会调用hugepage_subpool_get/put_pages()函数。
249 hugepage_subpool_get/put_pages被传递给巨    249 hugepage_subpool_get/put_pages被传递给巨页数量,以此来调整子池的 “已用页面” 计数
250 (get为下降,put为上升)。通常情    250 (get为下降,put为上升)。通常情况下,如果子池中没有足够的页面,它们会返回与传递的相同的值或
251 一个错误。                                   251 一个错误。
252                                                   252 
253 然而,如果预留与子池相关联,可    253 然而,如果预留与子池相关联,可能会返回一个小于传递值的返回值。这个返回值表示必须进行的额外全局
254 池调整的数量。例如,假设一个子    254 池调整的数量。例如,假设一个子池包含3个预留的巨页,有人要求5个。与子池相关的3个预留页可以用来
255 满足部分请求。但是,必须从全局    255 满足部分请求。但是,必须从全局池中获得2个页面。为了向调用者转达这一信息,将返回值2。然后,调用
256 者要负责从全局池中获取另外两个    256 者要负责从全局池中获取另外两个页面。
257                                                   257 
258                                                   258 
259 COW和预留                                      259 COW和预留
260 ==========                                        260 ==========
261                                                   261 
262 由于共享映射都指向并使用相同的    262 由于共享映射都指向并使用相同的底层页面,COW最大的预留问题是私有映射。在这种情况下,两个任务可
263 以指向同一个先前分配的页面。一    263 以指向同一个先前分配的页面。一个任务试图写到该页,所以必须分配一个新的页,以便每个任务都指向它
264 自己的页。                                   264 自己的页。
265                                                   265 
266 当该页最初被分配时,该页的预留    266 当该页最初被分配时,该页的预留被消耗了。当由于COW而试图分配一个新的页面时,有可能没有空闲的巨
267 页,分配会失败。                          267 页,分配会失败。
268                                                   268 
269 当最初创建私有映射时,通过设置    269 当最初创建私有映射时,通过设置所有者的预留映射指针中的HPAGE_RESV_OWNER位来标记映射的所有者。
270 由于所有者创建了映射,所有者拥    270 由于所有者创建了映射,所有者拥有与映射相关的所有预留。因此,当一个写异常发生并且没有可用的页面
271 时,对预留的所有者和非所有者采    271 时,对预留的所有者和非所有者采取不同的行动。
272                                                   272 
273 在发生异常的任务不是所有者的情    273 在发生异常的任务不是所有者的情况下,异常将失败,该任务通常会收到一个SIGBUS。
274                                                   274 
275 如果所有者是发生异常的任务,我    275 如果所有者是发生异常的任务,我们希望它能够成功,因为它拥有原始的预留。为了达到这个目的,该页被
276 从非所有者任务中解映射出来。这    276 从非所有者任务中解映射出来。这样一来,唯一的引用就是来自拥有者的任务。此外,HPAGE_RESV_UNMAPPED
277 位被设置在非拥有任务的预留映射    277 位被设置在非拥有任务的预留映射指针中。如果非拥有者任务后来在一个不存在的页面上发生异常,它可能
278 会收到一个SIGBUS。但是,映射/预留    278 会收到一个SIGBUS。但是,映射/预留的原始拥有者的行为将与预期一致。
279                                                   279 
280 预留映射的修改                             280 预留映射的修改
281 ==============                                    281 ==============
282                                                   282 
283 以下低级函数用于对预留映射进行    283 以下低级函数用于对预留映射进行修改。通常情况下,这些函数不会被直接调用。而是调用一个预留映射辅
284 助函数,该函数调用这些低级函数    284 助函数,该函数调用这些低级函数中的一个。这些低级函数在源代码(mm/hugetlb.c)中得到了相当好的
285 记录。这些函数是::                        285 记录。这些函数是::
286                                                   286 
287         long region_chg(struct resv_map *resv,    287         long region_chg(struct resv_map *resv, long f, long t);
288         long region_add(struct resv_map *resv,    288         long region_add(struct resv_map *resv, long f, long t);
289         void region_abort(struct resv_map *res    289         void region_abort(struct resv_map *resv, long f, long t);
290         long region_count(struct resv_map *res    290         long region_count(struct resv_map *resv, long f, long t);
291                                                   291 
292 在预留映射上的操作通常涉及两个    292 在预留映射上的操作通常涉及两个操作:
293                                                   293 
294 1) region_chg()被调用来检查预留映射    294 1) region_chg()被调用来检查预留映射,并确定在指定的范围[f, t]内有多少页目前没有被代表。
295                                                   295 
296    调用代码执行全局检查和分配,    296    调用代码执行全局检查和分配,以确定是否有足够的巨页使操作成功。
297                                                   297 
298 2)                                                298 2)
299   a) 如果操作能够成功,region_add()    299   a) 如果操作能够成功,region_add()将被调用,以实际修改先前传递给region_chg()的相同范围
300      [f, t]的预留映射。                     300      [f, t]的预留映射。
301   b) 如果操作不能成功,region_abort    301   b) 如果操作不能成功,region_abort被调用,在相同的范围[f, t]内中止操作。
302                                                   302 
303 注意,这是一个两步的过程, region    303 注意,这是一个两步的过程, region_add()和 region_abort()在事先调用 region_chg()后保证
304 成功。 region_chg()负责预先分配任何    304 成功。 region_chg()负责预先分配任何必要的数据结构以确保后续操作(特别是 region_add())的
305 成功。                                         305 成功。
306                                                   306 
307 如上所述,region_chg()确定该范围内    307 如上所述,region_chg()确定该范围内当前没有在映射中表示的页面的数量。region_add()返回添加
308 到映射中的范围内的页数。在大多    308 到映射中的范围内的页数。在大多数情况下, region_add() 的返回值与 region_chg() 的返回值相
309 同。然而,在共享映射的情况下,    309 同。然而,在共享映射的情况下,有可能在调用 region_chg() 和 region_add() 之间对预留映射进
310 行更改。在这种情况下,region_add()    310 行更改。在这种情况下,region_add()的返回值将与region_chg()的返回值不符。在这种情况下,全局计数
311 和子池计数很可能是不正确的,需    311 和子池计数很可能是不正确的,需要调整。检查这种情况并进行适当的调整是调用者的责任。
312                                                   312 
313 函数region_del()被调用以从预留映射    313 函数region_del()被调用以从预留映射中移除区域。
314 它通常在以下情况下被调用:             314 它通常在以下情况下被调用:
315                                                   315 
316 - 当hugetlbfs文件系统中的一个文件    316 - 当hugetlbfs文件系统中的一个文件被删除时,该节点将被释放,预留映射也被释放。在释放预留映射
317   之前,所有单独的file_region结构体    317   之前,所有单独的file_region结构体必须被释放。在这种情况下,region_del的范围是[0, LONG_MAX]。
318 - 当一个hugetlbfs文件正在被截断时    318 - 当一个hugetlbfs文件正在被截断时。在这种情况下,所有在新文件大小之后分配的页面必须被释放。
319   此外,预留映射中任何超过新文    319   此外,预留映射中任何超过新文件大小的file_region条目必须被删除。在这种情况下,region_del
320   的范围是[new_end_of_file, LONG_MAX]。      320   的范围是[new_end_of_file, LONG_MAX]。
321 - 当在一个hugetlbfs文件中打洞时。    321 - 当在一个hugetlbfs文件中打洞时。在这种情况下,巨页被一次次从文件的中间移除。当这些页被移除
322   时,region_del()被调用以从预留映    322   时,region_del()被调用以从预留映射中移除相应的条目。在这种情况下,region_del被传递的范
323   围是[page_idx, page_idx + 1]。               323   围是[page_idx, page_idx + 1]。
324                                                   324 
325 在任何情况下,region_del()都会返回    325 在任何情况下,region_del()都会返回从预留映射中删除的页面数量。在非常罕见的情况下,region_del()
326 会失败。这只能发生在打洞的情况    326 会失败。这只能发生在打洞的情况下,即它必须分割一个现有的file_region条目,而不能分配一个新的
327 结构体。在这种错误情况下,region_    327 结构体。在这种错误情况下,region_del()将返回-ENOMEM。这里的问题是,预留映射将显示对该页有
328 预留。然而,子池和全局预留计数    328 预留。然而,子池和全局预留计数将不反映该预留。为了处理这种情况,调用函数hugetlb_fix_reserve_counts()
329 来调整计数器,使其与不能被删除    329 来调整计数器,使其与不能被删除的预留映射条目相对应。
330                                                   330 
331 region_count()在解除私有巨页映射时    331 region_count()在解除私有巨页映射时被调用。在私有映射中,预留映射中没有条目表明存在一个预留。
332 因此,通过计算预留映射中的条目    332 因此,通过计算预留映射中的条目数,我们知道有多少预留被消耗了,有多少预留是未完成的
333 (Outstanding = (end - start) - region_count    333 (Outstanding = (end - start) - region_count(resv, start, end))。由于映射正在消
334 失,子池和全局预留计数被未完成    334 失,子池和全局预留计数被未完成的预留数量所减去。
335                                                   335 
336 预留映射帮助函数                          336 预留映射帮助函数
337 ================                                  337 ================
338                                                   338 
339 有几个辅助函数可以查询和修改预    339 有几个辅助函数可以查询和修改预留映射。这些函数只对特定的巨页的预留感兴趣,所以它们只是传入一个
340 地址而不是一个范围。此外,它们    340 地址而不是一个范围。此外,它们还传入相关的VMA。从VMA中,可以确定映射的类型(私有或共享)和预留
341 映射的位置(inode或VMA)。这些函    341 映射的位置(inode或VMA)。这些函数只是调用 “预留映射的修改” 一节中描述的基础函数。然而,
342 它们确实考虑到了私有和共享映射    342 它们确实考虑到了私有和共享映射的预留映射条目的 “相反” 含义,并向调用者隐藏了这个细节::
343                                                   343 
344         long vma_needs_reservation(struct hsta    344         long vma_needs_reservation(struct hstate *h,
345                                    struct vm_a    345                                    struct vm_area_struct *vma,
346                                    unsigned lo    346                                    unsigned long addr)
347                                                   347 
348 该函数为指定的页面调用 region_chg()    348 该函数为指定的页面调用 region_chg()。如果不存在预留,则返回1。如果存在预留,则返回0::
349                                                   349 
350         long vma_commit_reservation(struct hst    350         long vma_commit_reservation(struct hstate *h,
351                                     struct vm_    351                                     struct vm_area_struct *vma,
352                                     unsigned l    352                                     unsigned long addr)
353                                                   353 
354 这将调用 region_add(),用于指定的页    354 这将调用 region_add(),用于指定的页面。与region_chg和region_add的情况一样,该函数应在
355 先前调用的vma_needs_reservation后调用    355 先前调用的vma_needs_reservation后调用。它将为该页添加一个预留条目。如果预留被添加,它将
356 返回1,如果没有则返回0。返回值    356 返回1,如果没有则返回0。返回值应与之前调用vma_needs_reservation的返回值进行比较。如果出
357 现意外的差异,说明在两次调用之    357 现意外的差异,说明在两次调用之间修改了预留映射::
358                                                   358 
359         void vma_end_reservation(struct hstate    359         void vma_end_reservation(struct hstate *h,
360                                  struct vm_are    360                                  struct vm_area_struct *vma,
361                                  unsigned long    361                                  unsigned long addr)
362                                                   362 
363 这将调用指定页面的 region_abort()。    363 这将调用指定页面的 region_abort()。与region_chg和region_abort的情况一样,该函数应在
364 先前调用的vma_needs_reservation后被调    364 先前调用的vma_needs_reservation后被调用。它将中止/结束正在进行的预留添加操作::
365                                                   365 
366         long vma_add_reservation(struct hstate    366         long vma_add_reservation(struct hstate *h,
367                                  struct vm_are    367                                  struct vm_area_struct *vma,
368                                  unsigned long    368                                  unsigned long addr)
369                                                   369 
370 这是一个特殊的包装函数,有助于    370 这是一个特殊的包装函数,有助于在错误路径上清理预留。它只从repare_reserve_on_error()函数
371 中调用。该函数与vma_needs_reservation    371 中调用。该函数与vma_needs_reservation一起使用,试图将一个预留添加到预留映射中。它考虑到
372 了私有和共享映射的不同预留映射    372 了私有和共享映射的不同预留映射语义。因此,region_add被调用用于共享映射(因为映射中的条目表
373 示预留),而region_del被调用用于私    373 示预留),而region_del被调用用于私有映射(因为映射中没有条目表示预留)。关于在错误路径上需
374 要做什么的更多信息,请参见  “    374 要做什么的更多信息,请参见  “错误路径中的预留清理”  。
375                                                   375 
376                                                   376 
377 错误路径中的预留清理                    377 错误路径中的预留清理
378 ====================                              378 ====================
379                                                   379 
380 正如在:ref:`预留映射帮助函数<resv_m    380 正如在:ref:`预留映射帮助函数<resv_map_helpers>` 一节中提到的,预留的修改分两步进行。首
381 先,在分配页面之前调用vma_needs_res    381 先,在分配页面之前调用vma_needs_reservation。如果分配成功,则调用vma_commit_reservation。
382 如果不是,则调用vma_end_reservation。    382 如果不是,则调用vma_end_reservation。全局和子池的预留计数根据操作的成功或失败进行调整,
383 一切都很好。                                383 一切都很好。
384                                                   384 
385 此外,在一个巨页被实例化后,Page    385 此外,在一个巨页被实例化后,PagePrivate标志被清空,这样,当页面最终被释放时,计数是
386 正确的。                                      386 正确的。
387                                                   387 
388 然而,有几种情况是,在一个巨页    388 然而,有几种情况是,在一个巨页被分配后,但在它被实例化之前,就遇到了错误。在这种情况下,
389 页面分配已经消耗了预留,并进行    389 页面分配已经消耗了预留,并进行了适当的子池、预留映射和全局计数调整。如果页面在这个时候被释放
390 (在实例化和清除PagePrivate之前)    390 (在实例化和清除PagePrivate之前),那么free_huge_folio将增加全局预留计数。然而,预留映射
391 显示报留被消耗了。这种不一致的    391 显示报留被消耗了。这种不一致的状态将导致预留的巨页的 “泄漏” 。全局预留计数将比它原本的要高,
392 并阻止分配一个预先分配的页面。     392 并阻止分配一个预先分配的页面。
393                                                   393 
394 函数 restore_reserve_on_error() 试图处理    394 函数 restore_reserve_on_error() 试图处理这种情况。它有相当完善的文档。这个函数的目的
395 是将预留映射恢复到页面分配前的    395 是将预留映射恢复到页面分配前的状态。通过这种方式,预留映射的状态将与页面释放后的全局预留计
396 数相对应。                                   396 数相对应。
397                                                   397 
398 函数restore_reserve_on_error本身在试图    398 函数restore_reserve_on_error本身在试图恢复预留映射条目时可能会遇到错误。在这种情况下,
399 它将简单地清除该页的PagePrivate标    399 它将简单地清除该页的PagePrivate标志。这样一来,当页面被释放时,全局预留计数将不会被递增。
400 然而,预留映射将继续看起来像预    400 然而,预留映射将继续看起来像预留被消耗了一样。一个页面仍然可以被分配到该地址,但它不会像最
401 初设想的那样使用一个预留页。        401 初设想的那样使用一个预留页。
402                                                   402 
403 有一些代码(最明显的是userfaultfd    403 有一些代码(最明显的是userfaultfd)不能调用restore_reserve_on_error。在这种情况下,
404 它简单地修改了PagePrivate,以便在    404 它简单地修改了PagePrivate,以便在释放巨页时不会泄露预留。
405                                                   405 
406                                                   406 
407 预留和内存策略                             407 预留和内存策略
408 ==============                                    408 ==============
409 当git第一次被用来管理Linux代码时    409 当git第一次被用来管理Linux代码时,每个节点的巨页列表就存在于hstate结构中。预留的概念是
410 在一段时间后加入的。当预留被添    410 在一段时间后加入的。当预留被添加时,没有尝试将内存策略考虑在内。虽然cpusets与内存策略不
411 完全相同,但hugetlb_acct_memory中的这    411 完全相同,但hugetlb_acct_memory中的这个注释总结了预留和cpusets/内存策略之间的相互作
412 用::                                             412 用::
413                                                   413 
414                                                   414 
415         /*                                        415         /*
416          * 当cpuset被配置时,它打破    416          * 当cpuset被配置时,它打破了严格的hugetlb页面预留,因为计数是在一个全局变量上完
417          * 成的。在有cpuset的情况下    417          * 成的。在有cpuset的情况下,这样的预留完全是垃圾,因为预留没有根据当前cpuset的
418          * 页面可用性来检查。在任    418          * 页面可用性来检查。在任务所在的cpuset中缺乏空闲的htlb页面时,应用程序仍然有可能
419          * 被内核OOM'ed。试图用cpuset    419          * 被内核OOM'ed。试图用cpuset来执行严格的计数几乎是不可能的(或者说太难看了),因
420          * 为cpuset太不稳定了,任务    420          * 为cpuset太不稳定了,任务或内存节点可以在cpuset之间动态移动。与cpuset共享
421          * hugetlb映射的语义变化是不    421          * hugetlb映射的语义变化是不可取的。然而,为了预留一些语义,我们退回到检查当前空闲
422          * 页的可用性,作为一种最    422          * 页的可用性,作为一种最好的尝试,希望能将cpuset改变语义的影响降到最低。
423          */                                       423          */
424                                                   424 
425 添加巨页预留是为了防止在缺页异    425 添加巨页预留是为了防止在缺页异常时出现意外的页面分配失败(OOM)。然而,如果一个应用
426 程序使用cpusets或内存策略,就不能    426 程序使用cpusets或内存策略,就不能保证在所需的节点上有巨页可用。即使有足够数量的全局
427 预留,也是如此。                          427 预留,也是如此。
428                                                   428 
429 Hugetlbfs回归测试                             429 Hugetlbfs回归测试
430 =================                                 430 =================
431                                                   431 
432 最完整的hugetlb测试集在libhugetlbfs仓    432 最完整的hugetlb测试集在libhugetlbfs仓库。如果你修改了任何hugetlb相关的代码,请使用
433 libhugetlbfs测试套件来检查回归情况    433 libhugetlbfs测试套件来检查回归情况。此外,如果你添加了任何新的hugetlb功能,请在
434 libhugetlbfs中添加适当的测试。           434 libhugetlbfs中添加适当的测试。
435                                                   435 
436 --                                                436 --
437 Mike Kravetz,2017年4月7日                    437 Mike Kravetz,2017年4月7日
                                                      

~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~

kernel.org | git.kernel.org | LWN.net | Project Home | SVN repository | Mail admin

Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.

sflogo.php