Linux驱动开发—编写第一个最简单的驱动模块

news2024/9/22 1:20:40

文章目录

  • 开发驱动准备工作
    • 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 驱动就编写成功了

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

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

相关文章

谷歌账号异常,成功通过验证后这个界面操作指引:建议增加辅助手机和邮箱

许多朋友对下面这个界面都很熟悉&#xff0c;通常是账号被停用后的时候输入账号和密码后&#xff0c;还需要再次输入手机号码验证。而且这个时候输入国内的号码或者谷歌账号绑定的辅助手机号码都不管用&#xff0c;提示此电话号码用于验证的次数过多&#xff0c;或者此电话号码…

链表篇:03-合并有序链表

解题思路&#xff1a; 使用双指针&#xff0c;一个指针指向头节点&#xff0c;然后另外一个指针进行移动。让其头节点保持不动&#xff0c;最后循环遍历两个链表&#xff0c;将其挂到头指针所在的节点上。 temp 守卫节点&#xff0c;用于指向头节点&#xff0c;防止头节点丢…

机械学习—零基础学习日志(高数17——极限局部有界性)

零基础为了学人工智能&#xff0c;真的开始复习高数 这里我们更加详细讲解函数极限性质。上一篇文章里有一些内容还需要进一步补充。 局部有界性 这里是局部有界性的需要注意的事项。第3点&#xff0c;如果函数在闭区间内连续&#xff0c;则必定有界。试想一下&#xff0c;如…

Log4j2漏洞

Log4j2漏洞 步骤一:执行以下命令启动靶场环境并在浏览器访问!!! systemctl start docker cd vulhub/log4j/CVE-2021-44228 vi docker-compose.yml //编写docker-compose.xml的端口和版本号 docker-compose up -d # 访问网址 http://192.168.30.131:8983/solr/#/步骤二:先在自…

MyBatis入门如何使用操作数据库及常见错误(yml配置)

一&#xff0c;什么是MyBatis 是一款优秀的持久层框架&#xff0c;用于简化jdbc的开发 持久层&#xff1a;指的就是持久化操作的层&#xff0c;通常也就是数据访问层&#xff08;dao&#xff09;&#xff0c;也就是用来操作数据库。 也就是MyBatis是让你更加简单完成程序与数…

ECCV 2024前沿科技速递:GLARE-基于生成潜在特征的码本检索点亮低光世界,低光环境也能拍出明亮大片!

在计算机视觉与图像处理领域&#xff0c;低光照条件下的图像增强一直是一个极具挑战性的难题。暗淡的光线不仅限制了图像的细节表现&#xff0c;还常常引入噪声和失真&#xff0c;极大地影响了图像的质量和可用性。然而&#xff0c;随着ECCV 2024&#xff08;欧洲计算机视觉会议…

form表单按钮根据编辑/只读状态显示和隐藏

1. 场景阐述: form表单自定义按钮,在编辑模式显示,在只读模式隐藏 2. 效果: 这里的保存按钮是自定义按钮,在编辑状态的时候显示,非编辑状态下隐藏 3. 解决方案: 如下所示,只需要在按钮中添加odoo自带class类oe_edit_only即可 <header><button type"object"…

桌面管理利器:2024年度待办事项工具评选

国内外主流的10款待办事项桌面工具对比&#xff1a;PingCode、Worktile、滴答清单、番茄ToDo、Teambition、Tower、有道云笔记、TickTick、Any.do、Trello。 在忙碌的工作日中&#xff0c;管理日常任务和待办事项常常让人感到不胜其烦。选择合适的待办事项桌面工具&#xff0c;…

【Python实战因果推断】67_图因果模型2

目录 Are Consultants Worth It? Crash Course in Graphical Models Chains Are Consultants Worth It? 为了展示有向无环图(DAG)的力量&#xff0c;让我们考虑一个更有趣但处理因素并未随机化的情况。假设你是某公司的经理&#xff0c;正在考虑是否聘请顶级咨询顾问。你…

