[Conceptos básicos de Linux] - Análisis del principio de udev capturando eventos de uevent

uevent :

Uevent envía mensajes a los procesos del usuario a través del conector netlink, y la aplicación aquí simplemente recibe mensajes de multidifusión.

El código simple implementado por udev es el siguiente:



    #include <stdio.h>
    #include <string.h>
    #include <sys/socket.h>
    #include <linux/netlink.h>

    struct uevent {
        const char *action;
        const char *path;
        const char *subsystem;
        const char *firmware;
        int major;
        int minor;
    };

    static int open_uevent_socket(void)
    {
        struct sockaddr_nl addr;
        int sz = 64*1024; // XXX larger? udev uses
        int on = 1;
        int s;

        memset(&addr, 0, sizeof(addr));
        addr.nl_family = AF_NETLINK;
        addr.nl_pid = getpid();

        //可以接收所有的family消息 , 单播设置0
        addr.nl_groups = 0xffffffff;

//地址族(Address Family) PF_NETLINK
//协议号NETLINK_KOBJECT_UEVENT
//数据传输方式/socket类型 SOCK_DGRAM | SOCK_CLOEXEC
        s = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
        if(s < 0)
            return -1;

        setsockopt(s, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz));

        //SO_PEERCRED 远端的资格
        setsockopt(s, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on));

        if(bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
            close(s);
            return -1;
        }

        return s;
    }

    static void parse_event(const char *msg, struct uevent *uevent)
    {
    #if 1
        uevent->action = "";
        uevent->path = "";
        uevent->subsystem = "";
        uevent->firmware = "";
        uevent->major = -1;
        uevent->minor = -1;

            /* currently ignoring SEQNUM */
        while(*msg) {
            printf("%s\n", msg);
            if(!strncmp(msg, "ACTION=", 7)) {
                msg += 7;
                uevent->action = msg;
            } else if(!strncmp(msg, "DEVPATH=", 8)) {
                msg += 8;
                uevent->path = msg;
            } else if(!strncmp(msg, "SUBSYSTEM=", 10)) {
                msg += 10;
                uevent->subsystem = msg;
            } else if(!strncmp(msg, "FIRMWARE=", 9)) {
                msg += 9;
                uevent->firmware = msg;
            } else if(!strncmp(msg, "MAJOR=", 6)) {
                msg += 6;
                uevent->major = atoi(msg);
            } else if(!strncmp(msg, "MINOR=", 6)) {
                msg += 6;
                uevent->minor = atoi(msg);
            }

                /* advance to after the next \0 */
            while(*msg++)
                ;
        }

        printf("event { '%s', '%s', '%s', '%s', %d, %d }\n",
                        uevent->action, uevent->path, uevent->subsystem,
                        uevent->firmware, uevent->major, uevent->minor);
    #endif
    }

    #define UEVENT_MSG_LEN 1024
    void handle_device_fd(int fd)
    {
        printf("enter %s\n", __func__);
        for(;;) {
            char msg[UEVENT_MSG_LEN+2];
            char cred_msg[CMSG_SPACE(sizeof(struct ucred))];
            struct iovec iov = {msg, sizeof(msg)};
            struct sockaddr_nl snl;
            struct msghdr hdr = {&snl, sizeof(snl), &iov, 1, cred_msg, sizeof(cred_msg), 0};

            ssize_t n = recvmsg(fd, &hdr, 0);
            if (n <= 0) {
                break;
            }

            if ((snl.nl_groups != 1) || (snl.nl_pid != 0)) {
                /* ignoring non-kernel netlink multicast message */
                continue;
            }

            struct cmsghdr * cmsg = CMSG_FIRSTHDR(&hdr);
            if (cmsg == NULL || cmsg->cmsg_type != SCM_CREDENTIALS) {
                /* no sender credentials received, ignore message */
                continue;
            }

            struct ucred * cred = (struct ucred *)CMSG_DATA(cmsg);
            if (cred->uid != 0) {
                /* message from non-root user, ignore */
                continue;
            }

            if(n >= UEVENT_MSG_LEN) /* overflow -- discard */
                continue;

            msg[n] = '\0';
            msg[n+1] = '\0';

            struct uevent uevent;
            parse_event(msg, &uevent);
        }
    }

    int main(void)
    {
        int fd = 0;
        
        fd = open_uevent_socket();
        if (fd < 0) {
            printf("error!\n");
            return -1;
        }
        
        handle_device_fd(fd);
    }

Implementado en el kernel

lib / kobject_uevent.c

1. uevent_net_init crea socket

struct uevent_sock {
        struct list_head list;
        struct sock *sk;
};
static LIST_HEAD(uevent_sock_list);

static int uevent_net_init(struct net *net)
{
        struct uevent_sock *ue_sk;

        ue_sk = kzalloc(sizeof(*ue_sk), GFP_KERNEL);
        if (!ue_sk)
                return -ENOMEM;

        ue_sk->sk = netlink_kernel_create(net, NETLINK_KOBJECT_UEVENT,
                                          1, NULL, NULL, THIS_MODULE);
        if (!ue_sk->sk) {
                printk(KERN_ERR
                       "kobject_uevent: unable to create netlink socket!\n");
                kfree(ue_sk);
                return -ENODEV;
        }
        mutex_lock(&uevent_sock_mutex);
        list_add_tail(&ue_sk->list, &uevent_sock_list);    //ue_sk 添加到tail
        mutex_unlock(&uevent_sock_mutex);
        return 0;
}

