[Linux_IMX6ULL驱动开发]-基础驱动

news/2024/5/20 4:03:03/文章来源:https://blog.csdn.net/m0_72372635/article/details/137091757

 驱动的含义

如何理解嵌入式的驱动呢,我个人认为,驱动就是嵌入式上层应用操控底层硬件的桥梁。因为上层应用是在用户态,是无法直接操控底层的硬件的。我们需要利用系统调用(open、read、write等),进入内核态,通过打开对应的设备节点,通过read、write等通过编写的驱动函数来操控设备节点。

如何编写驱动

总的来说,驱动编写的大体步骤如下所示:

1、确定驱动的主设备号

2、定义自己的file_operation结构体,这个结构体的成员包含了很多的函数指针

3、我们需要在驱动文件中实现对应的函数,传入结构体中

4、编写一个驱动入口函数(对应的,也需要一个驱动卸载函数)

5、在驱动入口函数中,把file_operation结构体注册到内核当中、创建节点(class)、创建设备(相应的在驱动卸载函数中定义结构体从内核中卸载、节点、设备的卸载方法)

6、使用如下两个宏分别修饰入口函数和出口函数

7、使用 MODULE_LICENSE("GPL"); 遵守GPL协议,否则无法使用

基于如上步骤,我们进行以下操作

首先,我们需要三个文件,一个作为底层驱动文件,一个是上层APP文件,一个是Makefile

驱动文件hello_driver.c
上层应用文件hello_drv.c
Makefile

刚开始我们可能不知道到底要包含什么头文件,我们可以学习Linux内核中的文件来进行参考,我们可以打开 Linux-4.9.88\drivers\char\misc.c ,把里面的头文件拷贝过来使用。

首先我们需要定义一个全局变量作为驱动的设备号,然后定义一个file_operation结构体。需要注意,这两个变量都是全局变量,因为需要被多个函数使用。

file_operation结构体需要多个函数指针成员,在这里,我们定义四个函数,把函数指针赋值给结构体成员

其中需要注意的是,驱动和上层直接读写是需要通过两个函数来进行的,分别是 copy_to_user 和  copy_from_user,前者用于驱动中读的驱动函数,后者用于驱动中写的函数

同时,结构体成员函数的形参,返回值必须严格遵守一样的原则,否则会报错

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>/* 确定主设备号 */
static int major = 0;
/* 缓存数组 */
static char kernal_buf[1024];/* 数据超过1024,限制为1024 */
#define data_num(a,b) ( (a) < (b) ? (a) : (b) )/* 定义函数入口地址 */
static int hello_drv_open (struct inode *node, struct file *file)
{printk("%s %s %d \n",__FILE__,__FUNCTION__,__LINE__);return 0;
}static ssize_t hello_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{int return_size;printk("%s %s %d \n",__FILE__,__FUNCTION__,__LINE__);return_size = copy_to_user(buf, kernal_buf, data_num(1024,size));return return_size;}
static ssize_t hello_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{int return_size;printk("%s %s %d \n",__FILE__,__FUNCTION__,__LINE__);return_size = copy_from_user(kernal_buf, buf, data_num(1024,size));return return_size;}
static int hello_drv_rease (struct inode *node, struct file *file)
{printk("%s %s %d \n",__FILE__,__FUNCTION__,__LINE__);return 0;
}/* 定义文件结构体读,写,打开,卸载
*/
static struct file_operations hello_driver = {.owner = THIS_MODULE,.open = hello_drv_open,.read = hello_drv_read,.write = hello_drv_write,.release = hello_drv_rease,
};

当我们为file_operation结构体的成员指定了对应的函数指针后,我们需要指定一个入口函数以及一个出口函数,并且在入口函数中,把file_operation注册到内核、节点的创建和设备的创建,在出口函数中完成上述三个的卸载(节点需要另外创建一个全局变量,struct class类型)

