Linux驱动开发之构建第一个驱动程序

简介: Linux驱动开发基础

构建第一个驱动程序

驱动模块的组成

一个驱动模块主要由以下部分组成:、

组成 选项
头文件 必选
模块参数 可选
模块功能函数 可选
其他 可选
模块加载函数 必须
模块卸载函数 必须
模块许可声明 必须

1.头文件(必选)

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

2.模块参数(可选 )

模块参数是驱动模块加载时,需要传递给驱动模块的参数。

3.模块加载函数(必须 )

模块加载函数是模块加载时,需要执行的函数。

4.模块卸载函数(必须)

模块卸载函数是模块卸载时,需要执行的函数。

5.模块许可声明(必须)

模块许可声明表示模块受内核支持的程度,有许可权的模块会更受到开发人员的重视。需要使用MODULE_LICENSE表示该模块哦的许可权限。内核可识别的许可权限如下:

MODULE_LICENSE("GPL"); //任一版本的GNU公共许可权 
MODULE_LICENSE("GPL v2"); //GPL版本2许可权
MODULE_LICENSE("GPL and additional rights"); //GPL及其附加许可权
MODULE_LICENSE("Dual BSD/GPL");  //BSD/GPL双重许可权
MODULE_LICENSE("Dual MPL/GPL");  //MPL/GPL双重许可权
MODULE_LICENSE("Proprietary");   //专有许可权

第一个驱动模块

开始编写一个最简单的驱动模块

#include <linux/module.h>        
#include <linux/init.h>            //导入需要的头文件

static int hello_init(void)
{
    printk(KERN_ALERT "Hello World\n");   //加载函数打印 Hello World 信息
    return 0;
}

static void hello_exit(void)
{
    printk(KERN_ALERT "Goodbye, World\n"); //卸载函数
}

module_init(hello_init);    //指定模块加载函数
module_exit(hello_exit);    //指定模块卸载函数
MODULE_LICENSE("Dual BSD/GPL"); // 指定许可权为 Dual BSD/GPL

编译Hello World模块

正确编译内核模块,需要满足以下条件:

编译内核模块的条件

  1. 使用正确版本的编译工具、模块工具和其他必要的工具。
  2. 应该有一份内核源码,该源码的版本应该和系统目前 使用的内核版本一致。
  3. 内核源码应该至少编译一次,也就是执行过make命令。

Makefile文件

一个完整的Makefile文件如下:

ifeq ($(KERNELRELEASE),)
    KERNELDIR ?= /linux-2.6.29.4/linux-2.6.29.4
    PWD := $(shell pwd)
modules:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
clean:
    rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
else
    obj-m := hello.o
endif

编译模块

使用make命令就可以生成模块文件hello.ko了

模块的操作

命令 操作
insmod 加载模块
rmmod 卸载模块
modprobe 比较高级的加载和卸载模块命令
lsmod 查看已经加载的模块和信息
modinfo 查询模块的相关信息

加载Hello World模块后系统发生的变化

在路径/proc/modules下发生变化,modules文件中会增加如下一行:

cat modules | grep he*

lsmod | grep hello

/proc/devices 文件没有变化,因为hello.ko模块不是一个设备模块
/sys/module/目录会增加hello这个模块的基本信息。
在/sys/module/下会增加一个hello目录。 使用tree -a hello 可以查看以层次结构为组织的内核模块信息属性

模块参数和模块之间通信

可以使用module_param(参数名,参数数据类型,参数读写权限)来为模块定义个参数。

static long a = 1;
static int b = 1;
module_param(a,long,S_IRUGO);
module_param(b,int,S_IRUGO);

参数数据类型可以是byte、short、ushort、int、uint、long、ulong、bool、charp(字符指针类型)。不支持浮点类型,printk()也不支持浮点类型。

导出函数

使用EXPORT_SYMBOL使函数变为导出函数。

将模块加入内核

向内核添加模块

向liunx内核中添加驱动模块,需要完成3个工作:

  1. 编写驱动程序文件
  2. 将驱动程序文件放到linux内核源码的相应目录中,如果没有合适的目录,可以自己建立一个目录存放驱动程序文件。
  3. 在目录的Kconfig文件中添加新驱动程序对于的项目编译选择。
  4. 在目录的Makefile文件中添加新驱动程序的编译语句。

Kconfig

内核源码树的目录下都有两个文件Kconfig和Makefile。分布到各目录的Kconfig文件构成了一个分布式的内核配置数据库,每个Kconfig文件分别描述了所属目录源文档相关的内核配置菜单。在内核配置make menuconfig时,从Kconfig中读出菜单,用户选择后保存到.config这个内核配置文件中,在内核编译时,主目录中的Makefile调用这个.config文件,就知道了用户的选择。

