关于vmalloc缺页异常的一个问题

本文探讨了进程在新建时如何复制页表及其内核部分,并解析了vmalloc为何会导致缺页异常,而持久映射和固定映射则不会。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

进程页表和vmalloc缺页异常

在新建进程时,新的线程会复制老的线程的页表,复制老线程的文件映射,匿名映射等,而且会复制页表项的全局页表项的内核部分,因为内核部分的页表的映射(除vmalloc)在启动内核时就已经建立好了,因此复制了全局页表项页就相当于复制了内核的页表。

pgd_t *pgd_alloc(struct mm_struct *mm)
{
pgd_t *pgd;
pmd_t *pmds[PREALLOCATED_PMDS];

pgd = (pgd_t *)__get_free_page(PGALLOC_GFP);

if (pgd == NULL)
goto out;

mm->pgd = pgd;

if (preallocate_pmds(pmds) != 0)
goto out_free_pgd;

if (paravirt_pgd_alloc(mm) != 0)
goto out_free_pmds;

/*
 * Make sure that pre-populating the pmds is atomic with
 * respect to anything walking the pgd_list, so that they
 * never see a partially populated pgd.
 */
spin_lock(&pgd_lock);

pgd_ctor(mm, pgd);
pgd_prepopulate_pmd(mm, pgd, pmds);

spin_unlock(&pgd_lock);

return pgd;

out_free_pmds:
free_pmds(pmds);
out_free_pgd:
free_page((unsigned long)pgd);
out:
return NULL;
}
static void pgd_ctor(struct mm_struct *mm, pgd_t *pgd)
{
/* If the pgd points to a shared pagetable level (either the
   ptes in non-PAE, or shared PMD in PAE), then just copy the
   references from swapper_pg_dir. */
if (PAGETABLE_LEVELS == 2 ||
    (PAGETABLE_LEVELS == 3 && SHARED_KERNEL_PMD) ||
    PAGETABLE_LEVELS == 4) {
clone_pgd_range(pgd + KERNEL_PGD_BOUNDARY,
swapper_pg_dir + KERNEL_PGD_BOUNDARY,
KERNEL_PGD_PTRS);
}

/* list required to sync kernel mapping updates */
if (!SHARED_KERNEL_PMD) {
pgd_set_mm(pgd, mm);
pgd_list_add(pgd);
}
}


因此当进程在内核态运行时不需要切换页表,因此也不需要刷新tlb,高速缓存等。


