Linux驱动(二):模块化编程的基本操作

news2024/11/13 15:56:28

目录

  • 前言
  • 一、模块化编程
    • 1.模块化驱动代码框架
    • 2.printk详解
    • 3.应用操作
  • 二、多模块编程
  • 三、多文件编程
  • 四、函数传参


前言

  没多少东西,就是最基础的一些Linux驱动编写操作。


一、模块化编程

驱动加载到内核中的两种方法:

1.静态编译:就是将模块化的驱动代码就直接集成到了内核镜像里,缺点就是你每次都需要重新编译内核,烧写内核。这种方法的优点是启动时即有驱动支持还稳定,但不便于动态管理或升级驱动程序。

2.动态加载(模块化):将驱动程序通过Makefile编译成内核模块,可以在运行时通过insmod命令加载,使用rmmod命令卸载。这种方法使得驱动程序的管理更加灵活,允许在不重启系统的情况下加载或更新驱动程序。

  在Linux系统中,驱动程序的模块化编程是一种设计和实现设备驱动程序的方法,能够提供更大的灵活性可维护性。模块化编程允许驱动程序以模块的形式被加载或卸载,而无需重启系统。这种方式使得系统资源的管理更加高效,并简化了驱动程序的开发和调试过程。
  这里提到的模块化,跟C语言的.c.h模块化不同,这个里的模块化是指可以在内核运行时被动态地加载和卸载。优点:极大的缩小了内核的镜像大小。如同电脑装windows系统时,官方只给一个比较小的纯净版系统,后续装好后,根据自己的需求再去下载安装其他驱动,这个过程就是模块化的应用。

模块化指令
insmod:用于将模块插入内核。
rmmod:用于从内核中卸载模块。
lsmod:列出当前加载的模块。
modinfo:显示模块的信息。

1.模块化驱动代码框架

#include <linux/kernel.h>
#include <linux/module.h>

static int __init test_init(void)
{
	printk("test加载函数运行!\n");
	return 0;
}

static void __exit test_exit(void)
{
	printk("test卸载函数运行!\n");
}

module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");

理解
static int __init test_init(void):入口函数。类似于main,当该模块被加载时,入口函数里的代码便会运行。
static void __exit test_exit(void):出口函数。当该模块被卸载时,会执行该函数里的代码。

声明驱动模型出/入口函数

module_init(test_init);
module_exit(test_exit);

头文件

#include <linux/kernel.h>
#include <linux/module.h>

#include <linux/kernel.h>:包含了内核级的函数和宏,主要用于内核日志记录和调试(printk)。
#include <linux/module.h>:定义了内核模块的基本功能和宏,用于模块的加载、卸载及描述。

开源许可

MODULE_LICENSE("GPL");

   就是一个开源声明。指定了内核模块使用的许可证类型为 GNU 通用公共许可证(GPL)。这是一个自由软件许可证,允许用户自由使用、修改和分发代码,只要遵守相同的许可证条款。
   如果你不声明模块的许可证,内核在加载模块时会输出警告,提示模块没有许可证信息。

2.printk详解

  printk函数是 Linux 内核中的一个调试工具,用于在内核空间打印日志信息。类似于C语言中的printf函数,但是用于内核空间的打印,因此它有一些特定的用法和限制。
1.printk打印的东西有消息等级,Linux 内核共提供了八种不同的消息级别,分为级别 0~7。只有当消息等级大于控制台等级时(值越低等级越高),printk输出的消息才会被打印到终端上。
2.printk不能使用浮点功能,不能有 %f,%lf,其余功能和printf一样。

使用命令:cat /proc/sys/kernel/printk查看内核打印等级:
在这里插入图片描述
1.控制台日志级别:内核消息的最低级别,决定了哪些日志信息会显示在控制台上。比如,设置为 4 时,KERN_WARNING 及以上级别的信息会显示。
2.默认日志级别:在内核启动时的默认日志级别。如果内核日志级别未被设置,使用此值。
3.内核消息日志级别:日志写入内核日志缓冲区的级别。此级别决定了日志信息在内核日志缓冲区中的可见性。
4.串行端口日志级别:如果系统使用串行端口来记录日志,这个值决定了日志信息的级别。

消息等级宏(在 include/linux/kern_levels.h 文件中):

#define KERN_SOH   "\001"      /* ASCII Start Of Header */
#define KERN_SOH_ASCII '\001'

