1 以前的理解
在LDD3的十四章,是Linux设备模型,有说到这个部分。
不管是udev还是uevent,里面的u都是指的userspace,也就是用户空间。
之前的理解是自动在应用层也就是用户空间实现设备管理,处理内核的设备事件。事件来自sysfs和/sbin/hotplug。在驱动中,只要是使用了新版的函数,相应的事件就会自动发给udev,udev可以创建和删除设备节点。后面也大概得看了一下书。
====================20250118更新====================
2 LDD十四章
书里面还是讲得很细,重点就是下面这个图。
然后讲了kobject,kset,总线,类,热拔插很多内容。但是有一个很重要的概念没怎么说清楚,就是设备模型到底是干嘛的,着墨却不多。这也是我觉得这个书编排设计不大好的地方。
目前我的理解,设备模型,就是linux 2.6(2003年发布)里面集成的一套标准设备驱动框架,一套封装而已。你用了这套东西,那么sys里面的管理,热拔插,设备树,ACPI,电源管理,这些封装自动就给你干了,你要省一些事。
对一个linux设备驱动来说,你可以使用设备模型,也可以不使用,用了你要省不少事。
先理解这个,至于什么kobject,类,后面可以再慢慢看。
设备模型的核心架构是分层的,主要包括 总线(Bus)、设备(Device)、驱动(Driver) 和 类(Class)。 每一层都实现特定的功能:
总线层:管理设备和驱动的绑定,如 PCI 总线或 USB 总线。
设备层:表示具体的硬件设备,包含设备的基本属性。
驱动层:负责具体设备的操作逻辑。
类层:对功能相似的设备进行分类管理,例如块设备类、字符设备类。
3 lddbus代码
代码呢也不是书里说的scullp,而是lddbus。示例代码很简单。
首先还是一个初始化,一个退出。
module_init(ldd_bus_init);
module_exit(ldd_bus_exit);
init也很简单
static int __init ldd_bus_init(void)
{
int ret;
ret = bus_register(&ldd_bus_type);
if (ret) {
printk(KERN_ERR "Unable to register ldd bus, failure was %d\n",ret);
return ret;
}
if (bus_create_file(&ldd_bus_type, &bus_attr_version))
printk(KERN_ERR "Unable to create version attribute\n");
dev_set_name(&ldd_bus,"ldd0");
ret = device_register(&ldd_bus);
if (ret) {
printk(KERN_ERR "Unable to register ldd0, failure was %d\n",ret);
goto out_fail;
}
return 0;
out_fail:
bus_unregister(&ldd_bus_type);
return ret;
}
这里可以看到,传统的驱动是register_chrdev,注册字符设备。而设备模型呢,是用的bus_register和device_register,或者是class_create创建设备类,之后device_create。
卸载也很简单:
static void ldd_bus_exit(void)
{
device_unregister(&ldd_bus);
bus_unregister(&ldd_bus_type);
}
可以看到,就是两个unregister。传统的是直接unregister_chrdev。
4 lddbus运行
编译还是很简单,直接编,不过要用网友改过的版本哈。里面代码也有一点点问题,要加一个const。
之后直接
sudo insmod lddbus.ko
之后可以看到sys下面自动生成了两个部分。/sys/bus/ldd,和/sys/devices/ldd0。这里/sys/bus/ldd表示的是总线,/sys/devices/ldd0表示的具体设备。
ubuntu@VM-8-10-ubuntu:~/ldd3/lddbus$ tree /sys/bus/ldd
/sys/bus/ldd
├── devices
├── drivers
├── drivers_autoprobe
├── drivers_probe
├── uevent
└── version
cat /sys/bus/ldd/version
$Revision: 1.9 $
这里的查看版本,就是对应的代码中的version_show。
ubuntu@VM-8-10-ubuntu:~/ldd3/lddbus$ tree /sys/devices/ldd0
/sys/devices/ldd0/
├── power
│ ├── async
│ ├── autosuspend_delay_ms
│ ├── control
│ ├── runtime_active_kids
│ ├── runtime_active_time
│ ├── runtime_enabled
│ ├── runtime_status
│ ├── runtime_suspended_time
│ └── runtime_usage
└── uevent
uevent。
再删除或者加载后,内核都会收到event。
bash
ubuntu@VM-8-10-ubuntu:~/ldd3/lddbus$ udevadm monitor --environment --kernel
monitor will print the received events for:
KERNEL - the kernel uevent
KERNEL[515376.156676] remove /bus/ldd (bus)
ACTION=remove
DEVPATH=/bus/ldd
SUBSYSTEM=bus
SEQNUM=3175
KERNEL[515376.160202] remove /module/lddbus (module)
ACTION=remove
DEVPATH=/module/lddbus
SUBSYSTEM=module
SEQNUM=3176
KERNEL[515394.581522] add /bus/ldd (bus)
ACTION=add
DEVPATH=/bus/ldd
SUBSYSTEM=bus
SEQNUM=3177
KERNEL[515394.581579] add /module/lddbus (module)
ACTION=add
DEVPATH=/module/lddbus
SUBSYSTEM=module
SEQNUM=3178
同时,使用命令也可以手动发送事件:
bash
echo add | sudo tee /sys/devices/ldd0/uevent > /dev/null
echo add | sudo tee /sys/bus/ldd/uevent > /dev/null
这里要用tee,否则会出现权限不足的情况。
这样就可以在驱动的回调中收到这个消息。在测试中,发现只有发送到devices的才能在回调收到,如果是发到bus的,则收不到。
cpp
/*
* Respond to udev events.
*/
#if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 3, 0))
static int ldd_uevent(struct device *dev, struct kobj_uevent_env *env)
#else
static int ldd_uevent(const struct device *dev, struct kobj_uevent_env *env)
#endif
{
printk(KERN_DEBUG "lddbus ldd_uevent\n");
if (add_uevent_var(env, "LDDBUS_VERSION=%s", Version))
return -ENOMEM;
return 0;
}
此外,在udevadm monitor中,两个都可以收到,这样就可以编写脚本来处理事件了。
cpp
ubuntu@VM-8-10-ubuntu:~/ldd3/lddbus$ udevadm monitor --environment --kernel
monitor will print the received events for:
KERNEL - the kernel uevent
KERNEL[518487.496037] add /devices/ldd0 (ldd)
ACTION=add
DEVPATH=/devices/ldd0
SUBSYSTEM=ldd
SYNTH_UUID=0
LDDBUS_VERSION=$Revision: 1.9 $
SEQNUM=3201
KERNEL[518494.406092] add /bus/ldd (bus)
ACTION=add
DEVPATH=/bus/ldd
SUBSYSTEM=bus
SYNTH_UUID=0
SEQNUM=3202
踩的一些坑:
源代码设备没有注册到总线
cpp
struct device ldd_bus = {
.init_name = "ldd0",//缺了
.bus = &ldd_bus_type,//缺了
.release = ldd_bus_release
};
源代码只有.release,这样设备没有注册到总线,发送event的话是响应不到的。必须要增加上面的两句。之后在bus中查看,要在bus下面的devices下面出现一个链接到devices才行。
bash
ubuntu@VM-8-10-ubuntu:~/ldd3/lddbus$ ll /sys/bus/ldd/devices/
total 0
drwxr-xr-x 2 root root 0 Jan 18 15:22 ./
drwxr-xr-x 4 root root 0 Jan 18 15:13 ../
lrwxrwxrwx 1 root root 0 Jan 18 15:22 ldd0 -> ../../../devices/ldd0/
5 修改后的lddbus.c
cpp
/*
* A virtual bus for LDD sample code devices to plug into. This
* code is heavily borrowed from drivers/base/sys.c
*
* Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet
* Copyright (C) 2001 O'Reilly & Associates
*
* The source code in this file can be freely used, adapted,
* and redistributed in source or binary form, so long as an
* acknowledgment appears in derived source files. The citation
* should list that the code comes from the book "Linux Device
* Drivers" by Alessandro Rubini and Jonathan Corbet, published
* by O'Reilly & Associates. No warranty is attached;
* we cannot take responsibility for errors or fitness for use.
*
*/
/* $Id: lddbus.c,v 1.9 2004/09/26 08:12:27 gregkh Exp $ */
#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/version.h>
#include "lddbus.h"
MODULE_AUTHOR("Jonathan Corbet");
MODULE_LICENSE("Dual BSD/GPL");
static char *Version = "$Revision: 1.9 $";
/*
* Respond to udev events.
*/
#if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 3, 0))
static int ldd_uevent(struct device *dev, struct kobj_uevent_env *env)
#else
static int ldd_uevent(const struct device *dev, struct kobj_uevent_env *env)
#endif
{
printk(KERN_DEBUG "lddbus ldd_uevent\n");
if (add_uevent_var(env, "LDDBUS_VERSION=%s", Version))
return -ENOMEM;
return 0;
}
/*
* Match LDD devices to drivers. Just do a simple name test.
*/
static int ldd_match(struct device *dev, struct device_driver *driver)
{
return !strncmp(dev_name(dev), driver->name, strlen(driver->name));
}
/*
* The LDD bus device.
*/
static void ldd_bus_release(struct device *dev)
{
printk(KERN_DEBUG "lddbus release\n");
}
static int ldd_driver_probe(struct device *dev)
{
printk(KERN_DEBUG "lddbus probe\n");
pr_info("ldd_driver_probe: Device probed: %s\n", dev_name(dev));
return 0;
}
/*
* And the bus type.
*/
struct bus_type ldd_bus_type = {
.name = "ldd",
.match = ldd_match,
.uevent = ldd_uevent,
.probe = ldd_driver_probe,
};
struct device ldd_bus = {
.init_name = "ldd0",
.bus = &ldd_bus_type,
.release = ldd_bus_release
};
/*
* Export a simple attribute.
*/
static ssize_t version_show(const struct bus_type *bus, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n", Version);
}
static BUS_ATTR_RO(version);
/*
* LDD devices.
*/
/*
* For now, no references to LDDbus devices go out which are not
* tracked via the module reference count, so we use a no-op
* release function.
*/
static void ldd_dev_release(struct device *dev)
{ }
int register_ldd_device(struct ldd_device *ldddev)
{
ldddev->dev.bus = &ldd_bus_type;
ldddev->dev.parent = &ldd_bus;
ldddev->dev.release = ldd_dev_release;
dev_set_name(&ldddev->dev, ldddev->name);
return device_register(&ldddev->dev);
}
EXPORT_SYMBOL(register_ldd_device);
void unregister_ldd_device(struct ldd_device *ldddev)
{
device_unregister(&ldddev->dev);
}
EXPORT_SYMBOL(unregister_ldd_device);
/*
* Crude driver interface.
*/
static ssize_t show_version(struct device_driver *driver, char *buf)
{
struct ldd_driver *ldriver = to_ldd_driver(driver);
sprintf(buf, "%s\n", ldriver->version);
return strlen(buf);
}
int register_ldd_driver(struct ldd_driver *driver)
{
int ret;
driver->driver.bus = &ldd_bus_type;
ret = driver_register(&driver->driver);
if (ret)
return ret;
driver->version_attr.attr.name = "version";
driver->version_attr.attr.mode = S_IRUGO;
driver->version_attr.show = show_version;
driver->version_attr.store = NULL;
return driver_create_file(&driver->driver, &driver->version_attr);
}
void unregister_ldd_driver(struct ldd_driver *driver)
{
driver_unregister(&driver->driver);
}
EXPORT_SYMBOL(register_ldd_driver);
EXPORT_SYMBOL(unregister_ldd_driver);
static int __init ldd_bus_init(void)
{
int ret;
ret = bus_register(&ldd_bus_type);
if (ret) {
printk(KERN_ERR "Unable to register ldd bus, failure was %d\n",ret);
return ret;
}
if (bus_create_file(&ldd_bus_type, &bus_attr_version))
printk(KERN_ERR "Unable to create version attribute\n");
dev_set_name(&ldd_bus,"ldd0");
printk(KERN_DEBUG "lddbus ldd0\n");
ret = device_register(&ldd_bus);
if (ret) {
printk(KERN_ERR "Unable to register ldd0, failure was %d\n",ret);
goto out_fail;
}
//char *envp[] = { "CUSTOM_EVENT=my_event", "STATUS=online", NULL };
//kobject_uevent_env(&ldd_bus->kobj, KOBJ_CHANGE, envp);
return 0;
out_fail:
bus_unregister(&ldd_bus_type);
return ret;
}
static void ldd_bus_exit(void)
{
device_unregister(&ldd_bus);
bus_unregister(&ldd_bus_type);
}
module_init(ldd_bus_init);
module_exit(ldd_bus_exit);