vmalloc会产生缺页异常而持久映射和固定映射不会产生映射的原因。
主要原因就在于vmalloc映射的地址区域过大,不在一个PMD的范围内才会产生缺页异常。因为进程在内核态使用的本身的页表,而页表的内核部分的从TASK_SIZE到max_low_pn<<PAGE_SHIFT,持久映射和原子的持久映射的部分的PMD在内核启动时就确定了,因此内核的全局页表项中和这些PMD相关的部分已经固定好了不会改变的,因此只要进程copy了pgd就相当于复制了内核的页表。
由于持久映射一次映射一页,持久映射的大小LASK_PKMAP为1024或者512个页,而且PKMAP_BASE是PMD_MASK对齐的,在32位4K页下也就是4M内存对齐的,刚好在pgd中占一项,因此持久映射不会影响到内核的全局页表项,因此每个进程的pgd的内核部分都是有效的,每个进程都能看到新的映射,因此不会产生缺页异常,而固定映射的大小也不会超过1024,因此修改都是全局可见的,再说了固定的地址都保存在tlb不会被刷出的,因此固定映射页不会产生缺页异常。所以,若产生了缺页异常,且地址在内核部分,只有可能是在vmalloc区域。
<think>我们正在处理一个关于Linux vmalloc分配失败的问题。用户希望了解失败的原因和解决方案。根据提供的引用,我们可以分析如下: 引用[1]提到,vmalloc分配的内存大小由nr_vmalloc_pages记录,这是实实在在向buddy分配的内存。而/proc/meminfo中的VmallocUsed统计的就是这个值。但是,与/proc/vmallocinfo统计的内存有差异。 引用[2]展示了vmalloc函数调用__vmalloc,使用的标志是GFP_KERNEL | __GFP_HIGHMEM。 引用[3]讨论了vmalloc_fault缺页异常处理,它只发生在内核态且缺页的虚拟地址空间大于TASK_SIZE_MAX的情况。 引用[4]指出,GFP_ATOMIC分配请求可能会重复失败,如果系统中没有其他东西进行内存碎片整理。 综合以上,vmalloc分配失败的可能原因包括: 1. 物理内存不足:vmalloc需要从buddy分配器获取物理页,如果系统物理内存紧张,分配可能失败。 2. 内存碎片:由于vmalloc需要分配连续的物理页(尽管在虚拟地址空间是连续的,但物理页可以不连续,但每个物理页是连续的),如果物理内存碎片化严重,可能无法找到足够的物理页。 3. vmalloc区域空间不足:虽然vmalloc区域在32位系统上通常有较大的空间,但在64位系统上通常足够大,但如果频繁使用vmalloc,可能导致vmalloc区域耗尽。 4. 内核配置:如vmalloc区域大小配置过小。 解决方案: 1. 检查系统内存使用情况:使用`free -m`查看可用内存,如果内存不足,考虑增加内存或减少内存使用。 2. 检查内存碎片:虽然Linux内核有反碎片机制,但在极端情况下,碎片仍可能导致分配失败。可以尝试重启系统以缓解碎片问题。 3. 检查vmalloc区域使用情况:通过`/proc/vmallocinfo`和`/proc/meminfo`中的VmallocUsed和VmallocTotal来查看使用情况。 4. 调整内核参数:如果vmalloc区域不足,可以通过内核启动参数`vmalloc=`来增加vmalloc区域大小(例如`vmalloc=512MB`)。 5. 避免不必要的vmalloc:考虑使用kmalloc(分配物理连续内存)或使用大页(hugepage)来减少碎片。 6. 优化代码:避免频繁分配和释放vmalloc内存,考虑使用内存池。 另外,引用[4]提到GFP_ATOMIC分配失败可能与内存碎片有关,而vmalloc使用的是GFP_KERNEL标志(允许睡眠),所以通常不会因为原子分配而失败,但碎片问题同样会影响。 因此,我们可以给用户提供以下解答:</think>### Linux vmalloc 分配失败的原因与解决方案 #### **一、失败原因分析** 1. **物理内存不足** vmalloc 需从伙伴系统(buddy)申请物理页,当系统物理内存紧张时(如 `nr_vmalloc_pages` 激增),分配会失败。可通过 `/proc/meminfo` 的 `VmallocUsed` 字段监控[^1]。 2. **内核地址空间碎片化** vmalloc 区域(通常位于 `TASK_SIZE_MAX` 以上)若存在大量碎片化空隙,即使总空间充足,也无法分配连续虚拟地址范围[^3]。 3. **内存碎片问题** 物理页严重碎片化时,即使空闲页总量足够,也无法满足连续物理页请求(如 `GFP_ATOMIC` 场景),导致分配失败[^4]。 4. **并发分配竞争** 高并发场景下,多个线程同时调用 `vmalloc()` 可能耗尽临时内存池(如 `__vmalloc()` 中的 `GFP_KERNEL | __GFP_HIGHMEM` 竞争)[^2]。 5. **内核参数限制** vmalloc 区域大小由内核参数 `vmalloc=` 设定,默认值过小会导致空间耗尽(常见于频繁分配大对象的场景)。 --- #### **二、解决方案** 1. **释放内存资源** - 检查占用 vmalloc 的模块:`cat /proc/vmallocinfo | sort -nr -k 2` - 卸载非必要内核模块或重启服务释放内存。 2. **优化内存分配策略** ```c // 优先使用 kmalloc(物理连续内存) void *buf = kmalloc(size, GFP_KERNEL); // 大对象改用 vmalloc(虚拟连续即可) if (!buf) buf = vmalloc(size); ``` 3. **调整内核参数** 增大 vmalloc 区域(如启动参数添加 `vmalloc=512MB`),并减少碎片化: ```bash # 查看当前碎片情况 grep vmalloc /proc/buddyinfo ``` 4. **反碎片化措施** - 触发主动内存规整:`echo 1 > /proc/sys/vm/compact_memory` - 启用透明大页(THP):`echo always > /sys/kernel/mm/transparent_hugepage/enabled` 5. **代码层优化** - 避免频繁分配/释放:改用内存池或预分配策略。 - 减少 `GFP_ATOMIC` 使用:替换为允许睡眠的 `GFP_KERNEL`[^4]。 --- #### **三、诊断工具** 1. **实时监控** ```bash watch -n 1 "grep -e Vmalloc -e Commit /proc/meminfo" ``` 2. **分析调用栈** 通过 `kdump` 捕获崩溃日志,检查 `vmalloc_fault` 的调用路径[^3]。 3. **追踪分配过程** ```bash echo 'vmalloc* + __vmalloc*' > /sys/kernel/debug/tracing/set_ftrace_filter echo function > /sys/kernel/debug/tracing/current_tracer ``` > **关键提示**:若 `VmallocUsed` 接近 `VmallocTotal`(在 `/proc/meminfo` 中),表明 vmalloc 区域即将耗尽,需立即扩容或优化[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值