在 strace 中看到 openat,并不意味着源代码一定调用了 openat;调用 clock_gettime,也不保证追踪中出现同名系统调用。libc 提供的是公共接口语义,内部可以选择其他系统调用、用户态快路径,或组合多个步骤。
理解这层边界有两个用途:读反汇编时知道在哪里寻找参数与错误转换;做优化时确认自己是否保留了原 API 的语义。
三层接口:同一个名字可能指不同东西
| 层次 | 示例 | 谁处理 ABI 与错误 |
| 公共库函数 | read(fd, buf, n) | libc 实现该函数的完整约定 |
| 通用系统调用函数 | syscall(SYS_read, fd, buf, n) | libc 安排寄存器并转换错误;不等同于 read 的全部语义 |
| 原始指令封装 | 内联汇编 syscall / svc | 调用者按架构 ABI 传参并解释原始结果 |
在 x86-64 与 AArch64 Linux 原始 ABI 中,错误编码为 -errno。公共 syscall() 已经把错误转换为 -1,并写入 errno。因此看到 syscall(...) == -1 后,应读取 errno,不能把 -1 再取反当成错误号。syscall(2)
errno 是线程局部状态,成功返回不要求把它清零。只在接口返回值表明失败时解释它,并在后续可能覆盖 errno 的调用前保存。也不是所有 C 接口都通过 errno 报错,例如很多 pthread 函数直接返回错误号。
用一个失败调用看见转换
#define _GNU_SOURCE
#include <errno.h>
#include <stdio.h>
#include <sys/syscall.h>
#include <unistd.h>
int main(void)
{
long result = syscall(SYS_write, -1, "x", (size_t)1);
int saved = errno;
printf("result=%ld, errno=%d, EBADF=%d\n", result, saved, EBADF);
return !(result == -1 && saved == EBADF);
}
cc -std=c11 -Wall -Wextra -Werror wrapper.c -o wrapper
./wrapper
strace -e trace=write ./wrapper
无效文件描述符让内核返回 EBADF。应用观察到的是 -1 与 errno=EBADF;strace 会把原始错误解码成人类可读形式。printf 自己也可能产生 write,分析追踪时要把它与 fd=-1 的目标调用区分。
若改用 x86-64 篇的 raw_write,返回值才会直接是 -EBADF。不要在一段算法中混用这两种错误约定。
错误号要和产生它的返回值一起保存
| 接口约定 | 判断方式 | 容易误读的情况 |
| read / write / syscall | 返回 -1 后读取 errno | 成功时 errno 残留旧错误 |
| pthread_create 等 | 检查函数直接返回的错误号 | 返回非零却打印了无关 errno |
| getauxval 等零值有歧义的接口 | 按文档先清 errno,再结合结果 | 仅凭零值误判不存在 |
| 原始 syscall 汇编 | 判断架构错误区间 | 把未转换的 -errno 当成普通 -1 |
例如第一次 write 失败,随后为了记录上下文又打开日志文件,这次 open 也可能改变 errno。应紧接失败保存 saved_errno,再用保存值格式化报告。设置 errno=0 并不是所有调用前的固定仪式;只有接口约定需要消除旧值歧义时才这样做。
成功之后,旧 errno 去了哪里?
先失败再成功,能同时看到返回值已经变为 3,而 errno 仍可能为 9。它不是一盏由内核自动复位的全局错误灯。需要报告失败时,应立即保存这次返回值对应的 errno,避免后续调用覆盖。
正在呈现知识画面
成功之后,旧 errno 去了哪里?glibc syscall() 将原始 -errno 转为 -1 并设置 errno;成功不要求清零 errno。错误解释必须与同一次返回值配对。
{"id":"libc-errno-pair","title":"成功之后,旧 errno 去了哪里?","summary":"glibc syscall() 将原始 -errno 转为 -1 并设置 errno;成功不要求清零 errno。错误解释必须与同一次返回值配对。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"actions\"><button id=\"fail\" data-i18n=\"fail\"></button><button id=\"intr\" data-i18n=\"intr\"></button><button id=\"success\" data-i18n=\"success\"></button><button id=\"reset\" data-i18n=\"reset\"></button></div><div class=\"metrics\"><div><span data-i18n=\"raw\"></span><strong id=\"raw\"></strong></div><div><span data-i18n=\"returned\"></span><strong id=\"returned\"></strong></div><div><span data-i18n=\"errno\"></span><strong id=\"errno\"></strong></div><div><span data-i18n=\"use\"></span><strong id=\"use\"></strong></div></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n.budget{display:flex;height:28px;margin:16px 0;background:var(--surface);border-radius:5px;overflow:hidden}.budget i{display:block;background:var(--accent);transition:width .2s}.budget i.gold{background:var(--second)}.intervals{margin:16px 0}.interval{position:relative;height:36px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/36px var(--viz-font-ui);padding-left:7px}.table .tr>*{min-height:4.8em}.formula{white-space:pre-line;min-height:7.2em}.actions:has(>button:only-child){grid-template-columns:1fr}.metrics strong{min-height:3.2em}.rangegrid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:3px;margin:14px 0}.rangegrid .cell{min-height:30px;padding:3px 1px;font-size:10px}.message{min-height:7.2em;white-space:pre-line;font:13px/1.8 var(--viz-font-ui);overflow-wrap:anywhere}\n/* Allow CJK hanging punctuation without overflowing a metric's border box. */\n.metrics>div{padding-right:4px}.metrics span{line-break:anywhere}\n\n.metrics strong{font-size:17px}.metrics #use{min-height:5em}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}let raw=null,err=0;function draw(){set('raw',raw??'—');set('returned',raw===null?'—':raw<0?-1:raw);set('errno',err);set('use',t(raw===null?'pending':raw<0?'read':'ignore'));}for(const [id,n] of [['fail',-9],['intr',-4],['success',3]])$('#'+id).onclick=()=>{raw=n;if(n<0)err=-n;draw()};$('#reset').onclick=()=>{raw=null;err=0;draw()};draw();","audio":false,"strings":{"errno":"当前 errno","fail":"write:EBADF","heading":"成功之后,旧 errno 去了哪里?","ignore":"返回成功,不读取旧错误","intr":"write:EINTR","intro":"分别执行失败与成功的封装调用,观察返回值和线程局部 errno。","pending":"尚未调用","raw":"内核原始值","read":"返回失败,保存此错误号","reset":"重置","returned":"应用返回值","scope":"固定 Linux EBADF=9、EINTR=4。成功路径在此模型中保留 errno;具体函数成功后可能修改它,因此成功时不据此判断失败。","scopeLabel":"模型范围与约定","success":"write:成功3字节","use":"此时如何解释 errno"}}
源码里的宏解决了什么
glibc 的架构层安排调用号、输入输出寄存器与 clobber;内部调用层保留原始结果,公共封装层执行 errno 转换。具体宏名与展开方式随版本、架构和参数数量变化,不能把某份旧版宏展开当作 glibc 的永久接口。
阅读 inline asm 时重点检查:输出寄存器是否与输入调用号正确关联,第四个参数是否放到目标 ABI 要求的寄存器,硬件破坏的寄存器是否列入 clobber,以及内存可见性是否被准确描述。
asm volatile 表示该汇编具有不能随意删除等副作用;它本身不是内存屏障。"memory" clobber 告诉编译器汇编可能访问未列出的内存,限制相应编译器重排,也不自动生成 CPU 内存屏障。这些约束与 C/C++ 原子内存序是不同层的问题。GCC Extended Asm
线程取消不是“在慢调用前检查一下”
read、write 等接口具有取消点语义。glibc 的 read 实现经过 SYSCALL_CANCEL;实现需要处理取消请求与进入、阻塞及离开系统调用之间的竞争窗口。仅在调用前检查一次标志,可能在检查后收到取消请求并永久阻塞。glibc read 实现
直接改成 syscall(SYS_read, ...) 或 raw 汇编不能视为等价替换。它可能绕过 libc 的取消机制,进而改变线程停止与资源清理的行为。反过来,使用公共 read 也不意味着线程在任意位置都能被安全异步取消;应按取消类型与清理处理器的约定设计资源生命周期。
用确定的先后关系观察取消与清理
下面在 glibc/Linux 上建立管道,工作线程最终准备调用 read;主线程持有写端但不写入,所以没有数据,也不会提前出现 EOF。两个 barrier 保证先注册清理,再发出取消请求,最后恢复取消。默认延迟取消下,挂起请求在公共 read 的取消点被处理。取消状态
保存为 cancel-read.c:
#define _GNU_SOURCE
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
static pthread_barrier_t ready, requested;
static int ends[2];
static int cleaned;
static void require(int code) { if (code != 0) abort(); }
static void meet(pthread_barrier_t *barrier) {
int code = pthread_barrier_wait(barrier);
if (code != 0 && code != PTHREAD_BARRIER_SERIAL_THREAD) abort();
}
static void cleanup(void *unused) {
(void)unused;
if (close(ends[0]) != 0) abort();
cleaned = 1;
}
static void *worker(void *unused) {
(void)unused;
require(pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL));
pthread_cleanup_push(cleanup, NULL);
meet(&ready);
meet(&requested);
require(pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL));
char byte;
(void)read(ends[0], &byte, 1);
pthread_cleanup_pop(1);
return NULL;
}
int main(void) {
if (pipe(ends) != 0) return 1;
require(pthread_barrier_init(&ready, NULL, 2));
require(pthread_barrier_init(&requested, NULL, 2));
pthread_t thread;
require(pthread_create(&thread, NULL, worker, NULL));
meet(&ready);
require(pthread_cancel(thread));
meet(&requested);
void *result;
require(pthread_join(thread, &result));
if (close(ends[1]) != 0) return 1;
require(pthread_barrier_destroy(&ready));
require(pthread_barrier_destroy(&requested));
printf("canceled=%d cleaned=%d\n", result == PTHREAD_CANCELED, cleaned);
return !(result == PTHREAD_CANCELED && cleaned == 1);
}
cc -std=c11 -Wall -Wextra -pthread cancel-read.c -o cancel-read
./cancel-read
预期 canceled=1 cleaned=1。cleaned 由工作线程写、主线程在成功 join 后读,这个读取时点保证线程已经结束;不能把 join 去掉,然后让主线程无同步地轮询普通 int。
pthread_cleanup_push/pop 在 glibc 中是需要成对使用的宏,作用域必须匹配。本例清理处理器拥有读端,主线程拥有写端;关闭责任分开后,取消路径与正常路径都不会重复关闭同一个描述符。为突出协议,线程基础设施错误用 abort 结束示例;生产代码还需安排创建失败时的资源回收。清理处理器
不要直接把本例 read 替换成原始系统调用来做“更快”版本:那会改变取消点语义,挂起请求可能不能按本例协议终止等待。若选择自行管理停止,应为等待提供明确的唤醒通道并重新设计清理协议,而不是只在循环外添加一个布尔值。
vDSO:保持 API,替换实现路径
内核向进程映射 vDSO,libc 可以解析其中具有版本的符号。对支持的时钟,clock_gettime 路径能够读取内核共享的计时信息并在用户态计算结果;遇到不支持的环境或请求时,路径可能进入内核。
这不是按 clock_id 数值大小做一个通用白名单。不同架构、time64 兼容需求、vDSO 符号及其返回错误,都会影响实现。也不能无条件忽略 vDSO 错误后再次调用:具体错误传播和回退必须符合接口。glibc clock_gettime、vdso(7)
下面在原生 64 位 Linux 上对比两条路径:
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/syscall.h>
#include <time.h>
#include <unistd.h>
int main(void)
{
struct timespec a, b;
if (clock_gettime(CLOCK_MONOTONIC, &a) == -1) {
perror("clock_gettime");
return 1;
}
if (syscall(SYS_clock_gettime, CLOCK_MONOTONIC, &b) == -1) {
perror("syscall clock_gettime");
return 1;
}
printf("libc=%lld.%09ld, syscall=%lld.%09ld\n",
(long long)a.tv_sec, a.tv_nsec,
(long long)b.tv_sec, b.tv_nsec);
return 0;
}
编译后运行 strace -e trace=clock_gettime ./clock-paths。第二次调用明确进入内核,第一次在可用的 vDSO 路径上可能不产生这条 syscall 记录。两次调用读取同一个单调时钟,后一次不应早于前一次,但时钟分辨率允许两次结果相等;调度也可能使间隔变大,不能用一个很小的固定差值作为必过条件。没有 syscall 记录不代表函数没有执行。clock_gettime(3)
包装还包含历史兼容
glibc 可以用 openat 实现 open,32 位平台上的时间接口可能需要 time64 兼容分支。源代码函数名、动态符号名与内核 syscall 名不必一一对应;seccomp 策略和追踪过滤条件应根据目标环境的实际调用路径确定。open(2)
glibc 在 2.25 移除了 getpid 的 PID 缓存,不能继续用“当前 getpid 总是从缓存直接返回”解释基准结果。getpid(2)
musl 同样需要架构参数约定、线程局部 errno、取消点和适用的 vDSO 路径。比较两种 libc 时,应选定版本、目标架构和具体函数,检查构建产物与行为;用“每个 syscall 五行对五十行”推导性能没有可靠意义。
怎样衡量封装成本
先明确测量对象是原始进入/退出、公共 API 调用,还是包含调度和设备等待的端到端操作。记录内核、libc、CPU、编译选项及虚拟化环境,并把批量循环开销与目标调用区分。
strace 适合确认路径和错误,追踪本身会显著扰动延迟,不适合直接代表未追踪程序的微基准。对时钟 API 应确认是否使用 vDSO;对 futex 必须说明等待值、是否真正睡眠及唤醒方,不能把“无争用 FUTEX_WAIT”当作一个固定耗时操作。
只有在测量显示封装成本重要、并能明确承担取消与兼容语义时,才考虑绕过公共接口。减少无必要的系统调用、合并 I/O,通常比复制一份架构相关汇编更值得先检查。