std::pmr::memory_resource不能直接用于租户隔离,因其仅为接口抽象且无内置隔离机制;共享同一monotonic_buffer_resource时租户间内存仍可互相覆盖;真正的隔离需从分配源头切断可见性,确保租户a分配的内存租户b无法通过任何指针访问,崩溃不污染其他租户内存,用量须精确统计并设硬上限;linux下最轻量方案是为每个租户配置独立memcg配合mmap(map_private|map_anonymous),由cgroup v2实现内核级配额与故障隔离;需避免使用setrlimit(rlimit_as)等进程级限制,而应依赖memcg或userfaultfd等可嵌套、可审计机制。

为什么 std::pmr::memory_resource 不能直接当租户隔离用
它只是接口抽象,不自带隔离能力。你用同一个 std::pmr::monotonic_buffer_resource 给两个租户分配内存,它们照样能互相踩内存——因为底层还是共享同一块 buffer 或堆。真正的隔离必须从分配源头切断可见性。
- 租户 A 调用
allocate(),租户 B 无法通过任何指针访问到那块内存(哪怕地址猜对了,也会被 OS 拦住) - 租户崩溃不能导致其他租户的内存页被释放或污染
- 内存用量要可精确统计、可设硬上限,不能靠“事后查
std::pmr::get_default_resource()”这种模糊方式
Linux 上最轻量可行的隔离:每个租户一个 memcg + mmap(MAP_PRIVATE|MAP_ANONYMOUS)
不用重写分配器,也不用 fork 进程,靠 cgroup v2 的 memory controller 就能实现内核级配额和故障隔离。关键不是“怎么 malloc”,而是“malloc 出来的页归谁管”。
- 启动时为每个租户创建独立
/sys/fs/cgroup/memory/tenant-001/,写入memory.max(比如512M) - 租户线程在进入前调用
setns(2)切入对应 memcg(需提前 open 对应cgroup.procs文件描述符) - 所有后续
mmap(MAP_PRIVATE|MAP_ANONYMOUS)分配的页自动归属该 memcg,malloc底层也走这条路 - 超过
memory.max时,内核直接 OOM kill 该 memcg 内任意进程,不影响其他租户
注意:std::allocator 默认行为完全兼容,无需改代码;但若用了自定义 new 操作符并绕过 mmap(比如直接 sbrk),隔离就失效。
mprotect(PROT_NONE) 配合 mmap(MAP_NORESERVE) 实现按需隔离
有些场景需要更细粒度控制——比如一个租户只允许访问自己名下的对象池,连读都不行。这时不能只靠 memcg,得结合页表权限。
立即学习“C++免费学习笔记(深入)”;
- 用
mmap(..., MAP_ANONYMOUS|MAP_NORESERVE|MAP_HUGETLB)申请大块虚拟地址空间(不立即分配物理页) - 租户初始化时,仅对属于自己数据的虚拟页调用
mprotect(addr, len, PROT_READ|PROT_WRITE) - 其他页保持
PROT_NONE,任何访问触发SEGV_MAPERR,信号 handler 可记录越界行为 - 务必禁用
MAP_NORESERVE的反向效果:避免因 overcommit 导致看似成功分配实则后续write时 SIGBUS
这招对性能有影响(每次跨租户指针解引用都可能缺页异常),但能堵住 C++ 中裸指针乱跑的漏洞,比纯逻辑检查可靠。
别碰 setrlimit(RLIMIT_AS) 和 ulimit -v
它限制的是整个进程的虚拟地址空间总量,不是某个租户的。多租户共进程时,A 租户 malloc 太多,B 租户可能直接 std::bad_alloc,但根本不知道是哪个租户干的——错误不可追溯,配额不可分账。
- RLIMIT_AS 是进程级开关,无法 per-thread 或 per-cgroup 动态调整
- 即使你用
pthread_atfork在 fork 后设限,子进程的租户上下文也早已丢失 - 真正要用资源限制,只认
memcg或userfaultfd这类可嵌套、可审计的机制
实际部署中,最容易被忽略的是 memcg 的 memory.low 和 memory.pressure 接口——它们能让你在 OOM 前主动降级服务,而不是等 kernel 杀进程才反应过来。










