Chinaunix首页 | 论坛 | 博客
  • 博客访问: 102092
  • 博文数量: 65
  • 博客积分: 2520
  • 博客等级: 少校
  • 技术积分: 680
  • 用 户 组: 普通用户
  • 注册时间: 2010-05-22 15:10
文章分类

全部博文(65)

文章存档

2011年(1)

2010年(64)

我的朋友
最近访客

分类: LINUX

2010-06-04 17:58:08

usermode-helper API 的实现及使用

M. Tim Jones, 独立作家

简介: Linux® 系统调用接口允许用户空间应用程序调用内核功能,那么 内核调用用户空间应用程序又如何呢?探索 usermode-helper API,并学习如何调用用户空间应用程序并控制其输出。

发布日期: 2010 年 4 月 06 日

其他语言版本: 英文

联系 Tim

Tim 是一位很受欢迎的多产作家。查看 Tim 的资料,在 My developerWorks 与他和其他作者以及他的读者朋友联系。

调 用特定的内核函数(系统调用)是 GNU/Linux 中软件开发的原本就有的组成部分。但如果方向反过来呢,内核空间调用用户空间?确实有一些有这种特性的应用程序需要每天使用。例如,当内核找到一个设备, 这时需要加载某个模块,进程如何处理?动态模块加载在内核通过 usermode-helper 进程进行。

让我们从探索 usermode-helper 应用程序编程接口(API)以及在内核中使用的例子开始。 然后,使用 API 构造一个示例应用程序,以便更好地理解其工作原理与局限。

usermode-helper API 是个很简单的 API,其选项为用户熟知。例如,要创建一个用户空间进程,通常只要设置名称为 executable,选项都为 executable,以及一组环境变量(指向 execve 主页)。创建内核进程也是一样。但由于创建内核空间进程,还需要设置一些额外选项。

内核版本

本文探讨的是 2.6.27 版内核的 usermode-helper API。

表 1 展示的是 usermode-helper API 中一组关键的内核函数



API 函数描述
call_usermodehelper_setup准备 user-land 调用的处理函数
call_usermodehelper_setkeys设置 helper 的会话密钥
call_usermodehelper_setcleanup为 helper 设置一个清空函数
call_usermodehelper_stdinpipe为 helper 创建 stdin 管道
call_usermodehelper_exec调用 user-land

表 2 中还有一些简化函数,它们封装了的几个内核函数(用一个调用代替多个调用)。这些简化函数在很多情况下都很有用,因此尽可能使用他们。



API 函数描述
call_usermodehelper调用 user-land
call_usermodehelper_pipe使用 stdin 管道调用 user-land
call_usermodehelper_keys使用会话密钥调用 user-land

让我们先浏览一遍这些核心函数,然后探索简化函数提供了哪些功能。核心 API 使用了一个称为 subprocess_info 结构的处理函数引用进行操作。该结构(可在 ./kernel/kmod.c 中找到)集合了给定的 usermode-helper 实例的所有必需元素。该结构引用从 call_usermodehelper_setup 调用返回。该结构(以及后续调用)将会在 call_usermodehelper_setkeys(用于存储凭证)、call_usermodehelper_setcleanup 以及 call_usermodehelper_stdinpipe 的调用中进一步配置。最后,一旦配置完成,就可通过调用 call_usermodehelper_exec 来调用配置好的用户模式应用程序。

声明

该方法提供了一个从内核调用用户空间应用程序必需的函数。尽管这项功能有合理用途,还应仔细考虑是否需要其他实现。这是一个方法,但其他方法会更合适。

核心函数提供了最大程度的控制,其中 helper 函数在单个调用中完成了大部分工作。管道相关调用(call_usermodehelper_stdinpipe 和 helper 函数 call_usermodehelper_pipe)创建了一个相联管道供 helper 使用。具体地说,创建了管道(内核中的文件结构)。用户空间应用程序对管道可读,内核对管道可写。对于本文,核心转储只是使用 usermode-helper 管道的应用程序。在该应用程序(./fs/exec.c do_coredump())中,核心转储通过管道从内核空间写到用户空间。

