Linux设备模型

| 💡本文基于linux-v4.14.7,文中代码有删减,只保留了核心部分,删除了许多异常的处理

1. 概述

Linux 设备模型(Linux Device Model)是内核中统一描述和管理硬件及其驱动关系的基础框架,核心目标是:抽象硬件、统一驱动模型、支持热插拔、并向用户空间暴露一致的视图(sysfs / uevent)。

2. 数据结构定义

struct bus_type 代表总线

c 复制代码
struct bus_type {
	const char		*name;      /* 总线的名字 */
	struct subsys_private *p; /* 总线私有数据 */
};

struct subsys_private {
	struct kset subsys;
	struct kset *devices_kset; /* 所有dev都属于这个kset集合 */
	struct list_head interfaces;
	struct mutex mutex;

	struct kset *drivers_kset; /* 所有drv都属于这个kset集合 */
	struct klist klist_devices; /* 设备链表头节点 */
	struct klist klist_drivers; /* 驱动链表头节点 */
	struct blocking_notifier_head bus_notifier;
	unsigned int drivers_autoprobe:1;
	struct bus_type *bus;
	
	struct class *class;
};

struct device 代表设备

c 复制代码
struct device {
	struct device		*parent;

	struct device_private	*p;

	struct kobject kobj;
	const char		*init_name; /* dev名字e */
	const struct device_type *type;

	struct bus_type	*bus;		/* 指向所属bus */

	struct klist_node	knode_class; /* 作为class成员时使用的链表节点 */
	struct class		*class
};

struct device_private {
	struct klist klist_children;    /* 链接children devic的链表头节点 */
	struct klist_node knode_parent; /* 链接到parent的链接节点 */
	struct klist_node knode_driver; /* 链接到绑定驱动的链表节点 */
	struct klist_node knode_bus;    /* 用于挂到其所属bus的klist_devices链表中 */
	struct device *device;
}

struct device_driver 代表驱动

c 复制代码
struct device_driver {
	const char		*name;
	struct bus_type		*bus;

	struct module		*owner;

	enum probe_type probe_type;

	int (*probe) (struct device *dev);
	int (*remove) (struct device *dev);
	void (*shutdown) (struct device *dev);
	int (*suspend) (struct device *dev, pm_message_t state);
	int (*resume) (struct device *dev);

	struct driver_private *p;
};

struct driver_private {
	struct kobject kobj;
	struct klist klist_devices;   /* 用于链接绑定该驱动的dev节点 */
	struct klist_node knode_bus;  /* 用于挂到其所属bus的klist_drivers链表中 */
	struct module_kobject *mkobj;
	struct device_driver *driver;
};

3. 核心操作函数源码分析

当内核启动后,首先会依次调用devices_initbuses_initclasses_init函数,用于建立设备模型的基础目录结构。

c 复制代码
/**
 * driver_init:
 *  ├── devices_init
 *  ├── buses_init
 *  └── classes_init
*/
int __init devices_init(void)
{
  /* /sys/devices */
	devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);
	/* /sys/dev */
	dev_kobj = kobject_create_and_add("dev", NULL);
	/* /sys/dev/block */
	sysfs_dev_block_kobj = kobject_create_and_add("block", dev_kobj);
	/* /sys/dev/char */
	sysfs_dev_char_kobj = kobject_create_and_add("char", dev_kobj);
}

int __init buses_init(void)
{
  /* /sys/bus */
	bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);
	/* /sys/devices/system */
	system_kset = kset_create_and_add("system", NULL, &devices_kset->kobj);
}

int __init classes_init(void)
{
  /* /sys/class */
	class_kset = kset_create_and_add("class", NULL, NULL);
}

现在目录结构如下

3.1. 注册总线

bus_register 用于注册一个总线,首先将总线的目录注册到/sys/bus/[bus->name],函数内部使用bus_ksetbus_ktype是全局变量,所有总线都指向它。drivers_autoprobe用于控制驱动或者设备加载是是否自动探测。然后创建/sys/bus/[bus->name]文件,/sys/bus/[bus->name]/devices/sys/bus/[bus->name]/drivers 目录,创建两个链表priv→klist_devicespriv→klist_drivers 用于后续链接驱动和设备,在/sys/bus/[bus->name]/创建drivers_probedrivers_autoprobe文件和属性组文件。

c 复制代码
int bus_register(struct bus_type *bus)
{
	int retval;
	struct subsys_private *priv;
  
  /* 双向关联 */
	priv->bus = bus;
	bus->p = priv;

  /* 设置subsys的属性 */
	retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);
	priv->subsys.kobj.kset = bus_kset;
	priv->subsys.kobj.ktype = &bus_ktype;
	/* 设置驱动自动探测 */
	priv->drivers_autoprobe = 1;
	
	retval = kset_register(&priv->subsys);

  /* 创建/sys/bus/[bus->name]/uevent文件 */
	retval = bus_create_file(bus, &bus_attr_uevent);
  /* 创建/sys/bus/[bus->name]/devices和/sys/bus/[bus->name]/drivers */
	priv->devices_kset = kset_create_and_add("devices", NULL,
						 &priv->subsys.kobj);
	priv->drivers_kset = kset_create_and_add("drivers", NULL,
						 &priv->subsys.kobj);

  /* 创建/sys/bus/platform/drivers_probe 和/sys/bus/platform/drivers_probe/drivers_autoprobe */
	retval = add_probe_files(bus);
  /* 在/sys/bus/[bus->name]/下创建属性组相关文件 */
	retval = bus_add_groups(bus, bus->bus_groups);
}

