系统调用如何进入内核层次,深入glibc寻找open函数真实实现。

上一篇: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

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值