基于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,完成函数的定义

#include <linux/init.h>
#include <linux/module.h>
int add(int i,int j)
{return i+j;
}
//生成add的符号表文件
EXPORT_SYMBOL(add);
static int __init mycdev_init(void)
{return 0;
}
static void __exit mycdev_exit(void)
{}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

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

#include <linux/init.h>
#include <linux/module.h>extern int add(int i,int j);
static int __init mycdev_init(void)
{printk("调用模块1函数执行结果为:%d",add(3,5));return 0;
}
static void __exit mycdev_exit(void)
{}
module_init(mycdev_init);
module_exit(mycdev_exit);
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 <linux/fs.h>

注册字符设备驱动

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.编写代码

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
unsigned int major;
// 封装操作方法,这些操作方法在应用层进行系统调用时被回调
int mycdev_open(struct inode *inode, struct file *file)
{printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);return 0;
}
ssize_t mycdev_read(struct file *file, char *ubuf, size_t size, loff_t *lof)
{printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);return 0;
}
ssize_t mycdev_write(struct file *file, const char *ubuf, size_t size, loff_t *lof)
{printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);return 0;
}
int mycdev_close(struct inode *inode, struct file *file)
{printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);return 0;
}
// 定义操作方法结构体对象,保存封装的操作方法
struct file_operations fops = {.open=mycdev_open,.read=mycdev_read,.write=mycdev_write,.release=mycdev_close,
};
//入口函数
static int __init mycdev_init(void)
{// 注册字符设备驱动major = register_chrdev(0, "mychrdev", &fops);if (major < 0){printk("字符设备驱动注册失败\n");return major;}printk("注册字符设备驱动成功major=%d\n", major);return 0;
}
//出口函数
static void __exit mycdev_exit(void)
{//注销字符设备驱动unregister_chrdev(major,"mychrdev");
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

2.编译驱动

3.安装驱动内核模块

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

创建设备文件的命令:mknod /dev/mychrdev c 240 0

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

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

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

          240:主设备号

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

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

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{char buf[128] = {0};int fd = open("/dev/mychrdev", O_RDWR);if (fd < 0){printf("打开设备文件失败\n");return -1;}printf("打开设备文件成功\n");//调用readread(fd, buf, sizeof(buf));//调用writewrite(fd, buf, sizeof(buf));//调用closeclose(fd);return 0;
}

6.现象

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

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

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

4.1 API

头文件 #include <linux/uaccess.h>

函数原型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用户和内核数据传递实例

应用程序代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include<string.h>
int main(int argc, char const *argv[])
{char buf[128] = {0};int fd = open("/dev/mychrdev", O_RDWR);if (fd < 0){printf("打开设备文件失败\n");return -1;}printf("打开设备文件成功\n");fgets(buf,sizeof(buf),stdin);//在终端读一个字符串buf[strlen(buf)-1]='\0';write(fd, buf, sizeof(buf));//将数据传递给内核memset(buf,0,sizeof(buf));//清空数组read(fd, buf, sizeof(buf));//将内核空间数据传递到用户printf("buf:%s\n",buf);close(fd);return 0;
}

驱动程序代码 

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include<linux/uaccess.h>
unsigned int major;
char kbuf[128]={};
// 封装操作方法
int mycdev_open(struct inode *inode, struct file *file)
{printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);return 0;
}
ssize_t mycdev_read(struct file *file, char *ubuf, size_t size, loff_t *lof)
{printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);int ret;//拷贝数据到用户空间ret=copy_to_user(ubuf,kbuf,size);if(ret){printk("copy_to_user filed\n");return -EIO;}return 0;
}
ssize_t mycdev_write(struct file *file, const char *ubuf, size_t size, loff_t *lof)
{printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);int ret;//从用户空间拷贝数据到内核空间ret=copy_from_user(kbuf,ubuf,size);if(ret){printk("copy_from_user filed\n");return -EIO;}return 0;
}
int mycdev_close(struct inode *inode, struct file *file)
{printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);return 0;
}
// 定义操作方法结构体对象
struct file_operations fops = {.open=mycdev_open,.read=mycdev_read,.write=mycdev_write,.release=mycdev_close,
};
static int __init mycdev_init(void)
{// 注册字符设备驱动major = register_chrdev(0, "mychrdev", &fops);if (major < 0){printk("字符设备驱动注册失败\n");return major;}printk("注册字符设备驱动成功major=%d\n", major);return 0;
}
static void __exit mycdev_exit(void)
{//注销字符设备驱动unregister_chrdev(major,"mychrdev");
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

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

5.物理内存映射相关API

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

#include <linux/io.h>

函数原型: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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/112464.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2022年亚太杯APMCM数学建模大赛C题全球变暖与否全过程文档及程序

2022年亚太杯APMCM数学建模大赛 C题 全球变暖与否 原题再现&#xff1a; 加拿大的49.6C创造了地球北纬50以上地区的气温新纪录&#xff0c;一周内数百人死于高温&#xff1b;美国加利福尼亚州死亡谷是54.4C&#xff0c;这是有史以来地球上记录的最高温度&#xff1b;科威特53…

JVM内存模型概述

这里主要分为五大块&#xff0c;分别是&#xff1a;本地方法栈、方法区、java堆、程序计数器和java栈。其中重点是方法区、java堆和java栈。 下面就把各个区域的性质总结一下&#xff1a;&#xff08;说明&#xff0c;下面的只是结论&#xff0c;没有详细的对各个内存块进行详细…

运维监控Zabbix部署

目录 运维监控Zabbix部署 1. 简介 2. 安装 ​编辑 2.1 安装前准备 - Mysql 2.2 安装Zabbix Server 和 Zabbix Agent 2.2.1 安装Zabbix yum库 2.2.2 安装Zabbix Server、前端、Agent 2.2.3 初始化Mysql数据库 2.2.4 为Zabbix Server配置数据库 2.2.5 配置Zab…

AI驱动的未来:探索人工智能的无限潜力 | 开源专题 No.39

这一系列开源项目代表着多个领域的最新技术成果&#xff0c;包括深度学习、自然语言处理、计算机视觉和分布式训练。它们共同的特点是致力于教育、资源分享、开源精神、多领域应用以及性能和效率的追求&#xff0c;为广大开发者、研究者和学生提供了宝贵的工具和知识&#xff0…

AI全栈大模型工程师(九)Function Calling 的机制

文章目录 Function Calling 的机制Function Calling 示例 1:加法计算器Function Calling 实例 2:四则混合运算计算器后记Function Calling 的机制 Function Calling 示例 1:加法计算器 需求:用户输入任意可以用加法解决的问题,都能得到计算结果。 # 加载环境变量import o…

elasticsearch的docker安装与使用

安装 docker network create elasticdocker pull docker.elastic.co/elasticsearch/elasticsearch:8.10.4# 增加虚拟内存&#xff0c; 此处适用于linux vim /etc/sysctl.conf # 添加 vm.max_map_count262144 # 重新启动 sysctl vm.max_map_countdocker run --name es01 --net …

【MATLAB第80期】基于MATLAB的结构核岭回归SKRR多输入单输出回归预测及分类预测模型

【MATLAB第80期】基于MATLAB的结构核岭回归SKRR多输入单输出回归预测及分类预测模型 SKRR这是Gustau Camps-Valls等人在“用深度结构核回归检索物理参数”中提出的结构核岭回归&#xff08;SKRR&#xff09;方法。 参考文献&#xff1a; Camps-Valls,Retrieval of Physical Pa…

AM@两种余项型泰勒公式的对比和总结@常用函数的麦克劳林公式

文章目录 abstract两种余项型泰勒公式的对比和总结Maclaurin公式常用函数的Maclaurin公式推导例求极限按幂展开 abstract 泰勒公式的两种余项型(Penao&Lagrange)泰勒公式的对比和总结常用的Maclaurin公式列举(Peano余项型为主) 两种余项型泰勒公式的对比和总结 Taylor公式…

FL Studio21最新中文破解进阶高级完整版安装下载教程

目前水果软件最版本是FL Studio21&#xff0c;它让你的计算机就像是全功能的录音室&#xff0c;大混音盘&#xff0c;非常先进的制作工具&#xff0c;让你的音乐突破想象力的限制。喜欢音乐制作的小伙伴千万不要错过这个功能强大&#xff0c;安装便捷的音乐软件哦&#xff01;如…

[牛客]计算机网络习题笔记_1020

1、物理层&#xff1a;以太网 调制解调器 电力线通信(PLC) SONET/SDH G.709 光导纤维 同轴电缆 双绞线等。 2、数据链路层&#xff08;网络接口层包括物理层和数据链路层&#xff09;&#xff1a;Wi-Fi(IEEE 802.11) WiMAX(IEEE 802.16) ATM DTM 令牌环 以太网 FDD…

《数据结构、算法与应用C++语言描述》使用C++语言实现链表队列

《数据结构、算法与应用C语言描述》使用C语言实现链表队列 定义 队列的定义 队列&#xff08;queue&#xff09;是一个线性表&#xff0c;其插入和删除操作分别在表的不同端进行。插入元素的那一端称为队尾&#xff08;back或rear&#xff09;&#xff0c;删除元素的那一端称…

SpringBoot项目访问后端页面404

检查项目的路径和mapper映射路径没问题后&#xff0c;发现本级pom文件没有加入web启动模块的pom文件中 maven做项目控制时&#xff0c;要注意将maven模块加入到web启动模块中

【学习笔记】RabbitMQ-6 消息的可靠性投递2

参考资料 RabbitMQ官方网站RabbitMQ官方文档噼咔噼咔-动力节点教程 文章目录 十一、队列Queue的消息属性11.1 具体属性11.2 自动删除11.2 自定义参数11.2.1 **Message TTL** 消息存活时间11.2.2 **Auto expire** 队列自动到期时间11.2.3 **Overflow behaviour** 溢出行为11.2.4…

修炼k8s+flink+hdfs+dlink(五:安装dockers,cri-docker,harbor仓库,k8s)

一&#xff1a;安装docker。&#xff08;所有服务器都要安装&#xff09; 安装必要的一些系统工具 sudo yum install -y yum-utils device-mapper-persistent-data lvm2添加软件源信息 sudo yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/cent…

如何通过SK集成chatGPT实现DotNet项目工程化?

智能助手服务 以下案例将讲解如何实现天气插件 当前文档对应src/assistant/Chat.SemanticServer项目 首先我们介绍一下Chat.SemanticServer的技术架构 SemanticKernel 是什么&#xff1f; Semantic Kernel是一个SDK&#xff0c;它将OpenAI、Azure OpenAI和Hugging Face等大…

虚拟音频设备软件 Loopback mac中文版软件介绍

创建虚拟音频设备以从应用程序和音频输入设备获取声音&#xff0c;然后将其发送到音频处理应用程序&#xff0c;它就是—Loopback for Mac&#xff0c;Loopback mac为您提供高端工作室混音板的强大功能&#xff0c;有了它在Mac上传递音频会变得很容易。 Loopback for mac中文版…

Vue3.0里为什么要用 Proxy API 替代 defineProperty API ?

一、Object.defineProperty 定义&#xff1a;Object.defineProperty() 方法会直接在一个对象上定义一个新属性&#xff0c;或者修改一个对象的现有属性&#xff0c;并返回此对象 为什么能实现响应式 通过defineProperty 两个属性&#xff0c;get及set get 属性的 getter 函…

Swift使用Embassy库进行数据采集:热点新闻自动生成器

概述 爬虫程序是一种可以自动从网页上抓取数据的软件。爬虫程序可以用于各种目的&#xff0c;例如搜索引擎、数据分析、内容聚合等。本文将介绍如何使用Swift语言和Embassy库编写一个简单的爬虫程序&#xff0c;该程序可以从新闻网站上采集热点信息&#xff0c;并生成一个简单…

GCC优化相关

文章目录 优化选项博文链接 单独设置某段代码优化等级博文链接 优化选项 -O/-O0:无优化(默认)-O1:使用能减少目标文件大小以及执行时间并且不会使编译时间明显增加的优化。该模式在编译大型程序的时候会花费更多的时间和内存。在-O1 下&#xff0c;编译会尝试减少代码体积和代码…

Sarscape5.6版本中导入外部控制点、写入精密轨道文件与GACOS用于大气相位

SARscape中导入外部GCP点用于轨道精炼 https://www.cnblogs.com/enviidl/p/16524645.html在SAR处理时&#xff0c;有时会加入GCP点文件&#xff0c;SAR处理中用到的控制点分为两类&#xff1a;用于校正地理位置的几何控制点&#xff08;Geometry GCP&#xff09;和用于轨道精炼…