从Linux内核调用用户空间应用程序
简介: Linux? 系统调用接口允许用户空间应用程序调用内核功能,那么从 内核调用用户空间应用程序又如何呢?探索 usermode-helper API,并学习如何调用用户空间应用程序并控制其输出。
调用特定的内核函数(系统调用)是 GNU/Linux 中软件开发的原本就有的组成部分。但如果方向反过来呢,内核空间调用用户空间?确实有一些有这种特性的应用程序需要每天使用。例如,当内核找到一个设备,这时需要加载某个模块,进程如何处理?动态模块加载在内核通过 usermode-helper 进程进行。
让我们从探索 usermode-helper 应用程序编程接口(API)以及在内核中使用的例子开始。 然后,使用 API 构造一个示例应用程序,以便更好地理解其工作原理与局限。
usermode-helper API
usermode-helper API 是个很简单的 API,其选项为用户熟知。例如,要创建一个用户空间进程,通常只要设置名称为 executable,选项都为 executable,以及一组环境变量(指向 <font face="Courier New">execve</font>
主页)。创建内核进程也是一样。但由于创建内核空间进程,还需要设置一些额外选项。
表 1 展示的是 usermode-helper API 中一组关键的内核函数
表 1. usermode-helper API 中的核心函数
API 函数 | 描述 |
---|---|
<font face="Courier New">call_usermodehelper_setup</font> | 准备 user-land 调用的处理函数 |
<font face="Courier New">call_usermodehelper_setkeys</font> | 设置 helper 的会话密钥 |
<font face="Courier New">call_usermodehelper_setcleanup</font> | 为 helper 设置一个清空函数 |
<font face="Courier New">call_usermodehelper_stdinpipe</font> | 为 helper 创建 <font face="Courier New">stdin</font> 管道 |
<font face="Courier New">call_usermodehelper_exec</font> | 调用 user-land |
表 2 中还有一些简化函数,它们封装了的几个内核函数(用一个调用代替多个调用)。这些简化函数在很多情况下都很有用,因此尽可能使用他们。
表 2. usermode-helper API 的简化
API 函数 | 描述 |
---|---|
<font face="Courier New">call_usermodehelper</font> | 调用 user-land |
<font face="Courier New">call_usermodehelper_pipe</font> | 使用 <font face="Courier New">stdin</font> 管道调用 user-land |
<font face="Courier New">call_usermodehelper_keys</font> | 使用会话密钥调用 user-land |
让我们先浏览一遍这些核心函数,然后探索简化函数提供了哪些功能。核心 API 使用了一个称为 <font face="Courier New">subprocess_info</font>
结构的处理函数引用进行操作。该结构(可在 ./kernel/kmod.c 中找到)集合了给定的 usermode-helper 实例的所有必需元素。该结构引用从 <font face="Courier New">call_usermodehelper_setup</font>
调用返回。该结构(以及后续调用)将会在 <font face="Courier New">call_usermodehelper_setkeys</font>
(用于存储凭证)、<font face="Courier New">call_usermodehelper_setcleanup</font>
以及 <font face="Courier New">call_usermodehelper_stdinpipe</font>
的调用中进一步配置。最后,一旦配置完成,就可通过调用 <font face="Courier New">call_usermodehelper_exec</font>
来调用配置好的用户模式应用程序。
核心函数提供了最大程度的控制,其中 helper 函数在单个调用中完成了大部分工作。管道相关调用(<font face="Courier New">call_usermodehelper_stdinpipe</font>
和 helper 函数 <font face="Courier New">call_usermodehelper_pipe</font>
)创建了一个相联管道供 helper 使用。具体地说,创建了管道(内核中的文件结构)。用户空间应用程序对管道可读,内核对管道可写。对于本文,核心转储只是使用 usermode-helper 管道的应用程序。在该应用程序(./fs/exec.c <font face="Courier New">do_coredump()</font>
)中,核心转储通过管道从内核空间写到用户空间。
这些函数与 <font face="Courier New">sub_processinfo</font>
以及 <font face="Courier New">subprocess_info</font>
结构的细节之间的关系如图 1 所示。
图 1. Usermode-helper API 关系
表 2 中的简化函数内部执行 <font face="Courier New">call_usermodehelper_setup</font>
函数和 <font face="Courier New">call_usermodehelper_exec</font>
函数。表 2 中最后两个调用分别调用的是 <font face="Courier New">call_usermodehelper_setkeys</font>
和 <font face="Courier New">call_usermodehelper_stdinpipe</font>
。可以在 ./kernel/kmod.c 找到 <font face="Courier New">call_usermodehelper_pipe</font>
和 <font face="Courier New">call_usermodehelper</font>
的代码,在 ./include/linux/kmod.h 中找到 <font face="Courier New">call_usermodhelper_keys</font>
的代码。