系统调用不接受指针类型参数,而是将指针值作为地址填入寄存器(如rsi),由内核按该地址访问用户内存;需确保指针合法、可访问、长度足够,错误时返回负值并设errno。

系统调用本身不接受指针作为“参数类型”,而是通过寄存器传地址
Linux 系统调用(如 sys_read、sys_write)在用户态不可直接调用,必须经由封装(如 glibc 的 read())或手动触发 syscall 指令。你写的 C++ 代码里用的指针(比如 char* 缓冲区),最终只是被当作地址值填进某个寄存器(如 rdi、rsi),内核再按该地址去读写用户空间内存。所以重点不是“怎么用指针调用系统调用”,而是“怎么安全地把指针地址交给内核”。
常见错误现象:Segmentation fault —— 传了非法地址(比如空指针、未映射页、只读页);EINVAL 或 EFAULT —— 地址越界或未对齐(尤其某些 arch 对缓冲区长度/对齐有要求)。
- 必须确保指针指向的内存是用户空间合法、可访问、已分配的(比如用
new、malloc、栈变量,但不能是悬空指针或nullptr) - 避免传递栈上过小缓冲区给可能写满的系统调用(如
read(STDIN_FILENO, buf, 1024)中buf至少要 1024 字节) - 系统调用返回负值表示错误,需检查并转为
errno(glibc 封装会自动做,裸syscall不会)
用 syscall() 包装器传指针:以 read() 为例
最常用方式是包含 <sys></sys> 和 <unistd.h></unistd.h>,用 syscall(SYS_read, ...) 手动触发。注意参数顺序与 ABI 严格对应(x86_64 下是 rdi, rsi, rdx, r10, r8, r9)。
#include <sys>
#include <unistd.h>
#include <cerrno>
char buf[512];
long ret = syscall(SYS_read, STDIN_FILENO, buf, sizeof(buf));
if (ret == -1) {
// 错误:syscall 不自动设 errno,需手动检查
errno = -ret; // 内核返回负错误码,如 -EFAULT → errno=EFAULT
}
</cerrno></unistd.h></sys>
关键点:
Linux AI 服务器 — 将 Linux 服务器转换为本地 AI 推理集群。使用 systemd、NVIDIA CUDA 实现无头 Linux AI,零 GUI 开销。
-
SYS_read是宏定义(值为 0),不是函数名,不能加括号调用 - 第三个参数是
size_t长度,不是指针;第二个参数才是你要传的指针地址 - 裸
syscall()返回值是long,成功时非负,失败时为负错误码(如-14表示EFAULT) - 不要用
int接返回值——32 位截断可能导致误判(比如实际返回0xfffffffffffffeee)
用 inline asm 直接触发 sysenter/sysexit(极少见,仅作说明)
绕过 libc 的 syscall() 函数,手写汇编调用。这几乎没人需要,除非写 bootloader 或极端性能场景。它暴露了指针如何真正变成寄存器值:
char buf[64];
long n = 64;
long fd = STDIN_FILENO;
long ret;
asm volatile (
"syscall"
: "=a"(ret)
: "a"(0), "D"(fd), "S"(buf), "d"(n) // SYS_read=0, rdi=fd, rsi=buf, rdx=n
: "rcx", "r11", "r8", "r9", "r10", "r12", "r13", "r14", "r15"
);
这里 buf 被直接放进 rsi 寄存器 —— 它就是个数值,C++ 中的指针变量名在此刻彻底退化为地址整数。容易踩的坑:
- 忘记 clobber 列表(
"rcx", "r11")会导致寄存器被破坏,程序崩溃 - 不同架构指令不同(x86_64 用
syscall,x86_32 用int $0x80或sysenter),无法跨平台 - 没有栈对齐检查:某些系统调用(如
clone)要求 16 字节栈对齐,否则 SIGSEGV
为什么不该直接裸写系统调用 + 指针
glibc 的 read()、write() 等函数不只是简单包装 syscall。它们做了很多指针相关的安全增强:
- 自动重试被信号中断的调用(
EINTR)—— 裸syscall返回后你得自己循环 - 检查指针有效性前先验证长度(比如拒绝超大
count值,防止内核整数溢出) - 对某些系统调用(如
openat)做路径字符串合法性预检,减少内核态错误返回 - 在 musl 或 Android bionic 中,还可能做额外的 ASLR 兼容处理
真正需要裸指针传参的场景极少,比如调试内核模块、实现自定义 libc、或嵌入式无 libc 环境。日常开发中,用标准封装函数,把指针传给 read(fd, buf, size) 就够了——它内部已经把 buf 当地址塞进寄存器,你不需要知道那一步。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










