当前位置: 首页 > news >正文

Linux 系统调用在 ARM 上的实现与工作机制

Linux 系统调用在 ARM 上的实现与工作机制

本文以 Linux 4.4.94 源码为依据,面向 ARM 架构系统调用(syscall)的实现原理、工作机制与调用路径进行系统化讲解,并结合真实源码片段与实际案例(write(2))帮助理解。最后提供流程图、时序图与关键源码位置索引,形成可独立阅读的参考文档。

内容概览

  • 系统调用的角色与边界
  • ARM 用户态→内核态:SVC/SWI 陷入机制与 ABI 差异
  • 入口实现:vector_swi、系统调用表与分发
  • 参数与返回值传递规则(寄存器约定)
  • SYSCALL_DEFINE* 宏、SyS 包装与元数据
  • 用户内存访问与安全(uaccesscopy_from_user
  • 实战案例:write(2) 的完整调用路径
  • 兼容与特殊调用(OABI/EABI、ARM 私有调用)
  • 追踪与性能(tracepoints、ftrace)
  • 常见问题与调试建议
  • 参考源码位置与扩展阅读

为什么需要系统调用

  • 用户态程序不能直接访问关键内核资源(进程管理、文件系统、网络栈、内存管理等)。
  • 系统调用提供受控的内核服务入口,保证权限隔离、内存安全与一致性。调用路径:用户态发起 → CPU 指令触发异常 → 进入内核入口 → 参数校验与权限检查 → 调用具体 sys_* 内核实现 → 返回结果。

ARM 用户态到内核态:SVC/SWI 陷入机制

  • 指令层次:ARMv7 等架构中通过 SVC(以前称 SWI)指令触发软中断异常,进入内核异常向量 vector_swi
  • ABI 差异:
    • EABI(现代 ARM Linux 用户态)使用寄存器 r7 传递系统调用号(scno)。
    • OABI(旧 ABI)从 SWI 指令立即数字段取号(从 lr - 4 读取指令编码)。
  • 内核入口的通用流程:保存现场 → 获取系统调用号 scno → 禁用用户访问窗口 → 取 sys_call_table → 越界/追踪检测 → 分发到 sys_* 函数 → 返回路径(快速或慢速)。

关键入口源码(节选)

文件:arch/arm/kernel/entry-common.S

ENTRY(vector_swi)/* …保存现场、从 SPSR/PC 获取状态… *//* 获取系统调用号 */
#if defined(CONFIG_OABI_COMPAT)/* 旧 ABI:从 SWI 指令读取立即数 */ldr     r10, [lr, #-4]/* …处理大小端、掩码、号基数… */
#elif defined(CONFIG_AEABI)/* 纯 EABI:用户态将 scno 放在 r7 *//* scno 已在 r7 */
#elif defined(CONFIG_ARM_THUMB)/* Legacy ABI + 可能是 thumb 模式 */tst     r8, #PSR_T_BITaddne   scno, r7, #__NR_SYSCALL_BASEldreq   scno, [lr, #-4]
#else/* Legacy ABI:直接读指令 */ldr     scno, [lr, #-4]
#endifuaccess_disable tbladr     tbl, sys_call_table        @ 取系统调用表地址local_restart:ldr     r10, [tsk, #TI_FLAGS]      @ 检查追踪标志tst     r10, #_TIF_SYSCALL_WORKbne     __sys_tracecmp     scno, #NR_syscalls         @ 上限检查badr    lr, ret_fast_syscall       @ 设置返回地址ldrcc   pc, [tbl, scno, lsl #2]    @ 分发:跳到 sys_* 例程/* …ARM 私有号处理与错误回退… */
ENDPROC(vector_swi)/* sys_call_table 声明与填充 */
ENTRY(sys_call_table)
#include "calls.S"
ENDPROC(sys_syscall)
  • 说明:入口根据 ABI 从寄存器或指令立即数获取 scno 后,访问 sys_call_table[scno],直接跳转到对应的 sys_* 实现。

系统调用表与号码来源

  • 表定义:sys_call_table 在 ARM 由 calls.S 汇编文件生成;每行对应一个系统调用入口函数。
  • 号码定义:arch/arm/include/uapi/asm/unistd.h__NR_SYSCALL_BASE + N 定义各 __NR_* 号,ARM 私有号以 __ARM_NR_BASE 开始。
  • 数量一致性:entry-common.S 中对 NR_syscalls 和表大小做编译期校验,确保一致。

calls.S(片段)

文件:arch/arm/kernel/calls.S

/* 0 */   CALL(sys_restart_syscall)CALL(sys_exit)CALL(sys_fork)CALL(sys_read)CALL(sys_write)
/* 5 */   CALL(sys_open)CALL(sys_close)CALL(sys_ni_syscall)        /* was sys_waitpid */CALL(sys_creat)CALL(sys_link)
/* 10 */  CALL(sys_unlink)CALL(sys_execve)CALL(sys_chdir)CALL(OBSOLETE(sys_time))    /* used by libc4 */CALL(sys_mknod)
/* …中略… */
/* 120 */ CALL(sys_clone)
/* …中略… */
/* 180 */ CALL(ABI(sys_pread64, sys_oabi_pread64))CALL(ABI(sys_pwrite64, sys_oabi_pwrite64))
/* …中略… */
/* 225 */ CALL(sys_setxattr)CALL(sys_lsetxattr)CALL(sys_fsetxattr)

号段与 ARM 私有调用(片段)

文件:arch/arm/include/uapi/asm/unistd.h

#define __NR_execveat      (__NR_SYSCALL_BASE+387)
#define __NR_userfaultfd   (__NR_SYSCALL_BASE+388)
#define __NR_membarrier    (__NR_SYSCALL_BASE+389)
#define __NR_mlock2        (__NR_SYSCALL_BASE+390)/* ARM 私有 SWI 号段 */
#define __ARM_NR_BASE      (__NR_SYSCALL_BASE+0x0f0000)
#define __ARM_NR_breakpoint    (__ARM_NR_BASE+1)
#define __ARM_NR_cacheflush    (__ARM_NR_BASE+2)
#define __ARM_NR_set_tls       (__ARM_NR_BASE+5)
  • 说明:__ARM_NR_* 是 ARM 平台保留的 SWI 范围(如 set_tlscacheflush),入口根据号基数分支到相应处理流程。

参数与返回值传递(寄存器约定)

  • 传参寄存器:ARM 最多使用 r0r6 传递参数;EABI 用 r7 传递系统调用号。
  • 返回值:r0 用作返回值/错误码(负值表示错误)。
  • 获取与设置接口:arch/arm/include/asm/syscall.h 提供统一访问封装。
/* 读取系统调用号与参数 */
static inline int syscall_get_nr(struct task_struct *task, struct pt_regs *regs) {return task_thread_info(task)->syscall;
}static inline void syscall_get_arguments(struct task_struct *task,struct pt_regs *regs, unsigned int i, unsigned int n, unsigned long *args)
{if (i == 0) { args[0] = regs->ARM_ORIG_r0; /* 原始 r0 */ args++; i++; n--; }memcpy(args, &regs->ARM_r0 + i, n * sizeof(args[0]));
}/* 设置返回值 */
static inline void syscall_set_return_value(struct task_struct *task,struct pt_regs *regs, int error, long val)
{regs->ARM_r0 = (long) error ? error : val;
}

SYSCALL_DEFINE* 宏、SyS 包装与元数据

  • 宏位置:include/linux/syscalls.h
  • 作用:统一声明 sys_* 原型,生成 SyS* 别名包装,做参数类型拓宽/检查,并向 __syscalls_metadata 收集事件元数据(供 tracepoints 使用)。
#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)#define __SYSCALL_DEFINEx(x, name, ...)                      \asmlinkage long sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))\__attribute__((alias(__stringify(SyS##name))));       \static inline long SYSC##name(__MAP(x,__SC_DECL,__VA_ARGS__)); \asmlinkage long SyS##name(__MAP(x,__SC_LONG,__VA_ARGS__)) \ \{                                                         \long ret = SYSC##name(__MAP(x,__SC_CAST,__VA_ARGS__));\__MAP(x,__SC_TEST,__VA_ARGS__);                       \__PROTECT(x, ret,__MAP(x,__SC_ARGS,__VA_ARGS__));     \return ret;                                           \}                                                         \static inline long SYSC##name(__MAP(x,__SC_DECL,__VA_ARGS__))
  • 说明:源文件中按 SYSCALL_DEFINE3(write, ...) 定义,宏负责生成 sys_writeSyS_write 等包装,并注入可追踪的元数据。

用户内存访问与安全

  • uaccess_disable/uaccess_enable:在入口阶段根据需要禁用/启用用户访问窗口,避免错误的用户态访问。
  • copy_from_user/copy_to_user:显式拷贝并检查用户态指针合法性(access_ok)。
  • EFAULT/EPERM 等错误码:在参数验证或权限检查失败时返回负值错误码。

示例位置:fs/read_write.crw_copy_check_uvector() 使用 copy_from_user() 批量检入 iovec。


案例解析:write(2) 完整路径

用户态

/* 用户态伪代码 */
ssize_t n = write(fd, buf, count);  // r7=scno=__NR_write, r0..r2 传参

内核分发与实现(ARM 通用入口 → VFS 层)

文件:fs/read_write.c

SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,size_t, count)
{struct fd f = fdget_pos(fd);ssize_t ret = -EBADF;if (f.file) {loff_t pos = file_pos_read(f.file);ret = vfs_write(f.file, buf, count, &pos);if (ret >= 0)file_pos_write(f.file, pos);fdput_pos(f);}return ret;
}
  • 关键步骤:
    • fdget_pos(fd):解析文件描述符,获得 struct file* 与当前位置;失败则 -EBADF
    • vfs_write():进入 VFS 层,调度具体文件系统的 .write_iter/.write 实现;可能触发文件系统回写、页缓存更新等。
    • 位置更新与资源释放:成功时更新 f_pos,最后 fdput_pos() 释放引用。