Kconfig语法

主要关键字

config
menuconfig
choice/endchoice
comment
menu/endmenu //以上五项为菜单选项

if/endif //是一个条件选项

菜单入口

config 关键字定义一个新的配置选项,之后定义该配置选项的属性。属性可以有类型、输入提示,依赖关系,帮助信息和默认值等。

每个配置选项都必须指定一种类型,包括bool、tristate、string、hex和int,其中tristate和string是两种基本类型。

bool "set version information on all modules symbols" #定义bool类型菜单提示

依赖关系

depends on <expr>

如果定义了多个依赖关系,那么可以用&&来连接,表示与的关系。

bool "foo" if BAR #如果定义了BAR选项,那么使能foo选项
default y if BAR #如果定义BAR选项,那么foo的默认值为y,表示编译入内核

等价于


depends on BAR
bool "foo"
default y

菜单结构

菜单结构一般作为菜单入口的父菜单。

选择菜单(choice)

选择菜单定义一组选项。此选项的类型只能是boolean 和 tristate型。

"choice"
<choice options>
<choice block>
"endchoice"

在一个硬件有多个驱动的情况下可以使用choice菜单,使用choice 菜单可以实现最终只有一个驱动被编译进内核中,choice菜单可以接受的另一个选项是optional,这样选项就被设置被N,表示没有被选中。

注释菜单(comment)

comment <prompt>
<comment options>
目录
相关文章
|
3月前
|
安全 Linux 编译器
探索Linux内核的奥秘:从零构建操作系统####
本文旨在通过深入浅出的方式,带领读者踏上一段从零开始构建简化版Linux操作系统的旅程。我们将避开复杂的技术细节,以通俗易懂的语言,逐步揭开Linux内核的神秘面纱,探讨其工作原理、核心组件及如何通过实践加深理解。这既是一次对操作系统原理的深刻洞察,也是一场激发创新思维与实践能力的冒险。 ####
|
16天前
|
Prometheus 运维 监控
Prometheus+Grafana+NodeExporter:构建出色的Linux监控解决方案,让你的运维更轻松
本文介绍如何使用 Prometheus + Grafana + Node Exporter 搭建 Linux 主机监控系统。Prometheus 负责收集和存储指标数据,Grafana 用于可视化展示,Node Exporter 则采集主机的性能数据。通过 Docker 容器化部署,简化安装配置过程。完成安装后,配置 Prometheus 抓取节点数据,并在 Grafana 中添加数据源及导入仪表盘模板,实现对 Linux 主机的全面监控。整个过程简单易行,帮助运维人员轻松掌握系统状态。
120 3
|
3月前
|
Linux API 开发工具
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
ijkplayer是由B站研发的移动端播放器,基于FFmpeg 3.4,支持Android和iOS。其源码托管于GitHub,截至2024年9月15日,获得了3.24万星标和0.81万分支,尽管已停止更新6年。本文档介绍了如何在Linux环境下编译ijkplayer的so库,以便在较新的开发环境中使用。首先需安装编译工具并调整/tmp分区大小,接着下载并安装Android SDK和NDK,最后下载ijkplayer源码并编译。详细步骤包括环境准备、工具安装及库编译等。更多FFmpeg开发知识可参考相关书籍。
120 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
5月前
|
编解码 Linux 程序员
深度探索Linux操作系统 —— 构建根文件系统2
深度探索Linux操作系统 —— 构建根文件系统
58 12
|
5月前
|
前端开发 Linux
深度探索Linux操作系统 —— 构建桌面环境3
深度探索Linux操作系统 —— 构建桌面环境
72 12
|
5月前
|
存储 搜索推荐 Linux
深度探索Linux操作系统 —— 构建桌面环境1
深度探索Linux操作系统 —— 构建桌面环境
122 8
|
4月前
|
Linux 程序员 编译器
Linux内核驱动程序接口 【ChatGPT】
Linux内核驱动程序接口 【ChatGPT】
|
4月前
|
存储 Linux 开发工具
如何进行Linux内核开发【ChatGPT】
如何进行Linux内核开发【ChatGPT】
|
5月前
|
Linux 编译器 C语言
深度探索Linux操作系统 —— 构建桌面环境2
深度探索Linux操作系统 —— 构建桌面环境
50 6
|
5月前
|
Linux Shell 网络安全
深度探索Linux操作系统 —— 构建根文件系统1
深度探索Linux操作系统 —— 构建根文件系统
68 6
下一篇
开通oss服务