#define KERN_EMERG      KERN_SOH "0"   /* system is unusable */
#define KERN_ALERT      KERN_SOH "1"   /* action must be taken immediately */
#define KERN_CRIT       KERN_SOH "2"   /* critical conditions */
#define KERN_ERR        KERN_SOH "3"   /* error conditions */
#define KERN_WARNING    KERN_SOH "4"   /* warning conditions */
#define KERN_NOTICE     KERN_SOH "5"   /* normal but significant condition */
#define KERN_INFO       KERN_SOH "6"   /* informational */
#define KERN_DEBUG      KERN_SOH "7"   /* debug-level messages */

设置printk的消息等级printk(KERN_ERR "test111\n"); 在输出内容前添加消息等级的宏定义。若不设置消息等级,则为默认的消息等级。
修改内核的消息等级echo 0 4 1 7 >/proc/sys/kernel/printk ,修改控制台等级或默认消息等级。

3.应用操作

  当驱动程序的.c编写好后,我们直接用makefile生成.ko文件方便后续加载到内核中。

Makefile文件

obj-m += test.o #最终生成模块的名字就是 test.ko      
    
KDIR:=/home/zht/RK3588S/kernel   # 内核源码的路径 
    
CROSS_COMPILE_FLAG=/home/zht/RK3588S/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-
    #交叉编译器路径
all:
	make -C $(KDIR) M=$(PWD) modules ARCH=arm64 CROSS_COMPILE=$(CROSS_COMPILE_FLAG)
    #调用内核层 Makefile 编译目标为 modules->模块 文件在当前路径
    # 架构  ARCH=arm64 
clean:
	rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.markers *.order app *.mod

解释:
这段 Makefile 就是指定了内核源代码目录、交叉编译器路径,并通过调用内核 Makefile 编译当前目录下的模块文件,生成一个适用于 ARM 64 位架构的内核模块 (test.ko)。

make -C $(KDIR) M=$(PWD) modules ARCH=arm64 CROSS_COMPILE=$(CROSS_COMPILE_FLAG):调用内核的 Makefile 来编译模块。各个参数的含义如下:
-C $(KDIR):告诉 make 在 $(KDIR) 目录下执行 Makefile,也就是内核源代码的路径。
M=$(PWD):指定当前目录 ($(PWD)) 作为模块编译的目录。
modules:指定要编译的目标是内核模块。
ARCH=arm64:指定目标架构为 ARM 64 位架构(即 AArch64)。
CROSS_COMPILE=$(CROSS_COMPILE_FLAG):使用指定的交叉编译器来编译模块。

  生成.ko文件后直接用adb push推到硬件中,然后就能使用命令执行加载、卸载等操作。
在这里插入图片描述

二、多模块编程

  多模块编程就是同过加载多个模块A、B、C等,通过声明和调用,实现B模块调用A模块的内容。
例:

texta.c

#include <linux/kernel.h>
#include <linux/module.h>
void hello_text0(void)
{
	printk("hello_来自texta\n");
}

static int __init texta_init(void)
{
	printk("texta加载函数运行!\n");
	return 0;
}

static void __exit texta_exit(void)
{
	printk("texta卸载函数运行!\n");
}

module_init(texta_init);
module_exit(texta_exit);
EXPORT_SYMBOL(hello_text0);//模块内的函数如果要提供给其他模块使用,必须使用EXPORT_SYMBOL()导出
MODULE_LICENSE("GPL");

textb.c

#include <linux/kernel.h>
#include <linux/module.h>

extern void hello_text0(void);

static int __init textb_init(void)
{
	printk("textb加载函数运行!\n");
	hello_text0();
	return 0;
}

static void __exit textb_exit(void)
{
	printk("textb卸载函数运行!\n");
}



module_init(textb_init);
module_exit(textb_exit);
MODULE_LICENSE("GPL");

Makefile

obj-m += texta.o #这要生成两个.ko     
obj-m += textb.o   

KDIR:=/home/zht/RK3588S/kernel   #他就是你现在rk3588s里内核的路径 
    
CROSS_COMPILE_FLAG=/home/zht/RK3588S/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-
    #这是你的交叉编译器路径 --- 这里你也要替换成你自己的交叉编译工具的路径
all:
	make -C $(KDIR) M=$(PWD) modules ARCH=arm64 CROSS_COMPILE=$(CROSS_COMPILE_FLAG)
    #调用内核层 Makefile 编译目标为 modules->模块 文件在当前路径
    # 架构  ARCH=arm64 
clean:
	rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.markers *.order app *.mod

