• 基于Linux的驱动开发:内核模块传参、内核到处符号表、字符设备驱动


    内核模块传参

            内核模块:

                    int a , b;

            安装内核模块时:insmod demo.ko a = 100 b =10;

    1.内核模块传参的意义

            在安装内核模块时给内核模块中的变量进行数值传递,这样可以让我们的内核模块向上兼容更为复杂的应用程序,向下适配多种硬件

    2.内核模块传参相关API

            1.函数原型:module_param(name, type, perm)

            功能: 声明可以进行内核模块传参的变量

            参数:name : 变量名

                       type:要传参的数值类型---》byte(传递char), hexint(16进制的int)、short 、int、uint、long、ulong、charp(传递字符指针)、bool(0/1 y/n Y/N) 

                       perm:文件权限,通过module_param声明了要传参的变量,那么在sys/module/当前模块/parameters/下会生成一个以当前变量为名的文件,文件的权限为perm指定的权限,文件内容为变量的值

            注:通过modinfo查看您当前内核模块可以进行命令行传参的变量有哪些

            2.函数原型:MODULE_PARM_DESC(_parm, desc)

            功能:添加要传参的变量的描述,这个描述也可以通过modinfo查看

            参数:_parm :传参的变量名

                      desc:添加的描述

            注:给char类型的变量传参时,需要传递对应的ASCII十进制形式

                    给字符指针传递字符串时,字符串不可以有空格,若有空格,空格前的被当作参数传递给变量,后面的会被认为时一个不认识的变量

    示例代码 

    内核导出符号表

     

    1.导出符号表的意义

            导出符号表可以让不同模块之间实现资源的相互访问 ,比如模块2想要访问模块1的资源,只需将模块1资源的符号表导出给模块2,此时,模块2就可以访问模块1的资源了

    2.导出符号表的相关API

    EXPORT_SYMBOL(变量名|函数名)

    或者

    EXPORT_SYMBOL_CPL(变量名|函数名)

    3.导出符号表测试实例

    3.1编写代码

    定义demo1.c,完成函数的定义

    1. #include
    2. #include
    3. int add(int i,int j)
    4. {
    5. return i+j;
    6. }
    7. //生成add的符号表文件
    8. EXPORT_SYMBOL(add);
    9. static int __init mycdev_init(void)
    10. {
    11. return 0;
    12. }
    13. static void __exit mycdev_exit(void)
    14. {
    15. }
    16. module_init(mycdev_init);
    17. module_exit(mycdev_exit);
    18. MODULE_LICENSE("GPL");

     定义dem2.c,完成调用demo1.c中的函数

    1. #include
    2. #include
    3. extern int add(int i,int j);
    4. static int __init mycdev_init(void)
    5. {
    6. printk("调用模块1函数执行结果为:%d",add(3,5));
    7. return 0;
    8. }
    9. static void __exit mycdev_exit(void)
    10. {
    11. }
    12. module_init(mycdev_init);
    13. module_exit(mycdev_exit);
    14. MODULE_LICENSE("GPL");

    3.2编译

    先编译demo1.c将生成的符号表文件Module.symvers复制到demo2的路径下再编译demo2.c

    此时通过modinifo查看demo2.ko,显示demo2依赖于demo1

    注意 :在高版本的内核中不支持符号表文件直接复制,不然编译报未定义错误

    解决办法:在demo2的Makefile中加上demo1符号表的文件路径3.3安装流程

    先安装demo1,再安装demo2

    3.4卸载

    先卸载demo2,再卸载demo1

    字符设备驱动

    1.字符设备驱动的定义

    字符设备是以字节流的形式进行顺序访问的设备,针对字节设备设计的驱动框架叫做字符设备驱动。当前市面上绝大多数的设备都属于字符设备,比如键盘、鼠标、摄像头...

    2.字符设备驱动的框架

    1.当在内核中注册一个字符设备驱动后会得到驱动对应的设备号,设备号是设备的唯一标识。设备号分为主设备号和次设备号,主设备号(高12位):用来标识一类设备 ,此设备号(低20位):用来标识这一类中的某一个设备。设备号=主设备号<<20 | 次设备号

    2.根据设备号可以通过某种方式在文件系统中创建设备文件,相当于通过设备号将设备文件和驱动绑定

    3.当设备文件被创建后,应用程序中调用 open() | read() | write() | close()

    4.调用以上函数时,驱动中对应的操作应用方法会被回调

    5.在驱动的操作方法中完成硬件的控制

    3.字符设备驱动的注册和注销

    3.1相关API 

    头文件:#include

    注册字符设备驱动

    int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)

    功能:实现字符设备驱动的注册,一次申请256个设备的资源(0-255个此设备号)

    参数:major:主设备号

                    >0 静态指定主设备号

                    =0 动态申请主设备号

               name:注册得到的驱动名字

               fops:操作方法结构体指针,指向操作方法结构体变量

    返回值:

            失败返回错误码

            成功:若major>0,则返回申请得到主设备号

                       若major=0,则返回0

    操作方法结构体,用于保存和管理驱动的各自操作方法

    struct file_operations {

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

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

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

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

    };

    注销字符设备驱动

    void unregister_chrdev(unsigned int major, const char *name)

    功能:实现字符设备驱动的注销

    参数:major:驱动对应的主设备号

               name:注册时填写的驱动号

    返回值:无

    3.2字符设备驱动注册实例

    1.编写代码

    1. #include
    2. #include
    3. #include
    4. unsigned int major;
    5. // 封装操作方法,这些操作方法在应用层进行系统调用时被回调
    6. int mycdev_open(struct inode *inode, struct file *file)
    7. {
    8. printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
    9. return 0;
    10. }
    11. ssize_t mycdev_read(struct file *file, char *ubuf, size_t size, loff_t *lof)
    12. {
    13. printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
    14. return 0;
    15. }
    16. ssize_t mycdev_write(struct file *file, const char *ubuf, size_t size, loff_t *lof)
    17. {
    18. printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
    19. return 0;
    20. }
    21. int mycdev_close(struct inode *inode, struct file *file)
    22. {
    23. printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
    24. return 0;
    25. }
    26. // 定义操作方法结构体对象,保存封装的操作方法
    27. struct file_operations fops = {
    28. .open=mycdev_open,
    29. .read=mycdev_read,
    30. .write=mycdev_write,
    31. .release=mycdev_close,
    32. };
    33. //入口函数
    34. static int __init mycdev_init(void)
    35. {
    36. // 注册字符设备驱动
    37. major = register_chrdev(0, "mychrdev", &fops);
    38. if (major < 0)
    39. {
    40. printk("字符设备驱动注册失败\n");
    41. return major;
    42. }
    43. printk("注册字符设备驱动成功major=%d\n", major);
    44. return 0;
    45. }
    46. //出口函数
    47. static void __exit mycdev_exit(void)
    48. {
    49. //注销字符设备驱动
    50. unregister_chrdev(major,"mychrdev");
    51. }
    52. module_init(mycdev_init);
    53. module_exit(mycdev_exit);
    54. MODULE_LICENSE("GPL");

    2.编译驱动

    3.安装驱动内核模块

    可以查看/proc/devices文件,确定驱动是否被注册4.创建设备文件

    创建设备文件的命令:

            mknod  设备文件的名字和路径   设备文件类型   驱动的主设备号   次设备号

            例:mknod /dev/mychrdev c 240 0

    解析:mknod:创捷设备文件的命令码

               /dev/mychrdev:创建的设备文件的路径和名字

              c 设备文件类型(字符设备文件) d(块设备文件)

              240:主设备号

              0:次设备号 0-255都可以

    5.编写应用程序代码测试是否可以关联驱动

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. int main(int argc, char const *argv[])
    7. {
    8. char buf[128] = {0};
    9. int fd = open("/dev/mychrdev", O_RDWR);
    10. if (fd < 0)
    11. {
    12. printf("打开设备文件失败\n");
    13. return -1;
    14. }
    15. printf("打开设备文件成功\n");
    16. //调用read
    17. read(fd, buf, sizeof(buf));
    18. //调用write
    19. write(fd, buf, sizeof(buf));
    20. //调用close
    21. close(fd);
    22. return 0;
    23. }

    6.现象

    执行应用程序,驱动中的操作方法被回调

    4.用户和内核的数据传递

    用户空间和内核空间之间无法直接相互访问内存,二者进行数据交互需要使用数据传递函数

    4.1 API

    头文件 #include

    函数原型unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)

    功能:传递内核空间的数据到用户空间

    参数:to:用户空间保存数据的buf首地址

               from:内核空间保存数据的buf首地址

               n:传递的数据长度,以字节为单位

    返回值:成功返回0,失败返回未拷贝的字节数

    函数原型unsigned log copy_from_user(void *to, const void __user *from, unsigned long n)

    功能:传递用户空间的数据到内核空间

    参数:to:内核空间保存数据的buf首地址

               from:用户空间保存数据的buf首地址

               n:传递的数据长度,以字节为单位

    返回值:成功返回0,失败返回未拷贝的字节数

    4.2用户和内核数据传递实例

    应用程序代码

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. int main(int argc, char const *argv[])
    8. {
    9. char buf[128] = {0};
    10. int fd = open("/dev/mychrdev", O_RDWR);
    11. if (fd < 0)
    12. {
    13. printf("打开设备文件失败\n");
    14. return -1;
    15. }
    16. printf("打开设备文件成功\n");
    17. fgets(buf,sizeof(buf),stdin);//在终端读一个字符串
    18. buf[strlen(buf)-1]='\0';
    19. write(fd, buf, sizeof(buf));//将数据传递给内核
    20. memset(buf,0,sizeof(buf));//清空数组
    21. read(fd, buf, sizeof(buf));//将内核空间数据传递到用户
    22. printf("buf:%s\n",buf);
    23. close(fd);
    24. return 0;
    25. }

    驱动程序代码 

    1. #include
    2. #include
    3. #include
    4. #include
    5. unsigned int major;
    6. char kbuf[128]={};
    7. // 封装操作方法
    8. int mycdev_open(struct inode *inode, struct file *file)
    9. {
    10. printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
    11. return 0;
    12. }
    13. ssize_t mycdev_read(struct file *file, char *ubuf, size_t size, loff_t *lof)
    14. {
    15. printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
    16. int ret;
    17. //拷贝数据到用户空间
    18. ret=copy_to_user(ubuf,kbuf,size);
    19. if(ret)
    20. {
    21. printk("copy_to_user filed\n");
    22. return -EIO;
    23. }
    24. return 0;
    25. }
    26. ssize_t mycdev_write(struct file *file, const char *ubuf, size_t size, loff_t *lof)
    27. {
    28. printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
    29. int ret;
    30. //从用户空间拷贝数据到内核空间
    31. ret=copy_from_user(kbuf,ubuf,size);
    32. if(ret)
    33. {
    34. printk("copy_from_user filed\n");
    35. return -EIO;
    36. }
    37. return 0;
    38. }
    39. int mycdev_close(struct inode *inode, struct file *file)
    40. {
    41. printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
    42. return 0;
    43. }
    44. // 定义操作方法结构体对象
    45. struct file_operations fops = {
    46. .open=mycdev_open,
    47. .read=mycdev_read,
    48. .write=mycdev_write,
    49. .release=mycdev_close,
    50. };
    51. static int __init mycdev_init(void)
    52. {
    53. // 注册字符设备驱动
    54. major = register_chrdev(0, "mychrdev", &fops);
    55. if (major < 0)
    56. {
    57. printk("字符设备驱动注册失败\n");
    58. return major;
    59. }
    60. printk("注册字符设备驱动成功major=%d\n", major);
    61. return 0;
    62. }
    63. static void __exit mycdev_exit(void)
    64. {
    65. //注销字符设备驱动
    66. unregister_chrdev(major,"mychrdev");
    67. }
    68. module_init(mycdev_init);
    69. module_exit(mycdev_exit);
    70. MODULE_LICENSE("GPL");

    现象:在终端输入的字符串可以正常的打印出来,说明用户空间和内核空间数据传递成功

    5.物理内存映射相关API

    驱动控制硬件需要操作硬件的特殊功能寄存器,但是特殊功能寄存器内存物属于理内存,驱动加载到虚拟内存,想要在驱动中操作硬件寄存器,需要将硬件寄存器内存映射为虚拟内存

    #include

    函数原型:void *ioremap(phys_addr_t paddr, unsigned long size)

    功能:映射指定大小的物理内存为虚拟内存

    参数:paddr:要映射的物理内存首地址

               size:要映射的物理内存大小

    返回值:成功,返回映射成功的虚拟内存首地址,失败,返回NULL

    函数原型:void iounmap(const void __iomem *addr)

    功能:取消物理内存的映射

    参数:addr:要取消的虚拟内存首地址

    返回值:无

    注:有关字符设备驱动的示例代码,目前可查看上一篇文章"编写驱动代码控制LED灯亮灭"http://t.csdnimg.cn/ZBsgG

  • 相关阅读:
    python 移动测试之Appium环境搭建及简单应用
    企业数据治理的下一步是数据资产管理?
    Python-大数据分析之常用库
    airplay:实现
    nohup 命令的简单理解
    解决拦截器抛出异常处理类的500状态码Html默认格式响应 !
    Pandas数据分析系列9-数据透视与行列转换
    Java 第二阶段提升编程能力【坦克大战3.0】
    神经网络和pid有什么区别,基于神经网络的pid控制
    MindSpore是一种适用于端边云场景的新型开源深度学习训练/推理框架
  • 原文地址:https://blog.csdn.net/qq_46766479/article/details/133954861