Perfetto 系统调用跟踪(System Calls):从 ftrace raw_syscalls 到 Trace Processor 全链路解析
2026/9/18 5:26:34 网站建设 项目流程

Perfetto 系统调用跟踪(System Calls):从 ftrace raw_syscalls 到 Trace Processor 全链路解析

【免费下载链接】perfettoProduction-grade client-side tracing, profiling, and analysis for complex software systems.项目地址: https://gitcode.com/GitHub_Trending/pe/perfetto

在 Linux 和 Android(仅 userdebug 构建)上,Perfetto 通过 ftrace 的raw_syscalls事件记录每次系统调用的进入与退出:在sys_enter时记录系统调用号与原始参数值,在sys_exit时记录返回值;导入阶段 Trace Processor 借助内部系统调用映射表将编号翻译为sys_xxx名称,最终以普通 slice 的形式内联在每线程轨道中。读完本文,你将掌握如何在 TraceConfig 中启用系统调用跟踪、如何在 UI 中查看、如何用 SQL 查询过滤,以及 syscall 编号表是如何通过 extract_linux_syscall_tables 脚本从 Linux 内核源码生成并驱动导入期的(syscall_table.cc)。

能力边界:哪些系统上可以跟踪系统调用

系统调用跟踪不是通用的用户态能力,而是依赖内核 ftrace 的raw_syscalls/sys_enterraw_syscalls/sys_exit事件,因此有严格的平台前提:

  • Linux:需要内核开启 ftrace 且具备相应权限(通常需要 root 或合适的 tracefs 访问能力);
  • Android:仅userdebug 构建(或更高权限的构建)可用,普通 user 构建不允许访问 ftrace 原始系统调用事件;
  • 这也是 Perfetto 官方将系统调用跟踪定位为"内核侧数据源"的原因——它不是用户态插桩,而是内核事件流的一部分。

从实现上看,Perfetto 对系统调用的处理分为两条链路:

  1. 采集侧(traced_probes):启用linux.ftrace数据源,订阅raw_syscalls/sys_enterraw_syscalls/sys_exit事件,将系统调用号与参数、返回值写入 trace;
  2. 分析侧(Trace Processor):导入时通过系统调用映射表把编号解析为sys_xxx名称,与普通用户态 slice 一样进入每线程的 slice 栈。

数据模型:sys_enter / sys_exit 事件格式

系统调用事件在内核侧由raw_syscalls追踪点产生,Perfetto 将其定义为两个 proto 消息,见 protos/perfetto/trace/ftrace/raw_syscalls.proto:

