内核发送uevent的API,用户空间解析uevent

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/luckywang1103/article/details/50529534

内核发送uevent的API

内核发送uevent的API由lib/kobject_event.c文件实现,include/linux/kobject.h是头文件。

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

/* kobject_uevent不能用在中断上下文 */
int kobject_uevent(struct kobject *kobj, enum kobject_action action);
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action, char *envp[]);

在driver中可以调用kobject_uevent或者kobject_uevent_env来向用户空间发送uevent
kobject_uevent默认会发送”ACTION=xxx”,”DEVPATH=xxx”,”SUBSYSTEM=xxx”这三个uevent环境变量。
kobject_uevent_env可以发送一些如”xxx=xxx”的自定义的uevent环境变量。


用户空间解析uevent

从android代码system/core/init/devices.c中抽取

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

#define UEVENT_MSG_LEN 4096
struct luther_gliethttp {
    const char *action;
    const char *path;
    const char *subsystem;
    const char *firmware;
    int major;
    int minor;
};
static int open_luther_gliethttp_socket(void);
static void parse_event(const char *msg, struct luther_gliethttp *luther_gliethttp);

int main(int argc, char* argv[])
{
    int device_fd = -1;
    char msg[UEVENT_MSG_LEN+2];
    int n;

    device_fd = open_luther_gliethttp_socket();
    printf("device_fd = %d\n", device_fd);

    do {
        while((n = recv(device_fd, msg, UEVENT_MSG_LEN, 0)) > 0) {
            struct luther_gliethttp luther_gliethttp;

            if(n == UEVENT_MSG_LEN)
                continue;

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

            parse_event(msg, &luther_gliethttp);
        }
    } while(1);
}

static int open_luther_gliethttp_socket(void)
{
    struct sockaddr_nl addr;
    int sz = 64*1024;
    int s;

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

    s = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
    if (s < 0)
        return -1;

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

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

    return s;
}

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


    printf("========================================================\n");
    while (*msg) {

        printf("%s\n", msg);

        if (!strncmp(msg, "ACTION=", 7)) {
            msg += 7;
            luther_gliethttp->action = msg;
        } else if (!strncmp(msg, "DEVPATH=", 8)) {
            msg += 8;
            luther_gliethttp->path = msg;
        } else if (!strncmp(msg, "SUBSYSTEM=", 10)) {
            msg += 10;
            luther_gliethttp->subsystem = msg;
        } else if (!strncmp(msg, "FIRMWARE=", 9)) {
            msg += 9;
            luther_gliethttp->firmware = msg;
        } else if (!strncmp(msg, "MAJOR=", 6)) {
            msg += 6;
            luther_gliethttp->major = atoi(msg);
        } else if (!strncmp(msg, "MINOR=", 6)) {
            msg += 6;
            luther_gliethttp->minor = atoi(msg);
        }


        while(*msg++)
            ;
    }

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

这个程序放在后台运行,当内核中通过kobject_uevent_env或者kobject_uevent发送uevent至用户空间的时候,这个进程将对应的uevent消息打印出来。


udev与mdev

kobject_uevent和kobject_uevent_env这两个函数都会
调用netlink发送uevent消息,
调用uevent_helper,最终转换成对用户空间/sbin/mdev的调用。
uevent的用户空间程序有两个,一个是udev,另一个是mdev。

udev通过netlink监听uevent消息,他在系统启动的时候运行了一个daemon(守护进程)程序udevd,通过监听内核发送的uevent来执行相应的热插拔动作,他主要完成两个功能:
1\ 自动加载模块
2\ 根据uevent消息在dev目录下添加、删除设备节点

mdev是基于uevent_helper机制的,他在系统启动时修改了内核中的uevent_helper变量(通过写/proc/sys/kernel/hotplug),这样内核产生uevent时会调用uevent_helper所指的用户级程序,也就是mdev来执行相应的热插拔动作,uevent_helper的初始值在内核编译时刻配置:
—> Device Drivers
—> Generic Driver Options
—> (/proc/sys/kernel/hotplug) path to uevent helper

还有在android系统中是ueventd这个守护进程在接收uevent事件,源码位于system/core/init/ueventd.c,他读取的配置文件是ueventd.rc


udev

/etc目录下有一个uevent规则文件/etc/udev/rules.d/50-udev.rules
udev收到uevent消息后,在这个规则文件里匹配,如果匹配成功,则执行这个匹配定义的shell命令,例如规则文件中有这么一行:
ACTION=="add", SUBSYSTEM=="?*", ENV{MODALIAS}=="?*", RUN+="/sbin/modprobe $env{MODALIAS}"
所以,当收到uevent的add事件后,shell能自动加载在MODALIAS中定义的模块。


mdev

mdev配置

1\ 内核配置以支持mdev
保证CONFIG_HOTPLUG=Y和CONFIG_NET=Y
然后设置hotplug执行的路径,这里可以不设置,但是在开机的启动脚本中设置”echo /bin/mdev > /sys/proc/kernel/hotplug”
Device Drivers —>
Generic Driver Options —>
() path to uevent helper

2\ busybox配置mdev工具及对应的配置文件
Linux System Utilities —>
[*] mdev
[*] Support /etc/mdev.conf
[*] Support subdirs/symlinks
[*] Support regular expressions substitutions when renaming device
[*] Support command execution at device addition/removal
[*] Support loading of firmwares