这些函数与 sub_processinfo 以及 subprocess_info 结构的细节之间的关系如图 1 所示。



Usermode-helper API 关系

表 2 中的简化函数内部执行 call_usermodehelper_setup 函数和 call_usermodehelper_exec 函数。表 2 中最后两个调用分别调用的是 call_usermodehelper_setkeyscall_usermodehelper_stdinpipe。可以在 ./kernel/kmod.c 找到 call_usermodehelper_pipecall_usermodehelper 的代码,在 ./include/linux/kmod.h 中找到 call_usermodhelper_keys 的代码。


现在让我们看一看 usermode-helper API 所使用的内核空间。表 3 提供的并不是专门的应用程序列表,而是一些有趣应用的示例。



应用程序源文件位置
内核模块调用./kernel/kmod.c
电源管理./kernel/sys.c
控制组./kernel/cgroup.c
安全密匙生成./security/keys/request_key.c
内核事件交付./lib/kobject_uevent.c

最直接的 usermode-helper API 应用程序是从内核空间加载内核模块。request_module 函数封装了 usermode-helper API 的功能并提供了简单的接口。在一个常用的模块中,内核指定一个设备或所需服务并调用 request_module 来加载模块。通过使用 usermode-helper API,模块通过 modprobe 加载到内核(应用程序通过 request_module 在用户空间被调用)。

与模块加载类似的应用程序是设备热插拔(在运行时添加或删除设备)。该特性是通过使用 usermode-helper API,调用用户空间的 /sbin/hotplug 工具实现的。

关于 usermode-helper API 的一个有趣的应用程序(通过 request_module) 是文本搜索 API(./lib/textsearch.c)。该应用程序在内核中提供了一个可配置的文本搜索基础架构。该应用程序使用 usermode-helper API 将搜索算法当作可加载模块进行动态加载。在 2.6.30 内核版本中,支持三个算法,包括 Boyer-Moore(./lib/ts_bm.c),简单固定状态机方法(./lib/ts_fsm.c),以及 Knuth-Morris-Pratt 算法(./lib/ts_kmp.c)。

usermode-helper API 还支持 Linux 按照顺序关闭系统。当需要系统关闭电源时,内核调用用户空间的 /sbin/poweroff 命令来完成。其他应用程序如 表 3 所示,表中附有其源文件位置。


在 kernel/kmod.c 中可以找到 usermode-helper API 的源代码 和 API(展示了主要的用作内核空间的内核模块加载器)。这个实现使用 kernel_execve 完成脏工作(dirty work)。请注意 kernel_execve 是在启动时开启 init 进程的函数,而且未使用 usermode-helper API。

usermode-helper API 的实现相当简单直观(见图 2)。usermode-helper 从调用 call_usermodehelper_exec 开始执行(它用于从预先配置好的 subprocess_info 结构中清除用户空间应用程序)。该函数接受两个参数:subprocess_info 结构引用和一个枚举类型(不等待、等待进程中止及等待进程完全结束)。subprocess_info(或者是,该结构的 work_struct 元素)然后被压入工作队列(khelper_wq),然后队列异步执行调用。



usermode-helper API 的内部实现

当一个元素放入 khelper_wq 时,工作队列的处理函数就被调用(本例中是__call_usermodehelper),它在 khelper 线程中运行。该函数从将 subprocess_info 结构出队开始,此结构包含所有用户空间调用所需信息。该路径下一步取决于 wait 枚举变量。如果请求者想要等整个进程结束,包含用户空间调用(UMH_WAIT_PROC)或者是根本不等待(UMH_NO_WAIT),那么会从 wait_for_helper 函数创建一个内核线程。否则,请求者只是等待用户空间应用程序被调用(UMH_WAIT_EXEC),但并不完全。这种情况下,会为____call_usermodehelper() 创建一个内核线程。