2. kobject_uevent () / kobject_uevent_env () envía uevent al espacio de usuario

  •     netlink_broadcast_filtered () envía mensajes netlink;
  •     call_usermodehelper_setup () / call_usermodehelper_exec () llama al programa de espacio de usuario para procesar el mensaje uevent.
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];   //action字符串
        const char *devpath = NULL;
        const char *subsystem;
        struct kobject *top_kobj;
        struct kset *kset;
        const struct kset_uevent_ops *uevent_ops;
        int i = 0;
        int retval = 0;
#ifdef CONFIG_NET
        struct uevent_sock *ue_sk;
#endif

        pr_debug("kobject: '%s' (%p): %s\n",
                 kobject_name(kobj), kobj, __func__);

        /* search the kset we belong to */
        top_kobj = kobj;
        while (!top_kobj->kset && top_kobj->parent)
                top_kobj = top_kobj->parent;

        if (!top_kobj->kset) {
                pr_debug("kobject: '%s' (%p): %s: attempted to send uevent "
                         "without kset!\n", kobject_name(kobj), kobj,
                         __func__);
                return -EINVAL;
        }

        kset = top_kobj->kset;
        uevent_ops = kset->uevent_ops;

        /* skip the event, if uevent_suppress is set*/
        if (kobj->uevent_suppress) {
                pr_debug("kobject: '%s' (%p): %s: uevent_suppress "
                                 "caused the event to drop!\n",
                                 kobject_name(kobj), kobj, __func__);
                return 0;
        }
        /* skip the event, if the filter returns zero. */
        if (uevent_ops && uevent_ops->filter)
                if (!uevent_ops->filter(kset, kobj)) {
                        pr_debug("kobject: '%s' (%p): %s: filter function "
                                 "caused the event to drop!\n",
                                 kobject_name(kobj), kobj, __func__);
                        return 0;
                }

        /* originating subsystem */
        if (uevent_ops && uevent_ops->name)
                subsystem = uevent_ops->name(kset, kobj);
        else
                subsystem = kobject_name(&kset->kobj);
        if (!subsystem) {
                pr_debug("kobject: '%s' (%p): %s: unset subsystem caused the "
                         "event to drop!\n", kobject_name(kobj), kobj,
                         __func__);
                return 0;
        }

        /* environment buffer */
        env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);
        if (!env)
                return -ENOMEM;

        /* complete object path */
        devpath = kobject_get_path(kobj, GFP_KERNEL);
        if (!devpath) {
                retval = -ENOENT;
                goto exit;
        }
//添加ACTION,DEVPATH,SUBSYSTEM的键值
        /* default keys */
        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]; i++) {
                        retval = add_uevent_var(env, "%s", envp_ext[i]);
                        if (retval)
                                goto exit;
                }
        }

        /* let the kset specific function add its stuff */
        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;
                }
        }
        /* let the kset specific function add its stuff */
        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;

        mutex_lock(&uevent_sock_mutex);
        
        /* we will send an event, so request a new sequence number */
        retval = add_uevent_var(env, "SEQNUM=%llu", (unsigned long long)++uevent_seqnum);
        if (retval) {
                mutex_unlock(&uevent_sock_mutex);
                goto exit;
        }

#if defined(CONFIG_NET)
        /* send netlink message */
        list_for_each_entry(ue_sk, &uevent_sock_list, list) {
                struct sock *uevent_sock = ue_sk->sk;
                struct sk_buff *skb;
                size_t len;
                if (!netlink_has_listeners(uevent_sock, 1))
                        continue;

                /* 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;
						//添加头:action_string@devpath
                        /* 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 < env->envp_idx; i++) {
                                len = strlen(env->envp[i]) + 1;
                                scratch = skb_put(skb, len);
                                strcpy(scratch, env->envp[i]);
                        }

                        NETLINK_CB(skb).dst_group = 1;
                        //发送skb数据
                        retval = netlink_broadcast_filtered(uevent_sock, skb,
                                                            0, 1, GFP_KERNEL,
                                                            kobj_bcast_filter,
                                                            kobj);
                        /* ENOBUFS should be handled in userspace */
                        if (retval == -ENOBUFS || retval == -ESRCH)
                                retval = 0;
                } else
                        retval = -ENOMEM;
        }
#endif
        mutex_unlock(&uevent_sock_mutex);

        /* call uevent_helper, usually only enabled during early boot */
        if (uevent_helper[0] && !kobj_usermode_filter(kobj)) {
                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:
        kfree(devpath);
        kfree(env);
        return retval;
}

referencias:

 

 

Supongo que te gusta

Origin blog.csdn.net/u014674293/article/details/115131863
Recomendado
Clasificación