天天看点

linux热插拔

当用户向系统添加或删除设备时,内核会产生一个热插拔事件,并在/proc/sys/kernel/hotplug文件里查找处理设备连接的用户空间程序,这个用户空间程序主要有/sbin/hotplug与/sbin/mdev.

echo /sbin/hotplug > /proc/sys/kernel/hotplug

或者

echo /sbin/mdev > /proc/sys/kernel/hotplug

mdev -s

hotplug

是一个bash脚本具有如下类似的代码:

DIR="/etc/hotplug.d"
for I in "${DIR}/$1"*.hotplug "${DIR}/"default/*.hotplug ; do
    if [-f $I]; then
        test -x $I && $I $1;
    fi
done
exit 1      

1) 当driver执行kobject_uevent会调用hotplughelper,从而调用这个/sbin/hotplug脚本。

2) 该脚本在/etc/hotplug.d目录搜索所有以hotplug为后缀的程序并调用,

3) 传递给被调用的程序的参数就是事件的名字,

4) 被调用的程序还可以读取大量的环境变量,包括ACTION、DEVPATH、SUBSYSTEM等。

5) 被调用的程序根据这些环境变量在/lib/module/KERNEL_VERSION/modules.*map文件找到对应需要加载的模块并加载。

(*.map是当驱动程序使用MODULE_DEVICE_TABLE宏时,depmod程序使用这些信息并创建了/lib/module/KERNEL_VERSION/modules.*map文件。)

udev/mdev/vold

为用户空间提供使用固定设备名的动态/dev目录的解决办法。

mdev是简化的udev,是busybox所带的程序,适合嵌入式系统的使用。/sbin/mdev是一个链接,指向/bin/busybox

android系统中的vold机制与udev一样,android的源码NetlinkManager.cpp同样是监听基于netlink的套接字,并解析接收到的消息。

udev创建每个设备的名字和权限由/etc/udev/rules.d目录下的文件指定规则来设置,如果udev找不到所创建设备的权限文件,就将其缺省的权限设置为660,所有者root:root

热插拔设备

由于启动的时候运行了命令: echo /sbin/mdev > /proc/sys/kernel/hotplug,那么当有热插拔事件产生时,/sbin/mdev就会被调用,mdev根据环境变量中的ACTION和DEVPATH来确定此次热插拔事件的动作以及影响了/sys中的哪个目录,接着查看这个目录中是否有dev文件,如果有,就用这些信息在/dev目录下创建设备节点文件。

冷插拔设备

冷插拔的设备在开机时就存在,在udev启动前已经被插入了,对于冷插拔的设备,linux内核提供了sysfs下面的一个uevent节点,可以往该节点写入一个add,导致内核重新发送netlink,之后udev就可以收到冷插拔的netlink的消息了。

mdev -s

在/sys/class和/sys/block目录树中查找一个称为dev的文件,根据dev文件中记录的设备节点的主次设备号,从而在/dev目录下创建相应的设备节点。

/sys/bus/和/sys/class目录下的devices、drivers都是对/sys/devices目录下的文件的符号连接。

解决使用mdev时“cannot create /proc/sys/kernel/hotplug:nonexistent directory”错误

确保编译内核时编译如下选项:

CONFIG_PROC_FS=y

CONFIG_PROC_SYSCTL=y

CONFIG_HOTPLUG=y

CONFIG_NET=y

如果CONFIG_HOTPLUG和CONFIG_NET不选或没全选上的话,/proc/sys/kernel下将不会创建hotplug文件.(参见kernel/sysctl.c)

原理

struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...)
int device_register(struct device *dev)
    device_initialize(dev)
    device_add(dev)
        kobject_add(&dev->kobj, dev->kobj.parent, NULL);
        kobject_uevent(&dev->kobj, KOBJ_ADD);      

上面device_create和device_register都可以用来添加struct device,其实在device_create中就是调用的device_register。

在kobject_uevent中既可以通过netlink向用户空间程序udevd发送uevent事件,也可以直接调用用户空间程序hotplug_helper

需要注意的是,

如果在device_add中dev_t = 0,则最后udevd不会在/dev目录下创建相应的设备节点

如果在device_add中dev_t != 0,则mdev或者hotplug根据dev中的内容(major:minor)来生成设备节点。

device_add是在/sys/devices/目录下添加设备,/sys/bus/和/sys/class目录下的devices、drivers都是对/sys/devices目录下的文件的符号连接。

Udev完全工作在用户态,利用设备加入或者移除时内核所发送的热插拔事件来工作

在热插拔的时候,设备的详细信息会由内核通过netlink套接字发送出来,发出来的事情叫做的uevent,udev的命名策略、权限控制和事件处理都是在用户态下完成的,他利用从内核收到的信息来创建设备文件节点等工作。

下面这段程序就是用来接收内核netlink发出来的信息,将设备插到系统中会打印从内核中接收到的信息。

Udev就是用这种方式接收netlink的消息,并根据他的内容和用户设置的udev的规则做匹配来进行工作的。

#include <linux/netlink.h>

Static void die(char *s)
{
    Write(2, s, strlen(s));
    Exit(1);
}

Int main(int argc, char *argv[])
{
    Struct sockaddr_nl nls;
    Struct pollfd pfd;
    Char buf[512];

    //open hotplug event netlilnk socket
    Memset(nls, 0, sizeof(struct sockaddr_nl));
    Nls.nl_family = AF_NETLINK;
    Nls.nl_pid = getpid();
    Nls.nl_group = -1;

    Pfd.event = POLLIN;
    Pfd.fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
    If (pfd. Fd == -1)
        Die(“no root\n”);

    //listen to netlink socket
    If (bind(pfd.fd, (void *)&nls, sizeof(struct sockaddr_nl));
        Die(“bind failed\n”);
    While(-1 != poll(&pfd, 1, -1)) {
        Int i, len = recv(pfd.fd, buf, sizeof(buf), MSG_DONTWAIT);
        If (len == -1)
            Die(“recv\n”);

        I = 0;
        //print the data to stdout
        While(i < len) {
            Printf(“%s\n”, buf + i);
            I = i + strlen(buf + i) + 1
        }
    } 
    Die(“poll\n”);

    Return 0;
}