上一篇:https://blog.csdn.net/weixin_42523774/article/details/103207654
· 本文继续上文Linux为何一切皆文件,探寻应用程序如何通过系统调用,从应用层转换到内核层,本文以open函数为例,我们需要查询glibc的源码包[下载处],本文使用的是最新的glibc2.30版本的源码。
· 推荐使用Source Insight,这会让这个过程更容易。我们以open函数为例。
1.glibc中的寻找过程
· 将glibc的源码解压,加入source insight的工程中,然后逐步Ctrl+左键的不断寻找,寻找过程是这样的:
#include <fcntl.h> 应用程序需要包含的头文件
|-->#include <io/fcntl.h> 头文件<fcntl.h>中包含的头文件
|-->#include <features.h> glibc的基础定义在这里
|-->#include <bits/types.h> 定义了 __mode_t, __dev_t, __off_t 这些结构体
|-->#include <bits/fcntl.h> 获取类似O_*, F_*, FD_*的宏定义值,和所有open, fcntl函数用到的标志位
|-->#ifdef __O_TMPFILE (...) 第三个参数所需要用到的
|-->#ifndef __mode_t_defined (...) __mode_t, __dev_t, __off_t这些结构体的备份定义
#ifdef __USE_XOPEN2K8 (...140) XPG的所有符号都应该有效,如果都有定义则在此备份定义
#ifdef __USE_ATFILE (...167) *at函数所需定义,例如stat, fstat, lstat, fstatat。
extern int fcntl (int __fd, int __cmd, ...); 文件控制操作函数
extern int open (const char *__file, int __oflag, ...) __nonnull ((1)); open函数定义
· 到此我们知道了open函数不在这里,这就需要搜索一下open函数,应该是在 loadmsgcat.c中的448行:
# define open(name, flags) __open_nocancel (name, flags)
· 整个过程是这样的:
open #系统调用定义在<io/fcntl.h>中
|-->__open_nocancel # intl下的loadmsgcat.c中的448行define,实现在 sysdeps\unix\sysv\linux 的 open_nocancel.c中
|-->__OPEN_NEEDS_MODE # 判断是否有第三个mode参数,有就提取出来
|-->INLINE_SYSCALL_CALL (openat, AT_FDCWD, file, oflag, mode); # 调用系统调用openat,并输入4个参数
· 在进一步寻找发现是这个:
#define INTERNAL_SYSCALL_CALL(...) \
__INTERNAL_SYSCALL_DISP (__INTERNAL_SYSCALL, __VA_ARGS__)
· 这其实是宏函数的高级用法,不定参数的宏函数。后面还有多次使用,其实就是将 __VA_ARGS__替换成了这一堆的入参。后面的宏还会识别入参个数。此处经过预处理之后变成如下形式:
__INLINE_SYSCALL4(openat, AT_FDCWD, file, oflag, mode)
进一步转化:
INLINE_SYSCALL (openat, 4, AT_FDCWD, file, oflag, mode)
· 到此可以知道,核心就是 INLINE_SYSCALL 这个宏了。这个宏依据不同的平台实现有所不同,这个在编译glibc的时候是可以修改的,我们选择ARM平台,位于glibc-30\sysdeps\unix\sysv\linux\arm\sysdep.h
· 找到如下代码:
#define INLINE_SYSCALL(name, nr, args...) \
({ unsigned int _sys_result = INTERNAL_SYSCALL (name, , nr, args); \
if (__builtin_expect (INTERNAL_SYSCALL_ERROR_P (_sys_result, ), 0)) \
{ \
__set_errno (INTERNAL_SYSCALL_ERRNO (_sys_result, )); \
_sys_result = (unsigned int) -1; \
} \
(int) _sys_result; })
这里的核心是INTERNAL_SYSCALL ,其他都是返回值处理。进一步寻找:
#define INTERNAL_SYSCALL(name, err, nr, args...) \
INTERNAL_SYSCALL_RAW(SYS_ify(name), err, nr, args)
# 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; })
预编译过后变成:
register int _a1 asm ("r0"), _nr asm ("r7"); \
LOAD_ARGS_##AT_FDCWD (file, oflag, mode) \
_nr = __NR_openat; \
asm volatile ("swi 0x0 @ syscall " "__NR_openat" \
: "=r" (_a1) \
: "r" (_nr) ASM_ARGS_##AT_FDCWD \
: "memory"); \
_a1;
到此我们终于明白了,它使用了ARM汇编指令swi,这是触发软中断的指令,通过r0和r7寄存器传参,__NR_openat这个是在内核的include\asm-generic\unistd.h去查系统调用表再转换成内核调用的函数,AT_FDCWD是*at函数用来表明所使用的工作目录。
· 的确,我们在内核代码中找到了如下定义:
#define __NR_openat 56
__SC_COMP(__NR_openat, sys_openat, compat_sys_openat)
2 小结
· 本文中,我们通过深入glibc的代码了解了系统调用的整个过程。想继续深入open函数在内核中的实现吗?请见下篇《系统调用的内核实现,一文讲透open函数内核真实实现。》
如果觉得我的文章还有点收获,就点个赞吧! d=====( ̄▽ ̄*)b
下一篇:https://blog.csdn.net/weixin_42523774/article/details/103607874