端到端时序图

User-spaceARM CPUKernelSVC异常进入 vector_swi保存现场、解析 scno、uaccess_disabletbl=sys_call_table;边界检查与追踪处理跳转 sys_writevfs_write → 具体文件系统实现设置 r0 返回值;恢复现场返回用户态,write() 得到结果User-spaceARM CPUKernel

入口分支流程(EABI/OABI)

flowchart TDA{EABI?} -->|Yes| B[r7 -> scno]A -->|Legacy/OABI| C[读 SWI 立即数 (lr-4) -> scno]B --> D{scno < NR_syscalls?}C --> DD -->|Yes| E[跳转 sys_call_table[scno] → sys_*]D -->|No| F[ARM 私有号/未实现 → sys_ni_syscall]E --> G[ret_fast_syscall 返回路径]F --> H[错误码或特例处理]

(ASCII 备选)

EABI: r7 -> scno
OABI: [lr - 4] SWI -> scno└─ 检查上限 → sys_call_table[scno] → sys_* → 返回

系统调用的上下文切换

  • 概念拆分:

    • 特权级/栈切换(必然发生):用户态执行 SVC/SWI 后进入内核 vector_swi,在 ARM 上从用户栈切到 SP_svc,保存寄存器到 pt_regs,随后分发到 sys_*
    • 任务级上下文切换(条件发生):仅当系统调用出现阻塞、等待或被抢占时,才会调用 schedule() 切到其他任务;否则在当前任务内完成并快速返回用户态。
  • 触发任务级切换的常见情形:

    • I/O 等待(磁盘/网络/管道/套接字缓冲区满)。
    • 互斥量/等待队列(mutex_lock 可睡眠,wait_event* 挂起当前任务)。
    • 主动让出(sched_yield())。
    • 抢占(打开 CONFIG_PREEMPT 时在可抢占点被高优先级任务切走)。
  • 源码对应:

    • 入口与分发:arch/arm/kernel/entry-common.Svector_swisys_call_table)。
    • 典型阻塞点:fs/read_write.cvfs_write → 下层文件系统/驱动的 .write_iter/.write;在管道/套接字路径可能入等待队列后 schedule()
    • 追踪事件:trace/events/syscalls/sys_enter_writesys_exit_writesched/sched_switchsched/sched_wakeup

