1. 前言
限于作者能力水平,本文可能存在的谬误,因此而给读者带来的损失,作者不做任何承诺。
2. 背景
本篇基于 Linux 4.14 + ARM 32 + glibc-2.31
进行分析。
3. 系统调用的实现
3.1 系统调用的发起
3.1.1 起于用户空间
我们随意挑选一个系统调用,如常见的 write()
,glibc-2.31
对其实现如下:
ssize_t
__libc_write (int fd, const void *buf, size_t nbytes)
{
return SYSCALL_CANCEL (write, fd, buf, nbytes);
}
// 中间省略多个宏定义,对这些细节感兴趣的读者可以自行阅读 glibc 代码。
// ...
# define INTERNAL_SYSCALL_RAW(name, err, nr, args...) \
({ \
register int _a1 asm ("r0"), _nr asm ("r7"); \
LOAD_ARGS_##nr (args) \
_nr = name; \
asm volatile ("swi 0x0 @ syscall " #name \
: "=r" (_a1) \
: "r" (_nr) ASM_ARGS_##nr \
: "memory"); \
_a1; })
总之,系统调用语句 return SYSCALL_CANCEL (write, fd, buf, nbytes)
最终展开如下(为方便阅读,对最终结果的格式稍作了调整):
return ({
long int sc_ret;
int sc_cancel_oldtype = LIBC_CANCEL_ASYNC ();
sc_ret = ({
unsigned int _sys_result = ({
register int _a1 asm ("r0")/* 参数fd从寄存器r0传入 */, _nr asm ("r7")/* 系统调用编号从寄存器 r7 传入 */;
int _a3tmp = (int) (nbytes);
int _a2tmp = (int) (buf);
int _a1tmp = (int) (fd);
_a1 = _a1tmp;
register int _a2 asm ("a2") = _a2tmp; /* 参数 buf 从寄存器 r1 (即a2) 传入 */
register int _a3 asm ("a3") = _a3tmp; /* 参数 @nbytes 从寄存器 r2 (即 a3) 传入 */
_nr = __NR_write; /* 赋值系统调用编号 */
/* arm32 通过 swi 指令,发起系统调用 */
asm volatile ("swi 0x0 @ syscall __NR_write"
: "=r" (_a1)
: "r" (_nr), "r" (_a1), "r" (_a2), "r" (_a3)
: "memory");
_a1; /* 系统调用返回值,从寄存器 r0 传回 */
});
if (__builtin_expect (INTERNAL_SYSCALL_ERROR_P (_sys_result, ), 0))
{
__set_errno (INTERNAL_SYSCALL_ERRNO (_sys_result, )); /* 调用出错,设置错误码 errno */
_sys_result = (unsigned int) -1; /* 调用出错,返回 -1 */
}
(int) _sys_result; /* 系统调用返回值: sc_ret = _sys_result; */
});
LIBC_CANCEL_RESET (sc_cancel_oldtype);
sc_ret; /* write() 调用的返回值 */
});
注:
寄存器 r0/a1, r1/a2, r2/a3
是等同的,关于ARM32平台调用规范寄存器的更多细节,可参看ARM32调用规范文档《IHI0042J_2020Q2_aapcs32.pdf》
表格 Table 6.1: Table 2, Core registers and AAPCS usage
。
从上面代码的分析中,我们了解到ARM32平台的系统调用,是通过 swi
指令发起,以及系统调用参数传递的细节。接下来,我们继续分析系统调用进入内核空间后的工作细节。
3.1.2 进入内核空间
系统调用的过程,是和具体硬件架构相关的,在继续讨论内核空间系统调用的工作细节之前,我们先来聊一点 ARM32 架构和系统调用相关的内容。
先了解下 ARM32 CPU 的几种工作模式:
以及各 CPU 工作模式下的寄存器分布状况:
从上图表格可以看出:
. 有些寄存器是所有模式共享的,如寄存器 R0~R7 ;
. 有些寄存器模式独立的,如 R13 和 R14 ,User 和 System 模式共用一组空间,而 Supervisor 等其它模式使用各自独立的空间(R13_xxx,R14_xxx)。
再看一下 ARM32 异常和CPU模式的对应关系:
自从用户空间
swi
指令的运行,会导致 ARM32 CPU 产生一个异常,根据上图表格,CPU 将进入 Supervisor
模式,ARM32 CPU 进入异常时,还会自动做如下动作:
R14_svc = swi 下一条指令的地址(即用户空间的返回地址)
SPSR_svc = CPSR (即CPU当前状态寄存器,模式为 User)
CPSR[4:0] = 0b10011(切换到 Supervisor 模式)
CPSR[7] = 1(禁用当前 CPU 的一般中断)
...
PC = 异常向量地址(即跳转到异常向量地址处执行)
接下来,我们要找到内核 SWI 异常向量的定义位置,因为程序流程将跳转到该处执行。我们先要找到中断向量表,从 ARM32 架构的链接脚本片段:
/* @arch/arm/kernel/vmlinux.lds.S */
__vectors_start = .;
.vectors 0xffff0000 : AT(__vectors_start) {
*(.vectors)
}
. = __vectors_start + SIZEOF(.vectors);
__vectors_end = .;
/* 位于中断向量表偏移 0x1000 处 */
.stubs ADDR(.vectors) + 0x1000 : AT(__stubs_start) {
*(.stubs)
}
. = __stubs_start + SIZEOF(.stubs);
__stubs_end = .;
我们找到 swi
指令异常中断向量入口 vector_swi
:
/* @arch/arm/kernel/armv.S */
.section .stubs, "ax", %progbits
.word vector_swi /* swi 指令异常中断向量入口 */
...
.globl vector_fiq
.section .vectors, "ax", %progbits
.L__vectors_start:
W(b) vector_rst
W(b) vector_und
W(ldr) pc, .L__vectors_start + 0x1000 /* 软中断向量(swi) */
W(b) vector_pabt
W(b) vector_dabt
W(b) vector_addrexcptn
W(b) vector_irq
W(b) vector_fiq
兜兜转转,我们终于进入了我们的正题,内核空间系统调用流程的分析:
/*=============================================================================
* SWI handler
*-----------------------------------------------------------------------------
*/
.align 5
ENTRY(vector_swi)
...
3.2 系统调用的自动重新发起
3.3 系统调用的返回
4. 添加系统调用
5. 参考资料
《ARM Architecture Reference Manual.pdf》
《IHI0042J_2020Q2_aapcs32.pdf》
标签:__,swi,调用,int,vectors,简析,vector,Linux
From: https://blog.51cto.com/u_4594296/5926721