- 论坛徽章:
- 0
|
http://docs.google.com/Doc?id=dcbsxfpf_391dqr7pxdj
2008.12.16
khelper uevent udev
kernel 2.6.27
call user space helper
search key: call_usermodehelper_setup
最常用的一个函数是call_usermodehelper(user_apps,....), 其作用就是指定用户空间的程序路径和环境变量, 最终运行指定的user space的程序,
![]()
![]()
具体的启动user space的程序的方式如下:
1. khelper worker thread 的创建 kernel/kmod.c
void __init usermodehelper_init(void)
{
khelper_wq = create_singlethread_workqueue("khelper");
BUG_ON(!khelper_wq);
register_pm_notifier_callback();
}
这里不详细深入workqueue内部了, 就是一个kernel线程,worker_thread 不断轮询其队列, 调用一个具体work的指定函数.
2.一个具体work的建立
不知道orderly_poweroff具体怎么回事, 不过看看他的执行过程.
int orderly_poweroff(bool force)
{
int argc;
char **argv = argv_split(GFP_ATOMIC, poweroff_cmd, &argc); /*"/sbin/poweroff" */
static char *envp[] = {
"HOME=/",
"PATH=/sbin:/bin:/usr/sbin:/usr/bin",
NULL
};
int ret = -ENOMEM;
struct subprocess_info *info;
if (argv == NULL) {
printk(KERN_WARNING "%s failed to allocate memory for \"%s\"\n",
__func__, poweroff_cmd);
goto out;
}
info = call_usermodehelper_setup(argv[0], argv, envp, GFP_ATOMIC);
if (info == NULL) {
argv_free(argv);
goto out;
}
call_usermodehelper_setcleanup(info, argv_cleanup);
ret = call_usermodehelper_exec(info, UMH_NO_WAIT);
out:
......
return ret;
}
struct subprocess_info *call_usermodehelper_setup(char *path, char **argv,
char **envp, gfp_t gfp_mask)
{
struct subprocess_info *sub_info;
sub_info = kzalloc(sizeof(struct subprocess_info), gfp_mask);
if (!sub_info)
goto out;
INIT_WORK(&sub_info->work, __call_usermodehelper); /*exec 指定的user space程序, 运行环境是khelper*/
sub_info->path = path;
sub_info->argv = argv;
sub_info->envp = envp;
out:
return sub_info;
}
int call_usermodehelper_exec(struct subprocess_info *sub_info,
enum umh_wait wait)
{
DECLARE_COMPLETION_ONSTACK(done);
int retval = 0;
helper_lock();
if (sub_info->path[0] == '\0')
goto out;
if (!khelper_wq || usermodehelper_disabled) {
retval = -EBUSY;
goto out;
}
sub_info->complete = &done;
sub_info->wait = wait;
queue_work(khelper_wq, &sub_info->work);
if (wait == UMH_NO_WAIT) /* task has freed sub_info */
goto unlock;
wait_for_completion(&done);
retval = sub_info->retval;
out:
call_usermodehelper_freeinfo(sub_info);
unlock:
helper_unlock();
return retval;
}
3. 执行user space 的程序
这个是sub_info->work的执行函数, 运行在内核线程khelper内, 如注释所述, 这个内核线程不能睡眠, 所以另启动新的线程做事情.
static void __call_usermodehelper(struct work_struct *work)
{
struct subprocess_info *sub_info =
container_of(work, struct subprocess_info, work);
pid_t pid;
enum umh_wait wait = sub_info->wait;
/* CLONE_VFORK: wait until the usermode helper has execve'd
* successfully We need the data structures to stay around
* until that is done. */
if (wait == UMH_WAIT_PROC || wait == UMH_NO_WAIT)
pid = kernel_thread(wait_for_helper, sub_info, /*需要wait就会通过这个函数在此启动一个子进程,*/
CLONE_FS | CLONE_FILES | SIGCHLD); /*不过最终还是调用____call_usermodehelper*/
else
pid = kernel_thread(____call_usermodehelper, sub_info,
CLONE_VFORK | SIGCHLD);
switch (wait) {
case UMH_NO_WAIT:
break;
case UMH_WAIT_PROC:
if (pid > 0)
break;
sub_info->retval = pid;
/* FALLTHROUGH */
case UMH_WAIT_EXEC:
complete(sub_info->complete);
}
}
最终,没有什么意外,用exec执行指定程序, 各种参数环境变量一并传递到user space.
static int ____call_usermodehelper(void *data)
{
struct subprocess_info *sub_info = data;
struct key *new_session, *old_session;
int retval;
/* Unblock all signals and set the session keyring. */
new_session = key_get(sub_info->ring);
spin_lock_irq(¤t->sighand->siglock);
old_session = __install_session_keyring(current, new_session); /*无视.....*/
flush_signal_handlers(current, 1);
sigemptyset(¤t->blocked);
recalc_sigpending();
spin_unlock_irq(¤t->sighand->siglock);
key_put(old_session);
/* Install input pipe when needed */ /*有点像bash*/
if (sub_info->stdin) {
struct files_struct *f = current->files;
struct fdtable *fdt;
/* no races because files should be private here */
sys_close(0);
fd_install(0, sub_info->stdin);
spin_lock(&f->file_lock);
fdt = files_fdtable(f);
FD_SET(0, fdt->open_fds);
FD_CLR(0, fdt->close_on_exec);
spin_unlock(&f->file_lock);
/* and disallow core files too */
current->signal->rlim[RLIMIT_CORE] = (struct rlimit){0, 0};
}
/* We can run anywhere, unlike our parent keventd(). */
set_cpus_allowed_ptr(current, CPU_MASK_ALL_PTR);
/*
* Our parent is keventd, which runs with elevated scheduling priority.
* Avoid propagating that into the userspace child.
*/
set_user_nice(current, 0);
retval = kernel_execve(sub_info->path, sub_info->argv, sub_info->envp);
/* Exec failed? */
sub_info->retval = retval;
do_exit(0);
}
uevent 和 netlink
udev提供了设备命名, 和sysfs一起能够提供一个动态的生成/dev下的各种设备文件的方式, 供统一的库函数来操作sysfs.
uevent是一个通讯的纽带, 内核将设备的hotplug以及其他事件通知user space, uevent作为kobject的一部分, 是linux设备/驱动模型的一部分.
uevent 通过netlink吧消息传递给userspace, 在启动的早期也可能使用usermodehelper方式.
uevent的接口函数就是:kobject_uevent->(或者)kobject_uevent_env.
核心函数就是:
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
char *envp_ext[])
{
struct kobj_uevent_env *env;
const char *action_string = kobject_actions[action];
const char *devpath = NULL;
const char *subsystem;
struct kobject *top_kobj;
struct kset *kset;
struct kset_uevent_ops *uevent_ops;
u64 seq;
int i = 0;
int retval = 0
....
/*想要发送uevent,必须设置kset, kset提供uevent的ops, 包括一个事件过滤函数*/
/* search the kset we belong to */
top_kobj = kobj;
while (!top_kobj->kset && top_kobj->parent)
top_kobj = top_kobj->parent;
....
kset = top_kobj->kset;
uevent_ops = kset->uevent_ops;
/* skip the event, if the filter returns zero. */
if (uevent_ops && uevent_ops->filter)
if (!uevent_ops->filter(kset, kobj)) {
..... //debug
return 0;
}
/*对于device可以参考dev_uevent_name, subsystem是bus, 如果没有bus则是class. 没有提供name
* 的kset, 比如 /sys/bus 子系统, 其名字取kset内部的kobj的名字. 许多子系统出现在/sys/即sysfs的根目录
* (但是/sys 根目录不一定是子系统, 也有就是一个kobj,比如fs,kernel等...
*/
/* originating subsystem */
if (uevent_ops && uevent_ops->name)
subsystem = uevent_ops->name(kset, kobj);
else
subsystem = kobject_name(&kset->kobj);
if (!subsystem) {
.....//debug
return 0;
}
/* environment buffer */
env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);
if (!env)
return -ENOMEM;
/* complete object path : 参数之一是此设备/driver/.. 对应的sysfs路径*/
devpath = kobject_get_path(kobj, GFP_KERNEL);
if (!devpath) {
retval = -ENOENT;
goto exit;
}
/* default keys : 默认的参数是 action, dev patch 和subsystem*/
retval = add_uevent_var(env, "ACTION=%s", action_string);
if (retval)
goto exit;
retval = add_uevent_var(env, "DEVPATH=%s", devpath);
if (retval)
goto exit;
retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem);
if (retval)
goto exit;
/* keys passed in from the caller :调用方参数 */
if (envp_ext) {
for (i = 0; envp_ext; i++) {
retval = add_uevent_var(env, envp_ext);
if (retval)
goto exit;
}
}
/* let the kset specific function add its stuff : kset 特定处理 */
if (uevent_ops && uevent_ops->uevent) {
retval = uevent_ops->uevent(kset, kobj, env);
if (retval) {
pr_debug("kobject: '%s' (%p): %s: uevent() returned "
"%d\n", kobject_name(kobj), kobj,
__func__, retval);
goto exit;
}
}
/*
* Mark "add" and "remove" events in the object to ensure proper
* events to userspace during automatic cleanup. If the object did
* send an "add" event, "remove" will automatically generated by
* the core, if not already done by the caller.
*/
if (action == KOBJ_ADD)
kobj->state_add_uevent_sent = 1;
else if (action == KOBJ_REMOVE)
kobj->state_remove_uevent_sent = 1;
/* we will send an event, so request a new sequence number */
spin_lock(&sequence_lock);
seq = ++uevent_seqnum;
spin_unlock(&sequence_lock);
retval = add_uevent_var(env, "SEQNUM=%llu", (unsigned long long)seq);
if (retval)
goto exit;
#if defined(CONFIG_NET)
/* send netlink message : 默认情况下uevent通过netlink传递消息给user space*/
if (uevent_sock) {
struct sk_buff *skb;
size_t len;
/* allocate message with the maximum possible size */
len = strlen(action_string) + strlen(devpath) + 2;
skb = alloc_skb(len + env->buflen, GFP_KERNEL);
if (skb) {
char *scratch;
/* add header */
scratch = skb_put(skb, len);
sprintf(scratch, "%s@%s", action_string, devpath);
/* copy keys to our continuous event payload buffer */
for (i = 0; i envp_idx; i++) {
len = strlen(env->envp) + 1;
scratch = skb_put(skb, len);
strcpy(scratch, env->envp);
}
NETLINK_CB(skb).dst_group = 1;
netlink_broadcast(uevent_sock, skb, 0, 1, GFP_KERNEL);
}
}
#endif
/* call uevent_helper, usually only enabled during early boot: 启动的早期也许会用usermodehelper方式 */
if (uevent_helper[0]) {
char *argv [3];
argv [0] = uevent_helper;
argv [1] = (char *)subsystem;
argv [2] = NULL;
retval = add_uevent_var(env, "HOME=/");
if (retval)
goto exit;
retval = add_uevent_var(env,
"PATH=/sbin:/bin:/usr/sbin:/usr/bin");
if (retval)
goto exit;
retval = call_usermodehelper(argv[0], argv,
env->envp, UMH_WAIT_EXEC);
}
exit:
.....
}
关于udev
udev 通过uevent 经由netlink传递的消息来创建/dev下的各个设备文件, 从而实现动态的/dev tree 以及设备命名. 另外udevd 通过inotify 可以得知udev的的变化, 从而规则马上得以应用.
udevtrigger
是udev提供的一个辅助程序, udevtrigger通过向/sysfs
文件系统下现有设备的uevent节点写"add"字符串来重新触发uevent事件,使得udevd能够获得udev启动的所有已经注册的设备模块.这
个过程对应的内核代码是:
static ssize_t store_uevent(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
enum kobject_action action;
if (kobject_action_type(buf, count, &action) == 0) {
kobject_uevent(&dev->kobj, action);
goto out;
}
dev_err(dev, "uevent: unsupported action-string; this will "
"be ignored in a future kernel version\n");
kobject_uevent(&dev->kobj, KOBJ_ADD);
out:
return count;
}
关于udev的安装配置可以参考下面的文章:
http://blog.csdn.net/colorant/archive/2008/01/09/2031721.aspx
下面的文章给出一个udevd通过netlink获取uevent的例子:
http://blog.csdn.net/absurd/archive/2007/04/27/1587938.aspx
顺便copy其程序到这里:
#include stdio.h>
#include
#include string.h>
#include ctype.h>
#include
#include
#include socket.h>
#include
#include
#include errno.h>
static int init_hotplug_sock(void)
{
struct sockaddr_nl snl;
const int buffersize = 16 * 1024 * 1024;
int retval;
memset(&snl, 0x00, sizeof(struct sockaddr_nl));
snl.nl_family = AF_NETLINK;
snl.nl_pid = getpid();
snl.nl_groups = 1;
int hotplug_sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
if (hotplug_sock == -1) {
printf("error getting socket: %s", strerror(errno));
return -1;
}
/* set receive buffersize */
setsockopt(hotplug_sock, SOL_SOCKET, SO_RCVBUFFORCE, &buffersize, sizeof(buffersize));
retval = bind(hotplug_sock, (struct sockaddr *) &snl, sizeof(struct sockaddr_nl));
if (retval
printf("bind failed: %s", strerror(errno));
close(hotplug_sock);
hotplug_sock = -1;
return -1;
}
return hotplug_sock;
}
#define UEVENT_BUFFER_SIZE 2048
int main(int argc, char* argv[])
{
int hotplug_sock = init_hotplug_sock();
while(1)
{
char buf[UEVENT_BUFFER_SIZE*2] = {0};
recv(hotplug_sock, &buf, sizeof(buf), 0);
printf("%s\n", buf);
}
return 0;
}
本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u2/79526/showart_1741834.html |
|