wait_for_helper 线程中,会安装一个 SIGCHLD 信号处理函数,并为 ____call_usermodehelper 创建另一个内核线程。但在 wait_for_helper 线程中,会调用 sys_wait4 来等待 ____call_usermodehelper 内核线程(由 SIGCHLD 信号指示)结束。然后线程执行必要的清除工作(为 UMH_NO_WAIT 释放结构空间或简单地向 call_usermodehelper_exec() 回送一个完成报告)。

函数 ____call_usermodehelper 是实际让应用程序在用户空间启动的地方。该函数首先解锁所有信号并设置会话密钥环。它还安装了 stdin 管道(如果有请求)。进行了一些安装以后,用户空间应用程序通过 kernel_execve(来自 kernel/syscall.c)被调用,此文件包含此前定义的 pathargv 清单(包含用户空间应用程序名称)以及环境。当该进程完成后,此线程通过调用 do_exit() 而产生。

该进程还使用了 Linux 的 completion,它是像信号一样的操作。当 call_usermodehelper_exec 函数被调用后,就会声明 completion。当 subprocess_info 结构放入 khelper_wq 后,会调用 wait_for_completion(使用 completion 变量作为参数)。请注意此变量会存储到 subprocess_info 结构作为 complete 字段。当子线程想要唤醒 call_usermodehelper_exec 函数,会调用内核方法 complete,并判断来自 subprocess_info 结构的 completion 变量。该调用会解锁此函数使其能继续。可以在 include/linux/completion.h 中找到 API 的实现。

通过点击 参考资料 部分中的链接可以找到更多关于 usermode-helper API 的资料。


现在,让我们看看 usermode-helper API 的简单应用。首先看一下标准 API,然后学习如何使用 helper 函数使事情更简单。

在该例中,首先开发了一个简单的调用 API 的可加载内核模块。清单 1 展示了样板模块功能,定义了模块入口和出口函数。这两个函数根据模块的 modprobe(模块入口函数)或 insmod(模块入口函数),以及 rmmod(模块出口函数)被调用。



				
#include
#include
#include

MODULE_LICENSE( "GPL" );

static int __init mod_entry_func( void )
{
return umh_test();
}


static void __exit mod_exit_func( void )
{
return;
}

module_init( mod_entry_func );
module_exit( mod_exit_func );

usermode-helper API 的使用如 清单 2 所示,其中有详细描述。函数开始是声明所需变量和结构。以 subprocess_info 结构开始,它包含所有的执行用户空间调用的信息。该调用在调用 call_usermodehelper_setup 时初始化。下一步,定义参数列表,使 argv 被调用。该列表与普通 C 程序中的 argv 列表类似,定义了应用程序(数组第一个元素)和参数列表。需要 NULL 终止符来提示列表末尾。请注意这里的 argc 变量(参数数量)是隐式的,因为 argv 列表的长度已经知道。该例中,应用程序名是 /usr/bin/logger,参数是 help!,然后是 NULL 终止符。下一个所需变量是环境数组(envp)。该数组是一组定义用户空间应用程序执行环境的参数列表。本例中,定义一些常用的参数,这些参数用于定义 shell 并以 NULL 条目结束。



				
static int umh_test( void )
{
struct subprocess_info *sub_info;
char *argv[] = { "/usr/bin/logger", "help!", NULL };
static char *envp[] = {
"HOME=/",
"TERM=linux",
"PATH=/sbin:/bin:/usr/sbin:/usr/bin", NULL };

sub_info = call_usermodehelper_setup( argv[0], argv, envp, GFP_ATOMIC );
if (sub_info == NULL) return -ENOMEM;

return call_usermodehelper_exec( sub_info, UMH_WAIT_PROC );
}

