• 字符设备驱动程序


    Linux中,根据设备的类型可以分为三类:字符设备、块设备和网络设备。

    字符设备:应用程序按字节/字符来读写数据,通常不支持随机存取。我们常用的键盘、串口都是字符设备。

    块设备:应用程序可以随机访问设备数据。典型的块设备有硬盘、SD卡、闪存等,应用程序 可以寻址磁盘上的任何位置,并由此读取数据。此外,数据的读写只能以块的倍数进行。

    网络设备是一种特殊设备,它并不存在于/dev下面,主要用于网络数据的收发。

    0x01 前置知识

    用户使用open函数打开设备文件,做了些什么工作:

     (引自https://embed-linux-tutorial.readthedocs.io/zh_CN/latest/linux_driver/character_device.html

    设备文件通常在开机启动时自动创建的,不过,我们仍然可以使用命令mknod来创建一个新的设备文件,命令的基本语法如下:

    mknod 设备名 设备类型 主设备号 次设备号

    当我们使用上述命令,创建了一个字符设备文件时,实际上就是创建了一个设备节点inode结构体,并且将该设备的设备编号记录在成员i_rdev,将成员f_op指针指向了def_chr_fops结构体。这就是mknod负责 的工作内容。

    (之间的过程有点多,详细地可照着图看源码了解)总的来说用户调用open函数时,最终会调用file结构体中的f_op,即def_chr_fops。

    在Linux内核中,使用结构体cdev来描述一个字符设备。函数chrdev_open最终将该文件结构体file的成员f_op替换成了cdev对应的ops成员,并执行ops结构体中的open函数。

    我们使用对该文件描述符fd调用read、write函数,最终都会调用file结构体对应的函数,实际上也就是调用cdev结构体中ops结构体内的相关函数。

    总结一下整个过程,当我们使用open函数,打开设备文件时,会根据该设备的文件的设备号找到相应的设备结构体,从而得到了操作该设备的方法。也就是说如果我们要添加一个新设备的话,我们需要提供一个设备号,一个设备结构体以及操作该设备的方法(file_operations结构体)。接下来,我们将介绍以上的三个内容。

    0x02 设备驱动程序的编写

    (引自哪我也不记得了)

     1)定义cdev设备

    //第一种方式
    static struct cdev chrdev;
    //第二种方式
    struct cdev *cdev_alloc(void);

    2)分配/注销设备号

    Linux的各种设备都以文件的形式存放在/dev目录下,为了管理这些设备,系统为各个设备进行编号,每个设备号又分为主设备号和次设备号。主设备号用来区分不同种类的设备,如USB,tty等,次设备号用来区分同一类型的多个设备,如tty0,tty1……下图 列出了部分tty设备,他们的主设备号都是4,而不同的次设备号分别对应一个tty设备。

    内核提供了一种数据类型:dev_t,用于记录设备编号,该数据类型实际上是一个无符号32位整型,其中的12位用于表示主设备号,剩余的20位则用于表示次设备号。

    静态地为一个字符设备申请一个或多个设备编号

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

    参数说明:

    • from:dev_t类型的变量,用于指定字符设备的起始设备号,如果要注册的设备号已经被其他的设备注册了,那么就会导致注册失败。
    • count:指定要申请的设备号个数,count的值不可以太大,否则会与下一个主设备号重叠。
    • name:用于指定该设备的名称,我们可以在/proc/devices中看到该设备。

    动态分配设备编号

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

    参数说明如下:

    • dev:指向dev_t类型数据的指针变量,用于存放分配到的设备编号的起始值;
    • baseminor:次设备号的起始值,通常情况下,设置为0;
    • count、name:同register_chrdev_region类型,用于指定需要分配的设备编号的个数以及设备的名称。
    可通过cat /proc/devices查看内核分配的主设备号。
    使用上两个函数分配设备号对应地注销函数(将设备编号还给内核)为:
    void unregister_chrdev_region(dev_t from, unsigned count)

    内核还提供了register_chrdev函数用于分配设备号。该函数是一个内联函数,它不仅支持静态申请设备号,也支持动态申请设备号,并将主设备号返回,函数原型如下所示。

    static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
    {
        return __register_chrdev(major, 0, 256, name, fops);
    }

    参数说明:

    • major:用于指定要申请的字符设备的主设备号,等价于register_chrdev_region函数,当设置为0时,内核会自动分配一个未使用的主设备号。
    • name:用于指定字符设备的名称
    • fops:用于操作该设备的函数接口指针。

    同一类字符设备会在内核中申请256个,若用不到,会造成资源浪费

    注销函数:

    static inline void unregister_chrdev(unsigned int major, const char *name)
    {
      __unregister_chrdev(major, 0, 256, name);
    }

    3)初始化cdev

    将cdev结构体与file_operations结构相关联

    void cdev_init(struct cdev *cdev, const struct file_operations *fops)
    • cdev:struct cdev类型的指针变量,指向需要关联的字符设备结构体;
    • fops:file_operations类型的结构体指针变量,一般将实现操作该设备的结构体file_operations结构体作为实参。

    4)注册设备

    cdev_add函数用于向内核的cdev_map散列表(管理当前系统中的所有字符设备)添加一个新的字符设备

    int cdev_add(struct cdev *p, dev_t dev, unsigned count)
    • p:struct cdev类型的指针,用于指定需要添加的字符设备;
    • dev:dev_t类型变量,用于指定设备的起始编号;
    • count:指定注册多少个设备。

    5)file_operation *fops

    自己编写一个字符设备驱动:https://tutorial.linux.doc.embedfire.com/zh_CN/latest/linux_driver/character_device.html

  • 相关阅读:
    .NET开发人员遇到Maven
    基于VS Code创建Java command-line app
    IntelliJ IDEA连接TFS local workspace无法正常签入
    Xcode连接TFS Git用户名和密码不正确解决方案
    Fiddler如何捕捉DefaultHttpClient的HTTP请求
    IIS 6的日志time-taken字段没有值的解决方案
    简单的音乐轮播JS
    SpringCloud分布式开发理解
    SpringCloud分布式开发五大神兽
    socket长连接和短链接区别
  • 原文地址:https://www.cnblogs.com/dx-yll/p/12994200.html
Copyright © 2020-2023  润新知