it-swarm.cn

如何找到Linux内核系统调用的实现?

我试图通过查看内核源代码来了解一个函数mkdir的工作方式。这是一种尝试了解内核内部结构并在各种功能之间导航的尝试。我知道mkdir是在sys/stat.h中定义的。我找到了原型:

/* Create a new directory named PATH, with permission bits MODE.  */
extern int mkdir (__const char *__path, __mode_t __mode)
     __THROW __nonnull ((1));

现在,我需要查看在哪个C文件中实现此功能。从源目录,我尝试了

ack "int mkdir"

哪个显示

security/inode.c
103:static int mkdir(struct inode *dir, struct dentry *dentry, int mode)

tools/perf/util/util.c
4:int mkdir_p(char *path, mode_t mode)

tools/perf/util/util.h
259:int mkdir_p(char *path, mode_t mode);

但是它们都不符合sys/stat.h中的定义。

问题

  1. 哪个文件具有mkdir实现?
  2. 使用上面的函数定义,如何找出实现的文件?内核在定义和实现方法时遵循什么模式?

注意:我正在使用内核 2.6.36-rc1

376
Navaneeth K N

系统调用不会像常规函数调用那样处理。从用户空间到内核空间的转换需要特殊的代码,基本上是将一些内联汇编代码注入到调用站点的程序中。 “捕捉”系统调用的内核端代码也是底层的东西,您可能至少在一开始就不需要深入了解。

在内核源目录下的 _include/linux/syscalls.h_ 中,您会找到以下内容:

_asmlinkage long sys_mkdir(const char __user *pathname, int mode);
_

然后在_/usr/include/asm*/unistd.h_中找到以下内容:

_#define __NR_mkdir                              83
__SYSCALL(__NR_mkdir, sys_mkdir)
_

这段代码说mkdir(2)是系统调用#83。也就是说,系统调用是通过数字调用的,而不是通过地址调用,而不是像在您自己的程序或链接到该程序的库中的正常函数调用那样,通过地址进行调用。我上面提到的内联Assembly胶水代码使用它来进行从用户到内核空间的转换,并带上您的参数。

还有一点证据表明这里有些奇怪,那就是系统调用并不总是有严格的参数列表:例如,open(2)可以使用2个或3个参数。这意味着open(2)重载 ,这是C++的功能,而不是C,但是syscall接口是C兼容的。 (这与C的 (varargs功能 ,它允许单个函数采用可变数量的参数)不同。)

要回答您的第一个问题,不存在mkdir()的单个文件。 Linux支持许多不同的文件系统,并且每个文件系统都有自己的“ mkdir”操作实现。使内核将所有内容隐藏在单个系统调用之后的抽象层称为 [〜#〜] vfs [〜#〜] 。因此,您可能想开始使用vfs_mkdir()在_fs/namei.c_中进行挖掘。低级文件系统修改代码的实际实现在其他地方。例如,ext4实现称为ext4_mkdir(),在 _fs/ext4/namei.c_ 中定义。

关于第二个问题,是的,所有这些都有模式,但没有一条规则。实际上,您需要对内核的工作方式有一个相当广泛的了解,以便弄清楚应该在哪里寻找任何特定的系统调用。并非所有系统调用都涉及VFS,因此它们的内核端调用链并非都以_fs/namei.c_开始。例如,mmap(2)_mm/mmap.c_ 开始,因为它是内核的内存管理(mm)子系统的一部分。

我建议您获得Bovet和Cesati的“ 了解Linux内核 ”的副本。

388
Warren Young

这可能无法直接回答您的问题,但是当我试图了解实际上是针对最简单的Shell命令进行的基础系统调用时,我发现strace确实很棒。例如.

strace -o trace.txt mkdir mynewdir

系统要求命令mkdir mynewdir将转储到trace.txt中,以供您欣赏。

86
Banjer

Linux交叉引用(LXR) ¹是阅读Linux内核源代码的好地方。除了自由文本搜索结果外,搜索还返回类型匹配项(函数原型,变量声明等),因此它比单纯的grep更为方便(而且速度更快)。

LXR不会扩展预处理器定义。系统调用的名称在各处都被预处理器所破坏。但是,大多数(所有?)系统调用都是通过 SYSCALL_DEFINEx 宏系列之一定义的。由于mkdir接受两个参数,因此搜索SYSCALL_DEFINE2(mkdir会得出 声明mkdir syscall

SYSCALL_DEFINE2(mkdir, const char __user *, pathname, int, mode)
{
    return sys_mkdirat(AT_FDCWD, pathname, mode);
}

好的,sys_mkdirat表示它是mkdirat系统调用,因此单击它只会导致您进入include/linux/syscalls.h中的声明,但是定义就在上面。

mkdirat的主要工作是调用 vfs_mkdir (VFS是通用文件系统层)。单击该按钮将显示两个搜索结果:include/linux/fs.h中的声明以及上面几行的定义。 vfs_mkdir的主要工作是调用特定于文件系统的实现:dir->i_op->mkdir。要找到this的实现方式,您需要转向单个文件系统的实现,并且没有一成不变的规则-它甚至可能是内核外部的模块树。

¹ LXR是一个索引程序。有几个网站提供到LXR的界面,其已知版本集略有不同,Web界面也略有不同。它们往往会来来去去,因此,如果您不习惯使用一个,请在网络上搜索“ linux cross-reference”以查找另一个。

56

系统调用通常包装在SYSCALL_DEFINEx()宏中,这就是为什么简单的grep找不到它们的原因:

_fs/namei.c:SYSCALL_DEFINE2(mkdir, const char __user *, pathname, int, mode)
_

宏扩展后的最终函数名称最终为_sys_mkdir_。 SYSCALL_DEFINEx()宏添加了样板内容,例如每个syscall定义都需要具有的跟踪代码。

22
stefanha

注意:.h文件不是定义函数。它在.h文件中已声明,并在其他位置定义(实现)。这使编译器可以包含有关函数签名(原型)的信息,以允许对参数进行类型检查,并将返回类型与代码中的任何调用上下文匹配。

通常,C语言中的.h(头文件)用于声明函数和定义宏。

mkdir特别是系统调用。可能有一个GNU libc)包装该系统调用(实际上几乎可以肯定是)。可以找到mkdir的真正内核实现。通过搜索内核源代码,尤其是系统调用。

请注意,还将为每个文件系统实现某种目录创建代码。 VFS(虚拟文件系统)层提供了系统调用层可以调用的公共API。每个文件系统都必须注册供VFS层调用的功能。这允许不同的文件系统实现它们自己的语义,以了解目录的结构(例如,如果使用某种哈希方法存储它们,以使搜索特定条目更为有效)。我之所以这样说是因为,如果您正在搜索Linux内核源代码树,则可能会跳过这些文件系统特定的目录创建功能。

17
Jim Dennis

您找到的所有实现都不匹配sys/stat.h中的原型。也许使用此头文件搜索include语句会更成功吗?

8
greg0ire

这里有一些非常不错的博客文章,描述了用于寻找底层内核源代码的各种技术。

6
An̲̳̳drew