1. 以c标准库为例分析系统调用的实现
关于 什么是系统调用 本文不过多赘述
syscall()是一个较为底层的接口,允许程序通过系统调用号直接调用内核服务,函数原型如下:#include <sys/syscall.h> /* * number;系统调用号 * ...: 根据具体系统调用传递参数(最多 6 个) */ long syscall(long number, ...);- 在ubuntu的 man手册 中有
syscall()的用法和 实现细节 ,非常有用, 可以通过如下命令查看,在man手册中有如下两个表描述了不同的硬件架构,系统调用实现的细节man 2 syscall

- 通过上述两个表,对于怎么实现系统调用相信已经比较清楚了,通过一条软中断指令触发异常,使程序由用户态进入内核态,系统调用号和参数以及返回值则通过寄存器传递。
- 如下是
nolibc中实现的read()函数,架构为arm64,大家可以结合上面两张表分析一下面代码,有关gcc内嵌汇编的语法可以参考我的另外一篇文章《gcc内嵌汇编语法分析》

2 系统调用流程
第一章节以c标准库为例分析系统调用在用户态的实现,当执行完svc指令进入内核态之后又会执行哪些操作呢?本章将会继续进行讨论。
- 触发异常后第一步肯定是进入异常向量表,linux异常向量表在文件
./linux-orangepi/arch/arm64/kernel/entry.S中实现,这部分代码还是比较复杂的,需要对ARM64的体系架构和gcc汇编器由较为深入的认识,我们只关注大概流程。如下异常向量表中,kernel_ventry是一个宏,异常向量表的第九行kernel_ventry 0, t, 64, sync,展开后为el0t_64_sync,这个函数即为系统调用的入口

el0t_64_sync也是在./linux-orangepi/arch/arm64/kernel/entry.S通过汇编实现,如下图所示,他主要功能是保存内核传递的参数并调用el0t_64_sync_handler()

el0t_64_sync_handler()是一个c函数,在./linux-orangepi/arch/arm64/kernel/entry-common.c中实现,系统调用使用svc异常触发,会调用el0_svc()来处理svc异常。

el0_svc()在./linux-orangepi/arch/arm64/kernel/entry-common.c中实现如下,主要是调用do_el0_svc()处理系统调用static void noinstr el0_svc(struct pt_regs *regs) { enter_from_user_mode(regs); // 标记进入内核态,处理状态切换(如关闭用户态调试) cortex_a76_erratum_1463225_svc_handler(); // 针对 Cortex-A76 硬件缺陷的补丁处理 do_el0_svc(regs); // 系统调用核心逻辑入口 exit_to_user_mode(regs); // 准备返回用户态,恢复上下文并执行 eret }do_el0_svc()在.//linux-orangepi/arch/arm64/kernel/syscall.c中实现,核心函数是el0_svc_common()void do_el0_svc(struct pt_regs *regs) { fp_user_discard(); /* * regs:用户态传递过来参数的指针 * regs->regs[8]:从指针中找到系统调用号 * __NR_syscalls:系统调用的总数 * sys_call_table:系统调用表 */ el0_svc_common(regs, regs->regs[8], __NR_syscalls, sys_call_table); }sys_call_table在./linux-orangepi/arch/arm64/kernel/sys.c中定义,sys_call_table中包含所用系统调用的入口,如下图所示

el0_svc_common()在./linux-orangepi/arch/arm64/kernel/syscall.c中实现,主要功能是依据系统调用号在sys_call_table中找到对应的系统调用入口并执行- 至此,系统调用的整个流程分析完成,大家可以试着依据这个流程分析现有的系统调用,也可以试着增加一个系统调用,

2135

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