/* 节点的定义 全局变量 */
static struct class *hello_class;/* 入口函数 */
static int __init hello_init(void)
{int err;printk("%s %s %d \n",__FILE__,__FUNCTION__,__LINE__);/* 注册结构体到内核后,返回主设备号 */major = register_chrdev(0, "hello", &hello_driver);//创建节点 /dev/hellohello_class = class_create(THIS_MODULE, "hello_class");err = PTR_ERR(hello_class);if (IS_ERR(hello_class)){printk("%s %s %d \n",__FILE__,__FUNCTION__,__LINE__);/* 创建失败的话摧毁内核中的hello结构体 */unregister_chrdev( major, "hello");return -1;}/* 创建了节点后,需要创建设备 */	device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello"); return 1;
}/* 出口函数 */
static void __exit hello_exit(void)
{printk("%s %s %d \n",__FILE__,__FUNCTION__,__LINE__);/* 把device卸载 */device_destroy(hello_class, MKDEV(major, 0));/* 把class卸载 */class_destroy(hello_class);/* 把file_operation从内核中卸载 */unregister_chrdev( major, "hello");}

当写好了入口函数和出口函数后,还需通过两个宏声明,否则系统不知道这两个函数分别是入口函数和出口函数

/* 需要用某些宏表示上述两个函数分别是入口函数和出口函数 */
module_init(hello_init);
module_exit(hello_exit);

这就是一个驱动的具体框架了,整体完整代码如下

#include <linux/module.h>#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>/*  流程1.file_operation结构体,实现内部对应函数2.注册结构体到内核,同时使用宏声明入口和出口函数,指引进入3.创建节点,让上层应用函数可以打开 /dev/...,节点class创建完毕后,创建deviceclass提供了一种更高层次的设备抽象,而device则代表了具体的硬件设备*//* 确定主设备号 */
static int major = 0;
/* 缓存数组 */
static char kernal_buf[1024];
/* 节点的定义 */
static struct class *hello_class;/* 读多少的宏定义 */
#define data_num(a,b) ( (a) < (b) ? (a) : (b) )/* 定义函数入口地址 */
static int hello_drv_open (struct inode *node, struct file *file)
{printk("%s %s %d \n",__FILE__,__FUNCTION__,__LINE__);return 0;
}static ssize_t hello_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{int return_size;printk("%s %s %d \n",__FILE__,__FUNCTION__,__LINE__);return_size = copy_to_user(buf, kernal_buf, data_num(1024,size));return return_size;}
static ssize_t hello_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{int return_size;printk("%s %s %d \n",__FILE__,__FUNCTION__,__LINE__);return_size = copy_from_user(kernal_buf, buf, data_num(1024,size));return return_size;}
static int hello_drv_rease (struct inode *node, struct file *file)
{printk("%s %s %d \n",__FILE__,__FUNCTION__,__LINE__);return 0;
}/* 定义文件结构体读,写,打开,卸载
*/
static struct file_operations hello_driver = {.owner = THIS_MODULE,.open = hello_drv_open,.read = hello_drv_read,.write = hello_drv_write,.release = hello_drv_rease,
};/* 把结构体注册到内核为了能够把该结构体注册到内核需要init函数
*/
static int __init hello_init(void)
{int err;printk("%s %s %d \n",__FILE__,__FUNCTION__,__LINE__);/* 注册结构体到内核后,返回主设备号 */major = register_chrdev(0, "hello", &hello_driver);//创建节点 /dev/hellohello_class = class_create(THIS_MODULE, "hello_class");err = PTR_ERR(hello_class);if (IS_ERR(hello_class)){printk("%s %s %d \n",__FILE__,__FUNCTION__,__LINE__);/* 创建失败的话摧毁内核中的hello结构体 */unregister_chrdev( major, "hello");return -1;}/* 创建了节点后,需要创建设备 */	device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello"); return 1;
}/* 有注册函数就有卸载函数 */
static void __exit hello_exit(void)
{printk("%s %s %d \n",__FILE__,__FUNCTION__,__LINE__);/* 把device卸载 */device_destroy(hello_class, MKDEV(major, 0));/* 把class卸载 */class_destroy(hello_class);/* 把file_operation从内核中卸载 */unregister_chrdev( major, "hello");}/* 需要用某些宏表示上述两个函数分别是入口函数和出口函数 */
module_init(hello_init);
module_exit(hello_exit);/* 遵循GPL协议 */
MODULE_LICENSE("GPL");

