• 字符设备驱动模型


    <背景>

    在linux系统驱动程序中,因为要面临各种各样的硬件,字符设备,快设备,网络接口设备,USB设备,PCI设备,平台设备,混在设备 ,设备不同则所对应的驱动模型不同,这就导致我们要掌握众多的驱动模型,能从这些众多的驱动模型中找到共性,则是学号linux驱动的关键

     

    <linux 驱动程序的编写流程>

    创建设备驱动文件:

    mknode  /dev/文件名  类型(c,b...)  主设备号(当一个设备被挂载就会有相应的设备号) 次设备号(用于区分同类型的设备)

     

    设备号:

    静态创建设备号:

    mkdev(主设备号,次设备号) //就是将主设备号和次设备号连接起来

    分离设备号:

    主:major(设备号)

    次:minor(设备号)

     

    申请分配设备号:

    静态分配:

    register_chrdev_region()

    动态分配:

    alloc_chrdev_region()

     

    注销设备号:

    当一个设备使用完就需要将占有的设备号释放

    unregister_chrdev_region()

     

    linux 系统通过chardev[] 组织256 个device_struct 结构,主要就是记录设备的名称和函数

     

    linux 设备描述结构

     

    1)字符cdev结构体

     

          在Linux2.6 内核中,使用cdev结构体来描述一个字符设备,cdev结构体的定义如下:

     

    struct cdev {

     

          struct kobject kobj;

     

          struct module *owner;  /*通常为THIS_MODULE*/

     

          struct file_operations *ops; /*在cdev_init()这个函数里面与cdev结构            联系起来*/

     

          struct  list_head list; 

     

          dev_t  dev;  /*设备号可以动态分配/或静态分配*/

     

          unsigned int count;

    };

     

    设备描述结构的分配:

    静态分配:

    struct cdev  名称 //直接定义一个设备描述结构

     

    动态分配

    struct cdev  *pdev= cdev_alloc();

     

     

    初始化设备驱动程序:

    1)定义一个设备描述结构

    2)实现file_operation以便于应用程序的调用

    llseek()

    read()

    write()

    ioctl()/unlocked_ioctl() //该函数用于控制设备

    open()

    releade()

    注意:在Linux系统中每打开一个文件便会在Linux内核中关联一个struct file。

     

    来源于用户空间的指针不能被被内核空间直接使用,这时需要调用函数:

    int copy_from_user(void *to,const viod_user* from ,int n)

    int copy_to_user(void _user *to ,const void *from ,int n)

     

    3)调用函数操作设备描述结构的函数对对该结构进行操作(放在设备初始化函数中,最后用moudle_init() 函数进行加载)

    (1)将设备描述结构和设备操作集关系起来

    cdev_init(stuct cdev *,const struct file_operation *)

    (2)将设备描述结构注册到Linux系统中,让系统知道有这么个东西

        int cdev_add(struct cdev *,dev_t,unsigned);向linux系统注册一个cdev结构。

     

    卸载设备驱动程序:

    1)调用设备描述结构函数对该结构进行操作

    (1)调用函数void cdev_del(struct cdev *);来将之前注册的设备描述结构注销掉。

     

    实现对设备的控制:

    1)用于操作设备描述结构的函数:

    linux 2.6.36 以前:

    long (*ioctl)(struct inode *node ,struct file* filp ,unsigned int cmd ,unsigned long arg)

     

    Linux2.6.36 以后:

    long (*unlocked_iotcl )(struct file *filp,unsigned int cmd ,unsigned long arg)

     

    注意:这两个函数是使用在应用程序中的,和read(),write()一样。

    2)定义命令

    命令的实质就是一个整数,但是又不是一般的整数,该整数分为几个段:类型(8位),序号,参数传递方向,参数长度

    Type (类型/幻术):表明这是属于哪个设备的命令

    Number(序号):用来区分同一设备的不同命令

    Direction:参数传递的方向(IOC_NONE(没有数据传送,IOC_READ(读取参数),IOC_WRITE(向设备写入参数))

    Size:参数长度

     

    《《设备驱动模型》》

     注:几乎所有的设备结构体都包含"strcut kobject kobj"和"srtuct list_head list"该结构体。

    struct kobject kobj:

       该结构体用于构建Linux设备驱动模型的模型建立

    struct list_head

    {

        struct list_head *prev,*next;

    };

       该结构体用于建立设备文件和设备结构体建立联系,以找到对应的设备操作函数。

     假设应用程序大开设备文件A,那么设备会产生一个inode节点,这样就可以通过高inode节点的i_devices找到设备结构体,进而找到设备操作函数。 

    《驱动初始化》

     

    <分配设备描述结构>

     

    在linux的任何一种驱动模型中,都会有内核的一种结构来描述,字符设备在内核中使用结构体数组:
    struct cdev

    {

    struct kobject kobj;

    struct module *owner;

    const struct file operations *ops;  //设备操作集

    struct list_head list;

    dev_t dev;  //设备号

    unsigned int count;   //设备数

    }

    数据分析:

    (1)设备数:

    表示某种同类设备总共有几个

     

    (2)设备号:
    在目录/dev/ 中会有详细的描述,分为主设备号和次设备号。

    主设备号:通过主设备号和设备文件和设备驱动联系起来。

    次设备号:用于区分同类设备中的不同设备

    在linux系统中,dev_t 这种数据类型是32位,其中高12位主设备号,低20位是次设备号。

     

    使用宏:MKDEV(主设备号,次设备号) 将设备号组合起来

    使用宏:MAJOR(设备号) 将主设备号分离出来

    使用宏:MINOR(设备号) 将次设备号分离出来

     

    (3)分配设备号:

    1)静态分配:
    使用函数register_chrdev_region()向内核申请,缺点,可能申请的设备号已经被使用,这样就会导致申请失败。

    该函数会到该指针数组里面查找是否已经将该设备号注册进入内核。

    static struct char_device_struct {

    struct char_device_struct *next;

    unsigned int major;

    unsigned int baseminor;

    int minorct;

    const char *name;

    struct file_operations *fops;

    struct cdev *cdev;/* will die */

     

    } *chrdevs[MAX_PROBE_HASH];

    搜索方法:

    搜索以哈希列表的方式,首先需要有个一个索引值,在这里使用设备号。

                           i = major_to_index(major);

    调用:

                            i = major%255;

    利用i对chrdevs[i]进行扫描,如果i已经在节点上面了,将会在进行对之前的内存分配进行释放后,然后返回错误,反之就会将该设备注册到内核链表中。

     

     

    2)动态分配:

    使用函数alloc_chrdev_region() 由内核分配一个主设备号。优点是内核知道哪些设备号没有被使用,所以不会出现分配已使用的设备号导致分配失败。

    该内核函数会调用函数:

    95__register_chrdev_region(unsigned int major, unsigned int baseminor,

      96                           int minorct, const char *name)

      97{

      98        struct char_device_struct *cd, **cp;

      99        int ret = 0;

     100        int i;

     101

     102        cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);

     103        if (cd == NULL)

     104                return ERR_PTR(-ENOMEM);

     105

     106        mutex_lock(&chrdevs_lock);

     107

     108        /* temporary */

     109        if (major == 0) {

     110                for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {

     111                        if (chrdevs[i] == NULL)

     112                                break;

     113                }

     114

     115                if (i == 0) {

     116                        ret = -EBUSY;

     117                        goto out;

     118                }

     119                major = i;

     120                ret = major;

     

     121        }

       }

    该内核函数会扫描内核数组chrdevs[],知道找到没有被使用的设备号。

     

    将字符设备加入到系统中去调用函数:

    int cdev_add(struct cdev *p, dev_t dev,unsigned count)

    {

       p->dev = dev;

       p->count = count;

       return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);

    }

    该函数会操作操作一个全局变量cdev_map来将dev加入到哈希链表中(注:这里的cdev_map也是一个指针数组,该结构体如下。)

    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;

    }

    kobj_map()和注册设备号一样,通过扫描probe[]数组来将字符设备加入到内核链表中。

    (4)注销设备号:

    无论是用那种方法分配设备的设备号,都应该在驱动退出时,使用unregister_chrdev_region()来释放设备号。

    调用函数:

    void cdev_del(struct cdev *p)

    {

        cdev_unmap(p->dev,p->count);

        kobject_put(&p->kobj);

    }

    (5)设备操作集:

     

    当应用程序调用系统函数,该系统函数同时是需要访问硬件的,则系统首先会根据函数去找一个表,根据这个表找到相应的驱动函数,然后再根据这些驱动完成相应的操作。——这个表就是设备操作集。

     

    struct file_operations:

    struct file_operations是一个函数指针的集合集合,定义能在设备进行的操作,若果设备不支持某项操作,就将其赋值为空指针

    struct file_operations dev_fops

    {

    .llseek = NULL;

    .read = dev_read;

    .write = dev_write;

    .ioctl = dev_ioctl;

    .open = dev_open;

    .release = dev_release;

    }

     

    <初始设备描述结构>

    <注册设备描述结构>

    <硬件初始化>

    例:

    《字符设备驱动》

    <分配cdev>

    首先设备描述结构是一个结构体数组,即是一个变量,凡是变量的分配有两种方式,一种是静态分配,另一种对动态分配。

    1)静态分配

    struct cdev mdev

    2)动态分配

    使用函数cdev_alloc()

    struct dev *pdev = cdev_alloc();

     

    <初始化cdev>

    设备描述结构的初始化使用函数 cdev_init()来完成。

    函数原型:

    cdev_init(struct cdev *cdev ,const struct file_operations *fops)

    参数分析:

    cdev :待初始化的设备描述结构

    fops:设备对应的操作函数

     

    <注册cdev>

    字符设备的注册使用函数cdev_add()来完成。

    函数原型:
    cdev_add(struct cdev*p,dev_t dev,unsigned count)

    参数分析:

    p:待添加到内核中的设备描述结构

    dev:设备号

    count:该类设备的个数

     

    <硬件初始化>

    如果是一个实际的硬件,就去完成相应硬件设备的初始化。

     

     

    《实现设备操作》(针对设备结构描述里面的 struct file_operations )

    <打开设备文件>

    int (*open)(struct inode *,struct file*)

    操作open 是用来为以后的操作完成相应的第一步初始化工作,左右工作有:

    1)表明设备号

    2)启动设备

     

    <关闭设备>

    int (*release)(struct inode*,struct file*)

    该操作的作用和open刚好相反,主要作用:

    1)关闭设备

     

    <重新定位指针>

    loff_t(*llseek)(srtuct file*,loff_t ,int )

    <读文设备件>

    ssize_t (*read)(struct file*,char __user*,size_t ,loff_t *)

    主要完成两件事:

    1)从设备中读取数据(属于硬件访问操作)

    2)将读取到数据返回给应用数据

    注意:

    凡是来源于用户空间的指针,不能被内核直接应用,必须使用专门的函数。一下两个函数会在read()调用。

     

    int copy_from_user(void *to ,const void __user *from ,int n)

    int copy_to _user(void __user *to ,const ,const void *from ,int n)

     

    <写设备文件>

    ssize_t (*write)(struct file *,char __user ,size_t ,loff_t*)

     

    注意:

    struct file

    在linux系统中,每一打开的 文件都会关联一个struct file,他由内核在打开文件时创建,在关闭文件时释放。

    重要成员:
    loff_t f_pos /文件读写位置指针/

    struct file_operations *f_op /该文件所对应的操作/

     

    struct inode

    每一在文件系统中的文件都会关联一个inode 结构,该结构主要用来记录文件物理上的信息,因此他和打开的 文件file 不同,一个文件没有被打开时是不会关联file结构的,但是却会关联一个inode 结构。

    重要成员:

    dev_t i_rdev  /设备号/

     

    例:

    struct file_operations dev_fops

    {

    .llseek = NULL;

    .read = dev_read;

    .write = dev_write;

    .ioctl = dev_ioctl;

    .open = dev_open;

    .release = dev_release;

    }

     

    《驱动注销》

    使用函数cdev_del() 来将字符设备从内核中注销掉。

     

    <wiz_tmp_tag id="wiz-table-range-border" contenteditable="false" style="display: none;">

  • 相关阅读:
    event.srcElement就是指向触发事件的元素,他是什么就有什么的属性
    HTML title属性换行显示的方法
    O(∩_∩)O哈哈~
    练习题 英文正常语句倒序书写,标点位置不变
    累了就请休息一下。。。
    给自定义用户控件传递参数
    repeater做成gridview【更新删除编辑等】
    【公众号系列】12306购票送温暖
    【公众号系列】超详细SAP HANA JOB全解析
    【公众号系列】浅谈SAP项目管理的技能
  • 原文地址:https://www.cnblogs.com/big-devil/p/8589938.html
Copyright © 2020-2023  润新知