Linux 基础错误码(errno 1–34)技术参考

本文档说明include/uapi/asm-generic/errno-base.h中定义的 34 个基础错误码的用途、语义边界与常见使用场景。这些是所有架构共享的 POSIX 通用错误码,构成内核态与用户态之间最基础的错误契约。

约定:内核函数通过返回负值(如return -EINVAL;)传递错误;用户态系统调用失败时返回-1并把正值写入全局errno。二者是同一套编号,只是符号相反。


速查表

编号含义一句话定位
1EPERMOperation not permitted有权访问对象,但操作本身被策略禁止(≠ 无权限访问文件)
2ENOENTNo such file or directory路径 / 条目不存在
3ESRCHNo such process目标 PID / 线程不存在
4EINTRInterrupted system call阻塞调用被信号打断
5EIOI/O error底层设备 I/O 失败
6ENXIONo such device or address设备/地址不存在或未就绪
7E2BIGArgument list too long参数/数据超出上限
8ENOEXECExec format error可执行文件格式非法
9EBADFBad file numberfd 无效或模式不匹配
10ECHILDNo child processes无可等待的子进程
11EAGAINTry again资源暂不可用,稍后重试(=EWOULDBLOCK
12ENOMEMOut of memory内存分配失败
13EACCESPermission denied权限检查不通过(访问许可)
14EFAULTBad address用户指针非法 / 拷贝越界
15ENOTBLKBlock device required需要块设备但给了非块设备
16EBUSYDevice or resource busy资源被占用 / 正在使用
17EEXISTFile exists目标已存在
18EXDEVCross-device link跨设备操作不允许
19ENODEVNo such device设备不存在 / 操作不支持该设备
20ENOTDIRNot a directory期望目录却是非目录
21EISDIRIs a directory期望文件却是目录
22EINVALInvalid argument参数非法(最常用的“兜底”错误)
23ENFILEFile table overflow系统级打开文件数耗尽
24EMFILEToo many open files进程级 fd 耗尽
25ENOTTYNot a typewriterioctl 命令不适用于该 fd
26ETXTBSYText file busy正在执行的镜像被写
27EFBIGFile too large超过文件大小上限
28ENOSPCNo space left on device设备空间/资源耗尽
29ESPIPEIllegal seek对管道/socket 做 seek
30EROFSRead-only file system只读文件系统上写操作
31EMLINKToo many links硬链接数超限
32EPIPEBroken pipe对端已关闭的管道/socket 写
33EDOMMath argument out of domain数学函数定义域错误
34ERANGEMath 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 参数校验:EINVALEFAULTE2BIGERANGE

内核入口(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 设备与资源状态:ENODEVENXIOEBUSYENOMEM

驱动开发(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 可重试语义:EAGAINEINTR

对内核并发/迁移路径极其重要,务必与“真失败”区分:

  • 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:EBADFENOTTYEMFILE/ENFILE

  • EBADF(9):fd 无效,或用错误模式访问(如对只读 fd 写)。
  • ENOTTY(25):该 fd 不支持这个 ioctl 命令。DRM 驱动对未识别的DRM_IOCTL_*常返回它。
  • EMFILE(24)/ENFILE(23):分别是进程级RLIMIT_NOFILE)和系统级的 fd 耗尽。

1.6 文件系统语义:ENOENTEEXISTENOTDIREISDIRENOSPC

  • ENOENT(2):路径不存在——也广泛用于“查找某个键/条目失败”。
  • EEXIST(17)O_CREAT|O_EXCL时目标已存在;创建重复对象。
  • ENOTDIR(20)/EISDIR(21):类型不匹配的一对。
  • ENOSPC(28):空间耗尽,也用于“某种表 / 槽位满了”。
  • EROFS(30)/ETXTBSY(26)/EFBIG(27)/EMLINK(31):文件系统特定约束。

1.7 管道与进程:EPIPEESRCHECHILDESPIPE

  • EPIPE(32):向已关闭读端的管道/socket 写,同时触发SIGPIPE
  • ESRCH(3):目标进程/线程不存在(killptracesched_setaffinity)。
  • ECHILD(10)wait()时没有可等待的子进程。
  • ESPIPE(29):对不可 seek 的对象(管道/FIFO/socket)调用lseek

2. 内核编码惯例

  1. 返回负值return -EINVAL;;成功返回0或正的有效值(如已处理字节数)。

  2. 错误指针:用ERR_PTR(-ENOMEM)编码,IS_ERR()判定,PTR_ERR()取出。

    bo=amdgpu_bo_create(...);if(IS_ERR(bo))returnPTR_ERR(bo);
  3. 错误传播:优先保留下层返回的 errno,不要无脑改成-EINVAL,以免丢失语义(尤其-EAGAIN/-EINTR/-ENOMEM必须原样传播)。

  4. goto清理链:分配失败时按逆序释放,返回对应 errno。

  5. 不要用 errno 数值判断底层原因EAGAIN == EWOULDBLOCKEDEADLK == 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(...)返回-1strerror(errno)显示Invalid argument
  • 定位
    strace-etrace=ioctl-f./my_test2>&1|grep-iEINVAL
    内核侧常见校验:
    if(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 rols -ld检查路径每一段类型。
  • 处理:换可写路径(/tmptmpfs);创建前stat判存在性;确保父目录确为目录。

场景 9:目标进程/线程已消失 →-ESRCH

  • 语义锚点ESRCH的本义是“没有这个进程”,因此它天然绑在task 查找失败上,而不是 mm 引用计数归零。区分两条来源很重要:
    • task 查找为空 →-ESRCH(标准用法):find_get_task_by_vpid()/pid_task()返回 NULL。
      • mm/migrate.cfind_mm_struct():task 查不到即return ERR_PTR(-ESRCH);move_pages系统调用)。
      • mm/process_vm_access.cfind_get_task_by_vpid()为空 →rc = -ESRCH;process_vm_readv/writev)。
    • task 在、但没有 mm →-ESRCHkernel/fork.cmm_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);
  • 触发条件:对一个已退出或从未存在的 PID/TID 操作 —— 用户态kill(pid, sig)sched_setaffinity(pid, ...)ptrace(..., pid, ...)、读/proc/<pid>/...时进程刚好退出;或内核异步路径用保存的pid回查进程(restore worker、fault handler)而进程已被销毁。
  • 现象:系统调用返回-1errno == ESRCHNo 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|grepESRCH
    竞态确认:在kill与目标退出之间存在 TOCTOU 窗口——kill(pid,0)探活成功后,真正操作时进程已死。
  • 处理
    • 用户态把-ESRCH当作“进程正常结束”的非致命情况处理,而不是报错退出(尤其监控/清理类工具)。
    • 内核异步路径拿到-ESRCH静默放弃并释放已占资源,不要重试、不要打印 error 级日志(进程退出是常态)。
    • get_task_struct()持有 task 引用避免中途释放;查不到 task 才返回-ESRCH

场景快速索引

症状最可能的 errno首选排查动作
ioctl 参数报错但看着正常EINVAL清零结构体、核对 flag 与内核版本
迁移不前进但无报错EAGAIN检查页是否被 pin / 加重试上限
分配失败但显存有余ENOMEM看 VRAM 碎片 / 配额 / 回退 GTT
提交全部失败EBUSYENODEV查 GPU reset 日志
ioctl 返回 Bad addressEFAULTstrace 看用户指针、核对 size
Inappropriate ioctlENOTTY选对 DRM node / 命令号
等待被打断EINTR/ERESTARTSYS原样传播,勿改写
写文件失败EROFS/EEXIST/ENOTDIR检查挂载与路径类型