ARM工作模式
ARM处理器有7种工作模式,分别是用户模式、快速中断模式、外部中断模式、管理模式、数据访问终止模式、未定义指令终止模式和系统模式。
用户模式(User)
正常程序执行模式,权限最低,无法直接切换到其他模式。
快速中断模式(FIQ)
用于处理高速数据传输或通道中断,具有独立寄存器组以加快处理速度。
外部中断模式(IRQ)
处理通用外设中断请求,优先级低于FIQ。
管理模式(Supervisor/SVC)
操作系统内核使用的保护模式,复位或复位后进入此模式,用于系统初始化和管理。
数据访问终止模式(Abort)
处理存储器访问异常(如非法地址访问),支持虚拟内存和存储保护。
未定义指令终止模式(Undefined)
执行未定义指令时进入,可用于硬件协处理器的软件仿真。
系统模式(System)
运行特权级操作系统任务,与用户模式共享寄存器组,但具有更高权限。
用户态 linux arm处理器系统调用
Linux中,ARM64架构系统调用是使用管理模式实现的,使用svc #0指令触发系统调用,其核心流程如下:
指令触发
用户态程序通过svc #0(Supervisor Call)指令发起系统调用,参数通过AAPCS64规则传递:
系统调用号存于x8寄存器
参数按顺序存于x0~x5(最多6个)
返回值存于x0
svc #0触发EL0_SYNC异常
syscall定义
源码路径:bionic/libc/arch-arm64/bionic/syscall.S
ENTRY(syscall)
/* Move syscall No. from x0 to x8 */
mov x8, x0
/* Move syscall parameters from x1 thru x6 to x0 thru x5 */
mov x0, x1
mov x1, x2
mov x2, x3
mov x3, x4
mov x4, x5
mov x5, x6
svc #0
/* check if syscall returned successfully */
cmn x0, #(MAX_ERRNO + 1)
cneg x0, x0, hi
b.hi __set_errno_internal
ret
END(syscall)
系统调用号的定义
路径:bionic/libc/include/bits/glibc-syscalls.h
#define SYS_brk __NR_brk
#endif
#if defined(__NR_cachectl)
#define SYS_cachectl __NR_cachectl
#endif
#if defined(__NR_cacheflush)
#define SYS_cacheflush __NR_cacheflush
#endif
#if defined(__NR_capget)
#define SYS_capget __NR_capget
#endif
#if defined(__NR_capset)
#define SYS_capset __NR_capset
#endif
#if defined(__NR_chdir)
#define SYS_chdir __NR_chdir
#endif
#if defined(__NR_chmod)
#define SYS_chmod __NR_chmod
#endif
#if defined(__NR_chown)
#define SYS_chown __NR_chown
#endif
#if defined(__NR_chown32)
#define SYS_chown32 __NR_chown32
#endif
#if defined(__NR_chroot)
#define SYS_chroot __NR_chroot
#endif
#if defined(__NR_clock_adjtime)
#define SYS_clock_adjtime __NR_clock_adjtime
#endif
#if defined(__NR_clock_getres)
#define SYS_clock_getres __NR_clock_getres
#endif
#if defined(__NR_clock_gettime)
#define SYS_clock_gettime __NR_clock_gettime
#endif
#if defined(__NR_clock_nanosleep)
#define SYS_clock_nanosleep __NR_clock_nanosleep
#endif
#if defined(__NR_clock_settime)
#define SYS_clock_settime __NR_clock_settime
#endif
#if defined(__NR_clone)
#define SYS_clone __NR_clone
#endif
#if defined(__NR_close)
#define SYS_close __NR_close
#endif
#if defined(__NR_connect)
#define SYS_connect __NR_connect
#endif
#if defined(__NR_copy_file_range)
...
用户使用

用户直接使用syscall接口进行系统调用,第一个参数是调用号,后面是调用的具体参数最多不能超过6个。
内核态的实现
syscall调用号与执行函数的声明
内核syscall调用号与执行函数的声明:
源码路径:kernel/arch/arm64/include/asm/unistd32.h

这里截取部分系统调用号的设定。
对系统调用的统一管理:
源码路径:
kernel/arch/arm64/kernel/sys.c or sys32.c
void * const sys_call_table[__NR_syscalls] __aligned(4096) = {
[0 ... __NR_syscalls - 1] = sys_ni_syscall,
#include <asm/unistd.h>
};
整个头文件被整个引用,作为数据成员数据。
回调函数的实现
在内核态 基本所有的系统调用都是由SYSCALL_DEFINEX宏定义实现,以下是SYSCALL_DEFINEX的代码定义:
代码路径:kernel/include/linux/syscalls.h
#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE_MAXARGS 6
#define SYSCALL_DEFINEx(x, sname, ...) \
SYSCALL_METADATA(sname, x, __VA_ARGS__) \
__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
#define __PROTECT(...) asmlinkage_protect(__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__)); \
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_DEFINE1(brk, unsigned long, brk)为例:
上面会定义几个以下接口:
sys_brk();
SYS_brk();
SYSC_brk()其中SYSC_brk会接管{}里面的内容。SYS_brk会调用到函数SYSC_brk,而sys_brk作为SYSC_brk的别名,为系统调用的入口。
内核对各种系统调用的对接
用户syscall与内核系统调用的对接在Entry.s文件里面实现
文件路径:kernel/arch/arm64/kernel/entry.S 系统调用会触发el0_svc函数的调用
el0_svc:
adrp stbl, sys_call_table // 将sys_call_table加载到内存
mov wscno, w8 // 将系统调用号加载到wscno寄存器
mov wsc_nr, #__NR_syscalls //将__NR_syscalls值加载到wsc_nr寄存器
el0_svc_naked: // compat entry point
stp x0, xscno, [sp, #S_ORIG_X0] // save the original x0 and syscall number
enable_dbg_err_irq
ct_user_exit 1
ldr x16, [tsk, #TSK_TI_FLAGS] //加载当前task的flag
tst x16, #_TIF_SYSCALL_WORK
b.ne __sys_trace
cmp wscno, wsc_nr // 比较调用号与支持的最大值
b.hs ni_sys //大于等于跳转
mask_nospec64 xscno, xsc_nr, x19 // enforce bounds for syscall number
ldr x16, [stbl, xscno, lsl #3] // 具体的加载函数执行地址到x16寄存器
//x16:目标寄存器,用于存储加载的系统调用处理函数地址
//stbl:系统调用表基地址寄存器 sys_call_table
//xscno:系统调用号寄存器,存储当前系统调用的编号
//lsl #3:逻辑左移3位操作,相当于乘以8(64位系统指针大小为8字节)。因为sys_call_table是一个指针。如果调用 //号为0那么直接调用sys_call_table数组下标为0的地址回调函数。如果为1那么就下找到下标为1的地址,也就是下一个指针地址:sys_call_table+8 .以此类推,调用号为N的函数地址就是为:sys_call_table + N * 8
blr x16 // 调用到具体的调用号的回调函数
b ret_fast_syscall
ni_sys:
mov x0, sp
bl do_ni_syscall
b ret_fast_syscall
ENDPROC(el0_svc)
6625

被折叠的 条评论
为什么被折叠?



