straceでopenatが見えても、ソースコードが必ずしもopenatを呼び出しているとは限りません。clock_gettimeを呼び出しても、トレースに同名のシステムコールが出現する保証はありません。libcが提供する公共インタフェースのセマンティクスは、内部で他のシステムコール、ユーザー空間の高速パス、または複数のステップの組み合わせを選択する可能性があります。
この境界を理解することは、2つの目的で役立ちます。逆アセンブリを読む際に、引数とエラー変換を探す場所を知るため、そして最適化を行う際に、元のAPIのセマンティクスを維持できているかを確認するためです。
3層のインタフェース:同じ名前が異なるものを指すことがある
| 階層 | 例 | 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 になります。1つのアルゴリズム内でこれらの2つのエラー規約を混在させてはいけません。
エラー番号は、それを生成した戻り値と一緒に保存する
| インタフェース規約 | 判断方法 | 誤解しやすい状況 |
| read / write / syscall | -1を返した後、errnoを読む | 成功時にerrnoに古いエラーが残っている |
| pthread_create など | 関数が直接返すエラー番号をチェック | 非ゼロを返すが、無関係なerrnoを出力している |
| getauxval など、0値に曖昧さがあるインタフェース | ドキュメントに従ってまずerrnoをクリアし、結果と組み合わせて判断 | 0値のみで存在しないことを誤判断する |
| 生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のアーキテクチャ層は、呼び出し番号、入出力レジスタ、およびクロバー(破壊されるレジスタ)を配置します。内部呼び出し層は生結果を保持し、公共ラッパー層はerrno変換を実行します。具体的なマクロ名と展開方法は、バージョン、アーキテクチャ、および引数の数に応じて変化するため、古いバージョンのマクロ展開をglibcの恒久的インタフェースとして見なしてはいけません。
インラインアセンブリを読む際には、出力レジスタが入力呼び出し番号と正しく関連付けられているか、第4引数がターゲットABIが要求するレジスタに配置されているか、ハードウェアクロバーレジスタがクロバーリストに含まれているか、そしてメモリの可視性が正確に記述されているかを重点的に確認してください。
asm volatile は、そのアセンブリが随意に削除できない副作用を持つことを示します。それ自体はメモリバリアではありません。"memory" クロバーは、アセンブリがリストされていないメモリにアクセスする可能性があることをコンパイラに通知し、対応するコンパイラの並べ替えを制限しますが、CPUメモリバリアを自動的に生成しません。これらの制約は、C/C++の原子メモリ順序とは異なる層の問題です。GCC Extended Asm
スレッドキャンセルは「遅い呼び出しの前にチェックする」ことではない
read、writeなどのインタフェースはキャンセルポイントセマンティクスを持っています。glibcのread実装はSYSCALL_CANCELを経由します。実装は、キャンセルリクエストの処理と、システムコールへの進入、ブロッキング、および退出間の競合ウィンドウを処理する必要があります。呼び出し前にフラグを1回だけチェックすると、チェックの後にキャンセルリクエストを受け取り、永久にブロッキングする可能性があります。glibc read実装
直接 syscall(SYS_read, ...) や生アセンブリに変更しても、同等の置換とはみなせません。libcのキャンセルメカニズムをバイパスし、スレッドの停止とリソースクリーンアップの動作を変更する可能性があります。逆に、公共のreadを使用しても、スレッドが任意の場所で安全に非同期キャンセルされることを意味しません。キャンセルタイプとクリーンアッププロセッサの規約に従ってリソースのライフサイクルを設計してください。
確実な順序関係でキャンセルとクリーンアップを観察
以下では、glibc/Linux上でパイプを確立し、ワースレッドが最終的にreadを呼び出す準備をする。メインスレッドは書き込みエンドを保持するが書き込まないため、データはなく、EOFが事前に発生することもない。2つの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で2つのパスを比較する:
#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 を実行する。2回目の呼び出しは明確にカーネルに入り、1回目の呼び出しは利用可能なvDSOパスではこのsyscall記録を生成しない可能性がある。2回の呼び出しは同じ単調クロックを読み取り、2番目は1番目より早くならないが、クロックの分解能により2つの結果が等しいことが許容される。スケジューリングにより間隔が大きくなる可能性もあり、非常に小さい固定差値を必須の条件として使用することはできない。syscall記録がないからといって、関数が実行されていないわけではない。clock_gettime(3)
ラッパーには歴史的互換性も含まれる
glibcはopenでopenatを実装でき、32ビットプラットフォームの時間インタフェースはtime64互換ブランチを必要とするかもしれない。ソースコードの関数名、ダイナミックシンボル名、およびカーネルsyscall名は1対1で対応する必要はない。seccompポリシーとトレースフィルタ条件は、ターゲット環境の実際の呼び出しパスに基づいて決定する必要がある。open(2)
glibcは2.25でgetpidのPIDキャッシュを削除した。これ以上「現在のgetpidは常にキャッシュから直接返される」ということでベンチマーク結果を説明してはならない。getpid(2)
muslもアーキテクチャ引数規約、スレッドローカルerrno、キャンセルポイント、および適用可能なvDSOパスを必要とする。2つのlibcを比較する際には、バージョン、ターゲットアーキテクチャ、および具体的な関数を選択し、ビルド成果物と動作を確認する。「各syscallが5行、50行」ということでパフォーマンスを導き出すのは、信頼できる意味を持たない。
ラッパーコストをどのように測定するか
まず、測定対象が生進入/退出、公共API呼び出し、またはスケジューリングとデバイス待機を含むエンドツーエンド操作のいずれであるかを明確にする。カーネル、libc、CPU、コンパイルオプション、および仮想化環境を記録し、バッチループオーバーヘッドとターゲット呼び出しを区別する。
straceはパスとエラーを確認するのに適しているが、トレース自体はレイテンシに顕著な影響を与え、トレースされていないプログラムのマイクロベンチマークを直接代表するものではない。クロックAPIの場合、vDSOが使用されているかを確認する必要がある。futexの場合、待機値、実際にスリープしているか、およびウェイクアップ元を説明する必要があり、「競合のないFUTEX_WAIT」を固定の消費操作として扱うことはできない。
測定によってラッパーコストが重要であり、キャンセルと互換性セマンティクスを明確に引き受けることができる場合にのみ、公共インタフェースをバイパスすることを検討する。不要なシステムコールの削減やI/Oの結合は、通常、アーキテクチャ固有のアセンブリのコピーを作成するよりも、まず確認する価値がある。