[数据结构] AVL树 模拟实现AVL树

标题&#xff1a;[数据结构] AVL树 && 模拟实现AVL树 水墨不写bug 正文开始&#xff1a; 目录 &#xff08;一&#xff09;普通二叉搜索树的痛点 &#xff08;二&#xff09;AVL树简介 &#xff08;1&#xff09;AVL树的概念 &#xff08;三&#xff09;AVL树的…

LeetCode面试150——189轮转数组

题目难度&#xff1a;中等 默认优化目标&#xff1a;最小化平均时间复杂度。 Python默认为Python3。 目录 1 题目描述 2 题目解析 3 算法原理及程序实现 3.1 暴力求解 3.2 循环链表 3.3 环状替代 3.4 数组翻转 4 题目难度 参考文献 1 题目描述 给定一个整数数组 nu…

运维.Linux.bash学习笔记.数组及其使用

运维专题 Bash Shell数组及其使用 此笔记当前仍在修改和编写。 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this article:http…

基于N32L406+Freertos+letter_shell终端开源库移植

移植教程 这里首先感谢作者的开源 https://gitee.com/zhang-ge/letter-shell) [Letter shell 3.0 全新出发 | Letter (nevermindzzt.github.io)](https://nevermindzzt.github.io/2020/01/19/Letter shell 3.0全新出发/) 1.复制代码 将litter_shell文件夹中的所有文件复制到…

本地使用Git同步、配合Gitee同步至仓库并下拉到本地(亲手调试,全能跑通)

这几天在公司&#xff0c;同事都在使用Gitee上传项目&#xff0c;进行同步&#xff0c;我也进行了简单学习了解了一下版本控制软件Git&#xff0c;挺不错的&#xff0c;故写个笔记记录一下。 本篇博文主要涉及的内容&#xff1a; 1&#xff0c;本地写代码&#xff0c;通过Git同…

软件测试_接口测试面试题

接口测试是软件测试中的重要环节&#xff0c;它主要验证系统不同模块之间的通信和数据交互是否正常。在软件开发过程中&#xff0c;各个模块之间的接口是实现功能的关键要素&#xff0c;因此对接口进行全面而准确的测试是确保系统稳定性和可靠性的关键步骤。 接口测试的核心目…

树上dp学习总结2

今天也是侥幸刷了两道树上dp的问题&#xff0c;第一个还算简单&#xff0c;但是第二个真的可以说是我碰到的蓝题之首&#xff0c;做了一个晚上我只能留下了不争气的口水&#xff08;太饿了&#xff0c;该吃夜宵了&#xff09; P1131 [ZJOI2007] 时态同步 思路&#xff1a;一开…

RK3568笔记四十九:W25Q64驱动开发(硬件SPI1)

若该文为原创文章&#xff0c;转载请注明原文出处。 一、SPI介绍 串行外设接口 (Serial Peripheral interface) 简称 SPI&#xff0c;是一种高速的&#xff0c;全双工&#xff0c;同步的通信总线&#xff0c;并 且在芯片的管脚上只占用四根线&#xff0c;节约了芯片的管脚。 …

Word如何设置表格内容的中文和英文字体

1、选中需要设置的表格内容。 2、CtrlD&#xff0c;分别设置中文和英文字体&#xff0c;点确定即可。 提升自己最好的方法就是改变坏习惯&#xff0c;改变坏习惯最好的方法找习惯替代它。坏习惯不改&#xff0c;你永远受到限制&#xff0c;只能原地踏步。To do list&#xf…

爬取指定的天气网站数据

目 录 一、引言 &#xff08;一&#xff09;项目背景 &#xff08;二&#xff09;目标与意义 二、数据获取与处理 &#xff08;一&#xff09;使用的库和模块 &#xff08;二&#xff09;获取天气信息的函数 &#xff08;三&#xff09;数据预处理 三、数据分析…