前记
环境:linux-3.10+Android4.4
摘要:主要分析了内核启动过程中从设备树创建device资源和驱动加载过程中平台设备驱动的匹配过程。同时分析了内核uevent事件上报机制,用户空间对uevent事件的处理过程。linux系统上分析了Busybox中的mdev,android系统上分析了ueventd。深入地了解了设备模型中内核态与用户之间的交互手段。其实主要是因为想了解一下注册驱动时是如何创建设备节点和HWC中VSync信号的传递过程~~
1.设备树注册平台设备
linux内核在3.x版本以前存在着大量的冗余代码(板级描述细节),这些代码严重影响着代码的可用性和维护,在3.x版本以后,设备树出现了,大大地简化了代码。
设备树的基础知识不会在本文讲解,本文主要讲述内核是如何从设备树中的节点到设备的创建的过程。
本文的基础是建立在对内核启动和设备树有一定了解的条件下进行,若没有这两方面的基础的读者请先进行基础学习。
在start_kernel的过程中,最后的步骤是进行rest_init,rest_init是是搭建内核环境的重点,Init进程、设备驱动的加载和初始化、文件系统的挂载等都在这里完成。
在rest_init中,内核创建了两个线程,一个是kernel_init,用于初始化内核;一个是kthreadd,是内核进程的管理者。设备驱动是在kernel_init线程中创建和初始化的。
static int __ref kernel_init(void *unused)
{
kernel_init_freeable(); /*注册内核驱动模块、启动默认console等*/
……
}
static noinline void __init kernel_init_freeable(void)
{
wait_for_completion(&kthreadd_done); /*等待kthreadd线程完成*/
gfp_allowed_mask = __GFP_BITS_MASK;
/*
* init can allocate pages on any node
*/
set_mems_allowed(node_states[N_MEMORY]);
/*
* init can run on any cpu.
*/
set_cpus_allowed_ptr(current, cpu_all_mask);
cad_pid = task_pid(current);
smp_prepare_cpus(setup_max_cpus);
/*启动多核,创建多核环境*/
do_pre_smp_initcalls();
lockup_detector_init();
smp_init();
sched_init_smp();
/*设备驱动的注册*/
do_basic_setup();
/* Open the /dev/console on the rootfs, this should never fail */
if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
pr_err("Warning: unable to open an initial console.\n");
(void) sys_dup(0);
(void) sys_dup(0);
/*
* check if there is an early userspace init. If yes, let it do all
* the work
*/
if (!ramdisk_execute_command)
ramdisk_execute_command = "/init";
if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
ramdisk_execute_command = NULL;
prepare_namespace();
}
/* rootfs is available now, try loading default modules */
load_default_modules();
}
1.1 do_basic_setup
static void __init do_basic_setup(void)
{
cpuset_init_smp();
usermodehelper_init();
shmem_init();
driver_init(); /*bus、devices等kset、kobject注册 */
init_irq_proc();
do_ctors();
usermodehelper_enable();
do_initcalls(); /*模块的加载,***_init形式定义的模块*/
random_int_secret_init();
}
1.2 driver_init
void __init driver_init(void)
{
/* These are the core pieces */
devtmpfs_init();
devices_init(); /*device kset在这里注册,后面的uevent的处理与这有关*/
buses_init();
classes_init();
firmware_init();
hypervisor_init();
/* These are also core pieces, but must come after the
* core core pieces.
*/
platform_bus_init();
cpu_dev_init();
memory_dev_init();
}
int __init devices_init(void)
{
/*在/sys目录下创建device目录,里面的挂载这设备节点(可以是kset和kobject),而操作是device_uevent_ops*/
devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);
if (!devices_kset)
return -ENOMEM;
/*在/sys目录下创建dev目录。是一个kobject*/
dev_kobj = kobject_create_and_add("dev", NULL);
if (!dev_kobj)
goto dev_kobj_err;
sysfs_dev_block_kobj = kobject_create_and_add("block", dev_kobj);
if (!sysfs_dev_block_kobj)
goto block_kobj_err;
sysfs_dev_char_kobj = kobject_create_and_add("char", dev_kobj);
if (!sysfs_dev_char_kobj)
goto char_kobj_err;
return 0;
char_kobj_err:
kobject_put(sysfs_dev_block_kobj);
block_kobj_err:
kobject_put(dev_kobj);
dev_kobj_err:
kset_unregister(devices_kset);
return -ENOMEM;
}
所有/sys/device目录下的设备发出的uevent事件都是由device_uevent_ops来处理,后面再继续分析如何处理。本章的主要内容是分析设备树的节点在哪创建成平台设备。其实它是在do_initcalls阶段注册的,如何注册呢?我们下来看看do_initcalls主要做了些什么。
1.3 do_initcalls
static void __init do_initcalls(void)
{
int level;
for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
do_initcall_level(level);
}
initcall_levels是一系列的函数入口,其定义如下:
static initcall_t *initcall_levels[] __initdata = {
__initcall0_start,
__initcall1_start,
__initcall2_start,
__initcall3_start,
__initcall4_start,
__initcall5_start,
__initcall6_start,
__initcall7_start,
__initcall_end,
};
__initcall%_start形式的变量是一组函数的入口地址,这个东西是在vmlinux.lds文件中定义的:
.init.data : {
*(.init.data) *(.meminit.data) . = ALIGN(8); __start_mcount_loc = .; *(__mcount_loc) __stop_mcount_loc = .; *(.init.rodata) . = ALIGN(8); __start_ftrace_events = .; *(_ftrace_events) __stop_ftrace_events = .; *(.meminit.rodata) . = ALIGN(8); __clk_of_table = .; *(__clk_of_table) *(__clk_of_table_end) . = ALIGN(8); __clksrc_of_table = .; *(__clksrc_of_table) *(__clksrc_of_table_end) . = ALIGN(8); __cpuidle_method_of_table = .; *(__cpuidle_method_of_table) *(__cpuidle_method_of_table_end) . = ALIGN(32); __dtb_start = .; *(.dtb.init.rodata) __dtb_end = .; . = ALIGN(8); __irqchip_begin = .; *(__irqchip_of_table) *(__irqchip_of_end)
. = ALIGN(16); __setup_start = .; *(.init.setup) __setup_end = .;
__initcall_start = .; *(.initcallearly.init) __initcall0_start = .; *(.initcall0.init) *(.initcall0s.init) __initcall1_start = .; *(.initcall1.init) *(.initcall1s.init) __initcall2_start = .; *(.initcall2.init) *(.initcall2s.init) __initcall3_start = .; *(.initcall3.init) *(.initcall3s.init) __initcall4_start = .; *(.initcall4.init) *(.initcall4s.init) __initcall5_start = .; *(.initcall5.init) *(.initcall5s.init) __initcallrootfs_start = .; *(.initcallrootfs.init) *(.initcallrootfss.init) __initcall6_start = .; *(.initcall6.init) *(.initcall6s.init) __initcall7_start = .; *(.initcall7.init) *(.initcall7s.init) __initcall_end = .;
__con_initcall_start = .; *(.con_initcall.init) __con_initcall_end = .;
__security_initcall_start = .; *(.security_initcall.init) __security_initcall_end = .;
. = ALIGN(4); __initramfs_start = .; *(.init.ramfs) . = ALIGN(8); *(.init.ramfs.info)
}
从vmlinux.lds中可以看到,__initcall%_start是放在.initca0ll%.init section中的数据,这部分是***_init宏中定义的:
#define pure_initcall(fn) __define_initcall(fn, 0)
#define core_initcall(fn) __define_initcall(fn, 1)
#define core_initcall_sync(fn) __define_initcall(fn, 1s)
#define postcore_initcall(fn) __define_initcall(fn, 2)
#define postcore_initcall_sync(fn) __define_initcall(fn, 2s)
#define arch_initcall(fn) __define_initcall(fn, 3)
#define arch_initcall_sync(fn) __define_initcall(fn, 3s)
#define subsys_initcall(fn) __define_initcall(fn, 4)
#define subsys_initcall_sync(fn) __define_initcall(fn, 4s)
#define fs_initcall(fn) __define_initcall(fn, 5)
#define fs_initcall_sync(fn) __define_initcall(fn, 5s)
#define rootfs_initcall(fn) __define_initcall(fn, rootfs)
#define device_initcall(fn) __define_initcall(fn, 6)
#define device_initcall_sync(fn) __define_initcall(fn, 6s)
#define late_initcall(fn) __define_initcall(fn, 7)
#define late_initcall_sync(fn) __define_initcall(fn, 7s)
#define __define_initcall(fn, id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" #id ".init"))) = fn
这些section是使用了gcc的特性完成的,我们常见的驱动模块使用了module_init来声明驱动入口就是这个原理。而且这部分是按照优先级来排序的,从上面可以看到其分了15级来执行,使得操作有了先后,必要的操作可以放到前面执行,而驱动可以往后放。
1.4 设备树设备的创建
因此我们可以获知do_initcalls主要是执行了***_init定义的操作,而设备树创建设备是在下面这里:static int __init customize_machine(void)
{
/*
* customizes platform devices, or adds new ones
* On DT based machines, we fall back to populating the
* machine from the device tree, if no callback is provided,
* otherwise we would always need an init_machine callback.
*/
if (machine_desc->init_machine)
machine_desc->init_machine(); /*目标机器的初始化*/
#ifdef CONFIG_OF
else {
of_platform_populate(NULL, of_default_bus_match_table,NULL, NULL);
}
#endif
return 0;
}
arch_initcall(customize_machine);
machine_desc->init_machine的指向:
.init_machine = sunxi_dt_init,
static void __init sunxi_dt_init(void)
{
sunxi_setup_restart();
of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL);
}
int of_platform_populate(struct device_node *root,
const struct of_device_id *matches,
const struct of_dev_auxdata *lookup,
struct device *parent)
{
struct device_node *child;
int rc = 0;
root = root ? of_node_get(root) : of_find_node_by_path("/"); /*找到设备树的根*/
if (!root)
return -EINVAL;
for_each_child_of_node(root, child) { /*遍历根节点下的所有子节点*/
/*把每个子节点作为一个平台设备总线设备注册*/
rc = of_platform_bus_create(child, matches, lookup, parent, true);
if (rc)
break;
}
of_node_put(root);
return rc;
}
static int of_platform_bus_create(struct device_node *bus,
const struct of_device_id *matches,
const struct of_dev_auxdata *lookup,
struct device *parent, bool strict)
{
const struct of_dev_auxdata *auxdata;
struct device_node *child;
struct platform_device *dev;
const char *bus_id = NULL;
void *platform_data = NULL;
int rc = 0;
/* Make sure it has a compatible property */
if (strict && (!of_get_property(bus, "compatible", NULL))) {
pr_debug("%s() - skipping %s, no compatible prop\n",
__func__, bus->full_name);
return 0;
}
auxdata = of_dev_lookup(lookup, bus);
if (auxdata) {
bus_id = auxdata->name;
platform_data = auxdata->platform_data; /*将设备树中的属性变为平台设备的数据*/
}
if (of_device_is_compatible(bus, "arm,primecell")) {
of_amba_device_create(bus, bus_id, platform_data, parent);
return 0;
}
/*注册平台设备,同时绑定kset、kobject*/
dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
if (!dev || !of_match_node(matches, bus))
return 0;
for_each_child_of_node(bus, child) { /*遍历子节点,注册子节点设备*/
pr_debug(" create child: %s\n", child->full_name);
rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict);
if (rc) {
of_node_put(child);
break;
}
}
return rc;
}
struct platform_device *of_platform_device_create_pdata(
struct device_node *np,
const char *bus_id,
void *platform_data,
struct device *parent)
{
struct platform_device *dev;
if (!of_device_is_available(np))
return NULL;
/*of_device_alloc中有一个platform_device_alloc用于初始化平台设备*/
dev = of_device_alloc(np, bus_id, parent);
if (!dev)
return NULL;
#if defined(CONFIG_MICROBLAZE)
dev->archdata.dma_mask = 0xffffffffUL;
#endif
dev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
dev->dev.bus = &platform_bus_type; /*bus类型是platform bus*/
dev->dev.platform_data = platform_data;
if (of_device_add(dev) != 0) { /*注册设备*/
platform_device_put(dev);
return NULL;
}
return dev;
}
在platform_device_alloc中对platform设备的kset和kobject进行了赋值:
struct platform_device *platform_device_alloc(const char *name, int id)
{
struct platform_object *pa;
pa = kzalloc(sizeof(struct platform_object) + strlen(name), GFP_KERNEL);
if (pa) {
strcpy(pa->name, name);
pa->pdev.name = pa->name;
pa->pdev.id = id;
device_initialize(&pa->pdev.dev);
pa->pdev.dev.release = platform_device_release;
arch_setup_pdev_archdata(&pa->pdev);
}
return pa ? &pa->pdev : NULL;
}
void device_initialize(struct device *dev)
{
dev->kobj.kset = devices_kset; /*平台设备驱动的kset默认是device*/
kobject_init(&dev->kobj, &device_ktype); /*ktype默认是device_ktype*/
INIT_LIST_HEAD(&dev->dma_pools);
mutex_init(&dev->mutex);
lockdep_set_novalidate_class(&dev->mutex);
spin_lock_init(&dev->devres_lock);
INIT_LIST_HEAD(&dev->devres_head);
device_pm_init(dev);
set_dev_node(dev, -1);
}
在知道了设备的kobject和kset后,下面看注册过程:
int of_device_add(struct platform_device *ofdev)
{
BUG_ON(ofdev->dev.of_node == NULL);
ofdev->name = dev_name(&ofdev->dev);
ofdev->id = -1;
if (!ofdev->dev.parent)
set_dev_node(&ofdev->dev, of_node_to_nid(ofdev->dev.of_node));
return device_add(&ofdev->dev);
}
int device_add(struct device *dev)
{
struct device *parent = NULL;
struct kobject *kobj;
struct class_interface *class_intf;
int error = -EINVAL;
dev = get_device(dev);
if (!dev)
goto done;
if (!dev->p) {
error = device_private_init(dev);
if (error)
goto done;
}
if (dev->init_name) {
dev_set_name(dev, "%s", dev->init_name);
dev->init_name = NULL;
}
/* subsystems can specify simple device enumeration */
if (!dev_name(dev) && dev->bus && dev->bus->dev_name)
dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);
if (!dev_name(dev)) {
error = -EINVAL;
goto name_error;
}
pr_debug("device: '%s': %s\n", dev_name(dev), __func__);
parent = get_device(dev->parent); /*父节点*/
kobj = get_device_parent(dev, parent); /*父节点的kobj*/
if (kobj)
dev->kobj.parent = kobj;
/* use parent numa_node */
if (parent)
set_dev_node(dev, dev_to_node(parent));
/*kobject_add会在/sys/device目录下父节点所在目录下创建目录*/
error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
if (error)
goto Error;
/* notify platform of device entry */
if (platform_notify)
platform_notify(dev);
/*创建uevent节点提供用户读去设备信息*/
error = device_create_file(dev, &uevent_attr);
if (error)
goto attrError;
if (MAJOR(dev->devt)) {
error = device_create_file(dev, &devt_attr);
if (error)
goto ueventattrError;
error = device_create_sys_dev_entry(dev);
if (error)
goto devtattrError;
devtmpfs_create_node(dev);
}
/*创建链接,driver和subsystem*/
error = device_add_class_symlinks(dev);
if (error)
goto SymlinkError;
error = device_add_attrs(dev);
if (error)
goto AttrsError;
/*在/sys/bus/devices目录下创建链接,链接到/sys/devices下的项目*/
error = bus_add_device(dev);
if (error)
goto BusError;
error = dpm_sysfs_add(dev);
if (error)
goto DPMError;
device_pm_add(dev);
if (dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_ADD_DEVICE, dev);
kobject_uevent(&dev->kobj, KOBJ_ADD);
bus_probe_device(dev); /*匹配driver,若匹配成功则会调用driver或者bus的probe函数*/
if (parent)
klist_add_tail(&dev->p->knode_parent,
&parent->p->klist_children);
if (dev->class) {
mutex_lock(&dev->class->p->mutex);
/* tie the class to the device */
klist_add_tail(&dev->knode_class,
&dev->class->p->klist_devices);
/* notify any interfaces that the device is here */
list_for_each_entry(class_intf,
&dev->class->p->interfaces, node)
if (class_intf->add_dev)
class_intf->add_dev(dev, class_intf);
mutex_unlock(&dev->class->p->mutex);
}
done:
put_device(dev);
return error;
DPMError:
bus_remove_device(dev);
BusError:
device_remove_attrs(dev);
AttrsError:
device_remove_class_symlinks(dev);
SymlinkError:
if (MAJOR(dev->devt))
devtmpfs_delete_node(dev);
if (MAJOR(dev->devt))
device_remove_sys_dev_entry(dev);
devtattrError:
if (MAJOR(dev->devt))
device_remove_file(dev, &devt_attr);
ueventattrError:
device_remove_file(dev, &uevent_attr);
attrError:
kobject_uevent(&dev->kobj, KOBJ_REMOVE);
kobject_del(&dev->kobj);
Error:
cleanup_device_parent(dev);
if (parent)
put_device(parent);
name_error:
kfree(dev->p);
dev->p = NULL;
goto done;
}
1.5 平台设备驱动的注册
我们知道平台设备驱动将设备描述和驱动程序分开,降低了耦合性。上面只是分析了内核将设备树的设备描述创建为平台设备,但是要使驱动正常运行还需要有对应的驱动程序。作为分析,我们不以具体驱动为例分析,我们只分析一下平台驱动的注册。
平台设备驱动使用platform_driver_register进行注册:
int platform_driver_register(struct platform_driver *drv)
{
drv->driver.bus = &platform_bus_type;
/*默认的probe、remove和shutdown方法*/
if (drv->probe)
drv->driver.probe = platform_drv_probe;
if (drv->remove)
drv->driver.remove = platform_drv_remove;
if (drv->shutdown)
drv->driver.shutdown = platform_drv_shutdown;
return driver_register(&drv->driver);
}
int driver_register(struct device_driver *drv)
{
int ret;
struct device_driver *other;
BUG_ON(!drv->bus->p);
if ((drv->bus->probe && drv->probe) ||
(drv->bus->remove && drv->remove) ||
(drv->bus->shutdown && drv->shutdown))
printk(KERN_WARNING "Driver '%s' needs updating - please use "
"bus_type methods\n", drv->name)
other = driver_find(drv->name, drv->bus); /*查找是否已经注册过该设备*/
if (other) {
printk(KERN_ERR "Error: Driver '%s' is already registered, "
"aborting...\n", drv->name);
return -EBUSY;
}
ret = bus_add_driver(drv); /*将驱动注册到总线上*/
if (ret)
return ret;
ret = driver_add_groups(drv, drv->groups);
if (ret) {
bus_remove_driver(drv);
return ret;
}
kobject_uevent(&drv->p->kobj, KOBJ_ADD);
return ret;
}
int bus_add_driver(struct device_driver *drv)
{
struct bus_type *bus;
struct driver_private *priv;
int error = 0;
bus = bus_get(drv->bus);
if (!bus)
return -EINVAL;
pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name);
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv) {
error = -ENOMEM;
goto out_put_bus;
}
klist_init(&priv->klist_devices, NULL, NULL);
priv->driver = drv;
drv->p = priv;
priv->kobj.kset = bus->p->drivers_kset; /*drivers_kset是在bus_register中创建的*/
/*把驱动注册到/sys/bus/platform中,也就是说,所有的platform设备的驱动在/sys/bus/platform/driver中*/
error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL, "%s", drv->name);
if (error)
goto out_unregister;
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
if (drv->bus->p->drivers_autoprobe) {
error = driver_attach(drv); /*匹配设备*/
if (error)
goto out_unregister;
}
module_add_driver(drv->owner, drv); /*在/sys/modules目录下创建节点*/
error = driver_create_file(drv, &driver_attr_uevent);
if (error) {
printk(KERN_ERR "%s: uevent attr (%s) failed\n",
__func__, drv->name);
}
error = driver_add_attrs(bus, drv);
if (error) {
/* How the hell do we get out of this pickle? Give up */
printk(KERN_ERR "%s: driver_add_attrs(%s) failed\n",
__func__, drv->name);
}
if (!drv->suppress_bind_attrs) {
error = add_bind_files(drv);
if (error) {
/* Ditto */
printk(KERN_ERR "%s: add_bind_files(%s) failed\n",
__func__, drv->name);
}
}
return 0;
out_unregister:
kobject_put(&priv->kobj);
kfree(drv->p);
drv->p = NULL;
out_put_bus:
bus_put(bus);
return error;
}
1.6 平台设备驱动的匹配
平台设备驱动分了设备和驱动两部分,只有双剑合一才能正常运行,而合一的过程就是匹配。从上面的代码中知道,在设备注册的时候就调用bus_probe_device进行匹配,而bus_probe_device又调用了device_attach来进行匹配。而在驱动注册的时候调用的是driver_attach进行匹配。下面看看这两者的匹配过程:
设备端的匹配:
int device_attach(struct device *dev)
{
int ret = 0;
device_lock(dev);
if (dev->driver) { /*如果设备指定了驱动*/
if (klist_node_attached(&dev->p->knode_driver)) {
ret = 1;
goto out_unlock;
}
ret = device_bind_driver(dev);
if (ret == 0)
ret = 1;
else {
dev->driver = NULL;
ret = 0;
}
} else { /*一般会从注册到总线上的驱动进行匹配,匹配的方法是__device_attach*/
ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
pm_request_idle(dev);
}
out_unlock:
device_unlock(dev);
return ret;
}
int bus_for_each_drv(struct bus_type *bus, struct device_driver *start,
void *data, int (*fn)(struct device_driver *, void *))
{
struct klist_iter i;
struct device_driver *drv;
int error = 0;
if (!bus)
return -EINVAL;
klist_iter_init_node(&bus->p->klist_drivers, &i,
start ? &start->p->knode_bus : NULL);
while ((drv = next_driver(&i)) && !error) /*遍历注册到总线上的所有驱动*/
error = fn(drv, data); /*匹配过程是__device_attach*/
klist_iter_exit(&i);
return error;
}
static int __device_attach(struct device_driver *drv, void *data)
{
struct device *dev = data;
if (!driver_match_device(drv, dev)) /*匹配*/
return 0;
return driver_probe_device(drv, dev); /*匹配成功则调用probe*/
}
static inline int driver_match_device(struct device_driver *drv, struct device *dev)
{
/*如果总线没有match方法则直接返回成功,一般都会有的*/
return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}
platform bus的match方法是platform_match:
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv)) /*从驱动中的of_match_table和设备树中的数据进行比较*/
return 1;
/* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
/* Then try to match against the id table */
if (pdrv->id_table) /*从驱动中的id_table的name字段进行对比*/
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}
经过上面的匹配,如果设备与驱动的信息一致则认为是匹配的,后面就会执行驱动中的probe方法:
int driver_probe_device(struct device_driver *drv, struct device *dev)
{
int ret = 0;
if (!device_is_registered(dev))
return -ENODEV;
pm_runtime_barrier(dev);
ret = really_probe(dev, drv);
pm_request_idle(dev);
return ret;
}
static int really_probe(struct device *dev, struct device_driver *drv)
{
int ret = 0;
int local_trigger_count = atomic_read(&deferred_trigger_count);
atomic_inc(&probe_count);
WARN_ON(!list_empty(&dev->devres_head));
dev->driver = drv;
/* If using pinctrl, bind pins now before probing */
ret = pinctrl_bind_pins(dev);
if (ret)
goto probe_failed;
if (driver_sysfs_add(dev)) {
printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
__func__, dev_name(dev));
goto probe_failed;
}
/*执行probe方法,优先执行总线的probe方法,若其为空才会执行驱动的probe方法,但是一般平台设备总线的probe都为空*/
if (dev->bus->probe) {
ret = dev->bus->probe(dev);
if (ret)
goto probe_failed;
} else if (drv->probe) {
ret = drv->probe(dev);
if (ret)
goto probe_failed;
}
driver_bound(dev);
ret = 1;
pr_debug("bus: '%s': %s: bound device %s to driver %s\n",
drv->bus->name, __func__, dev_name(dev), drv->name);
goto done;
probe_failed: /*输出调试信息*/
devres_release_all(dev);
driver_sysfs_remove(dev);
dev->driver = NULL;
dev_set_drvdata(dev, NULL);
if (ret == -EPROBE_DEFER) {
/* Driver requested deferred probing */
dev_info(dev, "Driver %s requests probe deferral\n", drv->name);
driver_deferred_probe_add(dev);
/* Did a trigger occur while probing? Need to re-trigger if yes */
if (local_trigger_count != atomic_read(&deferred_trigger_count))
driver_deferred_probe_trigger();
} else if (ret != -ENODEV && ret != -ENXIO) {
/* driver matched but the probe failed */
printk(KERN_WARNING
"%s: probe of %s failed with error %d\n",
drv->name, dev_name(dev), ret);
} else {
pr_debug("%s: probe of %s rejects match %d\n",
drv->name, dev_name(dev), ret);
}
ret = 0;
done:
atomic_dec(&probe_count);
wake_up(&probe_waitqueue); /*唤醒在等待这个信号的线程*/
return ret;
}
驱动端的匹配:
int driver_attach(struct device_driver *drv)
{
/*与device的匹配一样,匹配的过程是 __driver_attach*/
return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
static int __driver_attach(struct device *dev, void *data)
{
struct device_driver *drv = data;
/*与device不一样,一个driver可以匹配多个device,从驱动的of_match_table中就可以知道,其可以匹配多个设备,因此driver的注册后需要遍历所有的device,而device注册后在匹配出一个driver后就结束了*/
if (!driver_match_device(drv, dev)) /*与device的匹配过程一样,调用platform_match*/
return 0;
if (dev->parent) /* Needed for USB */
device_lock(dev->parent);
device_lock(dev);
if (!dev->driver)
driver_probe_device(drv, dev);
device_unlock(dev);
if (dev->parent)
device_unlock(dev->parent);
return 0;
}
驱动和设备的注册会在/sys目录下建立节点,匹配成功后会真正加载驱动程序,使设备正常运行。至此,一个平台设备就已经成功运行了。
设备是特例,一个设备对应一个驱动程序,但是驱动是该类型设备的通性,如tvd这种设备,一个机器可能有好几个tvd输入,但是驱动只需要一个。
1.7 kset与kobj总结
推荐一篇博客,非常详细地阐述了设备模型:
http://www.wowotech.net/device_model/kobject.html --蜗窝科技
总结:
1.kobj对应一个目录;
2.kobj实现对象的生命周期管理(计数为0即清除);
3.kset包含一个kobj,相当于也是一个目录;
4.kset是一个容器,可以包容不同类型的kobj(甚至父子关系的两个kobj都可以属于一个kset);
5.注册kobj(如kobj_add()函数)时,优先以kobj->parent作为自身的parent目录;
其次,以kset作为parent目录;若都没有,则是sysfs的顶层目录;
同时,若设置了kset,还会将kobj加入kset.list。举例:
1、无parent、无kset,则将在sysfs的根目录(即/sys/)下创建目录;
2、无parent、有kset,则将在kset下创建目录;并将kobj加入kset.list;
3、有parent、无kset,则将在parent下创建目录;
4、有parent、有kset,则将在parent下创建目录,并将kobj加入kset.list;
6.注册kset时,如果它的kobj->parent为空,则设置它所属的kset(kset.kobj->kset.kobj)为parent,即优先使用kobj所属的parent;然后再使用kset作为parent。(如platform_bus_type的注册,如某些input设备的注册)
7.注册device时,dev->kobj.kset= devices_kset;然而kobj->parent的选取有优先次序:
kset和kobj与sysfs之间的关系图:
2 设备的uevent事件上报
在设备驱动中经常看到uevent事件的上报,在用户空间中处理这些时间,例如内核驱动的文件节点的创建(在/dev目录下创建设备节点)和热插拔事件处理等,那么本章节就去看看uevent是如和去实现的。本章是基于上一章的基础上进行的,因为uevent离不开kobject和kset,而且uevent在内核空间上报事件,处理是在用户空间,因此还需要分析用户空间的实现过程。
2.1 uevent事件上报
我们在设备或者驱动注册的时候经常看到kobject_uevent(****_obj,KOBJ_ADD)这条语句,这条语句是通知用户层有新的OBJ注册。而我们常见的热插拔也是使用kobject_uevent进行上报。
uevent支持下面几种事件上报:enum kobject_action {
KOBJ_ADD, /*Kobject(或上层数据结构)的添加事件*/
KOBJ_REMOVE, /*Kobject(或上层数据结构)的移除事件*/
KOBJ_CHANGE, /*kobject(或上层数据结构)的状态或者内容发生改变*/
KOBJ_MOVE, /*Kobject(或上层数据结构)更改名称或者Parent(sysfs更改了目录结构)的事件*/
KOBJ_ONLINE, /*kobject(或上层数据结构)的上线事件,即使能*/
KOBJ_OFFLINE, /*kobject(或上层数据结构)的下线事件,即失能*/
KOBJ_MAX /*uevent事件类型的最大值,即支持的个数*/
};
比较常用的是KOBJ_ADD、KOBJ_REMOVE和KOBJ_CHANGE三个事件。热插拔事件使用就是KOBJ_CHANGE。下面看看这些事件是如何上报的:
/*uevent的上报有两个函数,一个是无参的kobject_uevent,一个是带参数的kobject_uevent_env*/
int kobject_uevent(struct kobject *kobj, enum kobject_action action)
{
return kobject_uevent_env(kobj, action, NULL);
}
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
/* 找到kobj所属的kset,因为kset才有uevent上报 */
top_kobj = kobj;
while (!top_kobj->kset && top_kobj->parent)
top_kobj = top_kobj->parent;
if (!top_kobj->kset) {
return -EINVAL;
}
kset = top_kobj->kset;
uevent_ops = kset->uevent_ops; /*kset的uevent处理函数,在kset注册的过程中绑定*/
/* skip the event, if uevent_suppress is set*/
if (kobj->uevent_suppress) { /*上报的对象是否支持uevent事件上报,其实也是一种过滤机制*/
return 0;
}
if (uevent_ops && uevent_ops->filter) /*是否有过滤规则,有则进行过滤*/
if (!uevent_ops->filter(kset, kobj)) {
return 0;
}
/* kobj所属的子系统,如bus或者class,否则就是它自身 */
if (uevent_ops && uevent_ops->name)
subsystem = uevent_ops->name(kset, kobj);
else
subsystem = kobject_name(&kset->kobj);
if (!subsystem) {
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) {
//调用kset中的uevent对var添加值,包括设备号、ID等
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;
}
}
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) /* 自从某个版本之后就使用了NETLINK的方式上报事件了 */
/* 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;
/* 判断netlink是否有客户端在监听 */
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;
/* add header */
scratch = skb_put(skb, len);
/*netlink的数据报格式是action@devpath[envp]*/
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_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);
/* uevent_helper是早期版本定义的,但后面在编译的时候一般留空,提供了一个接口在/sys/kernel/uevent_helper,在内核启动后可以往这里输入要处理uevent的程序 */
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;
/* 内核运行uevent_helper中指定的那个程序,参数就是env->envp,指向的是add_uevent_var的值 */
retval = call_usermodehelper(argv[0], argv, env->envp, UMH_WAIT_EXEC);
}
exit:
kfree(devpath);
kfree(env);
return retval;
}
int call_usermodehelper(char *path, char **argv, char **envp, int wait)
{
struct subprocess_info *info;
gfp_t gfp_mask = (wait == UMH_NO_WAIT) ? GFP_ATOMIC : GFP_KERNEL;
/*封装启动程序的信息*/
info = call_usermodehelper_setup(path, argv, envp, gfp_mask,
NULL, NULL, NULL);
if (info == NULL)
return -ENOMEM;
return call_usermodehelper_exec(info, wait); /*执行*/
}
struct subprocess_info *call_usermodehelper_setup(char *path, char **argv,
char **envp, gfp_t gfp_mask,
int (*init)(struct subprocess_info *info, struct cred *new),
void (*cleanup)(struct subprocess_info *info),void *data)
{
struct subprocess_info *sub_info;
sub_info = kzalloc(sizeof(struct subprocess_info), gfp_mask);
if (!sub_info)
goto out;
INIT_WORK(&sub_info->work, __call_usermodehelper); /*红色部分就是需要执行的代码*/
sub_info->path = path;
sub_info->argv = argv;
sub_info->envp = envp;
sub_info->cleanup = cleanup;
sub_info->init = init;
sub_info->data = data;
out:
return sub_info;
}
int call_usermodehelper_exec(struct subprocess_info *sub_info, int wait)
{
DECLARE_COMPLETION_ONSTACK(done);
int retval = 0;
helper_lock();
if (!sub_info->path) {
retval = -EINVAL;
goto out;
}
if (sub_info->path[0] == '\0')
goto out;
if (!khelper_wq || usermodehelper_disabled) {
retval = -EBUSY;
goto out;
}
if (wait != UMH_NO_WAIT && current == kmod_thread_locker) {
retval = -EBUSY;
goto out;
}
sub_info->complete = &done;
sub_info->wait = wait;
queue_work(khelper_wq, &sub_info->work); /*加入到khelp进程中等待调度*/
if (wait == UMH_NO_WAIT) /* task has freed sub_info */
goto unlock;
if (wait & UMH_KILLABLE) {
retval = wait_for_completion_killable(&done);
if (!retval)
goto wait_done;
if (xchg(&sub_info->complete, NULL))
goto unlock;
}
wait_for_completion(&done);
wait_done:
retval = sub_info->retval;
out:
call_usermodehelper_freeinfo(sub_info);
unlock:
helper_unlock();
return retval;
}
void __init usermodehelper_init(void)
{
khelper_wq = create_singlethread_workqueue("khelper"); /*创建任务队列,使用ps可以看到*/
BUG_ON(!khelper_wq);
}
/*当khelper调度到uevent_helper时,执行下面的代码*/
static void __call_usermodehelper(struct work_struct *work)
{
/*获取uevent_helper的信息*/
struct subprocess_info *sub_info =container_of(work, struct subprocess_info, work);
int wait = sub_info->wait & ~UMH_KILLABLE;
pid_t pid;
/*创建线程去执行程序,不管是否等待其实都是执行____call_usermodehelper*/
if (wait == UMH_WAIT_PROC)
pid = kernel_thread(wait_for_helper, sub_info,
CLONE_FS | CLONE_FILES | SIGCHLD);
else {
pid = kernel_thread(call_helper, sub_info,
CLONE_VFORK | SIGCHLD);
kmod_thread_locker = NULL;
}
switch (wait) {
case UMH_NO_WAIT:
call_usermodehelper_freeinfo(sub_info);
break;
case UMH_WAIT_PROC:
if (pid > 0)
break;
/* FALLTHROUGH */
case UMH_WAIT_EXEC:
if (pid < 0)
sub_info->retval = pid;
umh_complete(sub_info);
}
}
static int ____call_usermodehelper(void *data)
{
struct subprocess_info *sub_info = data;
struct cred *new;
int retval;
spin_lock_irq(¤t->sighand->siglock);
flush_signal_handlers(current, 1);
spin_unlock_irq(¤t->sighand->siglock);
set_cpus_allowed_ptr(current, cpu_all_mask);
set_user_nice(current, 0);
retval = -ENOMEM;
new = prepare_kernel_cred(current);
if (!new)
goto fail;
spin_lock(&umh_sysctl_lock);
new->cap_bset = cap_intersect(usermodehelper_bset, new->cap_bset);
new->cap_inheritable = cap_intersect(usermodehelper_inheritable,
new->cap_inheritable);
spin_unlock(&umh_sysctl_lock);
if (sub_info->init) {
retval = sub_info->init(sub_info, new);
if (retval) {
abort_creds(new);
goto fail;
}
}
commit_creds(new);
/*使用do_execve的方式执行代码*/
retval = do_execve(sub_info->path,
(const char __user *const __user *)sub_info->argv,
(const char __user *const __user *)sub_info->envp);
if (!retval)
return 0;
fail:
sub_info->retval = retval;
do_exit(0);
}
至此,内核已经完成uevent的上报,现在的内核一般都会使用netlink的方式进行上报,而使用uevent_helper的方式还是有的,但是由于每次有时间上报都要fork一个新的进程来处理比较浪费CPU资源,常用的就是udev(嵌入式设备中一般使用mdev)。
2.2 netlink机制
netlink是一种基于网络的机制,允许在内核内部以及内核与用户层之间进行通讯。它的思想是基于BSD的网络套接字使用网络框架在内核和用户层之间进行通信。但netlink套接字大大扩展了可能的用途。该机制最重要的用户是通用对象模型,它使用netlink套接字将各种关于内核内部事务的状态信息传递到用户层。其中包括新设备的注册和移除、硬件层次上发生的特别的事件等等。在此之前的内核版本中,netlink曾经可以编译为模块,但现在只要内核支持网络,该机制就自动集成到内核中。
netlink与procfs或者sysfs中的文件对比,其具有下面这些优势(来源于深入linux内核架构12.11.2):
1.任何一方都不需要轮询,如果通过文件传递状态信息,那么用户层需要不断检查是否有新消息到达(udev)。
2.系统调用和ioctl也能够从用户层向内核传递信息,但比简单的netlink连接更难于实现。另外,使用netlink不会与模块有任何冲突,但模块和系统调用显然配合得不是很好。
3.内核可以直接向用户层发送消息,而无须用户层事先发出请求。使用文件也可以做到,但系统调用和Ioctl是不可能的。
4.除了标准的套接字,用户空间应用程序不需要使用其他东西来与内核交互。
netlink只支持数据报信息,但提供了双向通信,另外,netlink不仅支持单播消息,也可以进行多播。类似于任何其他套接字的机制,netlink的工作方式是异步的。
数据结构
指定地址:
struct sockaddr_nl {
__kernel_sa_family_t nl_family; /* AF_NETLINK */
unsigned short nl_pad; /* 0 */
__u32 nl_pid; /* port ID */
__u32 nl_groups; /* 多播组掩码 */
};
__kernel_sa_family_t的取指如下:
#define NETLINK_ROUTE 0 /* Routing/device hook */
#define NETLINK_UNUSED 1 /* Unused number */
#define NETLINK_USERSOCK 2 /* Reserved for user mode socket protocols */
#define NETLINK_FIREWALL 3 /* Unused number, formerly ip_queue */
#define NETLINK_SOCK_DIAG 4 /* socket monitoring */
#define NETLINK_NFLOG 5 /* netfilter/iptables ULOG */
#define NETLINK_XFRM 6 /* ipsec */
#define NETLINK_SELINUX 7 /* SELinux event notifications */
#define NETLINK_ISCSI 8 /* Open-iSCSI */
#define NETLINK_AUDIT 9 /* auditing */
#define NETLINK_FIB_LOOKUP 10
#define NETLINK_CONNECTOR 11
#define NETLINK_NETFILTER 12 /* netfilter subsystem */
#define NETLINK_IP6_FW 13
#define NETLINK_DNRTMSG 14 /* DECnet routing messages */
#define NETLINK_KOBJECT_UEVENT 15 /* Kernel messages to userspace */
#define NETLINK_GENERIC 16
/* leave room for NETLINK_DM (DM Events) */
#define NETLINK_SCSITRANSPORT 18 /* SCSI Transports */
#define NETLINK_ECRYPTFS 19
#define NETLINK_RDMA 20
#define NETLINK_CRYPTO 21 /* Crypto layer */
#define NETLINK_INET_DIAG NETLINK_SOCK_DIAG
几个比较常用的值:
NETLINK_ROUTE:netlink套接字最初的目的,即修改路由选择信息。
NETLINK_INET_DIAG:用来监控IP套接字,更多细节可以参见net/ipv4/inet_diag.c
NETLINK_XFPM:用于发送和接收有关IPSec(更一般地说,也可以是有关任何XFPM变换)的信息。
NETLINK_KOBJECT_UEVENT:内核通过通用对象模型向用户发送信息所采用的协议(反过来,从用户层到内核是不能采用此类消息的)。
消息格式:
struct nlmsghdr {
__u32 nlmsg_len; /* 消息长度,包括首部在内 */
__u16 nlmsg_type; /* 消息内容的类型 */
__u16 nlmsg_flags; /* 附加的标志 */
__u32 nlmsg_seq; /* 序列号 */
__u32 nlmsg_pid; /* 发送进程的端口ID */
};
●整个消息的长度,包括首部和任何所需的填充字节,保存在nlmsg_len中。
●消息类型由nlmsg_type表示。该值是协议族私有的,通用的netlink代码不会检查或修改。
●各种标志可以保存在nlmsg_flags中。所有可能的值都定义在<netlink.h>中,对我们来说,主要关注两个标志:如果消息包含一个请求,要求执行某个特定的操作(而不是传输一些状态信息),那么NLM_F_REQUEST将置位,而NLM_F_ACK要求在接收到上述消息并成功处理请求之后发送一个确认消息。
●nlmsg_seq包含一个序列号,表示一系列消息之间在时间上的前后关系。
●标识发送者的唯一的端口ID保存在nlmsg_pid中。
3 用户层上的uevent事件处理
在前面已经说过在内核中uevent的上报过程,但是上报了肯定会有其它的程序对上报的消息进行处理,在linux系统上会有udev(或轻量版的mdev),在android上会有ueventd进行处理,下面分这两种情况进行分析。
3.1 Linux mdev
在linux系统中,用户空间常用的是udev,嵌入式设备更多的是使用udev的轻量版mdev。我们可以通过<cat/sys/kernel/uevent_helper>的方式来看看使用哪个程序进行处理。
从上图可以看到,处理uevent事件的就是mdev。而mdev是busybox提供的一个工具:
因此我们要了解mdev的处理需要从busybox的源码中进行分析。
3.1.1 busybox—mdev的入口
平台:busybox-1.18.3
Busybox可以像内核一样进行配置和裁剪,选择所要的功能:
在配置过程中只要选择mdev功能,那么在系统上使用mdev来对uevent事件进行处理。
那么busybox是如何进入mdev的呢?毕竟mdev指向的是busybox,即mdev使用的就是busybox的源码。
通过objdump工具并结合源码,找到了busybox的入口:
Libbb/appletlib.c
#if ENABLE_BUILD_LIBBUSYBOX /*以lib的方式*/
int lbb_main(char **argv)
#else
int main(int argc UNUSED_PARAM, char **argv)
#endif
{
#ifndef PAGE_SIZE
# define PAGE_SIZE (4*1024) /* guess */
#endif
#ifdef M_TRIM_THRESHOLD
mallopt(M_TRIM_THRESHOLD, 2 * PAGE_SIZE);
#endif
#ifdef M_MMAP_THRESHOLD
mallopt(M_MMAP_THRESHOLD, 8 * PAGE_SIZE - 256);
#endif
#if defined(SINGLE_APPLET_MAIN)
lbb_prepare(applet_names IF_FEATURE_INDIVIDUAL(, argv));
return SINGLE_APPLET_MAIN(argc, argv);
#else
lbb_prepare("busybox" IF_FEATURE_INDIVIDUAL(, argv)); /*检测参数是否为help*/
#if !BB_MMU
/* NOMMU re-exec trick sets high-order bit in first byte of name */
if (argv[0][0] & 0x80) {
re_execed = 1;
argv[0][0] &= 0x7f;
}
#endif
applet_name = argv[0];
if (applet_name[0] == '-')
applet_name++;
applet_name = bb_basename(applet_name); /*获取shell中输入的命令的名称,如mdev*/
parse_config_file(); /*用于解析/etc/busybox.conf中的权限配置,但这个配置文件可能不存在*/
run_applet_and_exit(applet_name, argv); /*根据命令名执行对应的操作*/
/*当命令找不到时才会执行下面的操作,就是输出报错信息*/
full_write2_str(applet_name);
full_write2_str(": applet not found\n");
xfunc_die();
#endif
}
Busybox的main方法中,关键是run_applet_and_exit这个函数,它才是执行关键代码的部分:
void FAST_FUNC run_applet_and_exit(const char *name, char **argv)
{
int applet = find_applet_by_name(name); /*查找命令的位置*/
if (applet >= 0)
run_applet_no_and_exit(applet, argv); /*执行命令,进入命令的入口*/
if (!strncmp(name, "busybox", 7)) /*当我们输入<busybox ls>这种格式的命令时,走这里*/
/*busybox_main中会从下一个参数中提取命令,又会调用run_applet_no_and_exit 去执行真正的命令*/
exit(busybox_main(argv));
}
int FAST_FUNC find_applet_by_name(const char *name)
{
/* Do a binary search to find the applet entry given the name. */
const char *p;
p = bsearch(name, applet_names, ARRAY_SIZE(applet_main), 1, applet_name_compare);
if (!p)
return -1;
return p - applet_names;
}
find_applet_by_name中出现了两个关键的变量—applet_names和applet_main。这两个变量可以理解为一个键值对列表,把命令名和入口一一对应起来。它们定义在include/applet_tables.h中。
从他们的定义中可以看到,命令的名称与入口一一对应,经过测试(make menuconfig去掉mdev的支持重新看applet_tables.h这个文件)而applet_tables.h这个文件应该是在编译过程中生成的。
void FAST_FUNC run_applet_no_and_exit(int applet_no, char **argv)
{
int argc = 1;
while (argv[argc])
argc++;
/* Reinit some shared global data */
xfunc_error_retval = EXIT_FAILURE;
applet_name = APPLET_NAME(applet_no);
if (argc == 2 && strcmp(argv[1], "--help") == 0) {
if (!ENABLE_TEST || strcmp(applet_name, "test") != 0)
bb_show_usage();
}
if (ENABLE_FEATURE_SUID)
check_suid(applet_no);
exit(applet_main[applet_no](argc, argv)); /*跳转到命令的入口地址*/
}
从applet_tables.h中可以看到,mdev的入口函数就是mdev_main。从这里我们就已经分析完从busybox到mdev的跳转过程。
3.1.2 mdev处理机制
上面已经分析完mdev的进入过程,那么从现在开始就要分析mdev的处理机制了。
int mdev_main(int argc UNUSED_PARAM, char **argv)
{
RESERVE_CONFIG_BUFFER(temp, PATH_MAX + SCRATCH_SIZE); /*定义一个buffer*/
bb_sanitize_stdio(); /*配置标准输入输出*/
umask(0);
xchdir("/dev"); /*切换到/dev目录*/
/*<mdev –s>的作用是扫描所有设备,创建设备节点*/
if (argv[1] && strcmp(argv[1], "-s") == 0) {
struct stat st;
xstat("/", &st);
G.root_major = major(st.st_dev);
G.root_minor = minor(st.st_dev);
if (access("/sys/class/block", F_OK) != 0) {
/*递归遍历该目录下的所有子文件(目录)*/
recursive_action("/sys/block",
ACTION_RECURSE | ACTION_FOLLOWLINKS | ACTION_QUIET,
fileAction, dirAction, temp, 0);/*fileAction处理文件,dirAction处理目录*/
}
/*递归遍历/sys/class目录*/
recursive_action("/sys/class",ACTION_RECURSE | ACTION_FOLLOWLINKS,
fileAction, dirAction, temp, 0);
/*从上面可以看到,/dev目录下的节点都来源于/sys/block和/sys/class两个目录*/
} else {
char *fw;
char *seq;
char *action;
char *env_path;
static const char keywords[] ALIGN1 = "remove\0add\0";
enum { OP_remove = 0, OP_add };
smalluint op;
/*获取uevent的上报信息*/
action = getenv("ACTION");
env_path = getenv("DEVPATH");
G.subsystem = getenv("SUBSYSTEM");
if (!action || !env_path /*|| !G.subsystem*/)
bb_show_usage();
fw = getenv("FIRMWARE");
op = index_in_strings(keywords, action); /*获取是add还是remove事件*/
seq = getenv("SEQNUM");
if (seq) {
int timeout = 2000 / 32; /* 2000 msec */
do {
int seqlen;
char seqbuf[sizeof(int)*3 + 2];
seqlen = open_read_close("mdev.seq", seqbuf, sizeof(seqbuf-1));
if (seqlen < 0) {
seq = NULL;
break;
}
seqbuf[seqlen] = '\0';
if (seqbuf[0] == '\n' /* seed file? */
|| strcmp(seq, seqbuf) == 0 /* correct idx? */
) {
break;
}
usleep(32*1000);
} while (--timeout);
}
snprintf(temp, PATH_MAX, "/sys%s", env_path);
if (op == OP_remove) { /*remove事件,删除节点*/
if (!fw)
make_device(temp, /*delete:*/ 1);
}
else if (op == OP_add) {
make_device(temp, /*delete:*/ 0); /*add事件,创建节点*/
if (ENABLE_FEATURE_MDEV_LOAD_FIRMWARE) {
if (fw)
load_firmware(fw, temp);
}
}
if (seq) {
xopen_xwrite_close("mdev.seq", utoa(xatou(seq) + 1));
}
}
if (ENABLE_FEATURE_CLEAN_UP)
RELEASE_CONFIG_BUFFER(temp);
return EXIT_SUCCESS;
}
Mdev的主函数先根据命令参数选择功能,<mdev –s>扫描/sys/block和/sys/class目录下的设备,然后在/dev目录下创建节点。而由内核在上报uevent事件时启动的uevent_helper(mdev)则会提取uevent事件信息进行处理。
上面主要有两个函数比较重要--recursive_action和make_device。
3.1.3 扫描设备
recursive_action的作用就是递归扫描/sys/block和/sys/class这两个目录下的所有子文件(目录),根据文件类型的不同(文件or目录)而使用fileAction or dirAction来进行处理。int FAST_FUNC recursive_action(const char *fileName,unsigned flags,
int FAST_FUNC (*fileAction)(const char *fileName, struct stat *statbuf, void* userData, int depth),
int FAST_FUNC (*dirAction)(const char *fileName, struct stat *statbuf, void* userData, int depth),
void* userData,unsigned depth/*路径深度*/)
{
struct stat statbuf;
unsigned follow;
int status;
DIR *dir;
struct dirent *next;
if (!fileAction) fileAction = true_action;
if (!dirAction) dirAction = true_action;
follow = ACTION_FOLLOWLINKS;
if (depth == 0)
follow = ACTION_FOLLOWLINKS | ACTION_FOLLOWLINKS_L0;
follow &= flags;
/*lstat和stat的区别在于,lstat是获取链接文件的信息,而stat是获取链接文件指向的文件的信息*/
status = (follow ? stat : lstat)(fileName, &statbuf);
if (status < 0) {
#ifdef DEBUG_RECURS_ACTION
bb_error_msg("status=%d flags=%x", status, flags);
#endif
if ((flags & ACTION_DANGLING_OK)
&& errno == ENOENT
&& lstat(fileName, &statbuf) == 0
) {
return fileAction(fileName, &statbuf, userData, depth);
}
goto done_nak_warn;
}
/*如果一个文件是一个LINK文件,那么S_ISDIR就返回false,但是使用opendir进入的是LINK指向的目录*/
if ( /* (!(flags & ACTION_FOLLOWLINKS) && S_ISLNK(statbuf.st_mode)) || */
!S_ISDIR(statbuf.st_mode) /*如果这个文件(目标是一个特殊的文件)*/
) {
return fileAction(fileName, &statbuf, userData, depth);
}
if (!(flags & ACTION_RECURSE)) { /*如果没有设置递归标志,直接返回目录处理结果*/
return dirAction(fileName, &statbuf, userData, depth);
}
if (!(flags & ACTION_DEPTHFIRST)) {
status = dirAction(fileName, &statbuf, userData, depth);
if (!status)
goto done_nak_warn;
if (status == SKIP)
return TRUE;
}
dir = opendir(fileName); /*如果文件是LINK类型,指向的是一个dir,那么opendir返回非空*/
if (!dir) {
goto done_nak_warn;
}
status = TRUE;
while ((next = readdir(dir)) != NULL) {
char *nextFile;
nextFile = concat_subpath_file(fileName, next->d_name);
if (nextFile == NULL)
continue;
/* 递归下一个dir */
if (!recursive_action(nextFile, flags, fileAction, dirAction,
userData, depth + 1))
status = FALSE;
free(nextFile);
}
closedir(dir);
if (flags & ACTION_DEPTHFIRST) {
if (!dirAction(fileName, &statbuf, userData, depth))
goto done_nak_warn;
}
return status;
done_nak_warn:
if (!(flags & ACTION_QUIET))
bb_simple_perror_msg(fileName);
return FALSE;
}
/*遇到文件的处理*/
static int FAST_FUNC fileAction(const char *fileName,struct stat *statbuf UNUSED_PARAM,
void *userData,int depth UNUSED_PARAM)
{
size_t len = strlen(fileName) - 4; /* can't underflow */
char *scratch = userData;
/*只有目录下有dev节点的设备才会在/dev下创建节点*/
if (strcmp(fileName + len, "/dev") != 0 || len >= PATH_MAX)
return FALSE;
strcpy(scratch, fileName);
scratch[len] = '\0'; /*截断了”/dev”,相当于获取了dev所在的路径,上层就是设备名*/
make_device(scratch, /*delete:*/ 0); /*创建设备节点*/
return TRUE;
}
/*遇到目录的处理*/
static int FAST_FUNC dirAction(const char *fileName UNUSED_PARAM,
struct stat *statbuf UNUSED_PARAM,void *userData UNUSED_PARAM,int depth)
{
if (1 == depth) {
free(G.subsystem);
G.subsystem = strrchr(fileName, '/');
if (G.subsystem)
G.subsystem = xstrdup(G.subsystem + 1);
}
return (depth >= MAX_SYSFS_DEPTH ? SKIP : TRUE); /*检查目录深度*/
}
3.1.4 创建设备节点
无论调用mdev的是哪种情况,最后关键的还是make_device:
static void make_device(char *path, int delete)
{
char *device_name, *subsystem_slash_devname;
int major, minor, type, len;
mode_t mode;
parser_t *parser;
major = -1;
if (!delete) {
char *dev_maj_min = path + strlen(path);
strcpy(dev_maj_min, "/dev"); /*sysfs中设备目录下的dev节点记录的是设备号*/
len = open_read_close(path, dev_maj_min + 1, 64);
*dev_maj_min = '\0';
if (len < 1) {
if (!ENABLE_FEATURE_MDEV_EXEC)
return;
/*提取主次设备号*/
} else if (sscanf(++dev_maj_min, "%u:%u", &major, &minor) != 2) {
major = -1;
}
}
device_name = (char*) bb_basename(path); /*提取路径中最后一个’/’后面的字符串,也就是设备名*/
type = S_IFCHR;
/*sys/block下的都是块设备*/
if (strstr(path, "/block/") || (G.subsystem && strncmp(G.subsystem, "block", 5) == 0))
type = S_IFBLK;
subsystem_slash_devname = NULL;
/*获取设备的所属的子系统*/
if (strncmp(path, "/sys/block/", 11) == 0) /* legacy case */
path += sizeof("/sys/") - 1;
else if (strncmp(path, "/sys/class/", 11) == 0)
path += sizeof("/sys/class/") - 1;
else {
subsystem_slash_devname = concat_path_file(G.subsystem, device_name);
path = subsystem_slash_devname;
}
if (ENABLE_FEATURE_MDEV_CONF)
parser = config_open2("/etc/mdev.conf", fopen_for_read);
do {
int keep_matching;
struct bb_uidgid_t ugid;
char *tokens[4];
char *command = NULL;
char *alias = NULL;
char aliaslink = aliaslink; /* for compiler */
ugid.uid = ugid.gid = 0;
keep_matching = 0;
mode = 0660;
/*下面都是解析medev.conf文件,由于T3中没有使用这个配置文件,具体内容不清楚,不进行分析*/
if (ENABLE_FEATURE_MDEV_CONF
&& config_read(parser, tokens, 4, 3, "# \t", PARSE_NORMAL)
) {
char *val;
char *str_to_match;
regmatch_t off[1 + 9 * ENABLE_FEATURE_MDEV_RENAME_REGEXP];
val = tokens[0];
keep_matching = ('-' == val[0]);
val += keep_matching; /* swallow leading dash */
str_to_match = strchr(val, '/') ? path : device_name;
if (val[0] == '@') {
int cmaj, cmin0, cmin1, sc;
if (major < 0)
continue; /* no dev, no match */
sc = sscanf(val, "@%u,%u-%u", &cmaj, &cmin0, &cmin1);
if (sc < 1|| major != cmaj|| (sc == 2 && minor != cmin0)
|| (sc == 3 && (minor < cmin0 || minor > cmin1))
) {
continue; /* this line doesn't match */
}
goto line_matches;
}
if (val[0] == '$') {
/* regex to match an environment variable */
char *eq = strchr(++val, '=');
if (!eq)
continue;
*eq = '\0';
str_to_match = getenv(val);
if (!str_to_match)
continue;
str_to_match -= strlen(val) + 1;
*eq = '=';
}
/* else: regex to match [subsystem/]device_name */
{
regex_t match;
int result;
xregcomp(&match, val, REG_EXTENDED);
result = regexec(&match, str_to_match, ARRAY_SIZE(off), off, 0);
regfree(&match);
}
if (result|| off[0].rm_so
|| ((int)off[0].rm_eo != (int)strlen(str_to_match))
) {
continue; /* this line doesn't match */
}
}
line_matches:
if (get_uidgid(&ugid, tokens[1], 1) == 0)
bb_error_msg("unknown user/group %s on line %d", tokens[1], parser->lineno);
bb_parse_mode(tokens[2], &mode);
val = tokens[3];
if (ENABLE_FEATURE_MDEV_RENAME && val) {
char *a, *s, *st;
a = val;
s = strchrnul(val, ' ');
st = strchrnul(val, '\t');
if (st < s)
s = st;
st = (s[0] && s[1]) ? s+1 : NULL;
aliaslink = a[0];
if (aliaslink == '!' && s == a+1) {
val = st;
/* "!": suppress node creation/deletion */
major = -2;
}
else if (aliaslink == '>' || aliaslink == '=') {
val = st;
s[0] = '\0';
if (ENABLE_FEATURE_MDEV_RENAME_REGEXP) {
char *p;
unsigned i, n;
/* substitute %1..9 with off[1..9], if any */
n = 0;
s = a;
while (*s)
if (*s++ == '%')
n++;
p = alias = xzalloc(strlen(a) + n * strlen(str_to_match));
s = a + 1;
while (*s) {
*p = *s;
if ('%' == *s) {
i = (s[1] - '0');
if (i <= 9 && off[i].rm_so >= 0) {
n = off[i].rm_eo - off[i].rm_so;
strncpy(p, str_to_match + off[i].rm_so, n);
p += n - 1;
s++;
}
}
p++;
s++;
}
} else {
alias = xstrdup(a + 1);
}
}
}
if (ENABLE_FEATURE_MDEV_EXEC && val) {
const char *s = "$@*";
const char *s2 = strchr(s, val[0]);
if (!s2) {
bb_error_msg("bad line %u", parser->lineno);
if (ENABLE_FEATURE_MDEV_RENAME)
free(alias);
continue;
}
if (s2 - s != delete) {
command = xstrdup(val + 1);
}
}
}
{
const char *node_name;
node_name = device_name;
if (ENABLE_FEATURE_MDEV_RENAME && alias) /*如果是音频设备*/
node_name = alias = build_alias(alias, device_name);
/*创建设备节点,同时设置它的权限*/
if (!delete && major >= 0) {
if (mknod(node_name, mode | type, makedev(major, minor)) && errno != EEXIST)
bb_perror_msg("can't create '%s'", node_name);
if (major == G.root_major && minor == G.root_minor)
symlink(node_name, "root");
if (ENABLE_FEATURE_MDEV_CONF) {
chmod(node_name, mode);
chown(node_name, ugid.uid, ugid.gid);
}
if (ENABLE_FEATURE_MDEV_RENAME && alias) {
if (aliaslink == '>')
symlink(node_name, device_name);
}
}
/*如果mdev.conf中有指定相关操作,执行它*/
if (ENABLE_FEATURE_MDEV_EXEC && command) {
char *s = xasprintf("%s=%s", "MDEV", node_name);
char *s1 = xasprintf("%s=%s", "SUBSYSTEM", G.subsystem);
putenv(s);
putenv(s1);
if (system(command) == -1)
bb_perror_msg("can't run '%s'", command);
bb_unsetenv_and_free(s1);
bb_unsetenv_and_free(s);
free(command);
}
/*删除节点,remove事件*/
if (delete && major >= -1) {
if (ENABLE_FEATURE_MDEV_RENAME && alias) {
if (aliaslink == '>')
unlink(device_name);
}
unlink(node_name);
}
if (ENABLE_FEATURE_MDEV_RENAME)
free(alias);
}
if (ENABLE_FEATURE_MDEV_CONF && !keep_matching)
break;
/* end of "while line is read from /etc/mdev.conf" */
} while (ENABLE_FEATURE_MDEV_CONF);
if (ENABLE_FEATURE_MDEV_CONF)
config_close(parser);
free(subsystem_slash_devname);
}
至此,mdev处理uevent事件的过程分析完成了。
3.2 android ueventd
我们可以看到ueventd其实就是Init进程,使用ls –l进行列出可以看到进程名是ueventd,但是指向的是init:3.2.1 ueventd的启动
Ueventd是由init进程启动的,使用ps可以看到其父进程就是init,而它的启动是在init.rc中指定的。
on early-init
# Set init and its forked children's oom_adj.
write /proc/1/oom_adj -16
# Set the security context for the init process.
# This should occur before anything else (e.g. ueventd) is started.
setcon u:r:init:s0
start ueventd #启动ueventd
service ueventd /sbin/ueventd #ueventd在/sbin目录下
class core
critical
seclabel u:r:ueventd:s0
init.rc的解析可以去看init进程启动分析,这里就不详细说了,ueventd是在init进程的早期就启动了。
因此当我们要去找ueventd的实现,可以从Init的源码进行分析。System/core/init/init.c main()
if (!strcmp(basename(argv[0]), "ueventd")) /*argv[0]是在命令行中输入的可执行程序名称*/
return ueventd_main(argc, argv); /*ueventd就是执行到这里来*/
if (!strcmp(basename(argv[0]), "watchdogd"))
return watchdogd_main(argc, argv);
if (!strcmp(basename(argv[0]), "fswatcherd"))
return fswatcherd_main(argc, argv);
Init进程有4个入口,分别对应了init、ueventd、watchdogd和fdwatcherd。这四个进程使用的是同一个可执行文件,根据进程名的不同走不同的分支。执行ueventd其实最终执行的是ueventd_main。
从system/core/init/Android.mk文件中也可以看到,上面的除Init进程外,其它都是链接文件:
# Make a symlink from /sbin/ueventd and /sbin/watchdogd to /init
SYMLINKS := \
$(TARGET_ROOT_OUT)/sbin/ueventd \
$(TARGET_ROOT_OUT)/sbin/watchdogd \
$(TARGET_ROOT_OUT)/sbin/fswatcherd
$(SYMLINKS): INIT_BINARY := $(LOCAL_MODULE) //$(LOCAL_MODULE)就是init
$(SYMLINKS): $(LOCAL_INSTALLED_MODULE) $(LOCAL_PATH)/Android.mk
@echo "Symlink: $@ -> ../$(INIT_BINARY)"
@mkdir -p $(dir $@)
@rm -rf $@
$(hide) ln -sf ../$(INIT_BINARY) $@
在编译的时候就创建了init的链接文件—ueventd。
int ueventd_main(int argc, char **argv)
{
struct pollfd ufd;
int nr;
char tmp[32];
umask(000);
signal(SIGCHLD, SIG_IGN); /*注册进程死亡信号,ueventd进程死亡会通知父进程init*/
open_devnull_stdio(); /*重定向输出文件,具体作用是ueventd进程不会有任何打印输出*/
klog_init(); /*重定向log的输出,与标准输入输出一样,其实也不会产生任何的log*/
INFO("starting ueventd\n");
/*下面的信息获取分别从/proc/cmdline和/proc/cpuinfo中获取*/
import_kernel_cmdline(0, import_kernel_nv); /*解析内核启动参数*/
get_hardware_name(hardware, &revision); /*获取硬件平台名称和版本号*/
ueventd_parse_config_file("/ueventd.rc"); /*解析ueventd.rc*/
snprintf(tmp, sizeof(tmp), "/ueventd.%s.rc", hardware);
ueventd_parse_config_file(tmp); /*解析厂商的ueventd.rc*/
device_init(); /*重新触发所有设备的创建事件*/
ufd.events = POLLIN;
ufd.fd = get_device_fd(); /*使用poll的方式等待netlink socket事件*/
while(1) {
ufd.revents = 0;
nr = poll(&ufd, 1, -1); /*阻塞等待*/
if (nr <= 0)
continue;
if (ufd.revents == POLLIN)
handle_device_fd(); /*uevent事件处理*/
}
}
上面是ueventd的运行过程,总结来说,ueventd在处理uevent事件前做了三件大事:
1. 搭建输入输出环境—重定向了输入输出方式和log的路径。
2. 解析ueventd.rc文件,执行里面的操作
3. 重新触发设备驱动注册事件。
3.2.2 ueventd的输入输出重定向
下面逐一分析其实现和作用:void open_devnull_stdio(void)
{
int fd;
static const char *name = "/dev/__null__";
if (mknod(name, S_IFCHR | 0600, (1 << 8) | 3) == 0) { /*在/dev下创建节点,用于标准输入输出*/
fd = open(name, O_RDWR);
unlink(name); /*有意思的是在open后调用unlink,将设备节点删除了*/
if (fd >= 0) {
dup2(fd, 0); /*把标准输入、输出和错误输出重定向到删除后的节点*/
dup2(fd, 1); /*0:标准输入;1:标准输出;2:错误输出*/
dup2(fd, 2);
if (fd > 2) {
close(fd);
}
return;
}
}
exit(1);
}
open_devnull_stdio的操作会导致什么情况发生呢?那就是ueventd不会在终端上有任何的输出,因为它已经重定向了,然而又使用了unlink,因此在/dev下又不会存在__null__这个节点,因此,uevent的信息就消失得无影无踪了。
void klog_init(void)
{
static const char *name = "/dev/__kmsg__";
if (klog_fd >= 0) return; /* Already initialized */
if (mknod(name, S_IFCHR | 0600, (1 << 8) | 11) == 0) {
klog_fd = open(name, O_WRONLY);
if (klog_fd < 0)
return;
fcntl(klog_fd, F_SETFD, FD_CLOEXEC); /*FD_CLOEXEC 表示通过通过exec时文件关闭,fork不会*/
unlink(name);
}
}
klog_init与open_devnull_stdio的实现是一样的,将log变得消失无影无踪,在调试的过程,可以将unlink注释掉,那么log就会出来。
3.2.3 /dev目录下设备节点的创建
我们知道,ueventd是由init进程fork出来的,而init进程是在内核中启动,而其启动顺序是晚于驱动程序的注册,那么在/dev下的节点是如何创建出来的?这就是device_init所解决的问题了。
void device_init(void)
{
suseconds_t t0, t1;
struct stat info;
int fd;
sehandle = NULL;
if (is_selinux_enabled() > 0) {
sehandle = selinux_android_file_context_handle();
}
/* is 256K enough? udev uses 16MB! */
device_fd = uevent_open_socket(256*1024, true); /*打开netlink,用于接收uevent事件*/
if(device_fd < 0)
return;
fcntl(device_fd, F_SETFD, FD_CLOEXEC);
fcntl(device_fd, F_SETFL, O_NONBLOCK);
if (stat(coldboot_done, &info) < 0) { /*当系统是冷启动,也就是没有进行对uevent事件处理过*/
t0 = get_usecs();
coldboot("/sys/class"); /*将/sys/class,/sys/block,/sys/devices下的所有节点事件触发一遍*/
coldboot("/sys/block");
coldboot("/sys/devices");
t1 = get_usecs();
fd = open(coldboot_done, O_WRONLY|O_CREAT, 0000);
close(fd);
log_event_print("coldboot %ld uS\n", ((long) (t1 - t0)));
} else {
log_event_print("skipping coldboot, already done\n");
}
}
int uevent_open_socket(int buf_sz, bool passcred)
{
struct sockaddr_nl addr;
int on = passcred;
int s;
memset(&addr, 0, sizeof(addr));
addr.nl_family = AF_NETLINK; /*与内核的kobject_uevent_env的配置是一致*/
addr.nl_pid = getpid();
addr.nl_groups = 0xffffffff; /*接收广播消息,内核是不接收广播信息的*/
s = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT); /*创建socket*/
if(s < 0)
return -1;
setsockopt(s, SOL_SOCKET, SO_RCVBUFFORCE, &buf_sz, sizeof(buf_sz));
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 coldboot(const char *path)
{
DIR *d = opendir(path);
if(d) {
do_coldboot(d);
closedir(d);
}
}
static void do_coldboot(DIR *d)
{
struct dirent *de;
int dfd, fd;
dfd = dirfd(d);
fd = openat(dfd, "uevent", O_WRONLY);
if(fd >= 0) {
write(fd, "add\n", 4); /*往uevent属性节点写入action,可以使用echo进行测试*/
close(fd);
handle_device_fd(); /*处理接收到的uevent事件*/
}
while((de = readdir(d))) { /*遍历所以子目录*/
DIR *d2;
if(de->d_type != DT_DIR || de->d_name[0] == '.')
continue;
fd = openat(dfd, de->d_name, O_RDONLY | O_DIRECTORY);
if(fd < 0)
continue;
d2 = fdopendir(fd);
if(d2 == 0)
close(fd);
else {
do_coldboot(d2); /*递归所以子目录的uevent属性节点*/
closedir(d2);
}
}
}
就这样,所有的设备驱动再次产生了注册的uevent事件,而节点就是对事件解析的时候创建的,这个在后面分析。
3.2.4 ueventd.rc的解析和处理
剩下的就是ueventd.rc文件的解析了:
int ueventd_parse_config_file(const char *fn)
{
char *data;
data = read_file(fn, 0);
if (!data) return -1;
parse_config(fn, data);
DUMP();
return 0;
}
/*parse_config:与init.rc的处理函数是不一样的,使用了static,在本文件中找*/
static void parse_config(const char *fn, char *s)
{
struct parse_state state;
char *args[UEVENTD_PARSER_MAXARGS];
int nargs;
nargs = 0;
state.filename = fn;
state.line = 1;
state.ptr = s;
state.nexttoken = 0;
state.parse_line = parse_line_device; /*解析数据的函数*/
for (;;) {
int token = next_token(&state);
switch (token) {
case T_EOF:
state.parse_line(&state, 0, 0);
return;
case T_NEWLINE:
if (nargs) {
state.parse_line(&state, nargs, args); /*解析数据*/
nargs = 0;
}
break;
case T_TEXT:
if (nargs < UEVENTD_PARSER_MAXARGS) {
args[nargs++] = state.text;
}
break;
}
}
}
static void parse_line_device(struct parse_state* state, int nargs, char **args)
{
set_device_permission(nargs, args);
}
void set_device_permission(int nargs, char **args)
{
char *name;
char *attr = 0;
mode_t perm;
uid_t uid;
gid_t gid;
int prefix = 0;
char *endptr;
int ret;
char *tmp = 0;
if (nargs == 0)
return;
if (args[0][0] == '#')
return;
name = args[0];
/*ueventd.rc中非sysfs的格式是|name| |attr| |permission| |user| |group|*/
/*sysfs的格式是|name| |permission| |user| |group|*/
if (!strncmp(name,"/sys/", 5) && (nargs == 5)) {
INFO("/sys/ rule %s %s\n",args[0],args[1]);
attr = args[1];
args++;
nargs--;
}
if (nargs != 4) {
ERROR("invalid line ueventd.rc line for '%s'\n", args[0]);
return;
}
/* If path starts with mtd@ lookup the mount number. */
if (!strncmp(name, "mtd@", 4)) {
int n = mtd_name_to_number(name + 4);
if (n >= 0)
asprintf(&tmp, "/dev/mtd/mtd%d", n);
name = tmp;
} else {
int len = strlen(name);
if (name[len - 1] == '*') {
prefix = 1;
name[len - 1] = '\0';
}
}
perm = strtol(args[1], &endptr, 8); /*转换获取permission的值*/
if (!endptr || *endptr != '\0') {
ERROR("invalid mode '%s'\n", args[1]);
free(tmp);
return;
}
ret = get_android_id(args[2]);
if (ret < 0) {
ERROR("invalid uid '%s'\n", args[2]);
free(tmp);
return;
}
uid = ret;
ret = get_android_id(args[3]);
if (ret < 0) {
ERROR("invalid gid '%s'\n", args[3]);
free(tmp);
return;
}
gid = ret;
add_dev_perms(name, attr, perm, uid, gid, prefix); /*添加到链表中去*/
free(tmp);
}
那么那些设备节点的permission和uid、gid的值在哪使用呢?其实就是在接收到uevent的add事件中创建节点使用的。下面就分析事件的接收与处理了。
3.2.5 uevent事件的处理
void handle_device_fd()
{
char msg[UEVENT_MSG_LEN+2];
int n;
while ((n = uevent_kernel_multicast_recv(device_fd, msg, UEVENT_MSG_LEN)) > 0) {
if(n >= UEVENT_MSG_LEN) /* overflow -- discard */
continue;
msg[n] = '\0';
msg[n+1] = '\0';
struct uevent uevent;
parse_event(msg, &uevent); /*从msg中解析出uevent的键值对出来*/
handle_device_event(&uevent); /*根据解析出来的数据进行对应的操作*/
handle_firmware_event(&uevent);
}
}
Msg的解析过程非常简单,这里就不作分析了。
static void handle_device_event(struct uevent *uevent)
{
if (!strcmp(uevent->action,"add") || !strcmp(uevent->action, "change"))
fixup_sys_perms(uevent->path); /*匹配sysfs中的节点,如果符合则根据ueventd.rc的配置设置*/
if (!strncmp(uevent->subsystem, "block", 5)) { /*block设备*/
handle_block_device_event(uevent);
} else if (!strncmp(uevent->subsystem, "platform", 8)) { /*平台设备*/
handle_platform_device_event(uevent);
} else { /*通用设备*/
handle_generic_device_event(uevent);
}
}
我们就以平台设备和通用设备为例进行分析吧:
static void handle_platform_device_event(struct uevent *uevent)
{
const char *path = uevent->path;
if (!strcmp(uevent->action, "add"))
add_platform_device(path);
else if (!strcmp(uevent->action, "remove"))
remove_platform_device(path);
}
static void add_platform_device(const char *path)
{
int path_len = strlen(path);
struct listnode *node;
struct platform_node *bus;
const char *name = path;
if (!strncmp(path, "/devices/", 9)) {
name += 9;
if (!strncmp(name, "platform/", 9))
name += 9;
}
/*遍历已注册的设备和驱动,已注册则推出*/
list_for_each_reverse(node, &platform_names) {
bus = node_to_item(node, struct platform_node, list);
if ((bus->path_len < path_len) &&
(path[bus->path_len] == '/') &&
!strncmp(path, bus->path, bus->path_len))
/* subdevice of an existing platform, ignore it */
return;
}
INFO("adding platform device %s (%s)\n", name, path);
bus = calloc(1, sizeof(struct platform_node));
bus->path = strdup(path);
bus->path_len = path_len;
bus->name = bus->path + (name - path);
list_add_tail(&platform_names, &bus->list); /*简单地记录下来*/
}
平台设备的处理比较简单。
static void handle_generic_device_event(struct uevent *uevent)
{
char *base;
const char *name;
char devpath[96] = {0};
char **links = NULL;
name = parse_device_name(uevent, 64); /*提取设备的名称*/
if (!name)
return;
/*省略在/dev下创建目录的部分,部分同类型的设备会放在同一个目录下,例如graphics*/
……
links = get_character_device_symlinks(uevent);
if (!devpath[0])
snprintf(devpath, sizeof(devpath), "%s%s", base, name);
handle_device(uevent->action, devpath, uevent->path, 0,
uevent->major, uevent->minor, links); /*创建/删除设备节点*/
}
static void handle_device(const char *action, const char *devpath,
const char *path, int block, int major, int minor, char **links)
{
int i;
if(!strcmp(action, "add")) {
make_device(devpath, path, block, major, minor); /*创建节点,设备号*/
if (links) {
for (i = 0; links[i]; i++)
make_link(devpath, links[i]);
}
}
if(!strcmp(action, "remove")) {
#ifdef USE_USB_BT
if (!strcmp(devpath, usb_bt_devpath)) {
ERROR("disable bluetooth...\n");
send_to_usb_bt_app(3);
}
#endif
if (links) {
for (i = 0; links[i]; i++)
remove_link(devpath, links[i]);
}
unlink(devpath); /*使用unlink删除节点*/
}
if (links) {
for (i = 0; links[i]; i++)
free(links[i]);
free(links);
}
}
static void make_device(const char *path, const char *upath,int block, int major, int minor)
{
unsigned uid;
unsigned gid;
mode_t mode;
dev_t dev;
char *secontext = NULL;
/* get_device_perm 就是获取ueventd.rc中的值*/
mode = get_device_perm(path, &uid, &gid) | (block ? S_IFBLK : S_IFCHR);
if (sehandle) {
selabel_lookup(sehandle, &secontext, path, mode);
setfscreatecon(secontext);
}
/*设置权限*/
dev = makedev(major, minor);
setegid(gid);
mknod(path, mode, dev);
chown(path, uid, -1);
setegid(AID_ROOT);
#ifdef USE_USB_BT
if (is_usb_bt_device(path) > 0) {
//unlink(path);
uid = AID_BLUETOOTH;
gid = AID_BLUETOOTH;
mode = 0660;
setegid(gid);
//mknod(path, mode, dev);
chown(path, uid, -1);
setegid(AID_ROOT);
ERROR("find usb bt device...\n");
send_to_usb_bt_app(2);
}
// for usb bt adapter end
#endif
if (secontext) {
freecon(secontext);
setfscreatecon(NULL);
}
}
至此,android的uevent处理过程分析完成。
3.3 总结
已经学完mdev和ueventd两种处理uevent事件的工具了,下面总结一下两者的区别。
1. mdev是由内核线程khelper加载并运行的,ueventd是由init进程启动的。
2. ueventd使用了netlink的方式进行事件信息的接收,mdev是以内核启动新程序的方式以env的方式传递。
3. ueventd创建节点是根据uevent中的SUBSYSTEM字段进行判断,而mdev是根据uevent中的DEVPATH目录下是否具有dev节点来进行判断是否应该创建节点。
4. ueventd会根据不同类型的设备进行分类,所以可以看到android系统下的/dev目录会出现较多的子目录,而mdev并不会创建子目录,所有设备节点都是放在/dev下。
4 设备驱动创建设备节点
上面已经分析完uevent事件的上报和处理过程了,那么我们结合设备驱动中加入创建设备节点的部分源码。我们知道在创建设备时调用class_create和device_create,那么驱动加载后在/dev下就会出现我们的设备节点了。下面就来看看这两个函数做了些什么。
#define class_create(owner, name) \
({ \
static struct lock_class_key __key; \
__class_create(owner, name, &__key); \
})
struct class *__class_create(struct module *owner, const char *name,
struct lock_class_key *key)
{
struct class *cls;
int retval;
cls = kzalloc(sizeof(*cls), GFP_KERNEL);
if (!cls) {
retval = -ENOMEM;
goto error;
}
cls->name = name;
cls->owner = owner;
cls->class_release = class_create_release;
retval = __class_register(cls, key);
if (retval)
goto error;
return cls;
error:
kfree(cls);
return ERR_PTR(retval);
}
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...)
{
va_list vargs;
struct device *dev;
va_start(vargs, fmt);
dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);
va_end(vargs);
return dev;
}
struct device *device_create_vargs(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, va_list args)
{
struct device *dev = NULL;
int retval = -ENODEV;
if (class == NULL || IS_ERR(class))
goto error;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev) {
retval = -ENOMEM;
goto error;
}
dev->devt = devt;
dev->class = class; /*调用class_create创建的class*/
dev->parent = parent;
dev->release = device_create_release;
dev_set_drvdata(dev, drvdata);
retval = kobject_set_name_vargs(&dev->kobj, fmt, args);
if (retval)
goto error;
/* device_register会在sysfs中该设备目录下创建dev节点*/
/* error = device_create_file(dev, &devt_attr); */
/*static struct device_attribute devt_attr =
__ATTR(dev, S_IRUGO, show_dev, NULL); */
retval = device_register(dev); /*在这里会调用kobject_uevent上报一个KOBJ_ADD事件*/
if (retval)
goto error;
return dev;
error:
put_device(dev);
return ERR_PTR(retval);
}
int device_register(struct device *dev)
{
device_initialize(dev);
return device_add(dev);
}
void device_initialize(struct device *dev)
{
dev->kobj.kset = devices_kset; /*重点*/
kobject_init(&dev->kobj, &device_ktype);
INIT_LIST_HEAD(&dev->dma_pools);
mutex_init(&dev->mutex);
lockdep_set_novalidate_class(&dev->mutex);
spin_lock_init(&dev->devres_lock);
INIT_LIST_HEAD(&dev->devres_head);
device_pm_init(dev);
set_dev_node(dev, -1);
}
int device_add(struct device *dev)
{
……
parent = get_device(dev->parent);
kobj = get_device_parent(dev, parent);
if (kobj)
dev->kobj.parent = kobj;
……
error = device_create_file(dev, &uevent_attr);
if (error)
goto attrError;
if (MAJOR(dev->devt)) {
error = device_create_file(dev, &devt_attr); /*mdev所要查找的dev节点在这创建*/
if (error)
goto ueventattrError;
error = device_create_sys_dev_entry(dev);
if (error)
goto devtattrError;
devtmpfs_create_node(dev);
}
……
kobject_uevent(&dev->kobj, KOBJ_ADD); /*事件上报*/
……
}
Class、kset、kobj和ktype之间的关系将会在后面其他文档中进行学习分析,本文档不会深入。
一般在平台设备驱动中,我们会调用device_create创建一个字符设备驱动,这与我们前面说的平台设备驱动不一样,这是真实存在的驱动实例,而平台设备驱动是一个框架,设备和驱动都只是一种资源注册到内核中,用户无法调用这些驱动设备。device_register其实就有注册新设备的能力,为何驱动中要使用其外层封装函数device_create+class_create呢?那是因为class的作用,在调用device_create中,除了一些总线设备如I2C等,一般的字符设备驱动都是没有挂载在总线上,而class的作用就代替了bus的作用。这个问题是在kobject_uevent中成为事件上报中体现了。Linux_kernel/lib/kobject_uevent.c kobject_uevent_env()
kset = top_kobj->kset;
uevent_ops = kset->uevent_ops;
……
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;
}
if (uevent_ops && uevent_ops->name)
subsystem = uevent_ops->name(kset, kobj);
else
subsystem = kobject_name(&kset->kobj);
使用device_register(或间接调用的)注册的设备使用的uevent_ops如下:
static const struct kset_uevent_ops device_uevent_ops = {
.filter = dev_uevent_filter,
.name = dev_uevent_name,
.uevent = dev_uevent,
};
static int dev_uevent_filter(struct kset *kset, struct kobject *kobj)
{
struct kobj_type *ktype = get_ktype(kobj);
if (ktype == &device_ktype) {
struct device *dev = kobj_to_dev(kobj);
/*设备驱动必须是属于某一类Bus或者class才能上报,否则会被过滤掉*/
if (dev->bus)
return 1;
if (dev->class)
return 1;
}
return 0;
}
static const char *dev_uevent_name(struct kset *kset, struct kobject *kobj)
{
struct device *dev = kobj_to_dev(kobj);
/*subsystem就是其bus或者class*/
if (dev->bus)
return dev->bus->name;
if (dev->class)
return dev->class->name;
return NULL;
}
Class_create创建了一个class,使dev有了归属,在uevnet上报中不至于被过滤掉,device_create就是注册设备驱动,同时上报一个KOBJ_ADD事件。
就这样,调用device_create+class_create,就可以实现自动创建设备节点。整个流程如下图:
欢迎各位大神指点。
转载请标明出处:http://blog.youkuaiyun.com/zcyxiaxi/article/details/79196647