• 字符设备驱动框架学习总结


      linux内核版本:4.14.2

      内核把所有的字符设备驱动放在一个长度为255的数组中统一管理。每一个数组元素标识一个字符设备驱动,数组元素主要包含两个内容:设备号(包括主设备号和次设备号)和file_operations结构体。数组下标直接对应主设备号,因此,主设备号相同的字符设备驱动只能有255个;file_operations结构体是文件标准操作集,包括open、read、write、ioctl等基本文件操作。

      字符设备在注册时,会将设备号和file_operations结构体绑定;在创建设备文件时会将设备文件和设备号绑定;应用程序操作设备文件时,VFS会通过设备号将对设备文件的操作映射到file_operations对应的操作;因此,设备驱动程序中只要在file_operations相关操作函数中对硬件操作,那么用户空间的app通过设备文件就能调用file_operations结构体提供的操作间接地对设备进行操作。

      如上所述,编写一个字符设备驱动需要完成如下工作:

    1)申请设备号;

    2)定义一个file_operations结构体,并填充结构体,包括对硬件的相关操作;

    3)将设备号和file_operations结构体绑定并注册到kernel;

    4)用设备号创建设备文件。

     

      编写字符设备驱动有两种接口:

      一种接口是使用register_chrdev函数,这个函数直接完成设备号申请、file_operations结构体的绑定以及注册到kernel中;

          另一种接口将设备号申请、file_operrations结构体绑定以及注册步骤分开,其中,设备号申请用register_chrdev_region(静态申请)/ alloc_chrdev_region(动态申请)函数,file_operrations结构体绑定以及注册用cdev_alloc、cdev_init、cdev_add函数完成;

          其实,两种接口内部调用的函数都是一样的,第二种接口只是将第一种接口函数内部的函数拆分开来实现,如下是两种接口内部的函数调用

    老接口分析

    register_chrdev  -->只能指定主设备号,不能指定次设备号

          __register_chrdev

                __register_chrdev_region -->注册主次设备号

                cdev_alloc  -->kobject_init

                cdev->owner = fops->owner;

                cdev->ops = fops;

                cdev_add  -->kobj_map kobject_get

    新接口分析

    register_chrdev_region

          __register_chrdev_region

     

    alloc_chrdev_region

          __register_chrdev_region

          *dev = MKDEV(cd->major, cd->baseminor)

      两种接口创建设备文件的方法都是一样的,有两种方法创建设备文件:

    1)在shell中直接用命令行创建

          mknod /dev/xxx c 主设备号 次设备号

    2)在驱动程序用class_create、device_create函数创建,这种方法中,先用class_create创建一个设备类,然后调用device_create使用设备号基于这个设备类创建设备文件。设备文件最后是由应用程序udev(嵌入式中用的是mdev)来创建的,驱动程序调用class_create、device_create函数会通过netlink向udev传递消息(设备号和设备文件名),udev收到消息后会创建设备文件。

     

      class_create、device_create函数内部调用如下:

    class_create

          __class_create

                struct class *cls;

                __class_register

                      kset_register

                           kobject_uevent -->向udev发送一个事件

    device_create

          device_create_vargs

                device_create_groups_vargs

                      struct device *dev = NULL;

                      /* device_initialize和device_add完成设备注册 */

                      device_initialize

                           lockdep_set_novalidate_class

                           device_pm_init

                      device_add

                           kobject_add -->将设备添加

                           /* 操作sysfs的函数 */

                           /* */

                           device_create_file

                                 sysfs_create_file

                            device_add_class_symlinks

                               /* 创建符号链接 subsystem -> ../../../../class/lzt_class */

                                 sysfs_create_link(&dev->kobj, &dev->class->p->subsys.kobj, "subsystem");

                           device_add_attrs

                           bus_add_device

                           dpm_sysfs_add

                           device_create_sys_dev_entry

                           devtmpfs_create_node

                           kobject_uevent

                           bus_probe_device

     

      程序示例:

    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/uaccess.h>
    #include <linux/fs.h>
    #include <linux/cdev.h>
    
    
    #define USE_OLD    0
    #define USE_NEW    1
    #define MYCNT   1
    #define MYNAME  "testchar"
    
    //设备文件名/dev/char_test
    #define DEVNAME   "char_test"
    
    static int major = -1;
    static struct class *char_test_class;
    static dev_t mydev;
    static struct cdev *pcdev;
    static char kbuf[100];
    
    static int char_test_open(struct inode *inode, struct file *file)
    {
        printk(KERN_INFO "char_test_open
    ");
    
        return 0;
    }
    
    static int char_test_close(struct inode *inode, struct file *file)
    {
        printk(KERN_INFO "char_test_close
    ");
        return 0;
    }
    
    static const struct file_operations char_test = {
        .owner        = THIS_MODULE,
        .open         = char_test_open,
        .release = char_test_close,
    };
    
    static int __init chrdev_init(void)
    {
        int ret;
        printk(KERN_INFO "chrdev_init
    ");
    
    #if USE_OLD
        /* 注册字符设备 */
        if ((major = register_chrdev(0, MYNAME, &char_test)) < 0) {
            printk(KERN_ERR "%s: register_chrdev failed.
    ", MYNAME);
            goto lable0;
        }
        else {
            printk(KERN_INFO "register_chrdev successful, major is %d.
    ", major);
        }
    
        mydev = MKDEV(major, 0);
    #endif
    
    #if USE_NEW
        //动态获取主次设备号,即mydev
        ret = alloc_chrdev_region(&mydev, 0, MYCNT, MYNAME);
        if (ret < 0) {
            printk(KERN_INFO "alloc_chrdev_region failed.
    ");
    
            goto lable0;
        }
    
        printk(KERN_INFO "alloc_chrdev_region success.
    ");
        printk(KERN_INFO "major = %d minor = %d.
    ", MAJOR(mydev), MINOR(mydev));
    
        // 第二步:注册字符设备驱动
        pcdev = cdev_alloc();  //分配内存,实例化指针
        cdev_init(pcdev, &char_test);
        ret = cdev_add(pcdev, mydev, MYCNT);
        if (ret) {
            printk(KERN_INFO "cdev_add failed.
    ");
            goto lable1;
        }
    
        printk(KERN_INFO "cdev_add success.
    ");
    #endif
    
        /* 创建设备文件 */
        char_test_class = class_create(THIS_MODULE, "lll_class");
        if (IS_ERR(char_test_class)) {
            goto lable2;
        }
    
        // 最后一个参数就是在/dev目录下创建的设备文件的名字
        device_create(char_test_class, NULL, mydev, NULL, DEVNAME);
    
        return 0;
    
    lable2:
        cdev_del(pcdev);
    lable1:
    #if USE_OLD
        unregister_chrdev(major, DEVNAME);
    #endif
    
    #if USE_NEW
        unregister_chrdev_region(mydev, MYCNT);
    #endif
    lable0:
        return -EINVAL;
    }
    
    static void __exit chrdev_exit(void)
    {
        // 注销设备文件
        device_destroy(char_test_class, mydev);
        class_destroy(char_test_class);
        //注销字符设备
    
    #if USE_OLD
        unregister_chrdev(major, DEVNAME);
    #endif
    
    #if USE_NEW
        // 注销设备文件
        device_destroy(char_test_class, mydev);
        class_destroy(char_test_class);
        // 注销cdev
        cdev_del(pcdev);
        // 第二步去注销申请的主次设备号
        unregister_chrdev_region(mydev, MYCNT);
    #endif
        printk(KERN_INFO "chrdev_exit
    ");
    }
    
    module_init(chrdev_init);
    module_exit(chrdev_exit);
    
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("lll");
    MODULE_DESCRIPTION("module test");
    MODULE_ALIAS("alias xxx");

       insmod加载驱动后,生成设备文件/dev/char_test,在/sys/class目录下还会生成一个类/sys/class/lll_class

     

     

     

  • 相关阅读:
    Linux —— 查询端口对应进程,并杀死
    Mac m1 安装 swoole
    Linux——列出所有打开的端口
    Swoole——创建TCP服务
    PHP——16进制转10进制
    PHP——SWOOLE发送串口指令
    PHP实现Modbus RTU CRC16校验
    Docker之操作系统Alpine
    jmeter 动态参数
    Docker部署Apache Airflow
  • 原文地址:https://www.cnblogs.com/lztutumo/p/13340135.html
Copyright © 2020-2023  润新知