• misc子系统


    跟着内核学框架-从misc子系统到3+2+1设备识别驱动框架

     

    misc子系统在Linux中是一个非常简单的子系统,但是其清晰的框架结构非常适合用来研究设备识别模型。本文从misc子系统的使用出发,通过了解其机制来总结一套的设备识别的驱动框架,即使用使用同一个驱动,向上提供多个设备文件接口,向下控制多个(相应的)设备,这就需要该驱动可以根据不同的设备文件来控制与之相应的设备。

    misc的使用

    Linux 中有三大类设备:字符,网络,块设备,每一种设备又细分为很多类,比如字符设备就被预先分为很多种类,并在文件中标记了这些种类都使用了哪个主设备号,但即便如此,硬件千千万,总还是有漏网之鱼,对于这些难以划分类别的字符设备,Linux中使用"混杂",设备来统一描述,并分配给他们一个共同的主设备号10,只用此设备号进行区分设备,,这些设备主要包括随机数发生器,LCD,时钟发生器等。此外,和很多同样是对cdev进行再次封装的子系统一样,misc也会自动创建设备文件,免得每次写cdev接口都要使用class_create()和device_create()等。

    内核中提供的misc对象:

    //include/linux/miscdevice.h
     55 struct miscdevice  {    
     56         int minor;
     57         const char *name;
     58         const struct file_operations *fops;
     59         struct list_head list;
     60         struct device *parent;
     61         struct device *this_device;
     62         const char *nodename;
     63         umode_t mode;
     64 };

    我们只要像字符设备一样实现fops接口再给一个minor即可,如果minor使用宏MISC_DYNAMIC_MINOR(其实就是255),内核会自动分配一个次设备号,其他的内核已经实现约定好的次设备号可以参考"include/linux/miscdevice.h"。万事具备之后只需使用下面的API注册/注销到内核

    178 int misc_register(struct miscdevice * misc)
    238 int misc_deregister(struct miscdevice *misc)

    misc的分析

    misc的使用是不是很简单?但麻雀虽小五脏俱全,正是因为misc精简的结构,我们可以很容易的抓到其中体现的分层思想,misc的设计方法体现在很多使用cdev作为接口的子系统,而其中的清晰的分层思想更是Linux驱动的两大支柱之一(另外一个是分离)。我们可以借鉴其中的设计思路,提升我们的驱动程序的质量。下面,我们简单的分析一下misc的内部机制。

    misc_init

    作为Linux的一个子系统,misc子系统在Linux启动过程中就会完成准备工作,主要包括初始化数据结构,创建相应的class,创建、初始化并注册cdev对象到内核等。有了这些基础,我们就可以使用misc的众多好处进行编程。

    //drivers/char/misc.c
    56 static const struct file_operations misc_fops = {
    157         .owner          = THIS_MODULE,
    158         .open           = misc_open,
    159         .llseek         = noop_llseek,
    160 };
    268 static int __init misc_init(void)
    269 {
    272 #ifdef CONFIG_PROC_FS
    273         proc_create("misc", 0, NULL, &misc_proc_fops);
    274 #endif
    275         misc_class = class_create(THIS_MODULE, "misc");
    281         if (register_chrdev(MISC_MAJOR,"misc",&misc_fops))
    282                 goto fail_printk;
    283         misc_class->devnode = misc_devnode;
    284         return 0;
    292 }
    293 subsys_initcall(misc_init);   

    misc_init()
    --293-->系统启动的过程中就会初始化misc子系统
    --273-->根据系统配置,可能需要提供/proc接口
    --275-->在/sysfs中创建一个类,名为misc
    --281-->使用静态主设备号(10)、封装好的方法集misc_fops,register_chrdev()内部会创建一个cdev对象并使用这两个参数将其初始化并注册到内核,这个cdev对象将负责所有的混杂设备的设备号。关于cdev对象和设备号之间的关系参见cdev_map
    --158-->misc的cdev对象使用的fops,显然,至此和普通字符设备的调用过程一样,chrdev_open()->misc_open()。

    misc_register

    接下来,老规矩,我们从"XXX_register"开始分析,在Linux内核中,这些"XXX_register"往往就是一个设备对象注册到内核的接口,是研究当相应对象注册进去之后内核动作的最佳入口。

    178 int misc_register(struct miscdevice * misc)
    179 {  
    180         dev_t dev;
    187         if (misc->minor == MISC_DYNAMIC_MINOR) {
    188                 int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS);
    193                 misc->minor = DYNAMIC_MINORS - i - 1;
    194                 set_bit(i, misc_minors);
    195         } 
    206         dev = MKDEV(MISC_MAJOR, misc->minor);
    208         misc->this_device = device_create(misc_class, misc->parent, dev,
    209                                           misc, "%s", misc->name);
    210         if (IS_ERR(misc->this_device)) {
    211                 int i = DYNAMIC_MINORS - misc->minor - 1;
    212                 if (i < DYNAMIC_MINORS && i >= 0)
    213                         clear_bit(i, misc_minors);
    214                 err = PTR_ERR(misc->this_device);
    216         }
    222         list_add(&misc->list, &misc_list);
    226 }

    misc_register()
    --187--> 如果指定的minor是动态分配,那么进入相关语句块。
    --188--> 使dev用位图遍历API-find_first_zero_bit找到最小未用的设备号。
    --193--> 得到分配好的次设备号。
    --208--> (根据设备号)创建设备文件,使用的是misc_init中创建的misc_class,至此就可以实现misc设备文件的自动创建。就相当与我们在纯粹的cdev驱动中使用class_create()+device_create()创建设备文件。一个设备文件和一个设备号相联系,而misc的所有的设备号都和misc_init创建的cdev对象相联系,所以打开的任何一个misc设备文件首先回调的就是(chrdev_open()->)misc_open()。
    --222--> 关键,将这个新分配的misc加入到misc链表中,用于管理所有的misc设备,便于misc_open()提取具体设备的fops。

    misc_open

    构建的misc子系统,将设备添加到了该子系统中,接下来我们来看一下应用层程序是如何打开一个misc设备的。由于misc也是一种字符设备,所以其提供的接口也是位于/dev中。但是正如misc的定义,其中的设备五花八门却共用同一个主设备号,这就意味着最终被chrdev_open回调的misc_open一定要具备根据被打开的不同文件为file结构准备不同的操作方法这一能力,即在驱动中实现对子设备的识别,或者称之为"多态"。

    112 static int misc_open(struct inode * inode, struct file * file)
    113 {
    114         int minor = iminor(inode);
    115         struct miscdevice *c;
    116         int err = -ENODEV;
    117         const struct file_operations *new_fops = NULL;
    121         list_for_each_entry(c, &misc_list, list) {
    122                 if (c->minor == minor) {
    123                         new_fops = fops_get(c->fops);           
    124                         break;
    125                 }
    126         }
    144         replace_fops(file, new_fops);
    145         if (file->f_op->open) {
    146                 file->private_data = c;
    147                 err = file->f_op->open(inode,file);
    148         }
    152 }

    misc_open()
    --121-->遍历misc设备链表,根据被打开的设备的次设备号找到设备对象。
    --123-->存储这个设备对象的操作方法集unique_fops。
    --144-->将misc设备具体的操作方法集unique_fops替换到filp中的f_op中,这个位置原来是misc的cdev对象的fops,filp带着这个unique_fops从open()返回,就实现了不同的设备对应不同的操作方法,即面向对象的"多态"

    3+2+1多设备识别驱动模型

    通过上述对misc机制的分析,我们不难总结出一个支持设备识别的3+2+1驱动模型(3个函数+2个数据结构+1个封装):

    1. 初始化整个驱动组的xxx_init(),通常用模块加载函数或总线的probe函数实现;
    2. 用于注册一个子驱动的xxx_register(),需要EXPORT到符号表;
    3. 能够根据传入的inode识别具体的设备并将其操作方法集放到filp中的xxx_open()

    +

    1. 用于存储每一个驱动对象的通用链表或数组+priv_data
    2. 用于存储子设备号的位图。

    +

    1. 将所有的不同的设备用一个统一的结构进行封装

    至此,我们就可以写一写这个3+2+1驱动模型的模板。

    1个封装

    struct multidevice{
        struct list_head head;
        int minor;
        struct file_operations* fops;
        void *priv;     //私有数据,供read/write等接口识别的信息,以及其他数据都放这里
    };

    2个数据结构

    struct list_head multi_dev_list;
    unsigned int minors_map;   //根据设备号数目的不同选数据类型or数组

    3个函数

    int major,baseminor = 0,max_dev = sizeof(minors_map)*8;
    #define DEV_NAME "multi_device"
    struct class *cls;
    xxx_open(struct inode *inode,struct file *file){
        int minor = iminor(inode);
             struct multidevice *dp;
             const struct file_operations *new_fops = NULL;
                      list_for_each_entry(dp, &multi_dev_list, head) {
                         if (dp->minor == minor) {
                             new_fops = fops_get(dp->fops);           
                             break;
                     }
             }
             replace_fops(file, new_fops);
             if (file->f_op->open) {
                     file->private_data = dp
                     file->f_op->open(inode,file);
             }
    }
    
    xxx_init(void){
        dev_t devno,
        INIT_LIST_HEAD(&multi_dev_list);
        init_map(&minors_map);
        struct cdev *multi_cdev = cdev_alloc();
        cdev_init(multi_cdev, multi_fops);
        alloc_chrdev_region(&devno, baseminor, count,DEV_NAME);
        major = MAJOR(devno);
        cdev_add(multi_cdev , devno, count);
        cls = class_create(THIS_MODULE, DEV_NAME);
    }
    /*---------------下面是给待加驱动用的----------------------*/
    xxx_register(struct *multidevice dev){
                dev_t dev;
             if (dev->minor == MISC_DYNAMIC_MINOR) {
                     int i = find_first_zero_bit(minors_map, DYNAMIC_MINORS);
                     dev->minor = DYNAMIC_MINORS - i - 1;
                     set_bit(i, minors_map);
             } 
             dev_t pri_devno = MKDEV(major, dev->minor);
             device_create(multi_class, NULL, pri_devno, "%s", misc->name);
             list_add(dev->head, &multi_dev_list);
    }
    EXPORT_SYMBOL(xxx_register)

    

     
  • 相关阅读:
    Window10和Ubuntu 18.04双系统安装的引导问题解决
    Linux gcc链接动态库出错:LIBRARY_PATH和LD_LIBRARY_PATH的区别
    PaddleBook的部署安全性问题
    ubuntu docker中crontab任务不执行的问题
    zsh 自动补全导致命令显示重复
    For Freedom —— 代理篇
    搭建自己私有的PKM系统,各家PKM大比拼。。附:构建自己熟悉的基础Docker,破解联通光猫
    Docker上ubuntu新建用户的网络访问不通问题
    Android学习杂记
    jquery中的工具函数 Utilities
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/6423683.html
Copyright © 2020-2023  润新知