免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1858 | 回复: 0
打印 上一主题 下一主题

007 2.6.27 khelper uevent netlink udev [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2008-12-25 16:36 |只看该作者 |倒序浏览

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(&current->sighand->siglock);
    old_session = __install_session_keyring(current, new_session); /*无视.....*/
    flush_signal_handlers(current, 1);
    sigemptyset(&current->blocked);
    recalc_sigpending();
    spin_unlock_irq(&current->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
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP