提示:总线里面设备注册和注册流程分析
文章目录
前言
前面了解过
参考资料
驱动-注册自己的总线并创建属性文件
驱动-总线bus注册流程分析
platform总线注册流程分析
在总线下注册设备实验
设备模型
Linux 在线源码
一、设备注册
设备注册-驱动注册实验
源码程序 dvice.c 设备端源码如下:
编写驱动文件 device.c,在驱动中,Linux 内核中创建一个自定义设备并将其注册到自定义的总线上
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/configfs.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/device.h>
#include <linux/sysfs.h>
extern struct bus_type mybus;
void myrelease(struct device *dev)
{
printk("This is myrelease\n");
};
struct device mydevice = {
.init_name = "mydevice", // 设备的初始化名称
.bus = &mybus, // 所属总线
.release = myrelease, // 设备的释放回调函数
.devt = ((255 << 20 | 0)), // 设备号
};
// 模块的初始化函数
static int device_init(void)
{
int ret;
ret = device_register(&mydevice); // 注册设备
return 0;
}
// 模块退出函数
static void device_exit(void)
{
device_unregister(&mydevice); // 取消注册设备
}
module_init(device_init); // 指定模块的初始化函数
module_exit(device_exit); // 指定模块的退出函数
MODULE_LICENSE("GPL"); // 模块使用的许可证
MODULE_AUTHOR("fangchen"); // 模块的作者
驱动层直接用bus的测试程序,源码如下:
我们编写驱动代码,定义了一个名为 “mybus” 的总线,并实现了总线的匹配回调函数
mybus_match 和设备探测回调函数 mybus_probe。同时,还定义了一个名为 “value” 的属性文
件,并实现了属性的显示回调函数 mybus_show。
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/configfs.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/device.h>
#include <linux/sysfs.h>
int mybus_match(struct device *dev, struct device_driver *drv)
{
// 检查设备名称和驱动程序名称是否匹配
return (strcmp(dev_name(dev), drv->name) == 0);
};
int mybus_probe(struct device *dev)
{
struct device_driver *drv = dev->driver;
if (drv->probe)
drv->probe(dev);
return 0;
};
struct bus_type mybus = {
.name = "mybus", // 总线的名称
.match = mybus_match, // 设备和驱动程序匹配的回调函数
.probe = mybus_probe, // 设备探测的回调函数
};
EXPORT_SYMBOL_GPL(mybus); // 导出总线符号
ssize_t mybus_show(struct bus_type *bus, char *buf)
{
// 在 sysfs 中显示总线的值
return sprintf(buf, "%s\n", "mybus_show");
};
struct bus_attribute mybus_attr = {
.attr = {
.name = "value", // 属性的名称
.mode = 0664, // 属性的访问权限
},
.show = mybus_show, // 属性的 show 回调函数
};
// 模块的初始化函数
static int bus_init(void)
{
int ret;
ret = bus_register(&mybus); // 注册总线
ret = bus_create_file(&mybus, &mybus_attr); // 在 sysfs 中创建属性文件
return 0;
}
// 模块退出函数
static void bus_exit(void)
{
bus_remove_file(&mybus, &mybus_attr); // 从 sysfs 中移除属性文件
bus_unregister(&mybus); // 取消注册总线
}
module_init(bus_init); // 指定模块的初始化函数
module_exit(bus_exit); // 指定模块的退出函数
MODULE_LICENSE("GPL"); // 模块使用的许可证
MODULE_AUTHOR("fangchen"); // 模块的作者
实验测试结果
当我们编译好驱动.ko 文件 bus.ko 和 device.ko 后,先加载驱动bus.ko 然后加载设备驱动文件 device.ko ,我们看看实际结果:


也就是说生成了设备文件:/sys/devices/mydevice 且 在 sys/bus/mybus/devices 下面有了 mydevice 的软链接。
二、设备注册流程分析
如上设备注册的源码:
// 模块的初始化函数
static int device_init(void)
{
int ret;
ret = device_register(&mydevice); // 注册设备
return 0;
}
这里要重点分析的流程就是 device_register(&mydevice); 设备是如何注册到总线上去的?
源码device_register
/**
* device_register - register a device with the system.
* @dev: pointer to the device structure
*
* This happens in two clean steps - initialize the device
* and add it to the system. The two steps can be called
* separately, but this is the easiest and most common.
* I.e. you should only call the two helpers separately if
* have a clearly defined need to use and refcount the device
* before it is added to the hierarchy.
*
* For more information, see the kerneldoc for device_initialize()
* and device_add().
*
* NOTE: _Never_ directly free @dev after calling this function, even
* if it returned an error! Always use put_device() to give up the
* reference initialized in this function instead.
*/
int device_register(struct device *dev)
{
device_initialize(dev);
return device_add(dev);
}
慢慢发现驱动C的代码注释其实蛮清晰的,特别清晰 有时候。 就是说 设备注册分为两步,initialize 初始化设备和把设备添加到系统里面去。
device_initialize 设备初始化
device_initialize() 函数
device_initialize() 负责初始化设备结构的基本字段:
void device_initialize(struct device *dev)
{
dev->kobj.kset = devices_kset; // 设置设备所属的kset
kobject_init(&dev->kobj, &device_ktype); // 初始化kobject
INIT_LIST_HEAD(&dev->dma_pools); // 初始化DMA池链表
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); // 设置NUMA节点
}
device_add 添加设备到系统里面去
device_add() 是设备注册的核心函数,主要完成以下工作:
int device_add(struct device *dev)
{
struct device *parent = NULL;
struct kobject *kobj;
struct class_interface *class_intf;
int error = -EINVAL;
// 1. 参数检查和基本设置
dev = get_device(dev);
if (!dev->p) {
error = device_private_init(dev);
if (error)
goto done;
}
// 2. 设置父设备
if (dev->parent)
parent = get_device(dev->parent);
else if (dev->bus && dev->bus->dev_root)
parent = get_device(dev->bus->dev_root);
dev->parent = parent;
// 3. 设置设备名称
if (dev->init_name) {
dev_set_name(dev, "%s", dev->init_name);
dev->init_name = NULL;
}
// 4. 在sysfs中注册设备
error = kobject_add(&dev->kobj, dev->kobj.parent, "%s", dev_name(dev));
if (error)
goto Error;
// 5. 添加到设备层次结构中
if (parent)
klist_add_tail(&dev->p->knode_parent, &parent->p->klist_children);
// 6. 添加到总线的设备列表
if (dev->bus) {
mutex_lock(&dev->bus->p->mutex);
klist_add_tail(&dev->p->knode_bus, &dev->bus->p->klist_devices);
mutex_unlock(&dev->bus->p->mutex);
}
// 7. 触发uevent事件
kobject_uevent(&dev->kobj, KOBJ_ADD);
// 8. 绑定驱动程序
bus_probe_device(dev);
if (parent)
klist_add_tail(&dev->p->knode_driver, &parent->p->klist_drivers);
// 9. 添加到类设备列表
if (dev->class) {
mutex_lock(&dev->class->p->mutex);
klist_add_tail(&dev->p->knode_class, &dev->class->p->klist_devices);
class_dir_update(dev->class);
mutex_unlock(&dev->class->p->mutex);
}
// 10. 通知接口
list_for_each_entry(class_intf, &dev->class->p->interfaces, node)
if (class_intf->add_dev)
class_intf->add_dev(dev, class_intf);
return 0;
Error:
// 错误处理
...
return error;
}
文件创建分析
当设备通过 device_add 注册后,内核会在 /sys/devices/ 下创建对应的设备目录(如 /sys/devices/mydevice),并自动创建一些标准文件和子目录:
dev 文件创建
dev 文件是字符设备和块设备的主次设备号表示,格式为 major:minor。
创建过程:
- 在 device_add 中调用 devtmpfs_create_node 创建设备节点
- 通过 device_create_file 创建 dev 属性文件
- 实际由 dev_attr_show 函数实现读取操作
power 目录创建
power 目录包含与设备电源管理相关的文件,如:
- wakeup - 控制设备是否可唤醒系统
- control - 电源状态控制
- runtime_status - 运行时电源状态信息
创建过程:
- 由 dpm_sysfs_add 函数在 device_pm_add 中调用
- 使用 sysfs_create_group 创建 power 属性组
subsystem 符号链接 创建
subsystem 是一个指向设备所属总线的符号链接(如 /sys/bus/pci)。
创建过程:
- 在 device_add 中调用 bus_add_device
- 由 sysfs_create_link 创建符号链接
- 如果设备没有父设备,还会创建到 devices 目录的链接
uevent 文件 创建
uevent 文件用于触发或显示设备的 uevent 信息。
创建过程:
- 在 device_add 中调用 device_create_file 创建
- 由 uevent_attr 定义,使用 uevent_show 和 uevent_store 作为操作函数
- 写入此文件可以手动触发 uevent 事件
实现流程详解
以下是 device_add 中与 sysfs 创建相关的主要调用流程:
device_add(struct device *dev)
|
|--> device_initialize(dev); // 初始化设备基础属性
|
|--> dev_set_name(dev, "%s", dev->name); // 设置设备名称
|
|--> device_create_sys_dev_entry(dev); // 创建设备号文件
|
|--> device_add_class_symlinks(dev); // 创建类符号链接
|
|--> device_add_attrs(dev); // 添加设备属性
|
|--> bus_add_device(dev); // 将设备添加到总线
| |
| |--> sysfs_create_link(&dev->kobj, &bus->subsys.kobj, "subsystem");
| |--> sysfs_create_link(&bus->subsys.kobj, &dev->kobj, dev_name(dev));
|
|--> dpm_sysfs_add(dev); // 添加电源管理属性
| |
| |--> sysfs_create_group(&dev->kobj, &pm_attr_group);
|
|--> device_create_file(dev, &dev_attr_uevent); // 创建uevent文件
|
|--> device_create_file(dev, &dev_attr_dev); // 创建dev文件
|
|--> kobject_uevent(&dev->kobj, KOBJ_ADD); // 发送uevent事件
通过以上机制,Linux 设备模型在 device_add 时自动创建了标准化的 sysfs 接口,为设备管理和用户空间交互提供了统一的访问方式。
总结
注册设备及设备注册流程分析,通过设备注册实验+注册流程中几个文件或文件夹创建来逐步分析流程。
310

被折叠的 条评论
为什么被折叠?



