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

如何找到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

答案1

系统调用的处理方式与常规函数调用不同。它需要特殊的代码来实现从用户空间到内核空间的转换,基本上是在调用站点注入到程序中的一些内联汇编代码。 “捕获”系统调用的内核端代码也是低级的东西,您可能不需要深入理解,至少一开始是这样。

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。也就是说,系统调用是按编号调用的,而不是像您自己的程序中的正常函数调用或链接到您的程序的库中的函数那样按地址调用。我上面提到的内联汇编胶水代码使用它来进行从用户空间到内核空间的转换,并带走您的参数。

另一个证明事情有点奇怪的证据是,系统调用并不总是有严格的参数列表:open(2)例如,可以采用 2 或 3 个参数。这意味着open(2)超载是 C++ 的功能,而不是 C 的功能,但系统调用接口与 C 兼容。 (这和C的不一样可变参数特征,它允许单个函数采用可变数量的参数。)

要回答您的第一个问题,不存在任何单个文件mkdir()。 Linux 支持许多不同的文件系统,每个文件系统都有自己的“mkdir”操作实现。让内核隐藏单个系统调用背后的所有内容的抽象层称为虚拟FS。所以,您可能想开始深入研究fs/namei.c, 和vfs_mkdir().低级文件系统修改代码的实际实现在其他地方。例如,ext4 实现称为ext4_mkdir(),定义于fs/ext4/namei.c

至于你的第二个问题,是的,这一切都有模式,但没有单一的规则。您实际上需要的是对内核如何工作有一个相当广泛的了解,以便找出应该在哪里查找任何特定的系统调用。并非所有系统调用都涉及 VFS,因此它们的内核端调用链并不都以fs/namei.c. mmap(2),例如,开始于mm/mmap.c,因为它是内核内存管理(“mm”)子系统的一部分。

我建议您获取一份“了解 Linux 内核” 播威和切萨蒂。

答案2

这可能不会直接回答您的问题,但我发现strace在尝试理解底层系统调用时非常酷,即使是最简单的 shell 命令。例如

strace -o trace.txt mkdir mynewdir

该命令的系统调用mkdir mynewdir将转储到trace.txt,以供您查看。

答案3

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

LXR 不扩展预处理器定义。系统调用的名称到处都被预处理器破坏了。然而,大多数(所有?)系统调用都是用以下之一定义的:SYSCALL_DEFINEx宏系列。由于mkdir需要两个参数,因此搜索SYSCALL_DEFINE2(mkdir导致mkdir系统调用的声明:

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。寻找如何实现之后,您需要转向单个文件系统的实现,并且没有硬性规定——它甚至可以是内核树之外的模块。

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

答案4

注意:.h 文件没有定义功能。它是宣布在该 .h 文件中并在其他地方定义(实现)。这允许编译器包含有关函数签名(原型)的信息,以允许对参数进行类型检查并将返回类型与代码中的任何调用上下文相匹配。

一般来说,C 中的 .h(头)文件用于声明函数和定义宏。

mkdir特别是系统调用。可能有一个GNU围绕该系统调用的包装器(事实上几乎肯定是)。真正的内核实现mkdir可以通过搜索内核源代码和特别是系统调用来找到。

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

相关内容