解析:
  就是多了个EXPORT_SYMBOL ,EXPORT_SYMBOL是一个重要的机制,用于在 Linux 内核模块之间共享代码。使用该宏后,其他模块再使用extern声明外部函数,就能够实现跨模块使用该函数

  这里就是在texta中使用EXPORT_SYMBOL宏传入hello_text0,之后在textb用extern外部声明,最后就实现了外部模块函数的调用。

结果:
在这里插入图片描述

注意:由于是b调用a,所以加载时,要先加载a,再加载b。卸载时,要先卸载b,再卸a。

三、多文件编程

  多文件编译就是将一个模块的函数,在外边单独写一个.c文件,入口和出口函数还是只有一个,最后编译结果也是生成一个.ko文件

示例
texta.c

#include <linux/kernel.h>
#include <linux/module.h>
extern void waibu_hello(void);
static int __init texta_init(void)
{
	
	printk("texta加载函数运行!\n");
	waibu_hello();
	return 0;
}

static void __exit texta_exit(void)
{
	printk("texta卸载函数运行!\n");
}

module_init(texta_init);
module_exit(texta_exit);
MODULE_LICENSE("GPL");

textb.c

#include <linux/kernel.h>
void waibu_hello(void)
{
	printk("hello_来自textb\n");
}

Makefile

obj-m := textab.o
textab-objs = texta.o textb.o//因为是多文件编译,所以这里也要修改

KDIR:=/home/zht/RK3588S/kernel   #他就是你现在rk3588s里内核的路径 
    
CROSS_COMPILE_FLAG=/home/zht/RK3588S/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-
    #这是你的交叉编译器路径 --- 这里你也要替换成你自己的交叉编译工具的路径
all:
	make -C $(KDIR) M=$(PWD) modules ARCH=arm64 CROSS_COMPILE=$(CROSS_COMPILE_FLAG)
    #调用内核层 Makefile 编译目标为 modules->模块 文件在当前路径
    # 架构  ARCH=arm64 
clean:
	rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.markers *.order app *.mod

结果:
在这里插入图片描述

四、函数传参

  函数传参有点像c语言的主函数传参,大致一样。主要就是用了两个宏module_parammodule_param_array

  1. module_param
    语法: module_param(name, type, permissions);
    作用: 定义一个模块参数,其中 name 是参数名,type 是参数的数据类型,permissions 是权限设置。
int a = 0;
module_param(a, int, S_IRUGO | S_IWUSR);

a: 参数名,模块的用户可以通过此名称来设置参数值。
int: 数据类型,表示参数是整数类型。
S_IRUGO | S_IWUSR: 权限设置,S_IRUGO 表示所有用户都可以读取,S_IWUSR 表示只有当前用户可以写入。

  1. module_param_array
    语法: module_param_array(name, type, num, permissions);
    作用: 定义一个数组类型的模块参数,其中 name 是参数名,type 是数组元素的数据类型,num 是一个指针,用于存储数组元素的个数,permissions 是权限设置。
int arr[10] = {0};
int num = 0;
module_param_array(arr, int, &num, S_IRUGO | S_IWUSR);

arr: 参数名,表示数组参数。
int: 数组元素的数据类型,这里是整数类型。
&num: 用于存储数组元素的个数的指针。内核会将实际元素数量写入这个变量。
S_IRUGO | S_IWUSR: 权限设置,所有用户可读,当前用户可写。

应用示例:

send_data.c

#include <linux/kernel.h>
#include <linux/module.h>

int num =0;
static int a=1024;
static char *p = "zht";
static int arr[10]={0,1,2,3,4};

static int __init text_init(void)
{
	int i;
	printk("text加载函数运行!\n");
	printk("a=%d\n",a);
	printk("p=%s\n",p);
	printk("num=%d\n",num);
	for(i=0;i<10;i++)
		printk("arr[%d]=%d\n",i,arr[i]);
	return 0;
}

static void __exit text_exit(void)
{
	printk("text卸载函数运行!\n");
}
module_param(a,int,S_IRUGO|S_IWUSR);
module_param(p,charp,S_IRUGO|S_IWUSR);
module_param_array(arr,int,&num,S_IRUGO|S_IWUSR);
module_init(text_init);
module_exit(text_exit);
MODULE_LICENSE("GPL");

Makefile

obj-m += send_data.o     
    
KDIR:=/home/zht/RK3588S/kernel   #他就是你现在rk3588s里内核的路径 
    
CROSS_COMPILE_FLAG=/home/zht/RK3588S/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-
    #这是你的交叉编译器路径 --- 这里你也要替换成你自己的交叉编译工具的路径
