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

内核更新后Linux read()读取perf计数器偶发返回errno10(无子进程)

内核更新后perf_event_open的read()调用随机返回ECHILD错误问题

我在CentOS 9 Stream系统上运行一个C++程序,通过perf_event_open和read方法收集硬件计数器数据。程序在内核版本5.14.0-319.el9.x86_64下完全正常,但更新至5.14.0-472.el9.x86_64(以及略旧的5.14.0-467.el9.x86_64)后,read()方法偶尔会返回-1,错误时机随机:有时收集数百个样本才触发,有时仅20个左右就出现,也存在全程无错误的情况。

调用read()后立即保存的errno值为10,通过strerror_r获取的错误描述为No child processes(无子进程)。

初始化硬件计数器的示例代码

#define TOTAL_HW_COUNTERS 4
const char * pmu_names[TOTAL_HW_COUNTERS] = {"instructions", "cpu-cycles", "branch-misses","cache-misses"};
unsigned int pmu_offsets[TOTAL_HW_COUNTERS] = {PERF_COUNT_HW_INSTRUCTIONS, PERF_COUNT_HW_CPU_CYCLES, PERF_COUNT_HW_BRANCH_MISSES, PERF_COUNT_HW_CACHE_MISSES};
unsigned int pmu_types[TOTAL_HW_COUNTERS] = {PERF_TYPE_HARDWARE, PERF_TYPE_HARDWARE, PERF_TYPE_HARDWARE, PERF_TYPE_HARDWARE};

int initializePerf(pid_t child_pid) {
    static struct perf_event_attr pe;
    int fd;

    /* The first object is the leader */
    memset(&pe, 0, sizeof(pe));
    pe.type = pmu_types[0];
    pe.size = sizeof(pe);
    pe.config = pmu_offsets[0];
    pe.disabled = 1;
    pe.inherit = 1;
    pe.exclude_kernel = 0;
    pe.exclude_hv = 0;
    pe.read_format = PERF_FORMAT_GROUP | PERF_FORMAT_ID;
    fd = perf_event_open(&pe, child_pid, -1, -1, 0);
    if (fd == -1) {
        int errsv = errno;
        std::cerr << "Error opening leader " << pe.config << " errno: " << errsv << std::endl;
        exit(EXIT_FAILURE);
    }
    /* Add the rest of the perf_event_attr objects in a group with the leader */
    for (int i = 1; i < TOTAL_HW_COUNTERS; i++) {
        memset(&pe, 0, sizeof(pe));
        pe.type = pmu_types[i];
        pe.size = sizeof(pe);
        pe.config = pmu_offsets[i];
        pe.disabled = 1;
        pe.inherit = 1;
        pe.exclude_kernel = 0;
        pe.exclude_hv = 0;
        pe.read_format = PERF_FORMAT_GROUP | PERF_FORMAT_ID;

        if (perf_event_open(&pe, child_pid, -1, fd, 0) == -1) {
            int errsv = errno;
            std::cerr << "Error opening group counter " << pe.config << " errno: " << errsv << std::endl;
            exit(EXIT_FAILURE);
        }
    }
    return fd;
}

int main(int argc, char* argv[]) {
    initializePerf(0);
    // Do stuff
    return 0;
}

读取硬件计数器的代码

int readPerfCounters(int bytes_read, char * read_pmu_buf, int fd, int count) {
    char * bufptr = read_pmu_buf + bytes_read;
    int errsv;
    int size_read = read(fd, bufptr, count);
    errsv = errno;
    if (size_read == -1) {
        std::cerr << "Error reading perf counters at fd " << fd;
        std::cerr << " errno: " << errsv << std::endl;
        char buffer[ 256 ];
        char * errorMsg = strerror_r(errsv, buffer, 256 );
        std::cerr << errorMsg << std::endl; // Output: "No child processes"

    }
    return size_read;
}

疑惑与解答

1. "No child processes"错误与read()调用有何关联?

当你调用perf_event_open时传入child_pid=0,表示监控当前进程自身,加上pe.inherit=1会同时监控该进程创建的子进程。在较新的内核版本中,perf子系统的read操作增加了对监控目标存活状态的严格检查:

  • 如果程序运行过程中有子进程创建后快速退出,内核在采样周期内检测到对应计数器的目标实体失效,就会返回ECHILD
  • 使用PERF_FORMAT_GROUP时,组内任意一个计数器的目标进程/线程不存在,都会导致整个组的read调用返回该错误
  • 即使没有显式创建子进程,进程内部的线程短暂销毁重建(比如线程池的动态调整),也可能触发内核的状态检查返回ECHILD

2. 不同内核版本间的相关变更

CentOS 9内核从5.14.0-319到5.14.0-467/472的更新中,perf子系统的关键变更包括:

  • 修复了perf_event在进程退出时的资源泄漏问题,同时新增了已退出进程的文件描述符有效性检查逻辑
  • 优化了inherit模式下的计数器继承机制,子进程退出时内核会立即标记对应计数器状态为无效
  • 完善了read操作的错误处理流程,将之前被忽略的"目标进程不存在"场景明确映射为ECHILD错误返回

临时解决方案

  • 关闭inherit模式:如果不需要监控子进程,将pe.inherit设置为0,避免子进程生命周期变化影响主进程计数器读取
  • 错误重试机制:遇到ECHILD错误时,短暂延迟(比如1ms)后重新执行read操作,多数情况下能成功获取数据
  • 重新初始化计数器:如果重试失败,可以关闭当前perf文件描述符,重新调用initializePerf创建新的计数器实例

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 03:53:11