一、linux系统调用介绍
linux系统调用是linux为用户空间与内核空间交换提供的一组标准API,这些api能够让用户态进程访问内核代码,从而实现系统资源、硬件、文件读写的访问。需要注意的是,系统调用是用户态进入内核态的唯一入口,为了保证linux内核运行的稳定性,用户程序不能随意的访问内核,必须是经过校验、满足访问权限的程序才能调用相应的系统调用。 linux系统调用又被称为syscall,是通过中断方式实现,在arm平台上通过swi软中断实现系统调用。Linux系统调用主要分为以下几类: 1、文件操作:例如open、read、write、close等,用于对文件进行读写和其他操作。 2、进程管理:例如fork、exec、wait等,用于创建新进程、执行新程序、等待子进程结束等。 3、信号处理:例如kill、signal、sigaction等,用于发送信号、处理信号等。 4、网络通信:例如socket、bind、listen、accept、connect、send、recv等,用于创建套接字、绑定地址、监听连接、建立连接、发送和接收数据等。 5、系统信息获取:例如getpid、getppid、getuid、getgid等,用于获取当前进程ID、父进程ID、用户ID、组ID等信息。 6、时间操作:例如time、sleep等,用于获取当前时间、延迟进程执行等。其他操作:例如chmod、chown、mkdir、rmdir等,用于改变文件权限、改变文件所有者、创建目录、删除目录等。 7、Linux系统调用是应用程序与内核之间的关键接口,它们使得应用程序能够利用操作系统的底层功能和服务,实现各种复杂的操作。对于Linux程序员来说,了解和熟悉系统调用的使用方法和机制是非常重要的。二、linux系统调用流程(基于kernel-5.10)
当用户态执行系统调用时,会根据用户的操作,例如open转化为宏__NR_openat(注意最新glibc的open底层是由openat实现),然后在glibc中的linux/arm/arch-syscall.h找到对应的系统调用号,然后将系统调用号填入系统调用寄存器R7中,然后调用系统软中断swi,陷入内核态,然后在根据内核中断异常表(内核编译时读取arch/arm/tools/syscall.tbl生成的arch/arm/include/generated/calls-oabi.S or calls-eabi.S),调用相应的内核api接口sys_open。 下面是open函数从用户态到内核态的详细调用流程(基于glibc-2.34):1、step1
open-> weak_alias (__libc_open, open)-> //open函数的别名为__libc_open __libc_open-> SYSCALL_CANCEL (openat, AT_FDCWD, file, oflag, mode)-> INLINE_SYSCALL_CALL (__VA_ARGS__)
2、step2 展开宏INLINE_SYSCALL_CALL
INLINE_SYSCALL_CALL (__VA_ARGS__)-> INLINE_SYSCALL_CALL(openat, AT_FDCWD, file, oflag, mode)-> #define INLINE_SYSCALL_CALL(...) \ __INLINE_SYSCALL_DISP (__INLINE_SYSCALL, __VA_ARGS__)
3、step3 展开宏__INLINE_SYSCALL_DISP
INLINE_SYSCALL_CALL(openat, AT_FDCWD, file, oflag, mode)-> __INLINE_SYSCALL_DISP (__INLINE_SYSCALL,openat, AT_FDCWD, file, oflag, mode) #define __INLINE_SYSCALL_DISP(b,...) \ __SYSCALL_CONCAT (b,__INLINE_SYSCALL_NARGS(__VA_ARGS__))(__VA_ARGS__)
4、step4展开宏__SYSCALL_CONCAT
__INLINE_SYSCALL_DISP (__INLINE_SYSCALL,openat, AT_FDCWD, file, oflag, mode)-> __SYSCALL_CONCAT(__INLINE_SYSCALL,__INLINE_SYSCALL_NARGS(openat, AT_FDCWD, file, oflag, mode))(openat, AT_FDCWD, file, oflag, mode)-> __SYSCALL_CONCAT(__INLINE_SYSCALL,__INLINE_SYSCALL_NARGS_X (openat, AT_FDCWD, file, oflag, mode,7,6,5,4,3,2,1,0,))(openat, AT_FDCWD, file, oflag, mode)-> __SYSCALL_CONCAT(__INLINE_SYSCALL,4)(openat, AT_FDCWD, file, oflag, mode)-> __SYSCALL_CONCAT_X (__INLINE_SYSCALL,4)(openat, AT_FDCWD, file, oflag, mode) #define __INLINE_SYSCALL_NARGS(...) \ __INLINE_SYSCALL_NARGS_X (__VA_ARGS__,7,6,5,4,3,2,1,0,) #define __INLINE_SYSCALL_NARGS_X(a,b,c,d,e,f,g,h,n,...) n #define __SYSCALL_CONCAT(a,b) __SYSCALL_CONCAT_X (a, b)
5、step5展开宏__SYSCALL_CONCAT_X
__SYSCALL_CONCAT_X (__INLINE_SYSCALL,4)(openat, AT_FDCWD, file, oflag, mode) -> __INLINE_SYSCALL4(openat, AT_FDCWD, file, oflag, mode) #define __INLINE_SYSCALL4(name, a1, a2, a3, a4) INLINE_SYSCALL (name, 4, a1, a2, a3, a4)
6、step6展开宏__INLINE_SYSCALL4
__INLINE_SYSCALL4(openat, AT_FDCWD, file, oflag, mode)-> INLINE_SYSCALL (openat, 4 , AT_FDCWD, file, oflag, mode) #define INLINE_SYSCALL(name, nr, args...) \ ({ \ long int sc_ret = INTERNAL_SYSCALL (name, nr, args); \ __glibc_unlikely (INTERNAL_SYSCALL_ERROR_P (sc_ret)) \ ? SYSCALL_ERROR_LABEL (INTERNAL_SYSCALL_ERRNO (sc_ret)) \ : sc_ret; \ })
7、step7展开宏INLINE_SYSCALL到这里就与CPU架构相关了,以arm为例)
INLINE_SYSCALL (openat, 4 , AT_FDCWD, file, oflag, mode)-> INTERNAL_SYSCALL (openat, 4, AT_FDCWD, file, oflag, mode) #define INTERNAL_SYSCALL(name, nr, args...) \ INTERNAL_SYSCALL_RAW(SYS_ify(name), nr, args)
8、step8展开宏INTERNAL_SYSCALL
INTERNAL_SYSCALL (openat, 4, AT_FDCWD, file, oflag, mode)-> INTERNAL_SYSCALL_RAW(SYS_ify(openat), 4, AT_FDCWD, file, oflag, mode) #define SYS_ify(syscall_name) (__NR_##syscall_name)
9、step9展开宏SYS_ify
INTERNAL_SYSCALL_RAW(SYS_ify(openat), 4, AT_FDCWD, file, oflag, mode)-> INTERNAL_SYSCALL_RAW(__NR_openat,4, AT_FDCWD, file, oflag, mode)
10、step10 宏展开完毕,进入汇编
define INTERNAL_SYSCALL_RAW(name, 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; }) INTERNAL_SYSCALL_RAW(__NR_openat,4, AT_FDCWD, file, oflag, mode) -------> { register int _a1 asm ("r0"), _nr asm ("r7"); LOAD_ARGS_##nr (args) //展开args,然后设置到相应的寄存器中 _nr = name; //执行swi软中断,根据系统调用号,执行对应的内核函数 asm volatile ("swi 0x0 @ syscall " #name \ : "=r" (_a1) \ : "r" (_nr) ASM_ARGS_##nr \ : "memory"); \ _a1; }