【问题标题】:Calling system calls from the kernel code从内核代码调用系统调用
【发布时间】:2019-08-04 23:22:16
【问题描述】:

我正在尝试创建一种机制来读取进程的性能计数器。我希望从内核(4.19.2 版)本身内部执行此机制。

我可以从用户空间通过sys_perf_event_open() 系统调用来执行此操作,如下所示。

syscall (__NR_perf_event_open, hw_event, pid, cpu, group_fd, flags);

我想从内核空间调用这个调用。我从这里得到了一些基本的想法How do I use a Linux System call from a Linux Kernel Module

以下是我为实现这一目标而采取的步骤:

  1. 为了确保内核的虚拟地址保持有效,我使用了set_fs()get_fs()get_fd()

  2. 由于sys_perf_event_open()/include/linux/syscalls.h 中定义,我已将其包含在代码中。

最终,调用系统调用的代码如下所示:

mm_segment_t fs;
fs = get_fs();
set_fs(get_ds());
long ret =  sys_perf_event_open(&pe, pid, cpu, group_fd, flags);
set_fs(fs);

即使采取了这些措施,我仍然收到错误声明 “函数‘sys_perf_event_open’的隐式声明”。当定义它的头文件已经包含时,为什么会弹出这个?它是否与从内核代码中调用系统调用的方式有关?

【问题讨论】:

  • 大多数情况下(基于这个问题和你之前的问题),您需要花费更多时间阅读和理解现有代码(包括阅读和理解不同 CPU 提供的现有 Linux 的低级设施)代码建立在)之上;这样您就可以修改现有代码,使其执行您想要的操作或提供其他代码(例如内核模块)可以用来执行您想要的功能。
  • 非常不清楚的问题,显示出很多混乱。

标签: c gcc linux-kernel


【解决方案1】:

通常(不特定于 Linux)系统调用所做的工作可以分为 3 类:

  • 从用户上下文切换到内核上下文(并再次返回返回路径)。这包括更改处理器的特权级别、弄乱gs、摆弄堆栈以及进行安全缓解(例如针对 Meltdown)。这些东西很昂贵,如果你已经在内核中,它们是无用的和/或危险的。

  • 使用“函数编号”参数找到要调用的正确函数,并调用它。这通常包括一些健全性检查(函数是否存在?)和表查找,以及用于修改所需输入和输出参数的代码,因为用于系统调用(在用户空间中)的调用约定与调用约定不同正常的 C 函数使用。这些东西很昂贵,如果你已经在内核中,它们是无用的和/或危险的。

  • 最终被调用的普通 C 函数。这是您可能(见注)能够直接调用的函数,而无需使用任何昂贵、无用和/或危险的系统调用垃圾。

注意:如果您无法在不使用系统调用垃圾(任何部分)的情况下直接调用最终的普通 C 函数(例如,如果最终的普通 C 函数没有暴露给其他内核代码);那么您必须确定原因。例如,它可能没有暴露,因为它改变了用户空间状态,从内核调用它会破坏用户空间状态,所以它不会暴露/导出到其他内核代码,这样就不会有人意外破坏一切。再举一个例子,它可能没有理由不暴露给其他内核代码,您可以修改它的源代码,使其暴露/导出。

【讨论】:

    【解决方案2】:

    由于其他人已经提到的原因,不鼓励使用 sys_* 接口从内核内部调用系统调用。在 x86_64 的特殊情况下(我猜这是你的架构),从内核版本 v4.17 开始,现在硬要求不使用这样的接口(但有一些例外)。在此版本之前可以直接调用系统调用,但现在弹出您看到的错误(这就是为什么网上有很多使用 sys_* 的教程)。 Linux 文档中建议的替代方案是在系统调用和实际系统调用的代码之间定义一个包装器,该包装器可以在内核中像任何其他函数一样被调用:

    int perf_event_open_wrapper(...) {
        // actual perf_event_open() code
    }
    
    SYSCALL_DEFINE5(perf_event_open, ...) {
        return perf_event_open_wrapper(...);
    }
    

    来源:https://www.kernel.org/doc/html/v4.19/process/adding-syscalls.html#do-not-call-system-calls-in-the-kernel

    【讨论】:

      【解决方案3】:

      我们讨论的是哪个内核版本?

      无论如何,您可以通过查看系统映射文件来获取 sys_call_table 的地址,或者如果它已导出,则可以查找符号(查看 kallsyms.h),一旦您有地址系统调用表,您可以将其视为 void 指针数组(void **),并找到您想要的函数索引。即sys_call_table[__NR_open] 将是打开的地址,因此您可以将其存储在空指针中,然后调用它。

      编辑:你想做什么,为什么不调用系统调用就不能做到?您必须明白,系统调用是内核对用户态的 API,不应真正从内核内部使用,因此应避免这种做法。

      【讨论】:

      • 我正在使用内核 4.19.2,不,此调用未导出。我需要该机制位于内核本身中。 @Book Of Zeus 我正在尝试获取每个进程的性能计数器并将其存储在其任务结构中。如果没有系统调用,无法找到解决方法。
      • 如果我理解正确的话,我想你可能会发现 kprobes 很方便,看看它。此外,如果系统调用表没有导出,我要么查看系统的映射文件,要么在系统调用中 kprobe 一个函数(然后确定它是否是从系统调用调用的(地址应该在堆栈中是可用的),然后做我的事。因为例如,系统调用kill 会调用kill_something_info
      【解决方案4】:

      从内核代码调用系统调用

      (我主要是在回答这个标题;总结一下:甚至禁止考虑

      我不明白你的实际问题(我觉得你需要在你的问题中更多地解释它,这个问题不清楚并且缺乏很多有用的动机和背景)。但是遵循Unix philosophy- 的一般建议是最小化内核或内核模块代码的大小和漏洞区域,并尽可能方便地在用户空间中驱逐此类代码,特别是在systemd 的帮助下,只要您的内核代码需要一些系统调用。您的问题本身就违反了大多数 Unix 和 Linux 文化规范。

      您是否考虑过使用高效内核与用户级通信,尤其是netlink(7)socket(7)。也许你 想要一些特定于驱动程序的kernel thread

      我的直觉是(在某些用户级守护程序中,在启动时早期从 systemd 开始)AF_NETLINKsocket(2) 完全适合您的(无法解释的)需求。 eventd(2) 也可能是相关的。

      但只是想到从内核内部使用系统调用就会在我的大脑中触发一个巨大的闪烁红灯,我倾向于认为这是对操作系统内核普遍存在重大误解的症状。请花点时间阅读Operating Systems: Three Easy Pieces 以了解操作系统理念。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-11-29
        • 1970-01-01
        • 1970-01-01
        • 2016-01-06
        • 2011-11-16
        • 2010-09-20
        • 1970-01-01
        • 2011-12-15
        相关资源
        最近更新 更多