UDEVD与内核交互

本文探讨了Linux系统中UDEVD程序的作用,包括设备节点的动态管理、设备链接的创建以及设备规则的定制。详细介绍了UDEVD的内核配置需求、规则文件的结构与执行顺序,以及如何通过udevadm工具触发内核设备事件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

内核依据固定的标准与初始化次序命名设备,例如以太网设备,按照初始化次序命名为eth0、eth1等。UDEVD程序的存在,可以动态的添加/删除linux系统的设备节点,可以使用户定制化设备节点名称,创建设备链接等。


使用udevd,需要配置以下内核选项:

udevd要求:
  内核支持sysfs, procfs, signalfd, inotify, unix domain sockets, networking and hotplug。
  
  配置选项:
      CONFIG_HOTPLUG=y
      CONFIG_UEVENT_HELPER_PATH=""    //禁用应用层hotplug程序
      CONFIG_NET=y           //使能uevent通信的netlink接口     
      CONFIG_UNIX=y          //udevd内部进程间通信
      CONFIG_SYSFS=y         //用于udevd获取内核设备节点
      CONFIG_PROC_FS=y       //udevd参数调整
      CONFIG_INOTIFY_USER=y  //监控udevd rules规则文件变化
      CONFIG_SIGNALFD=y      //udevd信号处理

 可选项: 
      CONFIG_TMPFS=y
      CONFIG_TMPFS_POSIX_ACL=y (user ACLs for device nodes)
      CONFIG_BLK_DEV_BSG=y (SCSI devices)

内核配置中禁用掉/sbin/hotplug,如果未禁用,将导致系统不稳定,因为hostplug的存在导致kernel并行创建了许多进程,会出现内存溢出。需要将proc文件系统挂到/proc,将sysfs挂载到/sys,udev会需要这些目录。
 

UDEVD规则文件

udevd的定制规则文件分别位于三个目录下:

用户自定义规则文件目录       /etc/udev/rules.d
UDEV默认规则文件目录         /lib/udev/rules.d
UDEVD运行时动态添加规则目录  /run/udev/rules.d

规则文件以数字开头以rules为后缀,开头的数字表示执行的顺序。/etc/udev/rules.d目录的优先权高于/lib/udev/rules.d目录,如果出现同名的规则文件,/etc/udev/rules.d目录中的文件会覆盖后者。

例如对于存储设备,规则文件为60-persistent-storage.rules,udevd不仅可以按照内核的命名创建/dev目录下的设备节点,还可以创建设备链接文件。如下对于存储设备,根据文件系统的信息UUID,在目录/dev/disk/by-uuid/下创建设备的链接文件,文件名称为设备的文件系统UUID。

# ATA
KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", IMPORT{program}="ata_id --export $devnode"

# by-label/by-uuid links (filesystem metadata)
ENV{ID_FS_USAGE}=="filesystem|other|crypto", ENV{ID_FS_UUID_ENC}=="?*", SYMLINK+="disk/by-uuid/$env{ID_FS_UUID_ENC}"
ENV{ID_FS_USAGE}=="filesystem|other", ENV{ID_FS_LABEL_ENC}=="?*", SYMLINK+="disk/by-label/$env{ID_FS_LABEL_ENC}"


UDEVD程序使用文件系统的INOTIFY机制监控存放规则的三个文件目录:/lib/udev/rules.d目录、/etc/udev/rules.d目录和/run/udev/rules.d目录。

int main(int argc, char *argv[]) 
{
     monitor = udev_monitor_new_from_netlink(udev, "kernel");
     fd_inotify = udev_watch_init(udev);

     /* watch rules.d paths for changes */
     inotify_add_watch(fd_inotify, UDEV_RULES_DIR, IN_DELETE | IN_MOVE | IN_CLOSE_WRITE);
     inotify_add_watch(fd_inotify, UDEV_CONF_DIR "/rules.d", IN_DELETE | IN_MOVE | IN_CLOSE_WRITE);

     if (access(UDEV_ROOT_RUN "/udev/rules.d", F_OK) < 0)
          udev_mkdir_p(UDEV_ROOT_RUN "/udev/rules.d", 0755);
     inotify_add_watch(fd_inotify, UDEV_ROOT_RUN "/udev/rules.d", IN_DELETE | IN_MOVE | IN_CLOSE_WRITE);
}

监听内核UEVENT事件

UDEVD使用netlink套接口监听内核设备的uevent事件,套接口协议指定为NETLINK_KOBJECT_UEVENT(15)。

struct udev_monitor *udev_monitor_new_from_netlink_fd(struct udev *udev, const char *name, int fd)
{
    udev_monitor->sock = socket(PF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_KOBJECT_UEVENT);
}

