Linux kernel -- Uevent发送(热插拔)事件到用户空间

Uevent是一种在内核空间和用户空间之间通信的机制,主要用于热插拔事件(hotplug)。

uevent事件

根据include/linux/kobject.h中的定义,kobject对应的动作可分为以下几种:

enum kobject_action {
    KOBJ_ADD,
    KOBJ_REMOVE,
    KOBJ_CHANGE,
    KOBJ_MOVE,
    KOBJ_ONLINE,
    KOBJ_OFFLINE,
    KOBJ_MAX
};

这些动作对应的事件相应有以下几种:

static const char *kobject_actions[] = {
    [KOBJ_ADD] =        "add",
    [KOBJ_REMOVE] =     "remove",
    [KOBJ_CHANGE] =     "change",
    [KOBJ_MOVE] =       "move",
    [KOBJ_ONLINE] =     "online",
    [KOBJ_OFFLINE] =    "offline",
};

说明:本文所有的源代码均取自内核2.6.32。

uevent环境变量

Uevent事件具体以环境变量(即字符串)的形式发送到用户空间。

struct kobj_uevent_env {
    char *envp[UEVENT_NUM_ENVP]; /*环境变量指针数组*/
    int envp_idx; /*数组下标*/
    char buf[UEVENT_BUFFER_SIZE];
    int buflen;
};

发送事件方法

在内核中通过kobject_uevent_env函数将事件发送到用户空间,但是实际上发送事件的动作由调用函数kobject_uevent_env实现。

/**
 * kobject_uevent - notify userspace by ending an uevent
 *
 * @action: action that is happening
 * @kobj: struct kobject that the action is happening to
 *
 * Returns 0 if kobject_uevent() is completed with success or the
 * corresponding error when it fails.
 */
int kobject_uevent(struct kobject *kobj, enum kobject_action action)
{
    return kobject_uevent_env(kobj, action, NULL);
}
EXPORT_SYMBOL_GPL(kobject_uevent);

kobject_uevent_env函数主要做了两方面的工作:

  1. 获取整理了与即将发送的事件相关的环境变量,如ACTION、DEVPATH和SUBSYSTE等。
  2. 发送事件到用户空间。
/**
 * kobject_uevent_env - send an uevent with environmental data
 *
 * @action: action that is happening
 * @kobj: struct kobject that the action is happening to
 * @envp_ext: pointer to environmental data
 *
 * Returns 0 if kobject_uevent() is completed with success or the
 * corresponding error when it fails.
 */
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;

    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;
    }

    /* 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;
        }
    }

    /*
     * 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 */
    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 < 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;
            retval = netlink_broadcast(uevent_sock, skb, 0, 1,
                           GFP_KERNEL);
            /* ENOBUFS should be handled in userspace */
            if (retval == -ENOBUFS)
                retval = 0;
        } else
            retval = -ENOMEM;
    }
#endif

    /* call uevent_helper, usually only enabled during early boot */
    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:
    kfree(devpath);
    kfree(env);
    return retval;
}
EXPORT_SYMBOL_GPL(kobject_uevent_env);

第29~41行,查询当前kobject所属的kset。
第44~49行,如果设置了kobj->uevent_suppress(==1),则跳过该事件。
第50~57行,如果可以过滤该事件也跳过该事件。

至此,既然确定事件可以被发送,接下来设置要发送的环境变量:
第60~69行,获取subsystem。
第77~81行,获取devpath。
第84~92行,根据形参和获取的subsystem和devpath的值设置默认的环境变量 - ACTION、DEVPATH和SUBSYSTEM。
第95~101行,添加调用函数传递的其他环境变量。

即将发送事件给用户空间,那么需要标记该动作表示事件已(其实是即将)发送:
第120~123行,如果是添加事件则为kobj->state_add_uevent_sent赋值1;如果是移除事件则为kobj->state_remove_uevent_sent赋值1。

第126~131行,为即将发送的事件分配一个数字标识SEQNUM