如上,驱动程序hello_driver.c就完成了 ,在这里我们通过上层应用来打开驱动节点,然后往里面写入数据,然后在从里面读取数据。应用的代码如下


#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>/** ./hello_drv_test -w abc* ./hello_drv_test -r*/
int main(int argc, char **argv)
{int fd;char buf[1024];int len;/* 1. 判断参数 */if (argc < 2) {printf("Usage: %s -w <string>\n", argv[0]);printf("       %s -r\n", argv[0]);return -1;}/* 2. 打开文件 */fd = open("/dev/hello", O_RDWR);if (fd == -1){printf("can not open file /dev/hello\n");return -1;}/* 3. 写文件或读文件 */if ((0 == strcmp(argv[1], "-w")) && (argc == 3)){len = strlen(argv[2]) + 1;len = len < 1024 ? len : 1024;write(fd, argv[2], len);}else{len = read(fd, buf, 1024);		buf[1023] = '\0';printf("APP read : %s\n", buf);}close(fd);return 0;
}

 同时,我们还需要编写Makefile,Makefile和具体的解析如下所示

1、KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88

        定义了KERN_DIR变量,指向了内核的目录

2、all:

        标记了Makefile的第一个目标,执行make的时候执行

3、make -C $(KERN_DIR) M=`pwd` modules 

        -C $(KERN_DIR):这是make的一个选项,用于改变到另一个目录并读取那里的Makefile。这告诉make工具首先进入这个目录,并在那里查找Makefile。

        M=`pwd` modules:M的意思是指定模块源代码的的位置,当指定了module作为目标后,就是告诉系统想要构建内核模块。内核构建系统会查找当前目录(由M变量指定)中的模块源代码,并生成相应的模块文件(通常是.ko文件)。

4、$(CROSS_COMPILE)gcc -o hello_drv_test hello_drv_test.c 

        CROSS_COMPILE是环境变量,这列的意思是使用交叉编译器编译hello_drv_test.c 生成hello_drv_test.o。如果不存在交叉编译器会使用gcc

5、obj-m    += hello_driver.o

        这行告诉内核构建系统hello_driver.o是一个要构建的对象文件(即内核模块)


KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88all:make -C $(KERN_DIR) M=`pwd` modules $(CROSS_COMPILE)gcc -o hello_drv_test hello_drv_test.c clean:make -C $(KERN_DIR) M=`pwd` modules cleanrm -rf modules.orderrm -f hello_drv_testobj-m	+= hello_driver.o

驱动的安装、卸载和现象

当我们在服务器上面编译完成后,会生成如下几个文件

我们通过挂载,把这两个文件挂载到开发板上

当前挂载的目录下存在 hello_driver.ko  hello_drv_test这两个文件。

首先,我们需要安装驱动,使用 insmod + 驱动名 ,来安装驱动

(lsmod也可以查看安装的驱动程序)

如上图,驱动程序成功的安装了

在这里我们使用应用文件写入驱动程序,再从中读出

当我们不使用驱动的时候,使用 rmmod+驱动名 卸载

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

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

相关文章

RuleApp资源社区,知识付费社区,可对接typecho的小程序APP

强大的文章/社区/自媒体客户端&#xff0c;支持打包为安卓&#xff0c;苹果&#xff0c;小程序。包括文章模块&#xff0c;用户模块&#xff0c;支付模块&#xff0c;聊天模块&#xff0c;商城模块等基础功能&#xff0c;包含VIP会员&#xff0c;付费阅读等收费体系&#xff0c…

