LDD3学习8--linux的设备模型(udev)

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);
相关推荐
fanged1 小时前
LDD3学习7--硬件接口I/O端口(以short为例)
linux
学而知不足~1 小时前
Linux测试处理fps为30、1920*1080、一分钟的视频性能
linux·音视频
难以怀瑾1 小时前
测试工程师的linux 命令学习(持续更新中)
linux·运维·服务器
m0_748252601 小时前
【万字详细教程】Linux to go——装在移动硬盘里的Linux系统(Ubuntu22.04)制作流程;一口气解决系统安装引导文件迁移显卡驱动安装等问题
linux·运维·golang
兮动人2 小时前
Linux 下配置 Golang 环境
linux·运维·golang
bank_dreamer2 小时前
linux Debian包管理器apt安装软件包由于依赖关系安装失败解决方法
linux·运维·debian
诸葛百家2 小时前
linux下springboot项目nohup日志或tomcat日志切割处理方案
linux·spring boot·tomcat
s_little_monster3 小时前
【Linux】打破Linux神秘的面纱
linux·运维·经验分享·笔记·学习·学习方法
钟离墨笺3 小时前
【网络协议】【http】【https】AES-TLS1.2
linux·计算机网络·http·https
糯米汤圆~3 小时前
MySQL备份案例: mysqldump+binlog实现完全+增量备份
linux·运维·数据库·mysql