字符设备驱动模型

字符设备驱动模型

字符设备三者关系

创建设备文件

利用 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

udev工作流程

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

class几个对应关系

字设备驱动模型

1. 字符设备驱动的基本流程

  1. 分配主次设备号:

    • 使用 alloc_chrdev_region(动态分配) 或 register_chrdev_region (静态分配)函数分配设备号。
  2. 注册字符设备:

    2.1 字符设备的初始化

    ​ 使用 cdev_init 函数初始化 cdev 结构体,并关联 file_operations

    2.2 字符设备的添加

    ​ 使用 cdev_add 函数将字符设备添加到内核中。

  3. 创建设备节点:

    • 使用 mknod 命令或 udev 规则创建设备节点。
    • class_createdevice_create用于动态创建设备类和设备节点的函数。
    • **class_create**用于创建一个设备类,设备类通常对应于 /sys/class/ 下的一个目录。
    • device_create在设备类下创建设备节点。备节点会自动出现在 /dev/ 目录下,同时会在 /sys/class/ 下创建相应的属性文件。
  4. 实现文件操作函数:

    • 实现 file_operations 结构体中的函数指针,如 openreleasereadwrite 等。
  5. 注销字符设备:

    • 在模块卸载时,使用 cdev_delunregister_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也需要加入设备树的支持。
dtb

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 *);

platform流程图


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 351134995@qq.com

×

喜欢就点赞,疼爱就打赏