zoukankan      html  css  js  c++  java
  • 关于字符设备驱动

     一.  使用一个结构 struct cdev 描述字符设备

    struct cdev {
                struct kobject kobj;  //文件系统相关的sysfs---4 一般由系统来管理,不需要我们自己添加
                struct module *owner; //THIS_MODULE ---用于模块计数
                const struct file_operations *ops;  //操作方法集 ====>操作的是硬件, 向下操作
                struct list_head list;   //用于管理字符设备的链表
                dev_t dev;               //设备号  ==== 把软件和硬件结合起来  ===>主设备号<<20+次设备号
                unsigned int count;      //隶属于同一主设备号的次设备号的个数
            };

    这个结构中的 struct file_operations 成员是操作字符设备的方法集,这个结构中包含的都是函数指针,是在驱动程序中自己实现操作底层硬件的接口。

    struct file_operations {
        struct module *owner;
        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 *);
        unsigned int (*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 *);
        int (*flush) (struct file *, fl_owner_t id);
        int (*release) (struct inode *, struct file *);
        int (*fsync) (struct file *, loff_t, loff_t, int datasync);
        
    }; // 这里只是贴出部分

     

    二.  关于设备号 是通过一个宏制作的 如下图所示:

    设备号包含主设备号+此设备号;主设备号用于描述是哪一类设备,次设备号描述具体哪个设备;设备号 devnum = MKDEV(主设备号, 次设备号) 

    #define MINORBITS      20
    #define MINORMASK     ((1U << MINORBITS) - 1)
    #define MAJOR(dev)   ((unsigned int) ((dev) >> MINORBITS))  // 用来获取次设备号的
    #define MINOR(dev)    ((unsigned int) ((dev) & MINORMASK))   // 用来获取次设备号的
    #define MKDEV(ma,mi)  (((ma) << MINORBITS) | (mi))       // 用来制作设备号的

    可以看出使用 4 字节 32 bit 描述一个设备号 高12 bit 描述主设备号,低20 bit 描述次设备号

    三。 字符设备的操作流程:

      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 *p, dev_t dev , unsigned count);

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

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

          b)  unregister_chrdev_region(dev_t from, unsigned count);  // 释放申请的设备号

          设备号是固定的:查看设备号:【 cat /proc/devices 

    经过上面的 步骤 操作就可以在内核中注册一个设备并申请设备号,供这个模块使用;要想在应用层使用这个设备还需要,创建这个设备的节点(应用层操作的文件)。

    需要手动创建:

    在应用层只需要打开 【 /dev/mychardev0 】这个设备就可以实现操作底层了,操作底层的具体实现就这与这个设备关联的驱动程序中实现的

    2. 动态获取主设备号,不用手动创建设备节点(mknod )

     (1) // 动态获取主设备号,并注册到内核中

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

        参数: major :主设备号,填 0 时表示自动获取,不是 0 表示静态申请自己指定设备号,静态申请时成功返回 0

            name:告诉内核这个设备的名称,在 /proc/devices 下查看设备的名字

              fops :就是上面说的文件操作方法集,在驱动程序中重新实现,操作硬件,然后在应用层直接调用相应的IO 操作函数即可。

        返回值: 动态:返回值是动态申请到的设备号

            静态;成功返回0;负值和错误码表示失败

     (2)创建一个目录 

      

      

       这是一个宏函数参数:owner :这是一个宏,推向编译模块时自动创建的__thismodule 

                clsname:创建在  /sys/class/clsname  目录下

              返回值: struct class * 的结构指针

      (2)创建设备节点: 

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

          参数: class :指向应该注册此设备的 struct class 的指针,也就是上面的 class_create 的返回值

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

                devt :添加的字符设备的设备号

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

             fmt :用于设备名称的字符串,也就是设备节点的名称,在应用层操作的设备节点 

          如:

      过程:在加载模块时向内核申请主设备号,并注册到内核中,这个步骤完成了(a)定义并分配字符设备的空间;(b)向内核申请设备号,并添加到内核中;

        接下来就是为创建的设备节点创建一个目录,最后就是创建设备节点(如: /dev/Demochar0);在这 3 个步骤中,只要后面其中一个步骤出错就要释放上面申请的资源。

     

      3. 对于注册设备号,创建目录,创建设备节点,在卸载设备时就要相应的释放设备号,删除目录和删除设备节点

      (1)删除设备节点 :void device_destroy(struct class *class, dev_t devt)

          参数: class:模块入口时创建的目录

              devt: 指定设备的设备号(这个设备号包含主设备号和次设备号)

      (2)删除目录:void class_destroy(struct class *cls)

            参数: cls : 模块入口时创建的目录,

      (3)释放设备号等:static inline void unregister_chrdev(unsigned int major, const char *name)

            参数:major :删除设备的主设备号(通过 MKDEV() 制作)

                name : 注册设备号时,传递给内核的名字,要释放这个资源

         对于模块的入口主要是初始化一些操作,模块的处出口主要是把入口申请的资源释放

  • 相关阅读:
    第194场周赛
    刷leetcode的心得
    91. Decode Ways
    23. Merge k Sorted Lists
    19. Remove Nth Node From End of List
    21. Merge Two Sorted Lists
    222. Count Complete Tree Nodes
    958. Check Completeness of a Binary Tree
    课程学习总结报告
    结合中断上下文切换和进程上下文切换分析Linux内核一般执行过程
  • 原文地址:https://www.cnblogs.com/electronic/p/11143215.html
Copyright © 2011-2022 走看看