zoukankan      html  css  js  c++  java
  • Linux设备管理(二):内核中字符设备的管理

    /************************************************************************************

    *本文为个人学习记录,如有错误,欢迎指正。

    *本文参考资料: 

    *        https://www.cnblogs.com/embedded-tzp/p/4507240.html

    *        http://www.169it.com/tech-qa-linux/article-5682294992603241339.html

    *        https://blog.csdn.net/zhoujiaxq/article/details/7646013

    *        http://www.cnblogs.com/xiaojiang1025/p/6196198.html

    ************************************************************************************/

    1. 字符设备的管理框架

    Linux内核对设备的管理是基于kobject来进行的,详见Linux设备管理:kobject, kset, ktype分析。Linux对字符设备的管理框架依赖于struct kobj_map、struct cdev、dev_t dev、struct file_operations等数据结构。如下图所示。

    2. 字符设备数据结构

     Linux内核中关于字符设备的操作函数存放在 "/kernel/fs/char_dev.c" 文件中。

    2.1 dev_t dev

    一个字符设备或块设备都有一个主设备号(major)和一个次设备号(minor)。主设备号用来标识与设备文件相连的驱动程序,用来反映设备类型。次设备号被驱动程序用来辨别操作的是哪个设备,用来区分同类型的设备。

    Linux内核中,使用dev_t来描述设备号。

    typedef u_long dev_t;  // 在32位机中是4个字节,高12位表示主设备号,低20位表示次设备号。

    Linux内核中提供以下几个宏来操作dev_t。

    #define MAJOR(dev)    ((unsigned int) ((dev) >> MINORBITS))  // 从设备号中提取主设备号
    #define MINOR(dev)    ((unsigned int) ((dev) & MINORMASK))   // 从设备号中提取次设备号
    #define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi))         // 将主、次设备号拼凑为设备号

    2.2 struct cdev

    Linux内核中,使用struct cdev结构体来描述一个字符设备。

    <include/linux/cdev.h>  
    
    struct cdev 
    {   
      struct kobject kobj;              //内嵌的内核对象 
      struct module *owner;             //该字符设备所在的内核模块(所有者)的对象指针,一般为THIS_MODULE,主要用于模块计数  
      const struct file_operations *ops;//该结构描述了字符设备所能实现的操作集(打开、关闭、读/写、...),是极为关键的一个结构体
      struct list_head list;            //用来将已经向内核注册的所有字符设备形成链表
      dev_t dev;                        //字符设备的设备号,由主设备号和次设备号构成(如果是一次申请多个设备号,此设备号为第一个)
      unsigned int count;               //隶属于同一主设备号的次设备号的个数
    };

    2.3 struct file_operations

    Linux内核中,使用file_operations结构来管理设备驱动程序的函数,这个结构的每一个成员的名字都对应着一个函数调用。

    用户进程利用在对设备文件进行操作时(read/write等),系统调用通过设备文件的主设备号找到相应的设备驱动程序,然后读取其file_operations结构相应的函数指针,接着把控制权交给该函数,这是Linux的设备驱动程序工作的基本原理。

    struct file_operations 
    {
      struct module *owner;  
        /* 模块拥有者,一般为 THIS——MODULE */
      ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);  
        /* 从设备中读取数据,成功时返回读取的字节数,出错返回负值(绝对值是错误码) */
      ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);   
        /* 向设备发送数据,成功时该函数返回写入字节数。若为被实现,用户调层用write()时系统将返回 -EINVAL*/
      int (*mmap) (struct file *, struct vm_area_struct *);  
        /* 将设备内存映射内核空间进程内存中,若未实现,用户层调用 mmap()系统将返回 -ENODEV */
      long (*unlocked_ioctl)(struct file *filp, unsigned int cmd, unsigned long arg);  
        /* 提供设备相关控制命令(读写设备参数、状态,控制设备进行读写...)的实现,当调用成功时返回一个非负值 */
      int (*open) (struct inode *, struct file *);  
        /* 打开设备 */
      int (*release) (struct inode *, struct file *);  
        /* 关闭设备 */
      int (*flush) (struct file *, fl_owner_t id);  
        /* 刷新设备 */
      loff_t (*llseek) (struct file *, loff_t, int);  
        /* 用来修改文件读写位置,并将新位置返回,出错时返回一个负值 */
      int (*fasync) (int, struct file *, int);  
        /* 通知设备 FASYNC 标志发生变化 */
      unsigned int (*poll) (struct file *, struct poll_table_struct *);  
        /* POLL机制,用于询问设备是否可以被非阻塞地立即读写。当询问的条件未被触发时,用户空间进行select()和poll()系统调用将引起进程阻塞 */
    };

    2.4 struct kobj_map

    Linux内核中,所有的字符设备都会记录在一个cdev_map 变量中。cdev_map是一个struct kobj_map类型的指针,其中包含着一个struct probe*类型、大小为255的数组,数组的每个元素指向的一个probe结构封装了一个设备号和相应的设备对象(cdev)。

    struct kobj_map {
        struct probe {
            struct probe *next;      // 这样形成了链表结构
            dev_t dev;                   //设备号 */
            unsigned long range;     // 设备号的范围
            struct module *owner;
            kobj_probe_t *get;
            int (*lock) (dev_t, void *);
            void *data;              //指向struct cdev对象 
        } *probes[255];
        struct mutex *lock;
    }
    struct kobj_map

    字符设备驱动程序通过调用cdev_add把它所管理的字符设备对象的指针嵌入到一个类型为struct probe的节点之中,然后再把该节点加入到cdev_map所实现的哈希链表中。对系统而言,当设备驱动程序成功调用了cdev_add之后,就意味着一个字符设备对象已经加入到了系统,在需要的时候,系统就可以找到它。对用户态的程序而言,cdev_add调用之后,就已经可以通过文件系统的接口调用该设备的驱动程序(具体调用流程详见Linux字符设备:应用程序调用字符设备驱动程序的流程)。

    int cdev_add(struct cdev *p, dev_t dev, unsigned count)
    {
        p->dev = dev;
        p->count = count;
    
       /*申请并填充struct probe,再通过要加入系统的设备的主设备号major(major=MAJOR(dev))来获得probes数组的索引值i(i = major % 255),
         然后把一个类型为struct probe的节点对象加入到probes[i]所管理的链表中*/ 
        return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
    }

    cdev_map对字符设备的管理方式有两种:

    (1)一个cdev对象对应这一个/多个设备号的情况

    在cdev_map中, 一个probes对象就对应一个主设备号;多个设备号对应一个cdev时,其实只是次设备号在变,主设备号还是一样的,所以是同一个probes对象。

    (2)主设备号超过255的情况

    当主设备号超过255时,会进行probe复用,此时probe->next就派上了用场,比如probe[200]可以表示设备号200,455...3895等所有对255取余是200的数字。

     

  • 相关阅读:
    kNN算法python实现和简单数字识别的方法
    python3.4学习笔记(二十) python strip()函数 去空格 函数的用法
    SolrCloud分布式集群部署步骤
    hbases索引技术:Lily HBase Indexer介绍
    HBase1.0以上版本的API改变
    Hbase1.0 客户端api
    java.io.IOException: Could not locate executable nullinwinutils.exe in the Hadoop binaries
    Resharp最新破解方法
    Cloudera Manager 5和CDH5离线安装
    Apache Thrift
  • 原文地址:https://www.cnblogs.com/linfeng-learning/p/9307646.html
Copyright © 2011-2022 走看看