You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Linux实时应用:如何预触发缺页并锁定MAP_PRIVATE的mmap内存页

Linux实时应用页错误排查与解决方案

问题背景

开发Linux实时应用时,需要完全避免启动后的页错误。初始调用mlockall(MCL_CURRENT | MCL_FUTURE);无报错,但仍存在大量Locked: 0 kB的页面(这类页面会触发页错误)。将mlockall移至dlopen前可锁定后续加载的共享库,但编译时预加载的共享库内存未被锁定;同时用madvise实现预缺页时,对---p权限的MAP_PRIVATE页面返回-1或EPERM错误。


疑问1:为何MCL_CURRENT无法锁定已加载的预共享库?

mlockall(MCL_CURRENT)仅会锁定当前已分配物理内存的页面,而非虚拟地址空间中所有映射的页面。预加载的共享库在进程启动时,动态链接器仅建立了虚拟地址映射,其中大量页面(如只读代码段的未触发缺页部分、写时复制的未修改页面)尚未分配对应的物理内存,这类虚拟页面不会被MCL_CURRENT锁定,表现为Locked: 0 kB。

此外,部分共享库的段(如未初始化的BSS段、动态链接器延迟处理的段)在mlockall调用时还未完成映射,也会导致无法被锁定。


疑问2:如何预触发MAP_PRIVATE映射页面的缺页?

原方案问题分析

madvise(start, len, MADV_POPULATE_WRITE)要求目标页面具备写权限,但---p权限的页面无读、写、执行权限,调用时自然返回EPERM;即使是r--p的只读MAP_PRIVATE页面,MADV_POPULATE_WRITE也会因权限不足报错。

可行解决方案

针对不同权限的页面,采用不同的预缺页策略:

  1. 只读/可执行页面(r--p / r-xp):
    • 内核版本4.4及以上可使用MADV_POPULATE_READ,无需写权限即可预分配物理页;
    • 低版本内核可通过逐字节读取页面内容(用volatile变量防止编译器优化)触发缺页。
  2. 可写页面(rw-p / rw-s):
    • 使用MADV_POPULATE_WRITE预分配并锁定写时复制页面;
    • 或临时写入原值后恢复,触发物理页分配。
  3. 无权限页面(---p):
    • 这类页面通常是虚拟地址空间的间隙、未使用的映射段,进程运行时不会访问,可直接跳过。

修改后的prefault实现

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <inttypes.h>
#include <limits.h>

void prefault()
{
    const pid_t pid = getpid();
    FILE *fp;
    char path[PATH_MAX];
    char buf[4096];

    (void)snprintf(path, sizeof(path), "/proc/%" PRIdMAX "/maps", (intmax_t)pid);
    fp = fopen(path, "r");
    if (!fp) {
        perror("fopen");
        return;
    }

    volatile uint8_t val;
    while (fgets(buf, sizeof(buf), fp)) {
        void *start, *end, *offset;
        int major, minor, n, ret;
        uint64_t inode;
        char prot[5];
        char seg_path[PATH_MAX];

        n = sscanf(buf, "%p-%p %4s %p %x:%x %" PRIu64 " %s\n",
                   &start, &end, prot, &offset, &major, &minor,
                   &inode, seg_path);

        if (n < 7) continue;
        if (start >= end) continue;

        size_t len = (size_t)((uint8_t *)end - (uint8_t *)start);

        // 跳过无权限的---p页面
        if (prot[0] == '-' && prot[1] == '-' && prot[2] == '-') {
            continue;
        }

        // 处理只读/可执行页面
        if (prot[0] == 'r') {
#ifdef MADV_POPULATE_READ
            ret = madvise(start, len, MADV_POPULATE_READ);
            if (ret == -1) {
                // 降级为逐页读取
                uint8_t *addr = (uint8_t *)start;
                for (size_t i = 0; i < len; i += sysconf(_SC_PAGESIZE)) {
                    val = addr[i];
                }
            }
#else
            // 内核不支持MADV_POPULATE_READ时,直接逐页读取
            uint8_t *addr = (uint8_t *)start;
            for (size_t i = 0; i < len; i += sysconf(_SC_PAGESIZE)) {
                val = addr[i];
            }
#endif
        }

        // 处理可写页面
        if (prot[1] == 'w') {
            ret = madvise(start, len, MADV_POPULATE_WRITE);
            if (ret == -1) {
                // 降级为逐页写入恢复
                uint8_t *addr = (uint8_t *)start;
                for (size_t i = 0; i < len; i += sysconf(_SC_PAGESIZE)) {
                    uint8_t orig = addr[i];
                    addr[i] = orig; // 写入原值,触发写时复制
                }
            }
        }
    }

    (void)fclose(fp);
}

附加信息

相关/proc//smaps输出

$ cat /proc/<pid>/smaps |  grep -B 21 -A 2 "Locked:                0 kB"  | grep -B 1 "^Size:" | grep -v "Size" | grep -v "^--"
7effd0021000-7effd4000000 ---p 00000000 00:00 0 
7effd4021000-7effd8000000 ---p 00000000 00:00 0 
7effd80c6000-7effdc000000 ---p 00000000 00:00 0 
7effddf02000-7effddfa0000 rw-s 00000000 00:05 368                        /dev/some_char_device
7effddfa0000-7effde1a0000 rw-s f0000000 00:05 368                        /dev/some_char_device
7effde1c1000-7effde1c2000 ---p 00000000 00:00 0 
7effde1c6000-7effde1ca000 rw-s f7c00000 00:05 368                        /dev/some_char_device
7effde1ca000-7effde1cb000 ---p 00000000 00:00 0 
7effe221b000-7effe221c000 ---p 00000000 00:00 0 
7effe2220000-7effe2223000 rw-s 00000000 00:05 90                         /dev/another_char_device
7effe22df000-7effe22e0000 ---p 00013000 08:02 2234654                    /<path_to>/shared_library1.so
7effe22fd000-7effe22fe000 ---p 0000c000 08:02 2231701                    /<path_to>/shared_library2.so
7effe23fc000-7effe23fd000 ---p 0001c000 08:02 2234652                    /<path_to>/shared_library3.so
7effe2e15000-7effe2e16000 ---p 00215000 08:02 1957                       /usr/lib/x86_64-linux-gnu/libc.so.6
7effe2e40000-7effe2e41000 ---p 00011000 08:02 2234649                    /<path_to>/shared_library4.so
7effe2f14000-7effe2f15000 ---p 00046000 08:02 2232115                    /<path_to>/shared_library5.so
7effe321a000-7effe321b000 ---p 0021a000 08:02 855                        /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.30
7effe3258000-7effe3259000 ---p 0001f000 08:02 2234643                    /<path_to>/shared_library6.so
7effe327d000-7effe327e000 ---p 00021000 08:02 2234641                    /<path_to>/shared_library7.so
7effe328a000-7effe328b000 ---p 00009000 08:02 2232116                    /<path_to>/shared_library8.so
7effe348e000-7effe348f000 ---p 00102000 08:02 91759                      /<path_to>/shared_library9.so
7effe34c6000-7effe34c8000 r--p 00000000 08:02 175                        /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7effe34f2000-7effe34fd000 r--p 0002c000 08:02 175                        /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7ffc1d1b0000-7ffc1d1b4000 r--p 00000000 00:00 0                          [vvar]
7ffc1d1b4000-7ffc1d1b6000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0                  [vsyscall]

内容的提问来源于stack exchange,提问作者Jay S.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 17:49:59