平台设备驱动模型与uevent机制

前记

 环境:linux-3.10+Android4.4

 摘要:主要分析了内核启动过程中从设备树创建device资源和驱动加载过程中平台设备驱动的匹配过程。同时分析了内核uevent事件上报机制,用户空间对uevent事件的处理过程。linux系统上分析了Busybox中的mdevandroid系统上分析了ueventd。深入地了解了设备模型中内核态与用户之间的交互手段。其实主要是因为想了解一下注册驱动时是如何创建设备节点和HWCVSync信号的传递过程~~

1.设备树注册平台设备

 linux内核在3.x版本以前存在着大量的冗余代码(板级描述细节),这些代码严重影响着代码的可用性和维护,在3.x版本以后,设备树出现了,大大地简化了代码。

 设备树的基础知识不会在本文讲解,本文主要讲述内核是如何从设备树中的节点到设备的创建的过程。

 本文的基础是建立在对内核启动和设备树有一定了解的条件下进行,若没有这两方面的基础的读者请先进行基础学习。

start_kernel的过程中,最后的步骤是进行rest_initrest_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_actionmake_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_initopen_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_createdevice_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.csdn.net/zcyxiaxi/article/details/79196647


  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值