通过以上的准备工作环境变量均设置完毕,接下来就是将这些环境变量发送到用户空间。发送的方法可分为两种:Netlink socket执行uevent_helper
现在先分析Netlink socket方法。实现该方法的前提条件是定义了CONFIG_NET,然后将要发送的信息(message)存放到网络结构sk_buff中,最后调用netlink_broadcast函数广播到用户空间。这部分的源码见133~165行。
如果未定义CONFIG_NET,则可通过call_usermodehelper执行uevent_helper(缺省值是“/sbin/hotplug“)命令发送事件到用户空间。

获取热插拔事件实例

热插拔事件最终发送给udev,由udev根据获取的环境变量作相应的操作,如创建设备节点、移除设备节点等。udevadm是一个udev管理工具,可以获取内核发送的事件。下面以插拔优盘实例获取内核发送的事件。

# udevadm monitor --env
monitor will print the received events for:
UDEV - the event which udev sends out after rule processing
KERNEL - the kernel uevent

插入优盘事件

KERNEL[1468567824.269692] add      /devices/pci0000:00/0000:00:13.2/usb3/3-5 (usb)
UDEV_LOG=3
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:13.2/usb3/3-5
SUBSYSTEM=usb
DEVNAME=bus/usb/003/006
DEVTYPE=usb_device
DEVICE=/proc/bus/usb/003/006
PRODUCT=781/5590/100
TYPE=0/0/0
BUSNUM=003
DEVNUM=006
SEQNUM=1347
MAJOR=189
MINOR=261

KERNEL[1468567824.269860] add      /devices/pci0000:00/0000:00:13.2/usb3/3-5/3-5:1.0 (usb)
UDEV_LOG=3
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:13.2/usb3/3-5/3-5:1.0
SUBSYSTEM=usb
DEVTYPE=usb_interface
DEVICE=/proc/bus/usb/003/006
PRODUCT=781/5590/100
TYPE=0/0/0
INTERFACE=8/6/80
MODALIAS=usb:v0781p5590d0100dc00dsc00dp00ic08isc06ip50
SEQNUM=1348

KERNEL[1468567824.269899] add      /devices/pci0000:00/0000:00:13.2/usb3/3-5/3-5:1.0/host6 (scsi)
UDEV_LOG=3
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:13.2/usb3/3-5/3-5:1.0/host6
SUBSYSTEM=scsi
DEVTYPE=scsi_host
SEQNUM=1349
...
UDEV  [1468567830.919480] add      /devices/pci0000:00/0000:00:13.2/usb3/3-5/3-5:1.0/host6/target6:0:0/6:0:0:0/block/sdb (block)
UDEV_LOG=3
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:13.2/usb3/3-5/3-5:1.0/host6/target6:0:0/6:0:0:0/block/sdb
SUBSYSTEM=block
DEVNAME=/dev/sdb
DEVTYPE=disk
SEQNUM=1357
ID_VENDOR=SanDisk
ID_VENDOR_ENC=SanDisk\x20
ID_VENDOR_ID=0781
ID_MODEL=Ultra
ID_MODEL_ENC=Ultra\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20
ID_MODEL_ID=5590
ID_REVISION=1.00
ID_SERIAL=SanDisk_Ultra_4C531148531019111535-0:0
ID_SERIAL_SHORT=4C531148531019111535
ID_TYPE=disk
ID_INSTANCE=0:0
ID_BUS=usb
ID_USB_INTERFACES=:080650:
ID_USB_INTERFACE_NUM=00
ID_USB_DRIVER=usb-storage
ID_PATH=pci-0000:00:13.2-usb-0:5:1.0-scsi-0:0:0:0
ID_PART_TABLE_TYPE=dos
UDISKS_PRESENTATION_NOPOLICY=0
UDISKS_PARTITION_TABLE=1
UDISKS_PARTITION_TABLE_SCHEME=mbr
UDISKS_PARTITION_TABLE_COUNT=1
MAJOR=8
MINOR=16
DEVLINKS=/dev/block/8:16 /dev/disk/by-id/usb-SanDisk_Ultra_4C531148531019111535-0:0 /dev/disk/by-path/pci-0000:00:13.2-usb-0:5:1.0-scsi-0:0:0:0

