Linux系统调用是系统提供的从用户空间进入内核空间的方式。每一种系统调用在内核都实现了其对应功能。 在应用层我们看到的是open(),read(),write()等由C库封装好的接口,这些接口都对应了一个内核函数sys_xxx() [sys_open(),sys_read(),sys_write()]。下面开始分析C库函数是怎么调用到内核函数sys_xxx()。
先提出问题:
1、如何进入内核?
2、内核如何知道是发生了哪一种系统调用?
3、进入内核做了哪些操作?
现在开始解答这些问题:
1、C库函数通过软中断 swi 指令进入内核。(函数参数保存在r0->r3寄存器,如果参数超过4个,将超过的部分参数压入栈中)。
2、每一个系统调用都对应一个固定的系统调用号,在调用swi中断之前,C库函数会把系统调用号存入r7寄存器,当通过swi中断进入内核后,将r7寄存器中的系统调用号取出,通过这个系统调用号就能找到对应的处理函数sys_xxx()。
3.当 C库函数执行swi 指令后会自动的跳到内核设置好的中断向量表0x08处执行中断处理。中断向量表在内核启动时由start_kernel()调用early_trap_init() [路径:/arch/arm/kernel/trap.c] 将中断向量表拷贝到中断跳转地址0xFFFF0000(或0x00000000),当中断发生时,CPU会自动的跳转到这个地址执行中断处理。因为swi 指令会偏移0x80,所以实际的跳转地址就是 0xFFFF0000 + 0x08.
void __init early_trap_init(void *vectors_base)
{
unsigned long vectors = (unsigned long)vectors_base;
extern char __stubs_start[], __stubs_end[];
extern char __vectors_start[], __vectors_end[];
unsigned i;
vectors_page = vectors_base;
for (i = 0; i < PAGE_SIZE / sizeof(u32); i++)
((u32 *)vectors_base)[i] = 0xe7fddef1;
/* 将中断向量表拷贝到中断跳转地址,在arm中 中断跳转地址为0xFFFF0000 */
memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
memcpy((void *)vectors + 0x1000, __stubs_start, __stubs_end - __stubs_start);
kuser_init(vectors_base);
flush_icache_range(vectors, vectors + PAGE_SIZE * 2);
modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
}
__vectors_start 和__vectors_end 定义在链接脚本 vmlinux.lds中。
__vectors_start = .;
.vectors 0 : AT(__vectors_start) {
*(.vectors)
}
. = __vectors_start + SIZEOF(.vectors);
__vectors_end = .;
__stubs_start = .;
.stubs 0x1000 : AT(__stubs_start) {
*(.stubs)
}
. = __stubs_start + SIZEOF(.stubs);
__stubs_end = .;
中断向量表的内容位于 /arch/arm/kernel/entry-armv.S中,在中断向量表中偏移0x08就是跳转到W(ldr) pc, __vectors_start + 0x1000 这个地址。这句话表明执行地址为__vectors_start + 0x1000
__vectors_start:
W(b) vector_rst
W(b) vector_und
W(ldr) pc, __vectors_start + 0x1000
W(b) vector_pabt
W(b) vector_dabt
W(b) vector_addrexcptn
W(b) vector_irq
W(b) vector_fiq
从前面的连接脚本vmlinux.lds或者early_trap_init()都能发现__vectors_start 偏移 0x1000这个地址指向__stubs_start这个标号 , __stubs_start代码位于/arch/arm/kernel/entry-armv.S。
__stubs_start:
@ This must be the first word
.word vector_swi
现在我们知道代码将进入vector_swi [/arc/arm/kernel/entry-common.S] 执行。 vector_swi [/arc/arm/kernel/entry-common.S] 的主要事情就是,保存中断现场,获取系统调用号,根据系统调用号找到对应的sys_xxx(),然后恢复中断现场。
.type sys_call_table, #object
ENTRY(sys_call_table)
/* calls.S在编译时展开, ENTRY(sys_call_table) 会跳转到对应的标号地址,进入sys_xxx()*/
#include "calls.S"
/* 略... */
ENTRY(vector_swi)
/*
* 以上内容 略 。。。
* 备份用户空间进程行下文
*/
addne scno, r7, #__NR_SYSCALL_BASE /* r7中的系统调用号保存到scno */
USER( ldreq scno, [lr, #-4] )
#else
/* Legacy ABI only. */
USER( ldr scno, [lr, #-4] ) @ get SWI instruction
#endif
adr tbl, sys_call_table @ load syscall table pointer
/* 略... */
local_restart:
/*
* 以上内容 略 。。
*/
cmp scno, #NR_syscalls /* 检查系统调用号 */
adr lr, BSYM(ret_fast_syscall) /* 保存返回地址 */
ldrcc pc, [tbl, scno, lsl #2] /* PC指针跳转到ENTRY(sys_call_table) + (scno * 4)执行sys_xxx()*/
calls.S 内容如下所示 [/arch/arm/kernel],到这里我们就调用到了C库函数对应的系统调用函数sys_xxx()。
/* 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)
/* 15 */ CALL(sys_chmod)
CALL(sys_lchown16)
CALL(sys_ni_syscall) /* was sys_break */
CALL(sys_ni_syscall) /* was sys_stat */
CALL(sys_lseek)
/* 20 */ CALL(sys_getpid)
CALL(sys_mount)
CALL(OBSOLETE(sys_oldumount)) /* used by libc4 */
CALL(sys_setuid16)
CALL(sys_getuid16)
...
...
...
若有讲解不妥之处烦请在评论区指正!
如果有收获那就一键三连鼓励一下吧!