all:
	make -C $(KDIR) M=$(PWD) modules ARCH=arm64 CROSS_COMPILE=$(CROSS_COMPILE_FLAG)
    #调用内核层 Makefile 编译目标为 modules->模块 文件在当前路径
    # 架构  ARCH=arm64 
clean:
	rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.markers *.order app *.mod

不传参时:
在这里插入图片描述
传参时:
在这里插入图片描述


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

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

相关文章

【Python百日进阶-Web开发-Feffery】Day500 - dash使用秘籍

文章目录 前言:fac是什么?“人生苦短,我用Python;Web开发,首选Feffery!”↓↓↓ 今日笔记 ↓↓↓1 dash应用使用cdn加载静态资源1.1 页面效果1.2 项目源码2 suppress_callback_exceptions=True3 阻止首次回调3.1 阻止所有回调函数的首次回调3.2 阻止单个回调函数的首次回…

《JavaEE进阶》----5.<SpringMVC②剩余基本操作(CookieSessionHeader响应)>

Cookie和Session简介。 Spring MVC的 2.请求 Cookie的设置和两种获取方式 Session的设置和三种获取方式。 3.响应 1.返回静态页面 2.返回数据 3.返回HTML片段 4.返回JSON 5.设置状态码 6.设置header 三、&#xff08;接上文&#xff09;SpringMVC剩余基本操作 3.2postman请求 …

CSAPP Data Lab

CSAPP 的第一个 Lab&#xff0c;对应知识点为书中的第 2 章&#xff08;信息的表示与处理&#xff09;&#xff0c;要求使用受限制的运算符和表达式实现一些位操作。主要分为两个部分&#xff1a;整数部分和浮点数部分。其中整数部分限制较多&#xff0c;比较偏重技巧性&#x…

Red Hat Enterprise Linux 9—Red Hat 9.4Linux系统 Mac电脑虚拟机安装【保姆级教程】

Mac分享吧 文章目录 效果一、下载软件二、安装软件与配置1、安装2、配置 三、查看基本信息安装完成&#xff01;&#xff01;&#xff01; 效果 一、下载软件 下载软件 地址&#xff1a;www.macfxb.cn 二、安装软件与配置 1、安装 2、配置 三、查看基本信息 安装完成&#xf…

【国考】特值法

特值法 题干中存在乘除关系&#xff0c;且对应量未知。 例3&#xff1a;甲、乙、丙三个工程队的效率比为6&#xff1a;5&#xff1a;4,现将A、B两项工作量相同的工程交给这三个工程队,甲队负责A工程,乙队负责B工程,丙队参与A工程若干天后转而参与B工程.两项工程同时开工,耗时16…

【PyQt6 应用程序】视频百叶窗效果一键生成模块

在现代的多媒体创作中,音频和视频的结合是提升作品感染力的关键因素之一。尤其是短视频的制作,往往需要根据音频的节奏进行精细的剪辑和特效添加。PyQt6 作为一个功能强大的 Python GUI 库,为我们提供了极大的便利,使得我们可以轻松地创建功能丰富的应用程序。 本教程将一…

J.U.C并发工具集实战及原理分析

​在J.U.C里提供了很多的并发控制工具类&#xff0c;这些工具类可以使得线程按照业务的某种约束来执行。本节包含CountDownLatch、Semaphore、CyclicBarrier等工具类。目的是了解他们基本使用、原理及实际应用。 1. CountDownLatch主题 1.1 CountDownLatch简介 CountDownLat…

ShardingSphere-JDBC实现数据加解密

一、什么是ShardingSphere&#xff1f; ShardingSphere定位为轻量级 Java 框架&#xff0c;在 Java 的 JDBC 层提供的额外服务。 它使用客户端直连数据库&#xff0c;以 jar 包形式提供服务&#xff0c;无需额外部署和依赖&#xff0c;可理解为增强版的 JDBC 驱动&#xff0c;完…

最优化理论(一)

什么是最优化问题 最优化问题是决策问题&#xff0c;选择一下可以执行的策略来使得目标最优。 一个最优化问题包括&#xff1a; 决策变量一个或多个目标函数一个由科兴策略组成的集合&#xff0c;可由等式或者不等式刻画 最优化问题的基本形式&#xff1a; 最优化问题的分类…

upload-labs通关攻略

