本文档说明include/uapi/asm-generic/errno-base.h中定义的 34 个基础错误码的用途、语义边界与常见使用场景。这些是所有架构共享的 POSIX 通用错误码,构成内核态与用户态之间最基础的错误契约。
约定:内核函数通过返回负值(如
return -EINVAL;)传递错误;用户态系统调用失败时返回-1并把正值写入全局errno。二者是同一套编号,只是符号相反。
速查表
| 编号 | 宏 | 含义 | 一句话定位 |
|---|---|---|---|
| 1 | EPERM | Operation not permitted | 有权访问对象,但操作本身被策略禁止(≠ 无权限访问文件) |
| 2 | ENOENT | No such file or directory | 路径 / 条目不存在 |
| 3 | ESRCH | No such process | 目标 PID / 线程不存在 |
| 4 | EINTR | Interrupted system call | 阻塞调用被信号打断 |
| 5 | EIO | I/O error | 底层设备 I/O 失败 |
| 6 | ENXIO | No such device or address | 设备/地址不存在或未就绪 |
| 7 | E2BIG | Argument list too long | 参数/数据超出上限 |
| 8 | ENOEXEC | Exec format error | 可执行文件格式非法 |
| 9 | EBADF | Bad file number | fd 无效或模式不匹配 |
| 10 | ECHILD | No child processes | 无可等待的子进程 |
| 11 | EAGAIN | Try again | 资源暂不可用,稍后重试(=EWOULDBLOCK) |
| 12 | ENOMEM | Out of memory | 内存分配失败 |
| 13 | EACCES | Permission denied | 权限检查不通过(访问许可) |
| 14 | EFAULT | Bad address | 用户指针非法 / 拷贝越界 |
| 15 | ENOTBLK | Block device required | 需要块设备但给了非块设备 |
| 16 | EBUSY | Device or resource busy | 资源被占用 / 正在使用 |
| 17 | EEXIST | File exists | 目标已存在 |
| 18 | EXDEV | Cross-device link | 跨设备操作不允许 |
| 19 | ENODEV | No such device | 设备不存在 / 操作不支持该设备 |
| 20 | ENOTDIR | Not a directory | 期望目录却是非目录 |
| 21 | EISDIR | Is a directory | 期望文件却是目录 |
| 22 | EINVAL | Invalid argument | 参数非法(最常用的“兜底”错误) |
| 23 | ENFILE | File table overflow | 系统级打开文件数耗尽 |
| 24 | EMFILE | Too many open files | 进程级 fd 耗尽 |
| 25 | ENOTTY | Not a typewriter | ioctl 命令不适用于该 fd |
| 26 | ETXTBSY | Text file busy | 正在执行的镜像被写 |
| 27 | EFBIG | File too large | 超过文件大小上限 |
| 28 | ENOSPC | No space left on device | 设备空间/资源耗尽 |
| 29 | ESPIPE | Illegal seek | 对管道/socket 做 seek |
| 30 | EROFS | Read-only file system | 只读文件系统上写操作 |
| 31 | EMLINK | Too many links | 硬链接数超限 |
| 32 | EPIPE | Broken pipe | 对端已关闭的管道/socket 写 |
| 33 | EDOM | Math argument out of domain | 数学函数定义域错误 |
| 34 | ERANGE | Math result not representable | 结果溢出/超出可表示范围 |
1. 按主题分类详解
1.1 权限与策略:EPERMvsEACCES
这是最容易混淆的一对:
EACCES(13):访问许可检查失败。你对这个对象没有资格——文件权限位不允许、SELinux 拒绝、目录不可搜索等。EPERM(1):操作本身在语义上被禁止,通常与是否 root、是否持有 capability 有关,而不是对象的访问位。例如非特权用户kill()不属于自己的进程、非CAP_SYS_ADMIN调用某些 ioctl。
驱动 / DRM 场景:
if(!capable(CAP_SYS_ADMIN))return-EPERM;/* 特权操作被拒 */if(!drm_is_current_master(file))/* 非 master 不能改模式 */return-EACCES;1.2 参数校验:EINVAL、EFAULT、E2BIG、ERANGE
内核入口(ioctl / syscall)的第一道防线:
EINVAL(22):参数组合非法、标志位含未知位、枚举越界、对齐不满足。最常用的兜底错误。EFAULT(14):用户态指针非法。copy_from_user/copy_to_user返回非零时几乎总是转成-EFAULT。E2BIG(7):请求的数量/大小超过接口约定上限(如批量提交条目数过多)。ERANGE(34)/EDOM(33):多用于数值/数学语义;ERANGE也常表示“缓冲区放不下结果”。
if(args->flags&~VALID_FLAGS)return-EINVAL;if(copy_from_user(&k,uptr,sizeof(k)))return-EFAULT;if(args->count>MAX_ENTRIES)return-E2BIG;1.3 设备与资源状态:ENODEV、ENXIO、EBUSY、ENOMEM
驱动开发(amdgpu / DRM)核心错误族:
ENODEV(19):设备不存在,或该操作在此设备上不受支持。常用于 feature 未实现 / 硬件不具备能力。ENXIO(6):设备或地址不存在 / 未就绪。probe 阶段拿不到资源、MMIO 地址无效常用它。EBUSY(16):资源正被占用。BO 被 pin、显存区间被其他上下文持有、设备正在复位。ENOMEM(12):kmalloc/ 页分配 / dma 分配失败;显存或系统内存耗尽。
SVM / 迁移场景中的典型用法:
page=migrate_pfn_to_page(migrate.src[i]);if(!page)continue;/* 空洞,跳过 */if(!amdgpu_vram_mgr_new(...))return-ENOMEM;/* VRAM 分配失败 */if(kfd_process_device_busy(pdd))return-EBUSY;/* 设备忙,稍后重试或回退 */1.4 可重试语义:EAGAIN与EINTR
对内核并发/迁移路径极其重要,务必与“真失败”区分:
EAGAIN(11,等于EWOULDBLOCK):暂时性失败,调用方应重试。非阻塞 I/O 无数据、锁抢占失败、迁移过程中页状态发生变化需要重走。EINTR(4):阻塞调用在完成前被信号打断,通常需要重启系统调用或向上传播让用户态重试。
DRM/SVM 中EAGAIN常被用作“流程需要重来”的控制流信号,而非真正错误:
if(!migrate_vma_setup(&migrate))...;if(migrate.cpages!=npages)return-EAGAIN;/* 部分页未能隔离,让上层重试整个迁移 */if(dma_fence_wait_interruptible(fence))return-EINTR;注意:
-ERESTARTSYS(属于内核内部区间,>512)在返回用户态前会被转换为EINTR,用于自动重启系统调用。二者概念相关但不同层。
1.5 文件描述符与 ioctl:EBADF、ENOTTY、EMFILE/ENFILE
EBADF(9):fd 无效,或用错误模式访问(如对只读 fd 写)。ENOTTY(25):该 fd 不支持这个 ioctl 命令。DRM 驱动对未识别的DRM_IOCTL_*常返回它。EMFILE(24)/ENFILE(23):分别是进程级(RLIMIT_NOFILE)和系统级的 fd 耗尽。
1.6 文件系统语义:ENOENT、EEXIST、ENOTDIR、EISDIR、ENOSPC等
ENOENT(2):路径不存在——也广泛用于“查找某个键/条目失败”。EEXIST(17):O_CREAT|O_EXCL时目标已存在;创建重复对象。ENOTDIR(20)/EISDIR(21):类型不匹配的一对。ENOSPC(28):空间耗尽,也用于“某种表 / 槽位满了”。EROFS(30)/ETXTBSY(26)/EFBIG(27)/EMLINK(31):文件系统特定约束。
1.7 管道与进程:EPIPE、ESRCH、ECHILD、ESPIPE
EPIPE(32):向已关闭读端的管道/socket 写,同时触发SIGPIPE。ESRCH(3):目标进程/线程不存在(kill、ptrace、sched_setaffinity)。ECHILD(10):wait()时没有可等待的子进程。ESPIPE(29):对不可 seek 的对象(管道/FIFO/socket)调用lseek。
2. 内核编码惯例
返回负值:
return -EINVAL;;成功返回0或正的有效值(如已处理字节数)。错误指针:用
ERR_PTR(-ENOMEM)编码,IS_ERR()判定,PTR_ERR()取出。bo=amdgpu_bo_create(...);if(IS_ERR(bo))returnPTR_ERR(bo);错误传播:优先保留下层返回的 errno,不要无脑改成
-EINVAL,以免丢失语义(尤其-EAGAIN/-EINTR/-ENOMEM必须原样传播)。goto清理链:分配失败时按逆序释放,返回对应 errno。不要用 errno 数值判断底层原因:
EAGAIN == EWOULDBLOCK,EDEADLK == EDEADLOCK在部分架构相等,用宏名而非数字。
3. 用户态排查
if(ioctl(fd,DRM_IOCTL_XXX,&arg)<0)fprintf(stderr,"ioctl failed: %s\n",strerror(errno));命令行:
errno22# EINVAL Invalid argument (moreutils 提供的 errno 工具)errno-l# 列出全部perror...# 或用 strace 观察系统调用返回的 errno具体实战场景
下面每个场景给出:触发条件 → 现象 → 定位方法 → 处理方式,均取自内核 / DRM / amdgpu SVM 常见问题。
场景 1:ioctl 返回-EINVAL,但参数“看起来没问题”
- 触发条件:用户态填了一个内核尚未识别的 flag 位,或结构体
reserved字段非零。 - 现象:
ioctl(...)返回-1,strerror(errno)显示Invalid argument。 - 定位:
内核侧常见校验:strace-etrace=ioctl-f./my_test2>&1|grep-iEINVALif(args->flags&~AMDGPU_VM_VALID_FLAGS)/* 有未知位 */return-EINVAL;if(args->_pad)/* 保留字段必须清零 */return-EINVAL; - 处理:用户态
memset(&arg, 0, sizeof(arg))后再填字段;确认内核版本支持该 flag。这是新旧 UAPI 不匹配最典型的表现。
场景 2:迁移路径反复返回-EAGAIN,迁移“卡住不前进”
- 触发条件:
migrate_vma_setup()后cpages != npages——部分页正被其他 CPU/GPU 访问,无法一次性隔离。 - 现象:SVM range 迁移函数被上层不断重调,
dmesg里没有真错误,但吞吐上不去。 - 定位:
migrate_vma_setup(&migrate);if(migrate.cpages!=migrate.npages){pr_debug("only isolated %lu/%lu pages\n",migrate.cpages,migrate.npages);ret=-EAGAIN;/* 让上层重试整个 range */} - 处理:这是正常的控制流,不是 bug。但若无限重试,需检查是否有页被长期 pin(例如用户态持有 DMA-BUF、O_DIRECT I/O 在途),否则会活锁。区分“可重试”与“死循环”的关键是加重试次数上限 + backoff。
场景 3:分配显存得到-ENOMEM,而free显示还有余量
- 触发条件:VRAM碎片化或被 pin 的 BO 占据关键区间,连续大页分配失败;或达到了 per-process 显存配额。
- 现象:
amdgpu_bo_create()/amdgpu_vram_mgr_new()返回-ENOMEM,但rocm-smi --showmeminfo vram显示总量未满。 - 定位:
cat/sys/kernel/debug/dri/0/amdgpu_vram_mm# 查看 VRAM 分配器空洞分布dmesg|grep-i"amdgpu.*out of.*memory" - 处理:改用可回退到 GTT 的分配标志;触发 TTM 驱逐让出连续空间;或缩小单次分配粒度。SVM 场景下可回退到系统内存后再异步迁移。
场景 4:设备复位期间所有提交返回-EBUSY/-ENODEV
- 触发条件:GPU 挂起后进入 recovery(GPU reset),驱动临时把设备标记为不可用。
- 现象:先是
-EBUSY(资源忙),reset 过程中或失败后变为-ENODEV(设备消失)。 - 定位:
dmesg|grep-iE"GPU reset|ring.*timeout|amdgpu.*hang"cat/sys/kernel/debug/dri/0/amdgpu_gpu_recover - 处理:用户态应捕获
-ENODEV视为“设备需重新初始化”,而非立即退出;-EBUSY通常可短暂 backoff 后重试。驱动侧用amdgpu_in_reset()提前拦截提交并返回-EAGAIN让上层重排队。
场景 5:copy_from_user失败 →-EFAULT
- 触发条件:用户传入的地址范围部分不可读/不可写,或结构体大小与内核期望不一致导致越界拷贝。
- 现象:ioctl 返回
-EFAULT;严重时伴随用户态 SIGSEGV。 - 定位:
配合if(copy_from_user(&kdata,u64_to_user_ptr(args->ptr),args->size)){pr_debug("bad user ptr %llx size %u\n",args->ptr,args->size);return-EFAULT;}strace看传入指针,或用dmesg中的pr_debug(需echo -n 'file amdgpu_xxx.c +p' > /sys/kernel/debug/dynamic_debug/control)。 - 处理:确认用户态缓冲区已分配且大小正确;SVM 场景注意 range 边界对齐到页,避免尾页越界。
场景 6:对已识别 fd 发 ioctl 却得-ENOTTY
- 触发条件:ioctl 命令号不属于该驱动,或打开的是 render node 却发了只有 primary node 支持的命令(反之亦然)。
- 现象:
Inappropriate ioctl for device。 - 定位:核对命令号是否用对了
DRM_IOCTL_*宏;确认打开的是/dev/dri/card0还是/dev/dri/renderD128。 - 处理:DRM 对未知 ioctl 默认返回
-ENOTTY(部分路径是-EINVAL)。选对 node,或确认驱动是否注册了该 ioctl。
场景 7:等待 fence 时被信号打断 →-EINTR/-ERESTARTSYS
- 触发条件:
dma_fence_wait_interruptible()或 TTM 可中断驱逐等待期间进程收到信号(如 Ctrl-C、SIGTERM)。 - 现象:内核内部返回
-ERESTARTSYS,进入用户态被转为-EINTR。 - 定位:
ret=dma_fence_wait_interruptible(fence);if(ret)returnret;/* 常为 -ERESTARTSYS,勿改写成 -EINVAL! */ - 处理:必须原样向上传播,让系统调用自动重启或用户态重试。若被错误地转成其他 errno,会导致信号无法及时响应或误报失败。
场景 8:文件系统类操作在容器/只读根下失败
- 触发条件:在只读挂载点写文件(
-EROFS)、O_CREAT|O_EXCL目标已存在(-EEXIST)、路径中间是文件而非目录(-ENOTDIR)。 - 现象:常见于测试脚本在容器里创建 debugfs/tmp 文件失败。
- 定位:
mount | grep ro、ls -ld检查路径每一段类型。 - 处理:换可写路径(
/tmp、tmpfs);创建前stat判存在性;确保父目录确为目录。
场景 9:目标进程/线程已消失 →-ESRCH
- 语义锚点:
ESRCH的本义是“没有这个进程”,因此它天然绑在task 查找失败上,而不是 mm 引用计数归零。区分两条来源很重要:- task 查找为空 →
-ESRCH(标准用法):find_get_task_by_vpid()/pid_task()返回 NULL。mm/migrate.c的find_mm_struct():task 查不到即return ERR_PTR(-ESRCH);(move_pages系统调用)。mm/process_vm_access.c:find_get_task_by_vpid()为空 →rc = -ESRCH;(process_vm_readv/writev)。
- task 在、但没有 mm →
-ESRCH:kernel/fork.c的mm_access():mm=get_task_mm(task);if(!mm)mm=ERR_PTR(-ESRCH);/* 进程已退出或是内核线程,无地址空间 */elseif(!may_access_mm(mm,task,mode))mmput(mm),mm=ERR_PTR(-EACCES);
- task 查找为空 →
- 触发条件:对一个已退出或从未存在的 PID/TID 操作 —— 用户态
kill(pid, sig)、sched_setaffinity(pid, ...)、ptrace(..., pid, ...)、读/proc/<pid>/...时进程刚好退出;或内核异步路径用保存的pid回查进程(restore worker、fault handler)而进程已被销毁。 - 现象:系统调用返回
-1且errno == ESRCH(No such process);内核路径返回-ESRCH,异步 worker 直接放弃本次处理。 - 定位:
用户态复现与观察:task=find_get_task_by_vpid(pid);if(!task){pr_debug("process %d already gone\n",pid);return-ESRCH;/* 进程已退出,放弃本次处理 */}
竞态确认:在strace-etrace=kill,ptrace,sched_setaffinity ./tool2>&1|grepESRCHkill与目标退出之间存在 TOCTOU 窗口——kill(pid,0)探活成功后,真正操作时进程已死。 - 处理:
- 用户态把
-ESRCH当作“进程正常结束”的非致命情况处理,而不是报错退出(尤其监控/清理类工具)。 - 内核异步路径拿到
-ESRCH应静默放弃并释放已占资源,不要重试、不要打印 error 级日志(进程退出是常态)。 - 用
get_task_struct()持有 task 引用避免中途释放;查不到 task 才返回-ESRCH。
- 用户态把
场景快速索引
| 症状 | 最可能的 errno | 首选排查动作 |
|---|---|---|
| ioctl 参数报错但看着正常 | EINVAL | 清零结构体、核对 flag 与内核版本 |
| 迁移不前进但无报错 | EAGAIN | 检查页是否被 pin / 加重试上限 |
| 分配失败但显存有余 | ENOMEM | 看 VRAM 碎片 / 配额 / 回退 GTT |
| 提交全部失败 | EBUSY→ENODEV | 查 GPU reset 日志 |
| ioctl 返回 Bad address | EFAULT | strace 看用户指针、核对 size |
| Inappropriate ioctl | ENOTTY | 选对 DRM node / 命令号 |
| 等待被打断 | EINTR/ERESTARTSYS | 原样传播,勿改写 |
| 写文件失败 | EROFS/EEXIST/ENOTDIR | 检查挂载与路径类型 |