AArch64 普通函数调用和 Linux 系统调用都从 x0 开始传参,但它们不是同一套约定。普通函数用 bl 跳转并按 AAPCS64 分配寄存器;系统调用把调用号放进 w8,执行 svc #0,由内核完成请求。svc 的立即数不是 Linux 系统调用号。
本文讨论 Linux 原生 AArch64 用户态,不包含 AArch32 兼容 ABI。应用应使用目标平台头文件中的 SYS_* 或 _NR *,不要把 x86-64 的调用号带到 ARM64。
先掌握应用可见的约定
用途 AArch64 Linux 约定
进入内核 svc #0
系统调用号 w8,即 x8 的低 32 位
最多六个参数 x0、x1、x2、x3、x4、x5
主要返回值 x0
原始错误 x0 中的 -errno,错误范围 -4095 到 -1
libc 公共封装的错误 通常返回 -1,并设置线程局部 errno
同一个 x0 在入口表示参数 1,出口表示结果。例如 write 入口的 x0 是文件描述符,成功出口的 x0 是本次写入的字节数。syscall() 这个 C 函数也已经处理 errno,不等于一段直接返回原始寄存器的汇编。syscall(2)
写 w0 会清零高 32 位;宽度必须跟随参数类型
mov w0, #-1
x0 = 0x00000000ffffffff
写 32 位视图后的 64 位寄存器值
sxtw x0, w0
x0 = 0xffffffffffffffff
把有符号 32 位 -1 扩展为 64 位 -1
内核参数若是 int,按 int 解释低位;若是 long / 指针,需要完整宽度。
两种位模式对某些 int 参数效果相同,不代表对所有系统调用参数都可互换。
w 与 x 是同一个寄存器的不同宽度视图
写 w 寄存器会把对应 x 寄存器高 32 位清零,所以 mov w0, #-1 得到的完整 x0 值不是 64 位的 -1。若内核参数类型是 int,低 32 位的全 1 可以按该类型解释成 -1;若接口要求有符号 64 位数,就不能省略符号扩展。指针、size_t 和偏移参数也必须按目标 ABI 与接口定义准备,不能统一用 w 寄存器装载。Arm AArch64 寄存器
下面的位运算模型只说明寄存器位模式,不调用内核。它同时演示把返回值截成 32 位会掩盖完整 64 位结果,实际原始错误判断必须使用 x0 的宽度。
MASK32 = ( 1 < < 32 ) - 1
MASK64 = ( 1 < < 64 ) - 1
w_minus_one = ( - 1 ) & MASK32
x_after_w_write = w_minus_one
x_after_sxtw = ( w_minus_one | ( ~ MASK32 ) ) & MASK64
assert x_after_w_write == 0x 00000000ffffffff
assert x_after_sxtw == 0x ffffffffffffffff
def raw_error ( bits ) :
return ( bits & MASK64 ) >= ( ( - 4095 ) & MASK64 )
assert raw_error ( x_after_sxtw )
assert not raw_error ( x_after_w_write )
assert raw_error ( ( - 22 ) & MASK64 )
assert not raw_error ( 0x 0000000100000000)
assert ( 0x 0000000100000000 & MASK32 ) == 0
这里 x_after_w_write 不在 64 位错误区间,不能把低 32 位的 0xffffffff 看到就判为系统调用失败。另一方面,也不能因为同一个数不在错误区间,就宣布它是有效地址:是否为地址以及是否可访问仍由具体调用结果和映射条件决定。
同样的低32位,为什么不是同一个返回值?
固定输入 -1 切换扩展方式,两块十六进制中只有高半部改变,64位含义却从 4294967295 变成 -1。再选择 -4096:即使符号扩展后为负,也不属于 [-4095,-1] 的原始错误编码区间。
正在呈现知识画面 重新加载
同样的低32位,为什么不是同一个返回值? 写 w 寄存器清零高32位,符号扩展则复制符号位。只有完整64位落入保留错误区间,才按 -errno 解释。
{"id":"arm64-width-error","title":"同样的低32位,为什么不是同一个返回值?","summary":"写 w 寄存器清零高32位,符号扩展则复制符号位。只有完整64位落入保留错误区间,才按 -errno 解释。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"control\"><label for=\"value\" data-i18n=\"value\"></label><output for=\"value\" id=\"value-value\"></output><input id=\"value\" type=\"range\" min=\"-4096\" max=\"1\" value=\"-1\" step=\"1\"></div><div class=\"presets\" role=\"group\" data-i18n-label=\"mode\"><button data-mode=\"zero\" data-i18n=\"zero\"></button><button data-mode=\"sign\" data-i18n=\"sign\"></button></div><section class=\"panel\"><h3 data-i18n=\"bits\"></h3><div id=\"bits\"></div></section><div class=\"metrics\"><div><span data-i18n=\"signed\"></span><strong id=\"signed\"></strong></div><div><span data-i18n=\"result\"></span><strong id=\"result\"></strong></div></div><p class=\"formula\">0xfffffffffffff001 ≤ x0 ≤ 0xffffffffffffffff</p><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#bits{font:16px/2 var(--viz-font-mono);display:grid;grid-template-columns:1fr 1fr;gap:8px;text-align:center}#bits span{border:1px solid var(--rule);padding:10px 0;border-radius:6px}.metrics strong{font-size:17px}","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 mode='zero';function draw(){const n=BigInt($('#value').value),x=mode==='zero'?BigInt.asUintN(32,n):BigInt.asUintN(64,n),s=BigInt.asIntN(64,x),err=x>=BigInt.asUintN(64,-4095n),h=x.toString(16).padStart(16,'0');set('value-value',n.toString());pressed(mode);$('#bits').innerHTML='<span>'+h.slice(0,8)+'</span><span>'+h.slice(8)+'</span>';set('signed',s.toString());set('result',t(err?'error':'ok'));$('#result').dataset.error=err;$('#bits').dataset.hex=h;}$('#value').oninput=draw;document.querySelectorAll('[data-mode]').forEach(e=>e.onclick=()=>{mode=e.dataset.mode;draw()});draw();","audio":false,"strings":{"bits":"x0 的64位十六进制","error":"原始 -errno","heading":"同样的低32位,为什么不是同一个返回值?","intro":"选择扩展方式,比较完整64位值与 Linux 原始错误区间。","mode":"扩展方式","ok":"不在错误区间","reset":"重置","result":"错误区间判定","scope":"只比较整数表示与错误区间,不声明非错误数值是有效指针。BigInt 精确计算64位值。","scopeLabel":"模型范围与约定","sign":"sxtw:符号扩展","signed":"64位有符号解释","value":"32位有符号输入","zero":"写 w0:零扩展"}}
SVC 到底保存什么
AArch64 EL0 → EL1:异常状态由硬件保存,通用寄存器由内核保存
用户执行 svc #0
w8 是系统调用号,x0–x5 是参数
1
CPU 记录异常状态
返回 PC → ELR_EL1;PSTATE → SPSR_EL1
2
进入同步异常向量
选择 EL1 栈;ESR_EL1 标识 SVC 异常
3
Linux 保存寄存器并分派
形成 pt_regs,检查追踪等工作,调用处理函数
4
Linux 恢复上下文并执行 eret
x0 带回结果,返回用户态
5
图以 EL1 内核说明架构机制;虚拟化配置不改变应用使用的 syscall ABI。
以从 EL0 进入 EL1 为例,硬件保存返回地址与处理器状态,选择目标异常级的栈,并进入异常向量。来自较低异常级、AArch64 状态的同步异常槽位位于 VBAR_EL1 + 0x400;入口代码还需要根据异常原因区分 SVC、指令异常等。
硬件没有一次性把 x0–x30 全压入内存。Linux 软件入口负责保存所需通用寄存器、用户栈指针等,形成可供追踪、信号递送和返回路径操作的上下文。不要将“硬件没有自动保存所有通用寄存器”误写成“硬件不保存任何状态”。Arm 异常模型
阅读 Linux v6.18 时,可以从 entry.S 的 kernel_entry/kernel_exit 跟到 syscall.c 的 el0_svc_common 和 invoke_syscall。进入与退出还涉及追踪、信号、重调度及安全相关工作,不能用几条拼接的伪汇编代表完整内核实现。入口汇编 、系统调用分派
与 x86-64 对照时,比较同一层
问题 原生 x86-64 Linux 原生 AArch64 Linux
参数 4 r10,避开硬件使用的 rcx x3
返回位置由硬件暂存到哪里 rcx ELR_EL1(以 EL1 为例)
处理器状态由硬件暂存到哪里 r11 SPSR_EL1(以 EL1 为例)
建立入口内存帧 Linux 软件保存寄存器 Linux 软件保存寄存器
异常返回 满足条件用 SYSRET,否则 IRET ERET
AArch64 不存在 x86 SYSCALL 对 rcx/r11 的那种同名通用寄存器破坏,仍不能据此说“系统调用保留所有寄存器状态”。尤其是扩展向量状态有独立 ABI:SVE 的系统调用边界对高位向量、谓词寄存器等有明确约定,不能假定整套 SVE/SME 状态保持不变。Linux SVE ABI
性能也不能从表中推导。是否触发页表切换、内核缓解措施、CPU 型号、频率和虚拟化环境都会影响延迟,寄存器排列更整齐不等于必然更快。
一个可组装的完整例子
下面只依赖 AArch64 Linux 的 write=64、exit=93。它处理短写与 EINTR,其他失败退出;适合观察 ABI,不是通用异步 I/O 循环。
.section .rodata
message:
.ascii "hello\n"
.section .text
.global _start
_start:
adrp x1, message
add x1, x1, :lo12:message
mov x2, #6
.Lwrite:
mov x0, #1
mov w8, #64
svc #0
cmn x0, #4 // -EINTR
b.eq .Lwrite
cmp x0, #0
b.le .Lerror
add x1, x1, x0
subs x2, x2, x0
b.ne .Lwrite
mov x0, #0
b .Lexit
.Lerror:
mov x0, #1
.Lexit:
mov w8, #93 // 原始 exit 退出当前线程
svc #0
.section .note.GNU-stack,"",%progbits
在 AArch64 Linux 上可用本机 as、ld 构建;在其他架构上可只做交叉组装和链接:
clang -- target= aarch64-linux-gnu - c syscall-arm64.S - o syscall-arm64.o
ld.lld - m aarch64elf - o syscall-arm64 syscall-arm64.o
readelf - h - l syscall-arm64
生成目标文件不代表已在 ARM64 上执行验证。运行还需要 AArch64 Linux 或正确配置的模拟环境。此例没有 libc 启动代码,exit 也不负责刷新 stdio 缓冲。
让汇编叶子函数同时满足 C ABI 和 syscall ABI
前面的 _start 没有普通调用者。若想让 C 调用一个汇编封装,函数既要按 AAPCS64 接收参数与返回,也要在 svc 前放好 Linux 所需寄存器。以下 raw-write-arm64.S 把 C 的三个参数原样用于 write,直接返回原始结果。
.text
.global raw_write_arm64
.type raw_write_arm64, %function
raw_write_arm64:
mov w8, #64
svc #0
ret
.size raw_write_arm64, .-raw_write_arm64
.section .note.GNU-stack,"",%progbits
对应的 C 调用者保存为 raw-write-main.c:
extern long raw_write_arm64 ( int fd , const void * buffer , unsigned long size ) ;
int main ( void ) {
static const char message[ ] = " hello\n " ;
const char * next = message;
unsigned long left = sizeof message - 1 ;
while ( left != 0 ) {
long result = raw_write_arm64 ( 1 , next, left) ;
if ( result == - 4 ) continue ;
if ( result <= 0 ) return 1 ;
next += result;
left -= ( unsigned long ) result;
}
return 0 ;
}
这里 C 调用把 fd、buffer、size 放到 x0–x2 对应的参数位置,汇编只需设置 w8。bl 建立的返回地址在 x30;svc 的异常返回状态保存在异常级寄存器中,Linux 返回用户态后,此叶子封装可以 ret 回 C 调用者。
如果以后在封装中新增 bl helper,就必须保存并恢复自己的返回地址,并遵守被调用者保存寄存器规则。AAPCS64 的公共接口要求 SP 按 16 字节对齐;不要仿照只压一个 8 字节值的写法,让后续调用在错误的栈对齐下执行。AAPCS64 子程序调用
clang -- target= aarch64-linux-gnu - c raw-write-arm64.S - o raw-write-arm64.o
clang -- target= aarch64-linux-gnu - ffreestanding - c raw-write-main.c - o raw-write-main.o
readelf - sW raw-write-arm64.o
readelf - rW raw-write-main.o
以上两次编译无需 C 标准库头文件,适合在其他架构检查符号和调用重定位。它们并未完成 C 运行时链接。实际运行时,在 AArch64 Linux 使用本机工具链执行 cc raw-write-main.c raw-write-arm64.S -o raw-write-demo,再运行 ./raw-write-demo;预期输出 hello。
检查层次 能说明什么 不能替代什么
交叉组装 指令与重定位语法可接受 内核是否接受本次参数
ELF header / symbol / relocation 目标是 AArch64,调用引用已生成 运行时寄存器内容正确
本机或模拟环境运行 当前路径的输出与退出状态 所有错误路径、真实硬件性能
目标机压力与故障测试 信号、短写等条件下的行为 其他内核配置的普遍保证
CPU 特性和系统调用号是不同问题
getcpu、mlock2 并非 ARM64 独有系统调用。PR_PAC_RESET_KEYS 是 prctl 的一种操作,不是独立 syscall 编号。PAC、BTI、SVE、LSE 是否可用,需要结合处理器能力与内核暴露的 ABI 判断。
在 AArch64 Linux C 程序中,使用系统头文件中的位定义,不手抄 HWCAP 常量:
#include < sys/auxv.h>
#include < asm/hwcap.h>
int has_lse_atomics ( void )
{
return ( getauxval ( AT_HWCAP ) & HWCAP_ATOMICS) != 0 ;
}
HWCAP 描述内核允许用户态使用的能力,比根据 CPU 型号字符串猜测可靠;它不是系统调用是否实现的清单。Linux ELF HWCAP
LDXR/STXR 是用户态原子算法可能使用的指令,不需要每次进入内核。它们与带 acquire/release 语义的 LDAXR/STLXR 要区分;LSE 则提供另一组原子指令。原子性、内存顺序和等待唤醒是三个问题,不能从“没有 x86 lock 前缀”推导 NUMA 扩展性。实际同步如何进入内核,继续看 futex 。