vmalloc详解
vmalloc是一个接口函数, 内核代码使用它来分配在虚拟内存中连续但在物理内存中不一定连续的内存。
只需要一个参数,以字节为单位。
使用vmalloc的最著名的实例是内核对模块的实现. 因为模块可能在任何时候加载, 如果模块数据比较多, 那么无法保证有足够的连续内存可用, 特别是在系统已经运行了比较长时间的情况下.
如果能够用小块内存拼接出足够的内存, 那么使用vmalloc可以规避该问题。
因为用于vmalloc的内存页总是必须映射在内核地址空间中, 因此使用ZONE_HIGHMEM内存域的页要优于其他内存域. 这使得内核可以节省更宝贵的较低端内存域, 而又不会带来额外的坏处. 因此, vmalloc等映射函数是内核出于自身的目的(并非因为用户空间应用程序)使用高端内存页的少数情形之一.
内核在管理虚拟内存中的VMALLOC区域时, 内核必须跟踪哪些子区域被使用、哪些是空闲的. 为此定义了一个数据结构vm_struct:
31struct vm_struct { 32 struct vm_struct *next; 33 void *addr; 34 unsigned long size; 35 unsigned long flags; 36 struct page **pages; 37 unsigned int nr_pages; 38 phys_addr_t phys_addr; 39 const void *caller; 40};
一个vm_struct代表一个vmalloc区域。
通过next形成一个链表。
addr是映射的首地址,size为映射地址区间的大小。
pages是一组指针,这些指针描述映射到这个区间里面的一个个真实的物理页对应的page指针。
nr_pages表示该地址区间映射了多少物理页。
phys_addr仅当用ioremap映射了由物理地址描述的物理内存区域时才需要。该信息保存在phys_addr中。
caller指向调用__vmalloc_node_flags被调用的地址。
flags的取值如下:
12/* bits in flags of vmalloc's vm_struct below */ 13#define VM_IOREMAP 0x00000001 /* ioremap() and friends */ 14#define VM_ALLOC 0x00000002 /* vmalloc() */ 15#define VM_MAP 0x00000004 /* vmap()ed pages */ 16#define VM_USERMAP 0x00000008 /* suitable for remap_vmalloc_range */ 17#define VM_VPAGES 0x00000010 /* buffer for pages was vmalloc'ed */ 18#define VM_UNINITIALIZED 0x00000020 /* vm_struct is not fully initialized */ 19#define VM_NO_GUARD 0x00000040 /* don't add guard page */ 20#define VM_KASAN 0x00000080 /* has allocated kasan shadow memory */ 21/* bits [20..32] reserved for arch specific ioremap internals */
因为vmalloc的调用函数都在一起,贴上如下:
1710/** 1711 * __vmalloc_node - allocate virtually contiguous memory 1712 * @size: allocation size 1713 * @align: desired alignment 1714 * @gfp_mask: flags for the page level allocator 1715 * @prot: protection mask for the allocated pages 1716 * @node: node to use for allocation or NUMA_NO_NODE 1717 * @caller: caller's return address 1718 * 1719 * Allocate enough pages to cover @size from the page level 1720 * allocator with @gfp_mask flags. Map them into contiguous 1721 * kernel virtual space, using a pagetable protection of @prot. 1722 */ 1723static void *__vmalloc_node(unsigned long size, unsigned long align, 1724 gfp_t gfp_mask, pgprot_t prot, 1725 int node, const void *caller) 1726{ 1727 return __vmalloc_node_range(size, align, VMALLOC_START, VMALLOC_END, 1728 gfp_mask, prot, 0, node, caller); 1729} 1730 ... 1737 1738static inline void *__vmalloc_node_flags(unsigned long size, 1739 int node, gfp_t flags) 1740{ 1741 return __vmalloc_node(size, 1, flags, PAGE_KERNEL, 1742 node, __builtin_return_address(0)); 1743} 1744 1745/** 1746 * vmalloc - allocate virtually contiguous memory 1747 * @size: allocation size 1748 * Allocate enough pages to cover @size from the page level 1749 * allocator and map them into contiguous kernel virtual space. 1750 * 1751 * For tight control over page level allocator and protection flags 1752 * use __vmalloc() instead. 1753 */ 1754void *vmalloc(unsigned long size) 1755{ 1756 return __vmalloc_node_flags(size, NUMA_NO_NODE, 1757 GFP_KERNEL | __GFP_HIGHMEM); 1758} 1759EXPORT_SYMBOL(vmalloc);
最终调到__vmalloc_node_range,并把VMALLOC_START和VMALLOC_END传入,该函数是vmalloc的主要实现,用来从(start, end)中申请一段大小为size的虚拟地址空间,并给这块虚拟地址空间申请物理内存(基本是不连续的),并写入页表。
VMALLOC_START和VMALLOC_END在arm中的定义如下:
36/* 37 * Just any arbitrary offset to the start of the vmalloc VM area: the 38 * current 8MB value just means that there will be a 8MB "hole" after the 39 * physical memory until the kernel virtual memory starts. That means that 40 * any out-of-bounds memory accesses will hopefully be caught. 41 * The vmalloc() routines leaves a hole of 4kB between each vmalloced 42 * area for the same reason. ;) 43 */ 44#define VMALLOC_OFFSET (8*1024*1024) 45#define VMALLOC_START (((unsigned long)high_memory + VMALLOC_OFFSET) & ~(VMALLOC_OFFSET-1)) 46#define VMALLOC_END 0xff800000UL 47
可以看出VMALLOC_END为0xff800000UL,但VMALLOC_START与high_memory有关。high_memory在sanity_check_meminfo中被确定:
static void * __initdata vmalloc_min = 1082 (void *)(VMALLOC_END - (240 << 20) - VMALLOC_OFFSET); 1083 1084/* 1085 * vmalloc=size forces the vmalloc area to be exactly 'size' 1086 * bytes. This can be used to increase (or decrease) the vmalloc 1087 * area - the default is 240m. 1088 */ 1089static int __init early_vmalloc(char *arg) 1090{ 1091 unsigned long vmalloc_reserve = memparse(arg, NULL); 1092 1093 if (vmalloc_reserve < SZ_16M) { 1094 vmalloc_reserve = SZ_16M; 1095 pr_warn("vmalloc area too small, limiting to %luMB\n", 1096 vmalloc_reserve >> 20); 1097 } 1098 1099 if (vmalloc_reserve > VMALLOC_END - (PAGE_OFFSET + SZ_32M)) { 1100 vmalloc_reserve = VMALLOC_END - (PAGE_OFFSET + SZ_32M); 1101 pr_warn("vmalloc area is too big, limiting to %luMB\n", 1102 vmalloc_reserve >> 20); 1103 } 1104 1105 vmalloc_min = (void *)(VMALLOC_END - vmalloc_reserve); 1106 return 0; 1107} 1108early_param("vmalloc", early_vmalloc); 1109 1110phys_addr_t arm_lowmem_limit __initdata = 0; 1111 1112void __init sanity_check_meminfo(void) 1113{ 1114 phys_addr_t memblock_limit = 0; 1115 int highmem = 0; 1116 phys_addr_t vmalloc_limit = __pa(vmalloc_min - 1) + 1; 1117 struct memblock_region *reg; 1118 bool should_use_highmem = false; 1119 1120 for_each_memblock(memory, reg) { 1121 phys_addr_t block_start = reg->base; 1122 phys_addr_t block_end = reg->base + reg->size; 1123 phys_addr_t size_limit = reg->size; 1124 1125 if (reg->base >= vmalloc_limit) 1126 highmem = 1; 1127 else 1128 size_limit = vmalloc_limit - reg->base; 1129 1130 1131 if (!IS_ENABLED(CONFIG_HIGHMEM) || cache_is_vipt_aliasing()) { 1132 1133 if (highmem) { 1134 pr_notice("Ignoring RAM at %pa-%pa (!CONFIG_HIGHMEM)\n", 1135 &block_start, &block_end); 1136 memblock_remove(reg->base, reg->size); 1137 should_use_highmem = true; 1138 continue; 1139 } 1140 1141 if (reg->size > size_limit) { 1142 phys_addr_t overlap_size = reg->size - size_limit; 1143 1144 pr_notice("Truncating RAM at %pa-%pa to -%pa", 1145 &block_start, &block_end, &vmalloc_limit); 1146 memblock_remove(vmalloc_limit, overlap_size); 1147 block_end = vmalloc_limit; 1148 should_use_highmem = true; 1149 } 1150 } 1151 1152 if (!highmem) { 1153 if (block_end > arm_lowmem_limit) { 1154 if (reg->size > size_limit) 1155 arm_lowmem_limit = vmalloc_limit; 1156 else 1157 arm_lowmem_limit = block_end; 1158 } 1159 1160 /* 1161 * Find the first non-pmd-aligned page, and point 1162 * memblock_limit at it. This relies on rounding the 1163 * limit down to be pmd-aligned, which happens at the 1164 * end of this function. 1165 * 1166 * With this algorithm, the start or end of almost any 1167 * bank can be non-pmd-aligned. The only exception is 1168 * that the start of the bank 0 must be section- 1169 * aligned, since otherwise memory would need to be 1170 * allocated when mapping the start of bank 0, which 1171 * occurs before any free memory is mapped. 1172 */ 1173 if (!memblock_limit) { 1174 if (!IS_ALIGNED(block_start, PMD_SIZE)) 1175 memblock_limit = block_start; 1176 else if (!IS_ALIGNED(block_end, PMD_SIZE)) 1177 memblock_limit = arm_lowmem_limit; 1178 } 1179 1180 } 1181 } 1182 1183 if (should_use_highmem) 1184 pr_notice("Consider using a HIGHMEM enabled kernel.\n"); 1185 1186 high_memory = __va(arm_lowmem_limit - 1) + 1; 1187 1188 if (!memblock_limit) 1189 memblock_limit = arm_lowmem_limit; 1190 1191 /* 1192 * Round the memblock limit down to a pmd size. This 1193 * helps to ensure that we will allocate memory from the 1194 * last full pmd, which should be mapped. 1195 */ 1196 memblock_limit = round_down(memblock_limit, PMD_SIZE); 1197 1198 memblock_set_current_limit(memblock_limit); 1199}
如果在bootargs里面没有vmalloc=的字段,vmalloc占用的虚拟地址空间为240MB,如果设置了该参数大小为P,会用VMALLOC_END-P赋给vmalloc_min。
sanity_check_meminfo对于vmalloc来讲最重要的作用就是根据memblock里面的内存块确定arm_lowmem_limit的地址,使其不会与vmalloc区间重叠。
OK, VMALLOC_START与VMALLOC_END确定。
下面来看下这个函数的实现:
1649/** 1650 * __vmalloc_node_range - allocate virtually contiguous memory 1651 * @size: allocation size 1652 * @align: desired alignment 1653 * @start: vm area range start 1654 * @end: vm area range end 1655 * @gfp_mask: flags for the page level allocator 1656 * @prot: protection mask for the allocated pages 1657 * @vm_flags: additional vm area flags (e.g. %VM_NO_GUARD) 1658 * @node: node to use for allocation or NUMA_NO_NODE 1659 * @caller: caller's return address 1660 * 1661 * Allocate enough pages to cover @size from the page level 1662 * allocator with @gfp_mask flags. Map them into contiguous 1663 * kernel virtual space, using a pagetable protection of @prot. 1664 */ 1665void *__vmalloc_node_range(unsigned long size, unsigned long align, 1666 unsigned long start, unsigned long end, gfp_t gfp_mask, 1667 pgprot_t prot, unsigned long vm_flags, int node, 1668 const void *caller) 1669{ 1670 struct vm_struct *area; 1671 void *addr; 1672 unsigned long real_size = size; 1673 1674 size = PAGE_ALIGN(size); 1675 if (!size || (size >> PAGE_SHIFT) > totalram_pages) 1676 goto fail; 1677 1678 area = __get_vm_area_node(size, align, VM_ALLOC | VM_UNINITIALIZED | 1679 vm_flags, start, end, node, gfp_mask, caller); 1680 if (!area) 1681 goto fail; 1682 1683 addr = __vmalloc_area_node(area, gfp_mask, prot, node); 1684 if (!addr) 1685 return NULL; 1686 1687 /* 1688 * In this function, newly allocated vm_struct has VM_UNINITIALIZED 1689 * flag. It means that vm_struct is not fully initialized. 1690 * Now, it is fully initialized, so remove this flag here. 1691 */ 1692 clear_vm_uninitialized_flag(area); 1693 1694 /* 1695 * A ref_count = 2 is needed because vm_struct allocated in 1696 * __get_vm_area_node() contains a reference to the virtual address of 1697 * the vmalloc'ed block. 1698 */ 1699 kmemleak_alloc(addr, real_size, 2, gfp_mask); 1700 1701 return addr; 1702 1703fail: 1704 warn_alloc_failed(gfp_mask, 0, 1705 "vmalloc: allocation failure: %lu bytes\n", 1706 real_size); 1707 return NULL; 1708} 1709
先调用__get_vm_area_node在vmap_area组成的红黑树中找到一个位置,把由该空间组成的vmap_area插入红黑树。
然后调用setup_vmalloc_vm 把该空间保存在vm_struct中。
1317static void setup_vmalloc_vm(struct vm_struct *vm, struct vmap_area *va, 1318 unsigned long flags, const void *caller) 1319{ 1320 spin_lock(&vmap_area_lock); 1321 vm->flags = flags; 1322 vm->addr = (void *)va->va_start; 1323 vm->size = va->va_end - va->va_start; 1324 vm->caller = caller; 1325 va->vm = vm; 1326 va->flags |= VM_VM_AREA; 1327 spin_unlock(&vmap_area_lock); 1328}
然后回到__vmalloc_node_range中,申请完虚拟地址空间后,接着调用__vmalloc_area_node 来申请具体的物理页,并把这些页和对应的虚拟地址填入页表。
1591static void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask, 1592 pgprot_t prot, int node) 1593{ 1594 const int order = 0; 1595 struct page **pages; 1596 unsigned int nr_pages, array_size, i; 1597 const gfp_t nested_gfp = (gfp_mask & GFP_RECLAIM_MASK) | __GFP_ZERO; 1598 const gfp_t alloc_mask = gfp_mask | __GFP_NOWARN; 1599 1600 nr_pages = get_vm_area_size(area) >> PAGE_SHIFT; 1601 array_size = (nr_pages * sizeof(struct page *)); 1602 1603 area->nr_pages = nr_pages; 1604 /* Please note that the recursion is strictly bounded. */ 1605 if (array_size > PAGE_SIZE) { 1606 pages = __vmalloc_node(array_size, 1, nested_gfp|__GFP_HIGHMEM, 1607 PAGE_KERNEL, node, area->caller); 1608 area->flags |= VM_VPAGES; 1609 } else { 1610 pages = kmalloc_node(array_size, nested_gfp, node); 1611 } 1612 area->pages = pages; 1613 if (!area->pages) { 1614 remove_vm_area(area->addr); 1615 kfree(area); 1616 return NULL; 1617 } 1618 1619 for (i = 0; i < area->nr_pages; i++) { 1620 struct page *page; 1621 1622 if (node == NUMA_NO_NODE) 1623 page = alloc_page(alloc_mask); 1624 else 1625 page = alloc_pages_node(node, alloc_mask, order); 1626 1627 if (unlikely(!page)) { 1628 /* Successfully allocated i pages, free them in __vunmap() */ 1629 area->nr_pages = i; 1630 goto fail; 1631 } 1632 area->pages[i] = page; 1633 if (gfpflags_allow_blocking(gfp_mask)) 1634 cond_resched(); 1635 } 1636 1637 if (map_vm_area(area, prot, pages)) 1638 goto fail; 1639 return area->addr; 1640 1641fail: 1642 warn_alloc_failed(gfp_mask, order, 1643 "vmalloc: allocation failure, allocated %ld of %ld bytes\n", 1644 (area->nr_pages*PAGE_SIZE), area->size); 1645 vfree(area->addr); 1646 return NULL; 1647} 1648
首先为pages数组申请一段连续的虚拟地址空间(小于1页,使用kmalloc,大于1页,调vmalloc来保证虚拟地址空间的连续性),用来存入申请的物理页对应的page结构体,然后会申请nr_pages个物理页(注意是一次申请一页,因此申请到的页基本是不连续的)。
最终通过map_vm_area把这些物理页和虚拟地址空间对应起来,写入页表。
另外,社区还有对vmalloc的页面进行区分的patch。如下:
diff --git a/fs/proc/page.c b/fs/proc/page.c index 792c78a49174..fc83dae1af7b 100644 --- a/fs/proc/page.c +++ b/fs/proc/page.c @@ -156,6 +156,8 @@ u64 stable_page_flags(struct page *page) u |= 1 << KPF_BALLOON; if (PageTable(page)) u |= 1 << KPF_PGTABLE; + if (PageVMalloc(page)) + u |= 1 << KPF_VMALLOC; if (page_is_idle(page)) u |= 1 << KPF_IDLE; diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h index 42619e16047f..c51ddd27bfb4 100644 --- a/include/linux/mm_types.h +++ b/include/linux/mm_types.h @@ -141,6 +141,11 @@ struct page { spinlock_t ptl; #endif }; + struct { /* VMalloc pages */ + struct vm_struct *vm_area; + unsigned long vm_offset; + unsigned long _vm_id; /* MAPPING_VMalloc */ + }; /** @rcu_head: You can use this to free a page by RCU. */ struct rcu_head rcu_head; diff --git a/include/linux/page-flags.h b/include/linux/page-flags.h index 901943e4754b..5232433175c1 100644 --- a/include/linux/page-flags.h +++ b/include/linux/page-flags.h @@ -699,6 +699,31 @@ PAGE_TYPE_OPS(Kmemcg, kmemcg) */ PAGE_TYPE_OPS(Table, table) +/* + * vmalloc pages may be mapped to userspace, so we need some other way + * to distinguish them from other kinds of pages. Use page->mapping + * for this purpose. Values below 0x1000 cannot be real pointers. + */ +#define MAPPING_VMalloc (void *)0x440 + +#define PAGE_MAPPING_OPS(name) \ +static __always_inline int Page##name(struct page *page) \ +{ \ + return page->mapping == MAPPING_##name; \ +} \ +static __always_inline void __SetPage##name(struct page *page) \ +{ \ + VM_BUG_ON_PAGE(page->mapping != NULL, page); \ + page->mapping = MAPPING_##name; \ +} \ +static __always_inline void __ClearPage##name(struct page *page) \ +{ \ + VM_BUG_ON_PAGE(page->mapping != MAPPING_##name, page); \ + page->mapping = NULL; \ +} + +PAGE_MAPPING_OPS(VMalloc) + extern bool is_free_buddy_page(struct page *page); __PAGEFLAG(Isolated, isolated, PF_ANY); diff --git a/include/uapi/linux/kernel-page-flags.h b/include/uapi/linux/kernel-page-flags.h index 21b9113c69da..6800968b8f47 100644 --- a/include/uapi/linux/kernel-page-flags.h +++ b/include/uapi/linux/kernel-page-flags.h @@ -36,5 +36,6 @@ #define KPF_ZERO_PAGE 24 #define KPF_IDLE 25 #define KPF_PGTABLE 26 +#define KPF_VMALLOC 27 #endif /* _UAPILINUX_KERNEL_PAGE_FLAGS_H */ diff --git a/mm/vmalloc.c b/mm/vmalloc.c index 5fbf27e7f956..98bc690d472d 100644 --- a/mm/vmalloc.c +++ b/mm/vmalloc.c @@ -1535,7 +1535,7 @@ static void __vunmap(const void *addr, int deallocate_pages) for (i = 0; i < area->nr_pages; i++) { struct page *page = area->pages[i]; - BUG_ON(!page); + __ClearPageVMalloc(page); __free_pages(page, 0); } @@ -1704,6 +1704,9 @@ static void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask, area->nr_pages = i; goto fail; } + __SetPageVMalloc(page); + page->vm_area = area; + page->vm_offset = i; area->pages[i] = page; if (gfpflags_allow_blocking(gfp_mask)) cond_resched(); diff --git a/tools/vm/page-types.c b/tools/vm/page-types.c index cce853dca691..25cc21855be4 100644 --- a/tools/vm/page-types.c +++ b/tools/vm/page-types.c @@ -132,6 +132,7 @@ static const char * const page_flag_names[] = { [KPF_THP] = "t:thp", [KPF_BALLOON] = "o:balloon", [KPF_PGTABLE] = "g:pgtable", + [KPF_VMALLOC] = "V:vmalloc", [KPF_ZERO_PAGE] = "z:zero_page", [KPF_IDLE] = "i:idle_page",