调度事件视角(时序图)

User-spaceKernelSchedulersys_enter_write (trace/events/syscalls)vector_swi → 解析 scno → 跳转 sys_writesys_exit_write (ret_fast_syscall)schedule() 请求调度事件 sched_switch 切到其他任务当前任务被唤醒(sched_wakeup)继续 sys_write → vfs_write → 下层实现sys_exit_write (ret_slow_syscall 可含信号/重启)alt[无阻塞/不可抢占][阻塞/抢占发生]User-spaceKernelScheduler

阻塞路径(流程图)

flowchart TDA[sys_write(fd, buf, count)] --> B[vfs_write]B --> C{缓冲区可写?}C -->|Yes| D[提交数据 → 更新 f_pos]C -->|No| E[加入等待队列 (pipe/socket/filesystem)]E --> F[schedule() → 任务切换]F --> G[被唤醒 (sched_wakeup)]G --> CD --> H[sys_exit_write 返回用户态]

提示:阻塞/唤醒具体由管道/套接字或对应文件系统驱动实现;在 4.4 时代常见写入路径通过 file->f_op->write_iterwrite 进入,下层自行决定是否等待并调度。


兼容性与 ARM 私有调用

  • OABI 兼容:CONFIG_OABI_COMPAT 下,入口从 SWI 指令解析旧 ABI 号,并在 calls.SABI(...) 选择对应实现(如 sys_oabi_pread64)。
  • ARM 私有号:__ARM_NR_*(如 set_tlscacheflush)由入口通过号基数判断并分派到 ARM 特定处理路径。