C程序编译、链接与项目构建

C程序编译、链接与项目构建 摘要C编译环境静、动态库介绍gcc与g和程序编译、链接Visual Studio创建和链接库动态库的显示调用Windows下显示动态库的加载/查找方式 Make介绍安装使用 CMake介绍安装使用构建方式内部构建外部构建构建使用静/动态库常用[系统]变量常用指令CMake模块…

PostgreSQL关系型数据库介绍与部署

使用背景 在过去的几年中&#xff0c;PostgreSQL的使用量逐渐增加&#xff0c;而Oracle和MySQL的使用量则有所下降。这主要是由于以下几个原因&#xff1a;开源和免费、功能丰富、可扩展性强、安全性高、跨平台支持好、社区活跃、成熟稳定。这些因素使得PostgreSQL成为了许多开…

2024/3/23打卡数组分割(第14届蓝桥杯)——二项式+快速幂

题目 思路 分析该题&#xff0c;要将集合 划分成两个子集 &#xff0c;且两个子集的和都是偶数。 可知&#xff1a;偶数 偶数 偶数&#xff1b;偶数 奇数 奇数&#xff1b;奇数 奇数 偶数&#xff1b; 分析可得&#xff1a;如果该集合的和为奇数&#xff0c;就不能分…

八、C#计数排序算法

简介 计数排序是一种非比较性的排序算法&#xff0c;适用于排序一定范围内的整数。它的基本思想是通过统计每个元素的出现次数&#xff0c;然后根据元素的大小依次输出排序结果。 实现原理 首先找出待排序数组中的最大值max和最小值min。 创建一个长度为max-min1的数组count…

IP如何异地共享文件?

【天联】 组网由于操作简单、跨平台应用、无网络要求、独创的安全加速方案等原因&#xff0c;被几十万用户广泛应用&#xff0c;解决了各行业客户的远程连接需求。采用穿透技术&#xff0c;简单易用&#xff0c;不需要在硬件设备中端口映射即可实现远程访问。 异地共享文件 在…

Calico配置路由反射器 (RR) 模式

RR介绍 在 Calico 网络中&#xff0c;默认使用 Node-to-Node Mesh 全互联模式&#xff0c;即集群中的每个节点之间都会相互建立 BGP 连接&#xff0c;用于路由交换。然而&#xff0c;随着集群规模的扩大&#xff0c;全互联模式会导致连接数成倍增加&#xff0c;产生性能问题。为…

Linux 注入依赖环境

文章目录 配置依赖程序安装 JDK安装 Tomcat安装 mysql 配置依赖程序 下面配置依赖程序都以CentOS为例。 安装 JDK 可以直接使用 yum(CentOS) 直接进行安装。 先搜索&#xff0c;确定软件包的完整名称。 yum list | grep jdk再进行安装 进行安装的时候一定要先确保处在“管理…

前端学习--品优购项目

文章目录 前端学习--品优购项目1.案例铺垫文件建立与命名必备文件网站favicon图标网站TDK三大标签SEO优化常用命名 2.LOGO SEO优化3.实际代码4.申请免费域名 前端学习–品优购项目 1.案例铺垫 文件建立与命名 一个项目中为了方便实用和查找内容会有多个文件夹&#xff0c;比如…

idea插件开发案例:将批量插入方法转换成分批批量插入

代码: idea-plugin-demo 1.背景 excel导入时都会使用批量插入或者批量更新到数据库&#xff0c;这在mysql下没有问题。 但因为公司国产化需求&#xff0c;换成达梦数据库就不行了&#xff0c;报sql超长。 一开始想写mybatis拦截器处理&#xff0c;又怕出现bug&#xff0c;这个问…

MySQL为什么会选错索引