Pass-1 这里上传php文件说不允许上传 然后咱们开启抓包将png文件改为php文件 放包回去成功上传 Pass-2 进来查看提示说对mime进行检查 抓包把这里改为image/jpg; 放包回去就上传成功了 Pass-3 这里上传php文件它说不允许上传这些后缀的文件 那咱们就可以改它的后缀名来绕过…

Guitar Pro 8.2.1 Build 32+Soundbanks Win/Mac音色库 开心激活版 音乐软件Guitar Pro 8中文破解版

音乐软件Guitar Pro 8中文破解版是一个受吉他手喜爱的吉他和弦、六线谱、BASS 四线谱绘制、打印、查看、试听软件&#xff0c;它也是一款优秀的 MIDI 音序器&#xff0c;MIDI 制作辅助工具&#xff0c;可以输出标准格式的 MIDI。GP 的过人之处就在于它可以直接用鼠标和键盘按标…

过滤器 与 拦截器

文章目录 过滤器 与 拦截器一、过滤器&#xff08;Filter&#xff09;1、特点2、生命周期3、实现4、过滤器链1&#xff09;配置 order2&#xff09;执行顺序 二、拦截器 Inteceptor1、特点2、生命周期3、实现4、拦截器链1&#xff09;配置 order2&#xff09;执行顺序&#xff…

【Linux】保姆级 Linux 常见命令使用

&#x1f970;&#x1f970;&#x1f970;来都来了&#xff0c;不妨点个关注叭&#xff01; &#x1f449;博客主页&#xff1a;欢迎各位大佬!&#x1f448; 文章目录 1. Linux 是什么1.1 Linux 是什么1.2 关于 Linux 我们需要学什么 2. 需提前准备的东西2.1 环境 —— 如何获取…

关于PowerDesigner的使用

1.PowerDesigner概述&#xff1a; 1.PowerDesigner是一款开发人员常用的数据库建模工具&#xff0c;用户利用该软件可以方便地制作 数据流程图、概念数据模型 、 物理数据模型 &#xff0c;它几乎包括了数据库模型设计的全过程&#xff0c;是Sybase公司为企业建模和设计提供的…

蓝色炫酷碎粒子HTML5导航源码

源码介绍 蓝色炫酷碎粒子HTML5导航源码&#xff0c;源码由HTMLCSSJS组成&#xff0c;记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行效果&#xff0c;也可以上传到服务器里面&#xff0c;重定向这个界面 效果预览 源码获取 蓝色炫酷碎粒…

火焰传感器详解(STM32)

目录 一、介绍 二、传感器原理 1.原理图 2.引脚描述 三、程序设计 main.c文件 IR.h文件 IR.c文件 四、实验效果 五、资料获取 项目分享 一、介绍 火焰传感器是一种常用于检测火焰或特定波长&#xff08;760nm-1100nm&#xff09;红外光的传感器。探测角度60左右&am…

MQTT学习:MQTT vs AMQP,mosquitto安装,调试工具mqttfx mqttx

前言 物联网vs互联网? 数据量/数据源:物联网的数据多是设备的自动采集,其数量远远超过互联网,互联网的数据更多是人工生成的 MQTT 协议(Message Queuing Telemetry Transport)vs AMQP 协议(Advanced Message Queuing Protocol)是两种在物联网中广泛使用的协议。 物联网…

推荐一款灵活,可靠和快速的开源分布式任务调度平台

今天给大家推荐一款灵活&#xff0c;可靠和快速的开源分布式任务调度平台——SnailJob。 前言 什么是任务调度&#xff1f; 任务调度&#xff0c;是指在多任务的环境下&#xff0c;合理地分配系统资源&#xff0c;调度各个任务在什么时候&#xff0c;由哪一个处理器处理&…

【简单】 猿人学web第一届 第15题 备周则意怠,常见则不疑

数据接口分析 数据接口 https://match.yuanrenxue.cn/api/match/15 请求时需要携带 page 页码&#xff0c;m为加密参数 cookie中没有加密信息&#xff0c;携带 SessionId请求即可 加密参数还原 查看数据接口对应的 requests 栈 m参数 是通过 window.m() 方法执行后得到的 打上…

分布式系统中的Dapper与Twitter Zipkin:链路追踪技术的实现与应用

目录 一、什么是链路追踪&#xff1f; 二、核心思想Dapper &#xff08;一&#xff09;Dapper链路追踪基本概念概要 &#xff08;二&#xff09;Trace、Span、Annotations Trace Span Annotation 案例说明 &#xff08;三&#xff09;带内数据与带外数据 带外数据 带…