UDEV  [1468567831.033023] add      /devices/pci0000:00/0000:00:13.2/usb3/3-5/3-5:1.0/host6/target6:0:0/6:0:0:0/block/sdb/sdb1 (block)
UDEV_LOG=3
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:13.2/usb3/3-5/3-5:1.0/host6/target6:0:0/6:0:0:0/block/sdb/sdb1
SUBSYSTEM=block
DEVNAME=/dev/sdb1
DEVTYPE=partition
SEQNUM=1358
ID_VENDOR=SanDisk
ID_VENDOR_ENC=SanDisk\x20
ID_VENDOR_ID=0781
ID_MODEL=Ultra
ID_MODEL_ENC=Ultra\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20
ID_MODEL_ID=5590
ID_REVISION=1.00
ID_SERIAL=SanDisk_Ultra_4C531148531019111535-0:0
ID_SERIAL_SHORT=4C531148531019111535
ID_TYPE=disk
ID_INSTANCE=0:0
ID_BUS=usb
ID_USB_INTERFACES=:080650:
ID_USB_INTERFACE_NUM=00
ID_USB_DRIVER=usb-storage
ID_PATH=pci-0000:00:13.2-usb-0:5:1.0-scsi-0:0:0:0
ID_PART_TABLE_TYPE=dos
ID_FS_UUID=fd71e39b-9209-42d7-9873-bd5fccae5715
ID_FS_UUID_ENC=fd71e39b-9209-42d7-9873-bd5fccae5715
ID_FS_VERSION=1.0
ID_FS_TYPE=ext3
ID_FS_USAGE=filesystem
UDISKS_PRESENTATION_NOPOLICY=0
UDISKS_PARTITION=1
UDISKS_PARTITION_SCHEME=mbr
UDISKS_PARTITION_NUMBER=1
UDISKS_PARTITION_TYPE=0x0c
UDISKS_PARTITION_SIZE=15552446976
UDISKS_PARTITION_FLAGS=boot
UDISKS_PARTITION_SLAVE=/sys/devices/pci0000:00/0000:00:13.2/usb3/3-5/3-5:1.0/host6/target6:0:0/6:0:0:0/block/sdb
UDISKS_PARTITION_OFFSET=32256
UDISKS_PARTITION_ALIGNMENT_OFFSET=0
MAJOR=8
MINOR=17
DEVLINKS=/dev/block/8:17 /dev/disk/by-id/usb-SanDisk_Ultra_4C531148531019111535-0:0-part1 /dev/disk/by-path/pci-0000:00:13.2-usb-0:5:1.0-scsi-0:0:0:0-part1 /dev/disk/by-uuid/fd71e39b-9209-42d7-9873-bd5fccae5715

优盘插入后内核立即将该事件发送到用户空间,udev根据规则(/etc/udev/rules.d/*.rules或/lib/udev/rules.d/*.rules)创建了设备节点/dev/sdb和设备链接文件等。

  • 5
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Linux操作系统是一种开放源代码的操作系统,其用户空间可以通过使用netlink来监听uevent用户事件)。netlink是一种用于内核和用户空间之间进行通信的机制,可以实现内核对用户空间事件的广播。 在Linux中,用户空间使用netlink来监听uevent的主要目的是为了获取与设备相关的信息和事件通知。通过监听uevent用户空间可以获得设备的插拔状态、设备的属性变化、系统的电源管理事件等等。 使用netlink监听uevent的过程一般包括以下几个步骤: 1. 创建与内核通信的socket:用户空间需要创建一个socket,并使用socket系统调用将其与netlink协议相关联。 2. 绑定socket到uevent通信组:调用bind系统调用将socket绑定到uevent通信组,以便接收与设备相关的事件通知。 3. 接收并处理uevent:使用recv系统调用从socket中接收uevent消息,并在用户空间中对其进行处理。用户空间可以根据收到的uevent消息来进行相应的操作,例如更新设备列表、触发相关动作等。 需要注意的是,使用netlink监听uevent需要具备相应的权限。一般情况下,只有具有管理员权限的用户或特定的用户组才能够进行这类操作。 在Linux中,用户空间使用netlink监听uevent是实现设备管理和与设备相关的操作的重要手段之一。它可以使用户空间获取到内核层面的设备信息,并根据这些信息做出相应的响应和决策。这对于开发系统工具、设备驱动程序以及一些需要实时监控设备状态的应用程序非常有用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值