现在/sys/bus/的目录结构如下所示

3.2 device注册

函数bus_register 用于注册一个设备,其完成device结构体的初始化、然后把设备注册进系统。

复制代码
device_register
    ├── device_initialize:初始化各种结构体成员
    └── device_add:设备上线

device_initialize用于初始化device对象的基础成员,为后续device_add()/device_register()做准备。函数先设置devkset指向devices_kset,初始化各种链表和锁,最后将dev的状态设置为没有驱动,此时设备仍然不可见。

c 复制代码
void device_initialize(struct device *dev)
{
  /* 设置kset */
	dev->kobj.kset = devices_kset;
	
	kobject_init(&dev->kobj, &device_ktype);

	lockdep_set_novalidate_class(&dev->mutex);

	/* 状态为无驱动 */
	dev->links.status = DL_DEV_NO_DRIVER;
}

device_add这一阶段是"设备正式上线",核心动作包括:设置dev_name,建立parent-child关系,创建 /sys/devices/...目录,挂到bus->p->klist_devices ,触发uevent,尝试和driver进行匹配,从这里开始,设备对内核和用户态都是可见的。

c 复制代码
int device_add(struct device *dev)
{
	struct device *parent;
	struct kobject *kobj;
	struct class_interface *class_intf;
	struct kobject *glue_dir = NULL;
  /* 引用计数+1 */
	dev = get_device(dev);
  /* 初始化私有数据 */
	if (!dev->p) {
		error = device_private_init(dev);
	}

  /* 设置name */
	if (dev->init_name) {
		dev_set_name(dev, "%s", dev->init_name);
		dev->init_name = NULL;
	}
	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;
	}

	parent = get_device(dev->parent);
	/* 计算实际的父目录 */
	kobj = get_device_parent(dev, parent);
	if (kobj)
		dev->kobj.parent = kobj;
	/* use parent numa_node */
	if (parent && (dev_to_node(dev) == NUMA_NO_NODE))
		set_dev_node(dev, dev_to_node(parent));
  /* 添加到sysfs层次结构中 */
	error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
  /* 创建uevent文件 */
	error = device_create_file(dev, &dev_attr_uevent);
	/* 建立device和class之间的双向链接 */
	error = device_add_class_symlinks(dev);
	/* 创建属性文件 */
	error = device_add_attrs(dev);
	/* 添加dev到bus的链表 */
	error = bus_add_device(dev);
	/* 电源管理 */
	error = dpm_sysfs_add(dev);

	device_pm_add(dev);

	if (MAJOR(dev->devt)) {
	  /* 创建文件 */
		error = device_create_file(dev, &dev_attr_dev);
		error = device_create_sys_dev_entry(dev);
    /* 创建字符设备/dev/节点 */
		devtmpfs_create_node(dev);
	}
  /* 内核通知链,ADD事件*/
	if (dev->bus)
		blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
					     BUS_NOTIFY_ADD_DEVICE, dev);
  /*告知用户空间的ADD事件*/
	kobject_uevent(&dev->kobj, KOBJ_ADD);
	/* 驱动探测 */
	bus_probe_device(dev);
	if (parent)
		klist_add_tail(&dev->p->knode_parent,
			       &parent->p->klist_children);
  /* class和device绑定 */
	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);
	}
}

bus_add_device 建立两个软链接,并将dev加入到bus的链表进行管理。

c 复制代码
int bus_add_device(struct device *dev)
{
	struct bus_type *bus = bus_get(dev->bus);
	int error = 0;

	if (bus) {
		error = device_add_groups(dev, bus->dev_groups);
    /* 建立/sys/bus/<bus>/devices/<dev_name> → /sys/devices/.../<dev_name>的软链接 */
		error = sysfs_create_link(&bus->p->devices_kset->kobj,
						&dev->kobj, dev_name(dev));
    /* 建立/sys/devices/.../<dev>/subsystem → /sys/bus/<bus>的软链接 */
		error = sysfs_create_link(&dev->kobj,
				&dev->bus->p->subsys.kobj, "subsystem");
    /* 加入到klist_devices链表 */
		klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
	}
	return 0;
}

3.3. 注册驱动

函数driver_register用于注册一个驱动,首先根据名字在总线里面查找驱动,如果没有同名驱动的话则将驱动注册到总线,创建属性组,生成ueventADD事件。