mdev原理

mdev在busybox的代码包中能找到,位于busybox/util-linux/mdev.c文件中,他通过uevent_helper函数被调用。

a、执行mdev -s命令时,mdev扫描/sys/block(块设备保存在/sys/block目录下,内核2.6.25版本以后,块设备也保存在/sys /class/block目录下。mdev扫描/sys/block是为了实现向后兼容)和/sys/class两个目录下的dev属性文件,从该dev 属性文件中获取到设备编号(dev属性文件以”major:minor\n”形式保存设备编号),并以包含该dev属性文件的目录名称作为设备名 device_name(即包含dev属性文件的目录称为device_name,而/sys/class和device_name之间的那部分目录称为 subsystem。也就是每个dev属性文件所在的路径都可表示为/sys/class/subsystem/device_name/dev),在 /dev目录下创建相应的设备文件。例如,cat /sys/class/tty/tty0/dev会得到4:0,subsystem为tty,device_name为tty0。

b、当mdev因uevnet事件(以前叫hotplug事件)被调用时,mdev通过由uevent事件传递给它的环境变量获取到:引起该uevent 事件的设备action及该设备所在的路径device path。然后判断引起该uevent事件的action是什么。若该action是add,即有新设备加入到系统中,不管该设备是虚拟设备还是实际物理 设备,mdev都会通过device path路径下的dev属性文件获取到设备编号,然后以device path路径最后一个目录(即包含该dev属性文件的目录)作为设备名,在/dev目录下创建相应的设备文件。若该action是remote,即设备已 从系统中移除,则删除/dev目录下以device path路径最后一个目录名称作为文件名的设备文件。如果该action既不是add也不是remove,mdev则什么都不做。

由上面可知,如果我们想在设备加入到系统中或从系统中移除时,由mdev自动地创建和删除设备文件,那么就必须做到以下三点:1、在/sys/class 的某一subsystem目录下,2、创建一个以设备名device_name作为名称的目录,3、并且在该device_name目录下还必须包含一个 dev属性文件,该dev属性文件以”major:minor\n”形式输出设备编号。


mdev.txt

mdev配置文件的用法看busybox/docs/mdev.txt文件
mdev的配置文件是/etc/mdev.conf

以下是mdev.txt部分原文内容:

-----------
Basic Use
-----------
Mdev has two primary uses: initial population and dynamic updates.  Both require sysfs support in the kernel and have it mounted at /sys.  For dynamic updates, you also need to have hotplugging enabled in your kernel.
Here's a typical code snippet from the init script: 
[0] mount -t proc proc /proc 
[1] mount -t sysfs sysfs /sys 
[2] echo /sbin/mdev > /proc/sys/kernel/hotplug 
[3] mdev -s

------------- 
MDEV Config   (/etc/mdev.conf)
-------------
Mdev has an optional config file for controlling ownership/permissions of device nodes if your system needs something more than the default root/root 660 permissions.
The file has the format: 
    [-]<device regex>   <uid>:<gid> <permissions>
or
    @<maj[,min1[-min2]]>    <uid>:<gid> <permissions>
or 
    $envvar=<regex>        <uid>:<gid> <permissions>
For example: 
    hd[a-z][0-9]* 0:3 660

You can rename/move device nodes by using the next optional field.
    <device regex> <uid>:<gid> <permissions> [=path]
So if you want to place the device node into a subdirectory, make sure the path has a trailing /.  If you want to rename the device node, just place the name. 
    hda 0:3 660 =drives/ 
This will move "hda" into the drives/ subdirectory. 
    hdb 0:3 660 =cdrom 
This will rename "hdb" to "cdrom".

Similarly, ">path" renames/moves the device but it also creates a direct symlink /dev/DEVNAME to the renamed/moved device.

You can also prevent creation of device nodes with the 4th field as "!": 
    tty[a-z]. 0:0 660 !
    pty[a-z]. 0:0 660 !

If you also enable support for executing your own commands, then the file has the format: 
    <device regex> <uid>:<gid> <permissions> [=path] [@|$|*<command>] 
or
    <device regex> <uid>:<gid> <permissions> [>path] [@|$|*<command>]
or
    <device regex> <uid>:<gid> <permissions> [!] [@|$|*<command>]
For example:
# block devices
([hs]d[a-z])        root:disk   660 >disk/%1/0
([hs]d[a-z])([0-9]+)    root:disk   660 >disk/%1/%2 mmcblk([0-9]+)      root:disk   660 >disk/mmc/%1/0 mmcblk([0-9]+)p([0-9]+)  root:disk   660 >disk/mmc/%1/%2
# network devices
(tun|tap)       root:network    660 >net/%1

The special characters have the meaning:
    @ Run after creating the device.
    $ Run before removing the device.
    * Run both after creating and before removing the device.

The command is executed via the system() function (which means you're giving a command to the shell), so make sure you have a shell installed at /bin/sh.  You should also keep in mind that the kernel executes hotplug helpers with stdin, stdout, and stderr connected to /dev/null.

For your convenience, the shell env var $MDEV is set to the device name.  So if the device "hdc" was matched, MDEV would be set to "hdc".

参考文章

  1. 热插拔uevent事件用户空间截获方法和具体实现
  2. linux下热插拔事件的产生是怎样通知到用户空间,kobject_uevent_env之uevent
阅读更多
换一批

没有更多推荐了,返回首页