追踪与性能

  • 追踪路径:入口在 _TIF_SYSCALL_WORK 下调用 syscall_trace_enter/exit,结合 trace/events/syscalls.* 提供系统调用事件,便于 ftrace/perf/trace-cmd 等工具分析。
  • 元数据:SYSCALL_METADATA 将签名与参数类型注册到 __syscalls_metadata 段,配合事件系统生成统一视图。

常见问题与调试建议

  • 号错/越界:用户态传错 __NR_* 或越界会跳到 sys_ni_syscall 返回 -ENOSYS
  • ABI 不匹配:EABI 程序在 r7 放号;旧 OABI 需 SWI 立即数。交叉编译/兼容层需确认 ABI。
  • 指针非法:copy_from_user() 返回 -EFAULT;优先检查用户缓冲区是否可访问并长度合法。
  • 追踪排错:使用 strace -e trace=write 或 ftrace 的 events/syscalls/* 观察系统调用入参与耗时。

参考源码位置索引(Linux 4.4.94)

  • 入口与表:
    • arch/arm/kernel/entry-common.Svector_swisys_call_table、追踪分支)
    • arch/arm/kernel/calls.S(系统调用表项映射)
    • arch/arm/kernel/entry-armv.S / entry-v7m.S(不同 ARM 变体向量)
  • 号码与私有号:
    • arch/arm/include/uapi/asm/unistd.h__NR_*__ARM_NR_*
    • arch/arm/include/asm/unistd.h__NR_syscalls 数量)
  • 参数访问与返回:
    • arch/arm/include/asm/syscall.h
  • 宏与元数据:
    • include/linux/syscalls.hSYSCALL_DEFINE*SyS* 包装与 metadata)
  • 案例实现:
    • fs/read_write.cSYSCALL_DEFINE3(write)vfs_write

总结

  • 在 ARM 平台,系统调用通过 SVC/SWI 异常进入 vector_swi,依据 ABI 从寄存器或指令读取系统调用号,随后查表分发到对应 sys_* 函数。
  • 参数经寄存器传递并在入口阶段统一封装与校验;返回值通过 r0 返回(错误为负值)。
  • SYSCALL_DEFINE* 宏体系提供一致的声明、包装与事件元数据,便于可观测性与维护。
  • write(2) 为例,调用在 VFS 层进一步路由到具体文件系统,实现用户态到内核态的受控数据写入。

进一步阅读

  • Linux Kernel Documentation 与 trace/events/syscalls.*
  • ftrace/perf/trace-cmd 用户指南
  • ARM 体系结构参考手册(异常向量与寄存器约定)

(本文档对应源码版本:Linux 4.4.94,路径参考工作区实际结构。)

http://www.dtcms.com/a/577630.html

相关文章:

  • 红松小课如何成为激活老年人生活的新引擎?从兴趣学习到价值重塑!
  • 怎么才能去定义自己的生活呢?
  • 嘉兴云建站模板重庆网站备案大厅
  • Java并发实战:ConcurrentHashMap原理与常见面试题
  • 前端FAQ: 在React中,如何优化⼤列表的渲染性能?
  • 华硕ROC奥创中心Armoury Crate服务崩溃解决办法
  • 工业软件国产替代:突破“卡脖子”,筑牢制造业升级基石
  • 大专生就业是否存在学历歧视?
  • Java 8 Stream API 进阶实战:从基础到业务落地的全解析​
  • Java117 最长公共前缀
  • 共聚焦显微镜(LSCM)的针孔尺寸标准解析
  • 长春网站优化方式投票链接制作
  • 酷炫的网站欢迎页面wordpress图片分页
  • 深入理解 flex-shrink:CSS 弹性布局中的 “收缩” 智慧
  • React+Tailwind CSS+Shadcn UI
  • 神经网络—— 优化
  • 有名的网站制怎样才能把网站宣传做的更好
  • MIPI DSI和MIPI Tx IP 的建立
  • 基于时间的 SQL 盲注-延时判断和基于布尔的 SQL 盲注
  • 个人微信公众号怎么做微网站seo完整教程视频教程
  • C++_chapter10_C++IO流类库
  • 树莓派5-docker里的ros常用命令
  • 网站地图1 500 怎么做网站推广方案及预算
  • 餐饮网站方案一个完整的网站怎么做
  • 弄一个关于作文的网站怎么做如何建立网站卖东西
  • 在Ubunutu上学习C语言(二):数组和指针
  • 成品网站源码78w78使用方法网站建设服务领域
  • ESP32内存分布全解析
  • Graph-R1:智能图谱检索增强的结构化多轮推理框架
  • java学习--可变参数