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

news2024/11/17 11:42:55

 驱动的含义

如何理解嵌入式的驱动呢,我个人认为,驱动就是嵌入式上层应用操控底层硬件的桥梁。因为上层应用是在用户态,是无法直接操控底层的硬件的。我们需要利用系统调用(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/hello
	hello_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创建完毕后,创建device
		class提供了一种更高层次的设备抽象,而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/hello
	hello_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.88

all:
	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 clean
	rm -rf modules.order
	rm -f hello_drv_test

obj-m	+= hello_driver.o

驱动的安装、卸载和现象

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

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

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

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

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

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

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

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

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

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

相关文章

48 div 下面包含 el-radio, 点击 div 事件被触发多次

前言 这是一个最近碰到的一个很奇怪的问题 情况如下一个 div 下面有一个 el-radio, 然后 div 上面配置了 click 的回调为 handleClick 然后 但是点击 div 的时候, handleClick 触发了两次 然后 这里 来模拟一下, 并解决一下 这个问题 这里的知识主要是 设计到 label 和 …

机器人深度学习IMU和图像数据实现焊接精细操作

在双电极气体保护金属弧焊 &#xff08;DE-GMAW&#xff09; 中&#xff0c;对焊枪和旁路电极位置的精确控制是至关重要的。为了这一过程&#xff0c;科研团队提出了安装微型惯性测量单元&#xff08;IMU&#xff09;传感器和摄像头&#xff0c;来记录焊工控制焊枪的移动和微调…

来了!小学生Python创意编程(视频教学版)

目录 写在前面 推荐图书 推荐理由 写在最后 写在前面 在最好的年纪&#xff0c;一起来学Python吧&#xff01;本期博主给大家推荐一本适合小学生阅读的书籍&#xff0c;一起来看看吧~ 推荐图书 小学生Python创意编程&#xff08;视频教学版&#xff09; 直达链接&#x…

SOLIDWORKS 2024新功能之钣金和结构系统

达索系统SOLIDWORKS钣金和结构系统是大家比较熟悉的模块了&#xff0c;在SOLIDWORKS 2024版本中钣金和结构系统功能也做了很大的提升。接下来微辰三维带大家一起看看如何使用达索系统SOLIDWORKS 2024钣金和结构系统的一些新功能快速完成相应的设计。 达索系统SOLIDWORKS 2024的…

bugku-web-GET

这里很明显是让用get请求传递一个名为what的参数&#xff0c;这个参数如果为flag&#xff0c;就会输出flag

HBase的Python API(happybase)操作

一、Windows下安装Python库&#xff1a;happybase pip install happybase -i https://pypi.tuna.tsinghua.edu.cn/simple 二、 开启HBase的Thrift服务 想要使用Python API连接HBase&#xff0c;需要开启HBase的Thrift服务。所以&#xff0c;在Linux服务器上&#xff0c;执行如…

(4)(4.3) Kogger Sonar

文章目录 前言 1 推荐硬件 2 配置回声探测仪模块 3 连接ArduPilot硬件 4 参数说明 前言 KOGGER 声纳(KOGGER Sonar)是一款结构紧凑、成本低廉的水下回声测深仪模块&#xff0c;带有 UART 接口&#xff0c;电源电压为 5-14v。 1 推荐硬件 CP210x USB->UART 转换器和安装…

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

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

在 Windows 11 上安装 MongoDB

MongoDB 是一个流行的 NoSQL 数据库&#xff0c;它提供了灵活的数据存储方案&#xff0c;而 MongoDB Compass 则是一个可视化管理工具&#xff0c;可以更轻松地与 MongoDB 数据库交互和管理。在本文中&#xff0c;我们将介绍如何在 Windows 11 上安装 MongoDB&#xff0c;并配置…

好看又好用,这 10 个宝藏 App 免费拿走不谢!

目录 1. 综合AI工具箱——HuluAI 2. 文本视频生成工具——Jujilu 3.翻译软件 —— TTime 4.专业录屏和直播软件 —— OBS Studio 5.开源跨平台轻量计时软件 —— wnr 6.开源跨平台绘图 —— Drawio 7.开源三维建模动画渲染 —— Blender 8.跨平台的多功能软件 —— Pear…

C 语言贪吃蛇源码解析

贪吃蛇是一款经典的电子游戏&#xff0c;玩家控制一条不断成长的蛇&#xff0c;需要避免撞到自己的身体或者游戏边界&#xff0c;同时吃掉出现在屏幕上的食物以增长身体长度。 下面是一个简单的贪吃蛇游戏的C语言实现&#xff0c;使用了标准输入输出库conio.h和时间库windows.h…

【C++】1323. 扩建花圃问题

问题&#xff1a;1323. 扩建花圃问题 类型&#xff1a;整数运算 题目描述&#xff1a; 梅山小学有一块长方形花圃&#xff08;花圃的长宽都是整数&#xff09;&#xff0c;长 m 米&#xff0c;宽未知。 在修建校园时&#xff0c;花圃的长增加了 n 米&#xff0c;此时发现增加…

Mybatis-获取参数值的两种方式

1. ${ } 和 #{ } MyBatis获取参数值的两种方式&#xff1a;${ } 和 #{ } 对于初学者来说&#xff0c;理解MyBatis中获取参数值的两种方式——#{}和${}&#xff0c;关键在于明白它们如何影响SQL语句的构建以及为何在安全性、灵活性上有显著差异。下面我将用简单易懂的语言来解…

SpringBoot+Prometheus+Grafana实现应用监控和报警

一、背景 SpringBoot的应用监控方案比较多&#xff0c;SpringBootPrometheusGrafana是目前比较常用的方案之一。它们三者之间的关系大概如下图&#xff1a; 关系图 二、开发SpringBoot应用 首先&#xff0c;创建一个SpringBoot项目&#xff0c;pom文件如下&#xff1a; <…

java数据结构与算法刷题-----LeetCode540. 有序数组中的单一元素

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目录 1. 异或运算2. 全数组二分查找异或奇偶3. 偶数下标二分查找 1. 异…

Python学习之-正则表达式

目录 前言&#xff1a;1.re.serach1.1例子&#xff1a; 2.re.match2.1示例1&#xff1a;2.2 示例2&#xff1a; 3.re.findall3.1 示例 4.re.fullmatch4.1 示例1&#xff1a;4.2 示例2: 5.re.split5.1 示例1:5.2 示例2&#xff1a;5.3 示例3&#xff1a; 6.re.sub6.1 示例&#…

puzzle(1122)连线迷宫

目录 一&#xff0c;连线迷宫-经典模式 1&#xff0c;规则 2&#xff0c;策略 3&#xff0c;调整的局部性 4&#xff0c;八连通端点的线条合并 taptap小游戏 迷宫解谜 连线迷宫模式 一&#xff0c;连线迷宫-经典模式 1&#xff0c;规则 2&#xff0c;策略 分2步&#x…

脚本应使用项目的主要语言编写

原文&#xff1a;Joo Freitas - 2024.03.24 这是我长时间以来的一个深感赞同的观点。 我参与过的几乎所有项目&#xff0c;都有我们编写的用于自动化重复性过程的脚本。然而&#xff0c;大多数脚本在几周后变得过时且难以维护&#xff0c;因为我们要么不再需要它们&#xff0…

Golang hash/crc32 库实战指南:从基础到优化

Golang hash/crc32 库实战指南&#xff1a;从基础到优化 引言理解CRC32hash/crc32库概览实战技巧数据校验性能优化多线程应用 错误处理与调试错误处理调试 实际案例分析结论 总结重点回顾 引言 在现代软件开发中&#xff0c;数据的完整性和安全性至关重要。无论是数据库存储、…

计算机组成原理 — 指令系统

指令系统 指令系统指令的概述指令的格式指令的字长取决于 操作数类型和操作种类操作数的类型数据在存储器中的存放方式操作类型 寻址方式指令寻址数据寻址立即寻址直接寻址隐含寻址间接寻址寄存器寻址寄存器间接寻址基址寻址变址寻址堆栈寻址 RISC 和 CISC 技术RISC 即精简指令…