• 字符设备基础了解


    一.字符设备驱动

    1. 使用一个结构描述字符设备对象:
    struct cdev {
    struct kobject kobj;  // 文件系统相关,由系统来管理,不需要自己添加
    struct module *owner;  // 用于模块计数
    const struct file_operations *ops; // 对硬件的操作方法集
    struct list_head list;  // 用于管理字符设备的链表
    dev_t dev;   // 设备号;把软件和硬件结合起来;主设备号<<20 +次设备号
    unsigned int count; //  同一主设备号下的,次设备号的个数
    };

      2. 字符设备注册分 3 个步骤

            分配cdev

        初始化cdev

        添加cdev

       3. 驱动中字符设备文件的相关 3 个结构

        struct file 代表一个打开的文件,由内核打开时创建,关闭时释放

        struct inode 记录文件的物理上信息inode

        struct file_operations 函数指针集合

       4. 字符设备结构中的 struct file_operations 结构是对硬件操作的方法集,通过在驱动程序中重新实现并注册到内核中,供应用层调用

     

     

    方法一:手动申请字符设备cdev,静态创建设备节点

    1. 对字符设备的操作流程:总结编写一个字符设备驱动要必做的几步:

        (1)定义一个字符设备:struct cdev *cdev;

        (2)为字符设备分配空间:cdev_alloc(void);

        (3)初始化字符设备对象(对硬件操作的方法集):cdev_init(struct cdev *, const struct file_operations *);

        (4)向内核申请一个设备号:register_chrdev_region(dev_t from, unsigned count, const char * name);

        (5)添加当前的字符设备到内核中:cdev_add(struct cdev *, dev_t, unsigned);

        (6)卸载字符设备对象:

            a) cdev_del(struct cdev *); // 删除字符设备

            b) unregister_chrdev_region(dev_t from, unsigned count); //删除

       2. 设备号静态申请:int register_chrdev_region(dev_t from, unsigned count, const char *name)

       3. 手动创建设备节点: mknod  /dev/devchar0     c                222          0

                          命令  设备节点名称   字符设备  主设备号  次设备号

     

    方法二:自动申请cdev获取主设备号,动态创建设备节点( 3个步骤 )

    1.通过 register_chrdev 自动申请一个cdev字符设备,动态获取主设备号,并注册方法集,就完成了上面方法一中的前 5

      static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops);

      参数:major0时自动获取主设备号,>0 时静态获取不一定成功获取

            name:设备名称

            fops:对硬件操作的方法集

      返回值:return major ? 0 : cd->major;,成功返回主设备号,失败返回0

    2.创建放设备节点的目录:创建一个目录在 /sys/class/xxx

      class_create(owner, name) 这是一个宏函数,具体通过下面函数实现

        struct class *__class_create(struct module *owner, const char *name, struct lock_class_key *key)

      参数:owner:是一个宏表示这个模块

            name:创建的目录名,放在  /sys/class/ 下,如name=mychar--  /sys/class/mychar

      返回值:创建的这个目录对象的指针,根据这个指针找这个目录

    3.创建设备节点,代替mknod的操作

    struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...);

      参数:class:创建存放设备节点的目录

            parent:指向此新设备的父结构设备的指针(如果有的话),一般NULL

         devt:要添加的设备号,包含主设备号和次设备号

         drvdata:要添加到设备中的回调的数据

         fmt:用于设备名称的字符串,后面是可变参数,用于格式化fmt的;

      返回值:成功返回device  ;失败返回ERR_PTR(retval). 看内核中的例子怎么使用返回值

    如device_create(cls, NULL, MKDEV(major, 0), NULL, "Demochar%d",0); 这个设备节点的名称:“Demochar0

     

    4. 对于向系统申请的资源要记得归还

    void class_destroy(struct class *cls):删除cls创建的目录

    void device_destroy(struct class *class, dev_t devt):功能:释放设备节点

      参数:cls : 申请到的class

          devt: 设备号,包含主设备号和次设备号;通过宏函数 MKDEV(major,minor)制作

     

     

    二.关于设备号:

    主设备号用来标识与设备文件相连的驱动程序;反映的是设备类型。

    次设备号被驱动程序用来识别操作的是哪一个设备;用来区分同类型的设备。

     

    Linux使用一个结构描述设备号:dev_t 12位是主设备号,低20位是次设备号

    关于设备号操作的 3 个宏函数

    #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
    #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
    #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))

     

    主设备号的 2 种获取方法

      1.静态申请

        在内核源码路径下确定一个没有使用的主设备号 Documentation/devices.txt

        通过函数  register_chrdev_region 注册设备号

          int register_chrdev_region(dev_t from, unsigned count, const char *name)

      2. 动态分配:

        通过函数  alloc_chrdev_region 动态分配

          int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

          参数: dev :设备号,0 动态分配; >0

             baseminor:起始的次设备号

                count:需要分配的设备号数目

             name:设备名(体现在/proc/devices中)

         注销设备:void unregister_chrdev_region(dev_t from, unsigned count)

          释放从 from 开始的 count 个设备号

     

    有了设备号,创建设备文件 2 种方法

    3.使用 mknod 命令手动创建

      mknod  drvname  type  major  minor

      drvname:设备文件名

      type:设备文件类型 c字符设备; d块设备

      major:主设备号

      minor:次设备号

    4.自动创建(加载模块时注册创建,卸载模块时释放)

      1) 函数 register_chrdev 自动获取注册一个字符设备并申请设备号,同时注册file_operations操作方法集

      2)  函数class_create创建一个struct class类 ,这个类存放在sysfs下面,一旦创建好了这个类, 调用device_create函数在/dev目录下创建相应的设备节点。这样加载模块时,用户空间中udev会自动响应device_create函数,去/sysfs下寻找对用的类从而创建设备节点。

      3)  函数device_create返回值是创建一个struct device型设备关联设备号,class,和设备节点

     

     

    三.应用层与驱动间拷贝数据

    从底层拷贝数据给应用层:copy_to_user   

    从应用层拷贝数据给底层:copy_from_user

    内核提供了专门的函数用于访问用户号空间指针

    unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n)

    unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n)

    __builtin_constant_p(EXP) GCC编译器的内嵌函数,用于判断一个值是否为编译时常数,如果参数EXP 的值是常数,函数返回 1,否则返回 0

     

     --------驱动程序demo.c

    #include <linux/module.h>
    #include <linux/fs.h>
    #include <linux/device.h>
    #include <linux/cdev.h>
    #include <linux/kdev_t.h>
    #include <linux/uaccess.h>
    #include <linux/err.h>
    #include <linux/kernel.h>
    
    /* 变量定义区域 */ 
    const char * name = "demochdrev"; // 字符驱动名字
    unsigned int major;     // 主设备号
    const char *clsname = "mychr"; // 创建class类的名字(不清楚具体什么用途)
    struct class *mycls;
    struct device *mydev;
    
    // read 系统调用
    ssize_t demo_read(struct file *filp, char __user *buf, size_t count, loff_t * ppos)
    {
        printk(KERN_INFO "kernel read 
    ");
        return 0; 
    }
    
    // write 系统调用
    ssize_t demo_write(struct file *filp, const char __user *buf, size_t count, loff_t *offset)
    {
        printk(KERN_INFO "kernel write 
    ");
        return 0;
    }
    
    // close 系统调用
    int demo_release(struct inode *iod, struct file *filp)
    {
        printk(KERN_INFO "kernel release 
    "); 
        return 0;
    }
    
    // open 系统调用
    int demo_open(struct inode *iod, struct file *filp)
    {
        printk(KERN_INFO "kernel open 
    ");
        return 0;
    }
    
    // 对文件的操作方法集
    struct file_operations fops = {
            .owner = THIS_MODULE,
            .read = demo_read,     // 函数实现要在其上面,不然在此处找不到函数的声明
            .write = demo_write,
            .open = demo_open,
            .release = demo_release,
    };
    
    /* 模块 3 步操作 */
    // 模块入口,申请字符设备用到的资源
    static int __init demo_init(void)
    {
        printk(KERN_INFO "module init 
    ");
        // 1. 注册一个字符设备 cdev
        major = register_chrdev(0, name, &fops); // major=0 表示自动分配主设备号,其返回值是主设备号
        if(major <= 0){         // 注册字符设备失败
            printk(KERN_INFO "register chrdev fail 
    ");
        }
    
        // 2.自动创建设备节点 /sys/class 目录下的文件夹名
        // 2.1 创建一个 class 类
        mycls = class_create(THIS_MODULE, clsname); // 返回值 struct class* 类型
        if (IS_ERR(mycls)){     // 创建 class 失败
            printk(KERN_INFO "class create fail 
    ");
            unregister_chrdev(major, name); // 在每步检测申请失败了,就要释放前面申请的资源
            return PTR_ERR(mycls);
        }
        // 2.2 创建设备节点的名字
        mydev = device_create(mycls, NULL,MKDEV(major, 0) , NULL, "demochr%d", 0); // 最后 2 个参数会制作这个驱动的可见的名字,安装模块时会看见/dev/demochr0
        if (IS_ERR(mydev)) {     // 创建设备节点失败
            printk(KERN_INFO "failed to create device
    ");
            unregister_chrdev(major, name); 
            class_destroy(mycls);
            return PTR_ERR(mydev);
        }
        return 0;
    }
    
    // 模块出口,释放申请的资源
    static void __exit demo_exit(void)
    {
        printk(KERN_INFO "module exit 
    ");
        device_destroy(mycls, MKDEV(major, 0)); // 上面创建的顺序反向操作,栈操作
        class_destroy(mycls);
        unregister_chrdev(major, name);     
    }
    
    // 模块三要素
    module_init(demo_init);
    module_exit(demo_exit);
    MODULE_LICENSE("GPL");

    ----makefile文件

    #KERNELDIR=/lib/modules/3.13.0-32-generic/build   # Ubuntu系统上的内核源码路径
    KERNELDIR=/home/linux/share/kernel-3.4.39   # 目标板上使用内核源码路径
    
    PWD=$(shell pwd) // 当前路径
    all:
        make -C $(KERNELDIR) M=$(PWD) modules
    clean:
        make -C $(KERNELDIR) M=$(PWD) clean
    
    obj-m +=demo.o  # 生成的目标 demo.ko
  • 相关阅读:
    flex 布局
    5个有用的 CSS 布局生成器
    js 函数
    js 类定义的方法(最终)
    js && ||
    css position 盒子模型
    eldatepicker选择时间,限定选择的时间段
    Java基础学习总结——Java对象的序列化和反序列化
    pytorch自定义算子
    网站上视频下载后保存为MP4格式
  • 原文地址:https://www.cnblogs.com/electronic/p/11209708.html
Copyright © 2020-2023  润新知