内核中uevent netlink套接口创建由函数uevent_net_init完成。当内核中设备事件发生时,调用kobject_uevent函数利用netlink接口广播到用户层。

static int uevent_net_init(struct net *net)
{
    struct netlink_kernel_cfg cfg = {
        .groups = 1,
        .flags  = NL_CFG_F_NONROOT_RECV,
    };
    ue_sk->sk = netlink_kernel_create(net, NETLINK_KOBJECT_UEVENT, &cfg);
}
int kobject_uevent(struct kobject *kobj, enum kobject_action action)
{
    return kobject_uevent_env(kobj, action, NULL);
}

内核设备事件

Linux内核使用kobject系统进行设备管理,目前kobject定义了以下的设备事件:

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

可使用udevadm工具触发内核设备事件,即udevadm trigger命令,其默认的事件为change,使用-c选项指定触发事件类型,目前支持三种事件:add、remove和change。使用-t选项指定触发类型,目前支持subsystem和devices两种,不指定默认使用devices类型。事件触发的实现很简单,原理就是扫描系统的/sys目录,对设备的uevent文件写入相应的action字符。

static void exec_list(struct udev_enumerate *udev_enumerate, const char *action)
{
    udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(udev_enumerate)) {

        strscpyl(filename, sizeof(filename), udev_list_entry_get_name(entry), "/uevent", NULL);
        fd = open(filename, O_WRONLY|O_CLOEXEC);
        write(fd, action, strlen(action));
    }
}

在系统上电启动之后,udevd守护进程启动之前,系统已经初始化好了内核设备,导致udevd的规则没有生效,此时可在启动脚本中调用udevadm trigger重新出发系统设备的change事件,udevd借此执行定义的规则。
 
也可收到修改设备的uevent文件(需要root权限),触发设备事件,使用udevadm的另外一个命令monitor去监听我们收到触发的事件(udevadm monitor在后台运行,仅是为了让出shell终端来执行uevent文件修改),如下,如果使用monitor监控trigger命令,将看到所有设备的事件信息:

# udevadm monitor &
[1] 4706
monitor will print the received events for:
UDEV - the event which udev sends out after rule processing
KERNEL - the kernel uevent

# echo change >  /sys/block/sr0/uevent 
KERNEL[109812.475999] change   /devices/pci0000:00/0000:00:11.0/0000:02:05.0/ata4/host4/target4:0:0/4:0:0:0/block/sr0 (block)
UDEV  [109812.646193] change   /devices/pci0000:00/0000:00:11.0/0000:02:05.0/ata4/host4/target4:0:0/4:0:0:0/block/sr0 (block)

对于subsystem类型,trigger命令扫描/sys目录下的subsystem子目录,如果subsystem不存在,扫描bus子目录下的uevent文件。对于devices类型,如果udevd已经建立了系统设备的tags系统,trigger直接扫描/run/udev/tags目录。否则,如果/sys/subsystem存在,扫描其下的devices目录,不存在,则扫描/sys/bus目录和/sys/class目录,查找系统设备的uevent文件。

内核配置中的CONFIG_UEVENT_HELPER_PATH可指定uevent发生时调用的应用层处理程序,其在kobject_uevent_env中实现,调用call_usermodehelper_exec执行应用程序。目前此配置也不推荐使用。

int kobject_uevent_env(struct kobject *kobj, enum kobject_action action, char *envp_ext[])
{
#ifdef CONFIG_UEVENT_HELPER
    if (uevent_helper[0] && !kobj_usermode_filter(kobj)) {
        retval = add_uevent_var(env, "HOME=/");
        retval = add_uevent_var(env, "PATH=/sbin:/bin:/usr/sbin:/usr/bin");
        retval = init_uevent_argv(env, subsystem);

        info = call_usermodehelper_setup(env->argv[0], env->argv, env->envp, GFP_KERNEL, NULL, cleanup_uevent_env, env);
        if (info)
            retval = call_usermodehelper_exec(info, UMH_NO_WAIT);
    }
#endif
}

DEVTMPFS文件系统

内核配置CONFIG_DEVTMPFS后,将启用devtmpfs文件系统:

Device Drivers -> Generic Driver Options -> Maintain devtmpfs to mount at /dev

设置此选项之后,内核将devtmpfs文件系统mount到/dev目录下,此时/dev目录包含系统中的所有已知设备节点,内核使用默认命名创建这些节点,如果没有使用devtmpfs,dev目录将挂载一个tmpfs文件系统。对于不需要处理设备热插拨和自定义设备名称等需求的系统,可不使用udevd设备管理进程。

 

相关代码

eudev-3.2.5

Linux-4.15

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值