• Linux学习第11天:字符设备驱动开发:一字一符总见情


                 

    本文是驱动开发的第一篇笔记。主要内容是字符设备驱动开发最基础的内容,主要包括字符设备的概念、开发步骤以及一个十分重要的概念:设备号。其思维导图能简单的显示本文的基本框架,如下:

    一、字符设备

            字符设备就是一个一个字节,按照字节流进行读写操作的设备,读写数据是分先后顺序的。比如我们最常见的点灯、按键、 IIC、 SPI,LCD 等等都是字符设备,这些设备的驱动就叫做字符设备驱动。

            在 Linux 内核文件 include/linux/fs.h 中有个叫做 file_operations 的结构体,此结构体就是 Linux 内核驱动操作函数集合。

    二、字符设备驱动开发步骤

           ☆☆☆ Linux驱动开发的学习重点是学习其驱动框架。

    1.驱动模块的加载和卸载

            将驱动编译为模块最大的好处就是方便开发。模块的加载和卸载函数如下:

    module_init(xxx_init);  当使用insmod命令加载驱动时,xxx_init这个函数就会被调用。

    module_exit(xxx_exit);当使用rmmod命令卸载驱动时,xxx_exit这个函数就会被调用。

            字符设备驱动模块加载和卸载模板如下所示:

    1. 1 /* 驱动入口函数 */
    2. 2 static int __init xxx_init(void)
    3. 3 {
    4. 4 /* 入口函数具体内容 */
    5. 5 return 0;
    6. 6 }
    7. 7 8
    8. /* 驱动出口函数 */
    9. 9 static void __exit xxx_exit(void)
    10. 10 {
    11. 11 /* 出口函数具体内容 */
    12. 12 }
    13. 13
    14. 14 /* 将上面两个函数指定为驱动的入口和出口函数 */
    15. 15 module_init(xxx_init);
    16. 16 module_exit(xxx_exit);

            驱动编译完成以后扩展名为.ko

            insmod命令不能解决模块的依赖关系,所以推荐使用modprobe命令加载驱动模块。

    2.字符设备的注册与注销

            字符设备的注册与注销函数原型如下:

    1. static inline int register_chrdev(unsigned int major, const char *name,
    2. const struct file_operations *fops)
    3. static inline void unregister_chrdev(unsigned int major, const char *name)

            一般字符设备的注册在驱动模块的入口函数 xxx_init 中进行,字符设备的注销在驱动模块的出口函数 xxx_exit 中进行。实例如下所示:

    1. 1 static struct file_operations test_fops;
    2. 2 3
    3. /* 驱动入口函数 */
    4. 4 static int __init xxx_init(void)
    5. 5 {
    6. 6 /* 入口函数具体内容 */
    7. 7 int retvalue = 0;
    8. 8 9
    9. /* 注册字符设备驱动 */
    10. 10 retvalue = register_chrdev(200, "chrtest", &test_fops);
    11. 11 if(retvalue < 0){
    12. 12 /* 字符设备注册失败,自行处理 */
    13. 13 }
    14. 14 return 0;
    15. 15 }
    16. 16
    17. 17 /* 驱动出口函数 */
    18. 18 static void __exit xxx_exit(void)
    19. 19 {
    20. 20 /* 注销字符设备驱动 */
    21. 21 unregister_chrdev(200, "chrtest");
    22. 22 }
    23. 23
    24. 24 /* 将上面两个函数指定为驱动的入口和出口函数 */
    25. 25 module_init(xxx_init);
    26. 26 module_exit(xxx_exit);

    3.实现设备的具体操作函数
     

            file_operation结构体就是设备的具体操作函数。

            实例有如下要求:

    1.能够对chrtest进行打开和关闭操作。

    2.对chrtest进行读写操作。

            明白需求以后,在其中加入test_fops这个结构体变量从初始化操作,内容如下:

    1. 1 /* 打开设备 */
    2. 2 static int chrtest_open(struct inode *inode, struct file *filp)
    3. 3 {
    4. 4 /* 用户实现具体功能 */
    5. 5 return 0;
    6. 6 }
    7. 7 8
    8. /* 从设备读取 */
    9. 9 static ssize_t chrtest_read(struct file *filp, char __user *buf,
    10. size_t cnt, loff_t *offt)
    11. 10 {
    12. 11 /* 用户实现具体功能 */
    13. 12 return 0;
    14. 13 }
    15. 14
    16. 15 /* 向设备写数据 */
    17. 16 static ssize_t chrtest_write(struct file *filp,
    18. const char __user *buf,
    19. size_t cnt, loff_t *offt)
    20. 17 {
    21. 18 /* 用户实现具体功能 */
    22. 19 return 0;
    23. 20 }
    24. 21
    25. 22 /* 关闭/释放设备 */
    26. 23 static int chrtest_release(struct inode *inode, struct file *filp)
    27. 24 {
    28. 25 /* 用户实现具体功能 */
    29. 26 return 0;
    30. 27 }
    31. 28
    32. 29 static struct file_operations test_fops = {
    33. 30 .owner = THIS_MODULE,
    34. 31 .open = chrtest_open,
    35. 32 .read = chrtest_read,
    36. 33 .write = chrtest_write,
    37. 34 .release = chrtest_release,
    38. 35 };
    39. 36
    40. 37 /* 驱动入口函数 */
    41. 38 static int __init xxx_init(void)
    42. 39 {
    43. 40 /* 入口函数具体内容 */
    44. 41 int retvalue = 0;
    45. 42
    46. 43 /* 注册字符设备驱动 */
    47. 44 retvalue = register_chrdev(200, "chrtest", &test_fops);
    48. 45 if(retvalue < 0){
    49. 46 /* 字符设备注册失败,自行处理 */
    50. 47 }
    51. 48 return 0;
    52. 49 }
    53. 50
    54. 51 /* 驱动出口函数 */
    55. 52 static void __exit xxx_exit(void)
    56. 53 {
    57. 54 /* 注销字符设备驱动 */
    58. 55 unregister_chrdev(200, "chrtest");
    59. 56 }
    60. 57
    61. 58 /* 将上面两个函数指定为驱动的入口和出口函数 */
    62. 59 module_init(xxx_init);
    63. 60 module_exit(xxx_exit);

    4.添加LICENSE和作者信息

            LICENSE必须添加,作者信息可以不添加。两种的添加使用如下两个函数:

    MODULE_LICENSE()
    MODULE_AUTHOR()
    //添加模块 LICENSE 信息
    //添加模块作者信息

    内容如下:

    1. 1 /* 打开设备 */
    2. 2 static int chrtest_open(struct inode *inode, struct file *filp)
    3. 3 {
    4. 4 /* 用户实现具体功能 */
    5. 5 return 0;
    6. 6 }
    7. ......
    8. 57
    9. 58 /* 将上面两个函数指定为驱动的入口和出口函数 */
    10. 59 module_init(xxx_init);
    11. 60 module_exit(xxx_exit);
    12. 61
    13. 62 MODULE_LICENSE("GPL");
    14. 63 MODULE_AUTHOR("jia");

    三、设备号

    1.设备号的组成        

            Linux 中每个设备都有一个设备号,设备号由主设备号次设备号两部分组成,主设备号【12位】表示某一个具体的驱动次设备号【20位】表示使用这个驱动的各个设备。 Linux 提供了一个名为 dev_t 的数据类型表示设备号, dev_t 定义在文件include/linux/types.h 里面,定义如下:

    1. 12 typedef __u32 __kernel_dev_t;
    2. ......
    3. 15 typedef __kernel_dev_t dev_t;

    _u32 类型的定义如下:

    typedef unsigned int __u32;
    include/linux/kdev_t.h提供了几个关于设备号的操作函数(本质是宏),如下所示:

    1. 6 #define MINORBITS 20//MINORMASK设备号
    2. 7 #define MINORMASK ((1U << MINORBITS) - 1)
    3. 8
    4. 9#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))//宏MAJOR用于从dev_t中获取主设备号,将dev_t右移20位即可。
    5. 10 #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))宏MINOR用于从dev_t中获取从设备号,取dev_t右移20位即可。
    6. 11 #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))//宏MKDEV用于将给定的主设备号和次设备号的值组合成dev_t类型的设备号。

    2.设备号的分配

    1)、静态分配设备号

            cat/proc/devices命令可以查看当前系统中已经使用的设备号。

    2)、动态分配设备号

            设备号的申请函数如下:

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

            注销掉字符设备后要释放掉设备号,设备号释放函数如下:

    void unregister_chrdev_region(dev_t from,unsigned count)

    四、chrdevbase字符设备驱动开发

    1.实验程序编写

    1)、创建VSCode工程

    2)、添加头文件路径

    3)、编写实验程序

    2.编写测试APP

    3.编译驱动程序和测试APP

    1)、编译驱动程序

    2)、编译测试APP

    4.运行测试

    1)、加载驱动模块

    2)、创建设备节点文件

    3)、chrdevbase字符设备操作测试

    4)、卸载驱动模块

            作为初学者,我个人感觉重点还是重点关注驱动框架。所以在最后一部分,我没有过多的纠结实验中过多的细节,怕的就是一叶障目不见泰山,钻进某个细节里面不能自拔。

            总的来说,作为驱动开发最基础的一课,内容相对来说比较简单,希望以后的学习中能一步一个脚印,踏踏实实的走下去。因为,我相信一句话,这句话让我在工作两年后,没有选择放弃,没有自暴自弃,并最终考上了研究生,送给大家:坚持不懈,直到成功~


    Linux版本号4.1.15   芯片MX6ULL

    本文为参考正点原子开发板配套教程整理而得,仅用于学习交流使用,不得用于商业用途。

  • 相关阅读:
    分布式事务解决方案
    vue3初尝试
    【数据结构】堆及堆排序详解
    ps安装遇到问题
    天翼云实时云渲染,助力打造世界VR产业大会云上之城
    S/4HANA(本地部署或云版)跟 SAP 家族系统以及非SAP系统的集成,到底什么是推荐的方式?
    丢失宠物发布找寻平台
    Matlab:无穷和 NaN
    linux基本知识总结和shell的使用
    《CTF攻防世界web题》之茶壶我爱你(2)
  • 原文地址:https://blog.csdn.net/jiage987450/article/details/132782839