字符设备驱动模型

创建设备文件
利用 cat /proc/devices 查看申请到的设备名,设备号。
struct cdev
在Linux内核中, 使用cdev结构体来描述一个字符设备
<include/linux/cdev.h>
struct cdev {
struct kobject kobj; //内嵌的内核对象.
struct module *owner; //该字符设备所在的内核模块的对象指针.
const struct file_operations *ops; //该结构描述了字符设备所能实现的方法,是极为关键的一个结构体.
struct list_head list; //用来将已经向内核注册的所有字符设备形成链表.
dev_t dev; //字符设备的设备号,由主设备号和次设备号构成.
unsigned int count; //隶属于同一主设备号的次设备号的个数.
};
cdev_init()
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
memset(cdev, 0, sizeof *cdev); //将整个结构体清零
INIT_LIST_HEAD(&cdev->list); //初始化list成员使其指向自身
kobject_init(&cdev->kobj, &ktype_cdev_default); //初始化kobj成员
cdev->ops = fops; //初始化ops成员
}
cdev_add()
该函数向内核注册一个struct cdev结构 ,即正式通知内核由struct cdev *p代表的字符设备已经可以使用了。
1)第一个设备号 dev,
(2)和该设备关联的设备编号的数量。
cdev_del()
该函数向内核注销一个struct cdev结构 ,即正式通知内核由struct cdev *p代表的字符设备已经不可以使用了。
struct file_operations
struct file_operations {
struct module *owner;
fop_flags_t fop_flags;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iopoll)(struct kiocb *kiocb, struct io_comp_batch *,
unsigned int flags);
int (*iterate_shared) (struct file *, struct dir_context *);
__poll_t (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
......
} __randomize_layout;
创建设备节点
1)使用mknod手工创建 :mknod filename type major minor
2)自动创建设备节点: 利用 udev(mdev) 来实现设备文件的自动创建,首先应保证支持udev(mdev),由busybox配置。
在驱动用加入对udev 的支持主要做的就是:在驱动初始化的代码里 调用class_create(…)为该设备创建一个class ,再为每个设备调用device_create(…)创建对应的设备 。
内核中定义的struct class结构体,顾名思义,一个struct class结构体类型变量对应一个类,内核同时提供了class_create(…)函数,可以用它来创建一个类,这个类存放于 sysfs 下面,一旦创建好了这个类,再调用 device_create(…)函数来在/dev目录下创建相应的设备节点。
这样,加载模块的时候, 用户空间中的udev会自动响应 device_create()函数,去/sysfs下寻找对应的类从而创建设备节点
class_create()
class_create(owner, name)
owner:THIS_MODULE
name:名字
class_destroy()
void class_destroy(struct class *cls)
device_create()
struct device *device_create(const struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...)
功能:创建一个字符设备文件
参数:
struct class *class :类
struct device *parent:NULL
dev_t devt :设备号
void *drvdata :null、
const char *fmt :名字
device_destroy()
device_destroy(cls,devno);
udev

自2.6 内核开始,引入了 sysfs 文件系统 。sysfs 把连接在系统上的设备和总线组织成一个分级的文件,并提供给用户空间存取使用。 udev 运行在用户模式,而非内核中 。 udev 的初始化脚本在系统启动时创建设备节点,并且当插入新设备——加入驱动模块——在sysfs上注册新的数据后,udev会创新新的设备节点 。

字设备驱动模型
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");
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
