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

如何正确实现PID/TID缓存,规避fork场景下的异常问题?

gettid缓存与fork行为差异的优化方案探讨

考虑以下代码:

#define _GNU_SOURCE
#include <unistd.h>
#include <sys/syscall.h>
#include <stdio.h>

int main() {
    pid_t tid = gettid();
    printf("tid: %d", tid); 
    int ret = syscall(SYS_fork, 0);
    printf("tid: %d", gettid()); 
}
  • 在现代(>=2.25)glibc环境下,上述代码会输出三行内容,包含两个不同的值。
  • 而在musl环境下,上述代码会输出三行完全相同的内容。

这种差异源于缓存机制。libc的实现会缓存这类值以避免系统调用的开销,fork/clone包装器负责更新这些缓存值,但如果用户绕过这些包装器,就会导致意外结果。

getpid的手册页中有一个专门章节介绍相关历史:

从glibc 2.3.4到glibc 2.24版本,getpid()的glibc包装函数会缓存PID,目的是避免进程重复调用getpid()时产生额外的系统调用。通常这种缓存是透明的,但它的正确运行依赖于fork(2)、vfork(2)和clone(2)的包装函数支持:如果应用通过syscall(2)绕过glibc的这些包装函数调用上述系统调用,那么子进程中调用getpid()会返回错误的值(准确来说,会返回父进程的PID)。此外,即使通过glibc包装函数调用clone(2),也存在getpid()返回错误值的情况(相关案例讨论可参考clone(2)的BUGS章节)。而且,缓存代码的复杂性多年来一直是glibc内部一些BUG的根源。

我想知道是否存在一种正确实现缓存的方式。鉴于MADV_WIPEONFORK可以指示内核在fork时清除特定页面,我们能否构建如下结构:

struct ProcessLocalStorage {
  OnceFlag once; /* zero represents uninitialized */
  pid_t pid;
  pid_t getpid() {
    callonce(&this->once, []{ pid = syscall(SYS_getpid); });
    return pid;
  }
} *pls = mmap(...); /* hinted by MADV_WIPEONFORK */

pid_t gettid() {
  static thread_local pid_t pid = 0;
  static thread_local pid_t tid = 0;
  pid_t real_pid = pls->getpid();
  if (pid != real_pid) {
      pid = real_pid;
      tid = syscall(SYS_gettid);
  }
  return tid;
}

我能想到的一个问题是用户自行调用SYS_clone却未正确设置TLS,但我认为可以说明用户不应假设libc在这种场景下仍能正常工作。

内容的提问来源于stack exchange,提问作者Schrodinger ZHU

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 16:08:14