在平时不知道一有没有遇到过这种情况&#xff0c;我明明创建了索引&#xff0c;但是MySQL为何不用索引呢&#xff1f;为何要进行全索引扫描呢&#xff1f; 一、对索引进行函数操作 假设现在维护了一个交易系统&#xff0c;其中交易记录表 tradelog 包含交易流水号(tradeid)、交…

Ubuntu 中如何选择Java版本

如何在 Ubuntu 上安装多个版本的 Java 首先&#xff0c;我们得检查一下你的系统里是否已经装了 Java。这个很简单&#xff0c;只需运行下面这条命令&#xff1a; 在 Linux 上安装 Java 的实战示例update-java-alternatives --list 输出结果&#xff1a; 检查是否安装了 Java…

存储的过程

一、存储过程 1.1 概述 存储过程可以轻松而高效的去完成这个需求&#xff0c;有点类似shell脚本里的函数 1.2 特点 存储过程在数据库中创建并保存&#xff0c;它不仅仅是 SQL 语句的集合&#xff0c;还可以加入一些特殊的控制结构&#xff0c;也可以控制数据的访问方式。存储过…

lora-scripts 训练IP形象

CodeWithGPU | 能复现才是好算法CodeWithGPU | GitHub AI算法复现社区&#xff0c;能复现才是好算法https://www.codewithgpu.com/i/Akegarasu/lora-scripts/lora-trainstable-diffusion打造自己的lora模型&#xff08;使用lora-scripts&#xff09;-CSDN博客文章浏览阅读1.1k次…

web 技术中前端和后端交互过程

1、客户端服务器交互过程 客户端:上网过程中,负责浏览资源的电脑,叫客户端服务器:在因特网中,负责存放和对外提供资源的电脑叫服务器 服务器的本质: 就是一台电脑,只不过相比个人电脑它的性能高很多,个人电脑中可以通过安装浏览器的形式,访问服务器对外提供的各种资源。 个人…

如何在vue中使用echarts,与jquery中有啥不同。

一、vue中使用echarts的步骤 在 Vue 中使用 ECharts 可以按照以下步骤进行&#xff1a; 安装 ECharts&#xff1a;使用 npm 或 yarn 安装 ECharts&#xff1a; npm install echarts 在 Vue 组件中引入 ECharts&#xff1a; import echarts from echarts 在 Vue 组件的 mou…

http响应练习—在服务器端渲染html(SSR)

一、什么是服务器端渲染&#xff08;SSR&#xff09; 简单说&#xff0c;就是在服务器上把网页生成好&#xff0c;整个的HTML页面生成出来&#xff0c;生成出的页面已经包含了所有必要的数据和结构信息&#xff0c;然后直接发给浏览器进行展现。 二、例题 要求搭建http服务&a…

QT_day4:对话框

1、完善对话框&#xff0c;点击登录对话框&#xff0c;如果账号和密码匹配&#xff0c;则弹出信息对话框&#xff0c;给出提示”登录成功“&#xff0c;提供一个Ok按钮&#xff0c;用户点击Ok后&#xff0c;关闭登录界面&#xff0c;跳转到其他界面 如果账号和密码不匹配&…

其实StartAI也是一款修图工具 用StartAI修图之“去除背景”

其实StartAI不仅仅是一款AI绘画插件&#xff0c;更是一款可以对我们的摄影图片、广告海报进行修图的AI修图工具。StartAI包含了AI绘画、AI修图等多种复合型AI智能实用工具。 用【背景移除】功能对图片一个背景修图 1.实体广告图片 我们可以通过【背景移除】将广告图中的实体…

软考中级 --网络工程师真题试卷 2023下半年

在EIGRP协议中&#xff0c;某个路由器收到了两条路径到达目标网络&#xff0c;路径1的带宽为100Mbps&#xff0c;延迟2ms&#xff0c;路径2的带宽为50Mbps&#xff0c;迟为4ms&#xff0c;如果EIGRP使用带宽和延迟的综合度量标准&#xff0c;那么该路由器选择的最佳路径是(D)。…