c 复制代码
/**
 * driver_register:
 *   ├── driver_find:查找同名驱动
 *   ├── bus_add_driver:
 *	 └── kobject_uevent:生成ADD事件
*/
int driver_register(struct device_driver *drv)
{
	int ret;
	struct device_driver *other;
  /* 查找是否有同名驱动 */
	other = driver_find(drv->name, drv->bus);
	if (other) {
		return -EBUSY;
	}

	ret = bus_add_driver(drv);
	if (ret)
		return ret;
	ret = driver_add_groups(drv, drv->groups);
	if (ret) {
		return ret;
	}
	kobject_uevent(&drv->p->kobj, KOBJ_ADD);

	return ret;
}

上面注册总线时提到一个函数bus_add_driver 是将驱动注册到总线的核心功能实现。

c 复制代码
int bus_add_driver(struct device_driver *drv)
{
	struct bus_type *bus;
	struct driver_private *priv;
	int error = 0;
  /* 总线引用计数+1 */
	bus = bus_get(drv->bus);

	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
	
	klist_init(&priv->klist_devices, NULL, NULL);
	priv->driver = drv;
	drv->p = priv;
	/* 创建/sys/bus/[bus->name]/drivers/[drv->name]节点 */
	priv->kobj.kset = bus->p->drivers_kset;
	error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
				     "%s", drv->name);
  /* 加入链表管理 */
	klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
	if (drv->bus->p->drivers_autoprobe) {
	  /* 判断驱动的probe能不能放到异步线程中执行,而不是在同步路径里阻塞内核初始化流程 */
		if (driver_allows_async_probing(drv)) {
		  /* 异步遍历device,调用match函数匹配 */
			async_schedule(driver_attach_async, drv);
		} else {
		  /* 同步遍历device,调用match函数匹配 */
			error = driver_attach(drv);
			if (error)
				goto out_unregister;
		}
	}
	module_add_driver(drv->owner, drv);
  /* 创建uevent文件 */
	error = driver_create_file(drv, &driver_attr_uevent);
  /* 创建属性组 */
	error = driver_add_groups(drv, bus->drv_groups);

	return 0;
}

3.4 驱动和设备的探测

当添加设备时,会通过bus总线去探测驱动,如果设备指定了驱动,那么直接和它绑定;如果设备未指定驱动,那么遍历驱动链表查找,如果找到则调用probe函数执行。

c 复制代码
static int __device_attach(struct device *dev, bool allow_async)
{
	device_lock(dev);
	if (dev->driver) { /* 指定了驱动程序 */
		/* 设备绑定驱动 */
		ret = device_bind_driver(dev);
	} else { /* 未指定驱动程序 */
		ret = bus_for_each_drv(dev->bus, NULL, &data,
					__device_attach_driver);
	}
}

设备指定驱动时会调用device_bind_driver 函数,加入链表节点,发送bind事件。

c 复制代码
static void driver_bound(struct device *dev)
{
  /* 将dev加入到klist_devices链表进行管理 */
	klist_add_tail(&dev->p->knode_driver, &dev->driver->p->klist_devices);

  /* 发送BIND事件 */
	kobject_uevent(&dev->kobj, KOBJ_BIND);
}

设备探测驱动时,如果通过match函数找到匹配的驱动,那么则会调用probe函数

c 复制代码
static int __device_attach_driver(struct device_driver *drv, void *_data)
{
	ret = driver_match_device(drv, dev);

	return driver_probe_device(drv, dev);
}

当添加驱动时,driver_attach会去匹配设备,如果匹配成功则调用probe函数

c 复制代码
int driver_attach(struct device_driver *drv)
{
	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;

	driver_match_device(drv, dev);

	driver_probe_device(drv, dev);

	return 0;
相关推荐
晚风吹长发1 小时前
初步了解Linux中的信号捕捉
linux·运维·服务器·c++·算法·进程·x信号
阡陌..2 小时前
Linux下用docker调用pytorch-无法检测到cuda问题
linux·pytorch·docker
山上三树2 小时前
详细介绍信号量
linux
(Charon)2 小时前
【网络编程】从零开始理解 io_uring:Linux 网络编程的“核动力”引擎
linux·运维·服务器
历程里程碑2 小时前
Linux 10:make Makefile自动化编译实战指南及进度条解析
linux·运维·服务器·开发语言·c++·笔记·自动化
爱装代码的小瓶子2 小时前
【C++与Linux】文件篇(2)- 文件操作的系统接口详解
linux·c++
Cisco_hw_zte2 小时前
挂载大容量磁盘【Linux系统】
linux·运维·服务器
DolphinScheduler社区3 小时前
Linux 环境下,Apache DolphinScheduler 如何驱动 Flink 消费 Kafka 数据?
linux·flink·kafka·开源·apache·海豚调度·大数据工作流调度
艾莉丝努力练剑3 小时前
【AI时代的赋能与重构】当AI成为创作环境的一部分:机遇、挑战与应对路径
linux·c++·人工智能·python·ai·脉脉·ama