下一步,调用 call_usermodehelper_setup 来创建已初始化的 subprocess_info 结构。请注意使用了先前初始化的变量以及指示用于内存初始化的 GFP 屏蔽第四个参数。在安装函数内部,调用了 kzalloc(分配内核内存并清零)。该函数需要 GFP_ATOMICGFP_KERNEL 标志(前者定义调用不可以休眠,后者定义可以休眠)。快速测试新结构(即,非 NULL)后,使用 call_usermodehelper_exec 函数继续调用。该函数使用 subprocess_info 结构以及定义是否等待的枚举变量(在 “Usermode-helper API 内部” 一节中有描述)。全部完成! 模块一旦加载,就可以在 /var/log/messages 文件中看到信息。

还可以通过 call_usermodehelper API 函数进一步简化进程,它同时执行 call_usermodehelper_setupcall_usermodehelper_exec 函数。如清单 3 所示,它不仅删除函数,还消除了调用者管理 subprocess_info 结构的必要性。



				
static int umh_test( void )
{
char *argv[] = { "/usr/bin/logger", "help!", NULL };
static char *envp[] = {
"HOME=/",
"TERM=linux",
"PATH=/sbin:/bin:/usr/sbin:/usr/bin", NULL };

return call_usermodehelper( argv[0], argv, envp, UMH_WAIT_PROC );
}

请注意在清单 3 中,有着同样的安装并调用(例如初始化 argvenvp 数组)的需求。此处惟一的区别是 helper 函数执行 setupexec 函数。


usermode-helper API 是内核中重要的部分,这是由于其广泛多样的用途(从内核模块加载、设备热插拔到 udev 事件发布)。尽管 API 实际的应用程序非常重要,但内核也是很重要的一部分,是非常有用的 Linux 工具。


学习

  • 关于 usermode-helper API 的资料很少,但其实现相当简洁易于模仿。您可以通过 LXR(Linux Cross Referencer — 用于所有版本的源代码浏览器)回顾其实现。其中两份主要的文件是 和 。

  • /proc 文件系统提供了一种内核和用户空间交互的方法 — 尽管是虚拟文件系统。 可以在 “使用 /proc 文件系统来访问 Linux 内核的内容”(developerworks, 2006 年 3月)中学习更多关于 /proc 文件系统的知识。

  • Linux 系统调用接口提供了用户空间应用程序调用内核功能的方法。关于 Linux 系统调用的更多细节,包括如何添加新系统调用,请参考 “使用 Linux 系统调用的内核命令”(developerworks, March 2007 年 3 月)。

  • 为了演示 usermode-helper API,本文使用了可加载的内核模块安装测试应用程序到内核。想要学习更多可加载内核模块及其实现的知识,请参考 “Linux 可加载内核模块剖析” (developerworks, July 2008 年 7 月)。

  • 想要学习更多 2.6 内核工作队列接口的知识,请参考这篇较老的 ,它详细介绍了 API 和内核工作队列的操作。

  • developerWorks Linux 专区, 找到更多 Linux 开发者的资源。

  • 随时关注 developerWorks 技术活动网络广播

获得产品和技术

讨论

  • 加入 My developerWorks 社区。联系其他 developerWorks 用户,访问开发人员创办的博客、论坛、群组和维客。

M. Tim Jones

M. Tim Jones 是一位嵌入式固件架构师以及 Artificial Intelligence: A Systems Approach, GNU/Linux Application Programming(目前是第二版)、AI Application Programming(第二版)和 BSD Sockets Programming from a Multilanguage Perspective 的作者。他的工程学背景涵盖了从地球同步轨道卫星内核开发到嵌入式系统架构以及网络协议开发。Tim 是科罗拉多州朗蒙特市 Emulex Corp. 的一名顾问工程师。


来源: http://www.ibm.com/developerworks/cn/linux/l-user-space-apps/index.html
阅读(823) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~