总体流程:kill() -> kill.S -> swi陷入内核态 -> 从sys_call_table查看到sys_kill -> ret_fast_syscall -> 回到用户态执行kill()下一行代码。
详细过程概述
用户程序在用户空间通过软中断swi指令切入内核台,执行vector_swi处指令——这个指令产生软件中断,vector_swi指令在文件/kenel/arch/arm/kernel/entry-common.S中定义。
跟异常处理程序一样,首先保护现场(保护产生中断的用户空间现场),紧接着获取系统调用的系统调用号(这个过程在用户空间执行),以杀进程的kill()函数为例,用户空间kill()定义位于kill.S,当调用kill时,系统先保存r7内容,然后将_NR_kill值放入r7,再执行swi软件中断指令切换进内核态。
用户空间kill的系统调用号为_NR_kill,用户空间的系统调用号定义于/bionic/libc/kernel/uapi/asm-generic/unistd.h 。
(其中__NR_SYSCALL_BASE=0,也就是__NR_kill系统调用号=37。)
在Linux内核中,每个Syscall都有唯一的系统调用号对应。在内核中有与系统调用号对应的系统调用表,定义在文件/kernel/arch/arm/kernel/calls.S
(到这里可知37号系统调用对应sys_kill(),该方法所对应的函数声明在syscalls.h文件)
sys_kill()为内核空间的对应系统调用,对应执行过程的函数声明在syscalls.h文件
asmlinkage是gcc标签,代表函数读取的参数来自于栈中,而非寄存器。
sys_kill()定义在内核源码找不到直接定义,而是通过syscalls.h文件中的SYSCALL_DEFINE宏定义。
总结:
内核空间
1.系统调用的函数原型的指针:位于文件/kernel/arch/arm/kernel/calls.S,格式为CALL(sys_xxx),定义了内核空间系统调用的目标函数入口
2.系统调用号的宏定义:位于文件/kernel/arch/arm/include/Uapi/asm/unistd.h,记录着内核空间的系统调用号,格式为#define__NR_xxx (__NR_SYSCALL_BASE+[num])
3.系统调用的函数声明:位于文件/kernel/include/linux/syscalls.h,格式为asmlinkage long sys_xxx(args …);
4.系统调用的函数实现:不同函数位于不同文件,比如kill()位于/kernel/kernel/signal.c文件,格式为SYSCALL_DEFINEx(x, sname, …)
用户空间
1.系统调用号的宏定义:位于文件/bionic/libc/kernel/uapi/asm-arm/asm/unistd.h,记录着用户空间的系统调用号,格式为#define__NR_xxx (__NR_SYSCALL_BASE+[num])。这个文件就是由内核空间同名的头文件自动生成的,所以该文件与内核空间的系统调用号是完全一致。
2汇编定义相关函数的中断调用过程:位于文件/bionic/libc/arch-arm/syscalls/xxx.S,比如kill()位于kill.S,在用户空间也有函数声明,格式为:
先保存,再内陷,发生系统调用。
其他:
新添加系统调用:除了上面的过程,还需要新增系统调用号还需要修改syscalls总个数,但强烈不建议自己新增系统调用号,尽量保持与linux kernel主线一致,兼容性更好。
-=------------------------------------————————————————————————————————————
Linux系统中用户空间访问内核的手段:系统调用(一般通过中断的方式),异常,陷入。系统调用是用户空间访问内核的唯一合法手段。
中断:一个硬件或者软件请求,要求CPU暂停当前的工作,去处理更重要的事情。例子:在x86及其上可以通过int指令进行软件中断,而在磁盘完成读写操作后会向CPU发起硬件中断。
中断有两个重要的属性,中断号和中断处理程序。中断号用来标识不同的中断,不同的中断具有不同的中断处理程序。在操作系统内核中维护着一个中断向量表(Interrupt Vector Table),这个数组存储了所有中断处理程序的地址,而中断号就是相应中断在中断向量表中的偏移量。