message SysEnterFtraceEvent { optional int64 id = 1; // 系统调用号(架构相关) repeated uint64 args = 2; // 系统调用的原始参数值 } message SysExitFtraceEvent { optional int64 id = 1; // 系统调用号 optional int64 ret = 2; // 系统调用返回值(负数通常表示 errno) }

关键点在于args是"原始参数值":Perfetto 不会像 strace 那样把每个参数格式化为具名含义(如把 fd 翻译成路径),而是原样记录寄存器中的数值,因此适合做时序分析(什么时候调用了、持续多久、是否返回错误),而不是完整的参数语义分析。

在 Trace Processor 导入期,FtraceParser::ParseSysEnterEvent 与 FtraceParser::ParseSysExitEvent 分别解码这两类事件:

  • 进入事件:读取id得到系统调用号,遍历args生成args[0]args[1]…… 参数名并绑定为参数,再调用SyscallTracker::Enter
  • 退出事件:读取idret,把返回值绑定为参数,再调用SyscallTracker::Exit

随后 SyscallTracker 通过SyscallNumberToStringId将系统调用号映射为sys_xxx名称,并在该线程的轨道(InternThreadTrack(utid))上执行 slice 的Begin/End,从而让系统调用成为普通 slice 栈中的一员。两个值得注意的细节:

  • sys_rt_sigreturn不返回,因此被插入为 instant 事件而非一对 Begin/End slice;
  • sys_write在 Android atrace slice 场景下存在嵌套修复逻辑(MaybeTruncateOngoingWriteSlice,参见 ftrace_parser.cc),并会统计truncated_sys_write_duration以处理"trace 开始时写系统调用尚未结束"的边界情况。

系统调用映射表:编号 → 名称的翻译机制

Trace Processor 使用一张内部系统调用映射表把架构相关的系统调用号翻译成名称,目前支持四种架构(文档中写作 x86、x86_64、ArmEabi、aarch32、aarch64,与源码中的命名对应):

架构枚举(源码)内核 uname machine来源表
kX86_64x86_64arch/x86/entry/syscalls/syscall_64.tbl
kX86i686arch/x86/entry/syscalls/syscall_32.tbl
kArm32armv7l/armv8larch/arm/tools/syscall.tbl
kArm64aarch64include/uapi/asm-generic/unistd.h

映射关系定义在 src/kernel_utils/syscall_table.h 与 src/kernel_utils/syscall_table.cc 中:

  • ArchFromString根据uname的 machine 字符串选择架构(注意armv8l被识别为 32 位用户态的 Arm32);
  • 在 Linux/Android 上,FromCurrentArch通过uname()自动选择当前机器对应的表;
  • GetById(id)由系统调用号反查名称,GetByName(name)支持名称反查编号,返回std::nullopt/nullptr表示未知调用;
  • 每张表的条目上限为kMaxSyscalls = 550,名称字符串统一内联存放,偏移量用uint16_t索引(超过 0xffff 时会显式报错)。

映射表是自动生成的

这些表不是手写的,而是由 tools/extract_linux_syscall_tables 脚本从 Linux 内核源码(当前指向 v6.7)拉取生成:

  • 对 x86_64、x86、arm32 解析.tbl新格式(parse_tlb);
  • 对 arm64 解析asm-generic/unistd.h中的#define __NR_xxx N旧式头文件(parse_def);
  • 生成结果写入 src/kernel_utils/syscall_table_generated.h,文件头注明"DO NOT EDIT. Auto-generated"。

因此如果内核版本新增了系统调用号,需要重新运行该脚本更新表。SyscallTracker 在导入时会把"架构相关的系统调用号"直接预计算为StringId数组(arch_syscall_to_string_id_),避免每条事件都做两次查表转换,这是典型的空间换时间优化。

在 UI 中查看系统调用

在 Perfetto UI 中,系统调用以sys_xxx命名的 slice内联显示在每线程的 slice 轨道上,与用户态 slice(如Chrome_*binder等)交错堆叠:

图中可以看到sys_epoll_pwaitsys_recvfromsys_ioctl等 slice 与普通用户态 slice 处于同一线程轨道,通过sys_前缀即可肉眼区分。这样无需跳转单独的"系统调用视图",直接沿用 Perfetto 熟悉的轨道时间线交互(缩放、悬停查看参数、点击查看详情)。

用 SQL 查询系统调用 slice

在 SQL 层面,系统调用与任何其他用户态 slice 事件没有任何结构差异——它们同样落在每线程 slice 栈(thread_track对应的轨道)中,只是名称以sys_前缀开头。因此可以直接用slices表查询并按名称过滤:

select ts, dur, t.name as thread, s.name, depth from slices as s left join thread_track as tt on s.track_id = tt.id left join thread as t on tt.utid = t.utid where s.name like 'sys_%'

查询结果示例(来自官方文档):

tsdurthreadname
856325324372751439867648s.nexuslaunchersys_epoll_pwait
856325324376970990FpsThrottlerThrsys_recvfrom
8563253243783762657surfaceflingersys_ioctl
8563253244195741250android.anim.lfsys_recvfrom
85632532442816827344android.anim.lfsys_ioctl
856325324451345573FpsThrottlerThrsys_getuid

围绕该查询可以延伸出多种实用分析:

  • 按线程聚合系统调用耗时:按utid/线程名分组,用sum(dur)avg(dur)定位哪些线程频繁陷入内核、哪些调用耗时最长;
  • 观察阻塞性调用sys_epoll_pwait这类长耗时 slice 往往对应线程的休眠等待,可结合thread_state表判断线程状态(如S/D)与系统调用的对应关系;
  • 统计调用频率count(*)配合ts分桶,观察某段时间内系统调用风暴;
  • 查看参数与返回值:系统调用 slice 携带args[0]args[1]… 以及ret参数(见上文 Parse 逻辑),在 UI 的 slice 详情或args表中可以看到;ret为负时通常对应-errno,可用于发现反复失败的调用(如sys_openat返回-ENOENT)。

由于系统调用 slice 与用户态 slice 共享同一线程轨道,还能利用 span join 把用户态函数(如 binder 调用)与内核态系统调用重叠起来分析"用户态等待内核返回"的耗时构成。

TraceConfig:如何开启系统调用跟踪

开启系统调用跟踪需要启用linux.ftrace数据源,并订阅raw_syscalls/sys_enterraw_syscalls/sys_exit两个 ftrace 事件:

data_sources: { config { name: "linux.ftrace" ftrace_config { ftrace_events: "raw_syscalls/sys_enter" ftrace_events: "raw_syscalls/sys_exit" } } }

进阶:按需裁剪系统调用子集

全量记录系统调用会产生大量事件(高频率的sys_read/sys_write/sys_epoll_wait可能占满 ftrace 环形缓冲区)。如果只想关注特定调用,可在ftrace_config中设置syscall_events子集字段(见 protos/perfetto/config/ftrace/ftrace_config.proto):

data_sources: { config { name: "linux.ftrace" ftrace_config { syscall_events: "sys_read" syscall_events: "sys_open" } } }

关于该字段有两个版本行为需要注意:

  • Perfetto v43 之前:只设置syscall_events还不够,配置中必须同时显式包含raw_syscalls/sys_{enter,exit}
  • Perfetto v43 及之后:只需设置syscall_events字段即可,工具会自动处理(该字段在 Android U 引入)。

此外,ftrace_config中还有buffer_size_kb(默认值及 lower-bound 语义见 ftrace_config.proto)等字段可用于调整 ftrace 缓冲区大小,缓解高频率系统调用带来的丢事件风险;若事件被丢弃,Trace Processor 会通过stats表中的相关计数器反映出来。

命令行快速采集示例

使用 Perfetto 命令行工具(Android 上的perfetto或桌面的tracebox/trace_processor配套的采集端)时,可以编写上述 TraceConfig 的 pbtxt 文件,然后执行:

# Android (userdebug 构建) adb shell perfetto -c /data/local/tmp/syscall_trace.pbtxt -o /data/local/tmp/syscall_trace.perfetto-trace adb pull /data/local/tmp/syscall_trace.perfetto-trace # 本地分析 trace_processor --query "select ts, dur, name from slices where name like 'sys_%' limit 10" syscall_trace.perfetto-trace

采集端会通过 ftrace 事件订阅把raw_syscalls事件写入 trace 文件,随后即可用 Trace Processor、UI 或本文的 SQL 查询做分析。

深入实现:从事件到 slice 的关键调用链

将上述环节串起来,一条系统调用从内核到 UI/SQL 的完整链路为:

  1. 内核raw_syscalls/sys_enterraw_syscalls/sys_exit追踪点产生事件(携带idargsret);
  2. 采集traced_probes的 ftrace 数据源(linux.ftrace)读取并序列化为 SysEnterFtraceEvent / SysExitFtraceEvent 消息;
  3. 导入:FtraceParser 解码事件,提取系统调用号并绑定参数/返回值;
  4. 翻译:SyscallTracker::Enter/Exit 借助SyscallTable(syscall_table.cc)把编号翻译为sys_xxx名称,在每线程轨道上执行 slice Begin/End;
  5. 查询:系统调用以sys_前缀 slice 落入slices表,与用户态 slice 一样可通过 Perfetto SQL 查询,或在 Perfetto UI 中直接查看。

单元测试对这套机制提供了覆盖,例如 src/trace_processor/importers/syscalls/syscall_tracker_unittest.cc 验证了编号→名称翻译、Begin/End 配对等核心行为,可作为理解语义的参考。

常见问题与注意事项

  • 为什么 UI 里看不到系统调用?最常见原因是配置中未启用raw_syscalls/sys_{enter,exit};其次是设备为 Android user 构建(非 userdebug),无权访问该系统调用追踪点;
  • args为什么只是数字?Perfetto 记录的是原始寄存器参数值,不做 strace 式的参数语义化,这是设计取舍——关注时序与频率而非参数含义时开销更低;
  • 系统调用号是架构相关的:同一数字在 x86_64 与 aarch64 上对应不同调用,Trace Processor 依据导入时的架构信息选择映射表,因此不要跨架构混用对编号的假设;
  • 高频率调用可能丢事件:全量sys_事件带宽较大,建议用syscall_events子集过滤,并按需调大buffer_size_kb
  • trace 边界问题:trace 开始时已进入但未退出的系统调用没有对应sys_enter,导入器通过MaybeTruncateOngoingWriteSlice等逻辑处理,涉及truncated_sys_write_duration统计项。

通过系统调用跟踪,你可以把"线程在等什么"这个经典性能问题落地:结合sys_slice、thread_state与用户态 slice,快速定位线程被阻塞在内核调用的位置与时长,是分析 IO 等待、锁竞争、Binder 调用与网络阻塞的高价值数据源。

【免费下载链接】perfettoProduction-grade client-side tracing, profiling, and analysis for complex software systems.项目地址: https://gitcode.com/GitHub_Trending/pe/perfetto

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询