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

Nav*_*K N 376 source system-calls linux-kernel

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

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

现在我需要看看这个函数是在哪个 C 文件中实现的。从源目录,我试过

ack "int mkdir"
Run Code Online (Sandbox Code Playgroud)

其中显示

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);
Run Code Online (Sandbox Code Playgroud)

但它们中没有一个与 中的定义匹配sys/stat.h

问题

  1. 哪个文件有mkdir实现?
  2. 有了上面这样的函数定义,我怎么能找出哪个文件有实现呢?内核在定义和实现方法时是否遵循任何模式?

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

War*_*ung 388

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

include/linux/syscalls.h您的内核源代码目录下,您会发现:

asmlinkage long sys_mkdir(const char __user *pathname, int mode);
Run Code Online (Sandbox Code Playgroud)

然后在 中/usr/include/asm*/unistd.h,您会发现:

#define __NR_mkdir                              83
__SYSCALL(__NR_mkdir, sys_mkdir)
Run Code Online (Sandbox Code Playgroud)

这段代码说的mkdir(2)是系统调用#83。也就是说,系统调用是按编号调用的,而不是像您自己程序中的普通函数调用或链接到您的程序的库中的函数那样按地址调用。我上面提到的内联汇编粘合代码使用它来进行从用户空间到内核空间的转换,同时将您的参数带入其中。

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

要回答您的第一个问题,不mkdir()存在单个文件。Linux 支持许多不同的文件系统,每个系统都有自己的“mkdir”操作实现。允许内核将所有内容隐藏在单个系统调用后面的抽象层称为VFS。因此,您可能想开始深入研究fs/namei.c, 与vfs_mkdir(). 低级文件系统修改代码的实际实现在别处。例如,ext4 实现被调用ext4_mkdir(),定义在fs/ext4/namei.c.

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

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

  • @DavAlPi:据我所知,Bovet & Cesati 仍然是关于这个主题的最好的单本书。当我需要用更多最新的材料补充它时,我会在我正在使用的内核的源代码树的“文档”子目录中进行挖掘。 (2认同)

Ban*_*jer 86

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

strace -o trace.txt mkdir mynewdir
Run Code Online (Sandbox Code Playgroud)

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

  • +1 绝妙的把戏!我以前没用过 (5认同)
  • 更好的是,制作输出文件 trace.strace,并在 VIM 中打开它。VIM 将突出显示它,使阅读更容易。 (3认同)

Gil*_*il' 56

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

LXR 不扩展预处理器定义。系统调用的名字被到处的预处理器弄乱了。但是,大多数(所有?)系统调用都是使用SYSCALL_DEFINEx宏系列之一定义的。由于mkdir需要两个参数,搜索SYSCALL_DEFINE2(mkdir导致syscall声明mkdir

SYSCALL_DEFINE2(mkdir, const char __user *, pathname, int, mode)
{
    return sys_mkdirat(AT_FDCWD, pathname, mode);
}
Run Code Online (Sandbox Code Playgroud)

好的,这sys_mkdirat意味着它是mkdirat系统调用,因此单击它只会将您带到 中的声明include/linux/syscalls.h,但定义就在上面。

的主要工作mkdirat是调用vfs_mkdir(VFS 是通用文件系统层)。单击它会显示两个搜索结果:中的声明include/linux/fs.h和上面几行的定义。的主要工作vfs_mkdir是调用特定于文件系统的实现:dir->i_op->mkdir. 要了解是如何实现的,您需要转向单个文件系统的实现,并且没有硬性规定——它甚至可以是内核树之外的模块。

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


小智 22

系统调用通常包含在SYSCALL_DEFINEx()宏中,这就是为什么一个 simplegrep找不到它们:

fs/namei.c:SYSCALL_DEFINE2(mkdir, const char __user *, pathname, int, mode)
Run Code Online (Sandbox Code Playgroud)

宏展开后的最终函数名称最终为sys_mkdir. 该SYSCALL_DEFINEx()宏添加了样板文件,例如每个系统调用定义需要具有的跟踪代码。


Jim*_*nis 17

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

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

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

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


gre*_*ire 8

您发现的所有实现都与 sys/stat.h 中的原型不匹配 也许搜索包含此头文件的语句会更成功?


小智 6

这里有几篇非常棒的博客文章,描述了用于寻找低级内核源代码的各种技术。

  • 请不要只张贴指向博客或论坛的链接,请总结它们的内容以便读者可以看到它们的内容,并在网站消失时留下一些东西。此外,您的第一个链接是关于 libc,这与此问题无关。 (12认同)