字符设备驱动模型
1. 字符设备驱动的基本流程
分配主次设备号:
- 使用
alloc_chrdev_region
(动态分配) 或register_chrdev_region
(静态分配)函数分配设备号。
- 使用
注册字符设备:
2.1 字符设备的初始化
使用
cdev_init
函数初始化cdev
结构体,并关联file_operations
。2.2 字符设备的添加
使用
cdev_add
函数将字符设备添加到内核中。创建设备节点:
- 使用
mknod
命令或udev
规则创建设备节点。 class_create
和device_create
用于动态创建设备类和设备节点的函数。**class_create**
用于创建一个设备类,设备类通常对应于/sys/class/
下的一个目录。device_create
在设备类下创建设备节点。备节点会自动出现在/dev/
目录下,同时会在/sys/class/
下创建相应的属性文件。
- 使用
实现文件操作函数:
- 实现
file_operations
结构体中的函数指针,如open
、release
、read
、write
等。
- 实现
注销字符设备:
- 在模块卸载时,使用
cdev_del
和unregister_chrdev_region
函数注销字符设备并释放设备号。
- 在模块卸载时,使用
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#define DEVICE_NAME "my_char_device"
static int major;
static struct cdev my_cdev;
static struct class *my_class;
static struct device *my_device;
static int my_open(struct inode *inode, struct file *file)
{
printk(KERN_INFO "Device opened\n");
return 0;
}
static int my_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "Device closed\n");
return 0;
}
static ssize_t my_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{
printk(KERN_INFO "Read operation\n");
return 0;
}
static ssize_t my_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
{
printk(KERN_INFO "Write operation\n");
return count;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = my_open,
.release = my_release,
.read = my_read,
.write = my_write,
};
static int __init my_init(void)
{
dev_t dev;
int ret;
// 动态分配设备号
ret = alloc_chrdev_region(&dev, 0, 1, DEVICE_NAME);
if (ret < 0) {
pr_err("Failed to allocate device number\n");
return ret;
}
major = MAJOR(dev);
// 初始化 cdev
cdev_init(&my_cdev, &fops);
my_cdev.owner = THIS_MODULE;
// 注册字符设备
ret = cdev_add(&my_cdev, dev, 1);
if (ret < 0) {
pr_err("Failed to add cdev\n");
unregister_chrdev_region(dev, 1);
return ret;
}
// 创建设备类
my_class = class_create(THIS_MODULE, "my_device_class");
if (IS_ERR(my_class)) {
pr_err("Failed to create class\n");
cdev_del(&my_cdev);
unregister_chrdev_region(dev, 1);
return PTR_ERR(my_class);
}
// 创建设备节点
my_device = device_create(my_class, NULL, dev, NULL, "my_device");
if (IS_ERR(my_device)) {
pr_err("Failed to create device\n");
class_destroy(my_class);
cdev_del(&my_cdev);
unregister_chrdev_region(dev, 1);
return PTR_ERR(my_device);
}
printk(KERN_INFO "Device registered with major number %d\n", major);
return 0;
}
static void __exit my_exit(void)
{
dev_t dev = MKDEV(major, 0);
// 销毁设备节点
device_destroy(my_class, dev);
// 销毁设备类
class_destroy(my_class);
// 注销字符设备
cdev_del(&my_cdev);
unregister_chrdev_region(dev, 1);
printk(KERN_INFO "Device unregistered\n");
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple character device driver using class_create and device_create");
2.Linux设备模型
linux 设备驱动模型
设备模型是Linux内核中一个非常重要的概念,很多复杂的驱动(比如platform、USB、I2C),都是以设备模型为基础进行构建的。如果你在阅读驱动源码时感觉很吃力,感觉太复杂,错综复杂,无法真正理解其全景框架和底层的运行逻辑,这里真诚地建议你可以尝试从设备模型学起:设备模型以最核心的kobject和kset数据结构构建了设备树的基本骨架,又通过device、bus、driver、class进一步封装,构建了设备模型的基本能力:总线匹配、电源管理、热插拔机制… 本期课程从设备模型最核心的kobject和kset讲起,全网首创使用OOP思想进行讲解,一步一步讲解内核中设备模型的封装过程,通过实际编程,从零编写一个总线子系统,向大家展示一个内核模块是如何从最基本的功能,慢慢迭代和进化成一个子系统的,总线是如何match的,设备是如何probe的,热插拔事件是如何产生和发送的,我们如何监听和解析这些热插拔事件?设备节点是如何自动生成的?通过本期课程的学习,通过自己编程来实现这些功能,你将真正理解这些底层细节,真实地感受到它是如何一步一步实现和运行的,而不仅仅是停留在脑海中的一个抽象概念。 通过本期课程的学习,你的预期收获如下: 理解kobject、kset、attribute、uevent在设备模型中的作用 掌握sysfs文件系统:注册、挂载、文件的打开读写流程 理解sysfs文件系统和设备模型的关联 掌握驱动中的device、bus、device_driver、class编程接口 真正理解热插拔事件:hotplug/uevent 学会自定义、发送、解析热插拔uevent事件 学会自己编程,实现设备节点的自动创建 学会如何编写总线型(bus)驱动 学会从零实现一个bus子系统 理解udev/mdev在设备模型中的作用
什么是设备树 dts(device tree)
设备树(Device Tree)是描述计算机的特定硬件设备信息的数据结构,以便于操作系统的内核可以管理和使用这些硬件,包括CPU或CPU,内存,总线和其他一些外设。dtb文件会被保存到ROM中,最终通过bootbolader被加载到内核,这样内核就可以通过解析设备树来让驱动去控制实际的硬件了。
(1) dts
硬件的相应信息都会写在.dts为后缀的文件中,每一款硬件可以单独写一份xxxx.dts,一般在Linux源码中存在大量的dts文件,对于arm架构可以在arch/arm/boot/dts找到相应的dts
(2) dtc
dtc是编译dts的工具,可以在Ubuntu系统上通过指令apt-get install device-tree-compiler安装dtc工具,不过在内核源码scripts/dtc路径下已经包含了dtc工具。
(3) dtb
dtb(Device Tree Blob),dts经过dtc编译之后会得到dtb文件,dtb通过Bootloader引导程序加载到内核。所以Bootloader需要支持设备树才行;Kernel也需要加入设备树的支持。
platform_device
struct platform_device
{
//设备的名字,用于和驱动进行匹配的`
const char *name;
//内核中维护的所有的设备必须包含该成员
struct device dev;
//资源个数
u32 num_resources;
//描述资源
struct resource * resource;
...
}
设备注册
platform_device_register(struct platform_device *);
platform_driver
struct platform_driver
{
//当驱动和硬件信息匹配成功之后,就会调用probe函数,驱动所有的资源的注册和初始化全部放在probe函数中
int (*probe)(struct platform_device *);
//硬件信息被移除了,或者驱动被卸载了,全部要释放,释放资源的操作就放在该函数中
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
//内核维护的所有的驱动必须包含该成员,通常driver->name用于和设备进行匹配
struct device_driver driver;
//往往一个驱动可能能同时支持多个硬件,这些硬件的名字都放在该结构体数组中
const struct platform_device_id *id_table;
bool prevent_deferred_probe;
}
驱动注册和卸载
platform_driver_register(struct platform_driver *);
platform_driver_unregister(struct platform_driver *);
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 351134995@qq.com