【无标题】

一、内核编程与应用编程的差异

开发驱动程序,需要解决 3 个核心问题:

如何在已经运行的操作系统内核中,执行我们自己编写的驱动代码

掌握驱动代码的标准编写框架

驱动最终是对硬件寄存器进行操作,原理和裸机编程一致,但内核不能直接访问物理地址,需要完成虚拟地址和物理地址的映射

Linux 驱动分为两种加载模式:静态加载编译进 zImage 内核镜像、动态加载生成 ko 模块。

1.1 静态加载,驱动编译进 zImage

把驱动直接编译进内核 zImage 镜像:

编写驱动.c源文件

  • 准备编译相关文件:.config、Kconfig、Makefile
  • 执行 make menuconfig,图形配置界面勾选驱动选项 <*>, 代表编译进内核
  • 重新编译生成 zImage 镜像,镜像体积会增大,驱动成为内核的一部分
    将新zImage烧录开发板,开发板重启后新增驱动代码方可生效

缺点:每次修改驱动都需要重新编译内核、烧写镜像,必须重启开发板,调试效率低。

1.2 动态加载,生成 ko 模块

将驱动编译为独立的.ko模块文件,独立于zImage,系统启动后可以手动加载卸载,调试灵活。

编写驱动.c源文件

修改 Makefile:obj‑m += led.o,‑m表示编译为外部 ko 模块

执行make modules编译模块,生成.ko文件

开发板 Linux 系统运行后,执行insmod xxx.ko手动加载驱动

执行rmmod xxx卸载模块,无需重启开发板

ko 不属于 zImage 的一部分,可以按需加载卸载。

常用模块操作命令

bash 复制代码
insmod myhell.ko    # 加载内核模块
lsmod               # 查看已经加载的内核模块
rmmod myhell        # 卸载内核模块
ls /sys/module      # 查看sysfs下模块相关信息

二、字符设备重要内核宏与函数编写最简驱动模块,需要掌握如下内核接口:

二、 字符设备重要内核宏与函数

函数 / 宏 功能说明
module_init() 驱动模块加载入口宏,执行 insmod 时调用该函数
module_exit() 驱动模块卸载入口宏,执行 rmmod 时调用该函数
MODULE_LICENSE("GPL") 模块许可证声明,必须添加,否则内核产生污染警告
printk() 内核打印函数,类似用户态 printf,不支持浮点数输出,输出等级受内核配置控制
pr_info(fmt,...) 带输出等级打印,输出普通日志信息
pr_err(fmt,...) 带输出等级打印,输出错误日志信息

前置开发环境准备:配置好 tags、cscope 源码索引,完成 vim/vscode 自动补全环境。

三、方式一:在内核源码目录内编写驱动

在drivers/char目录新增驱动文件,借助内核Kconfig/Makefile编译体系完成编译。

3.1 创建驱动源码

在drivers/char目录新建mydevtest.c

c 复制代码
#include "linux/printk.h"
#include <linux/init.h>
#include <linux/module.h>

static int __init mymodule_init(void)
{
    pr_info("i'm coming...,mymodule_init ok\n");
    return 0;
}

static void __exit mymodele_exit(void)
{
    pr_info("mymodule_init byebye\n");
    return;
}

module_init(mymodule_init);
module_exit(mymodele_exit);
MODULE_LICENSE("GPL");

__init:标记初始化函数,内核启动完成后该段内存可以被释放
__exit:标记卸载函数,如果驱动静态编译进内核,该函数会被丢弃

3.2 修改同目录 Kconfig

在 drivers/char/Kconfig 中添加驱动配置项

Kconfig

bash 复制代码
config MYMODULE
    tristate "mymodule inner kernel test"
    default y
    help
      driver test kconfig makefile

tristate 为三态选项:Y 编译进内核、M 编译成 ko 模块、N 不编译;config MYMODULE 定义配置变量,供 Makefile 使用。

3.3 修改同目录 Makefile

在 drivers/char/Makefile 增加下面一行

makefile

bash 复制代码
obj-$(CONFIG_MYMODULE) += myhello.o
CONFIG_MYMODULE 由 menuconfig 的选择决定:
选择 Y:展开为obj-y += myhello.o,编译进 zImage
选择 M:展开为obj-m += myhello.o,编译生成 ko 模块
选择 N:该行直接忽略,不编译该文件

3.4 编译模块,开发板加载测试

执行make menuconfig,找到mymodule inner kernel test配置项,选择 M,编译模块

执行make modules编译模块,生成myhello.ko

将 ko 文件拷贝至开发板 nfs 根文件系统

开发板终端执行命令

bash

bash 复制代码
insmod myhell.ko
lsmod
rmmod myhello

控制台输出对应打印信息,代表模块加载卸载正常。

四、方式二:内核源码目录外搭建独立驱动工程

实际项目普遍采用该方案,驱动代码完全独立于内核源码树,不需要修改内核内部 Kconfig、Makefile。

前置条件:内核源码已经完整编译完成。

4.1 工程目录结构

plaintext

project/

├── app/ # 用户态测试应用程序

│ ├── app.c

│ └── Makefile

├── drv/ # 内核驱动代码

│ ├── drv.c

│ ├── Makefile

│ └── .clangd # vscode自动补全配置文件

└── Makefile # 顶层总Makefile,一键编译app和驱动

drv 目录 Makefile(驱动模块编译)

bash 复制代码
#makefile
MODULE_NAME:=drv
修改为你本机已编译完成内核源码的绝对路径
bash 复制代码
KER_DIR := /home/linux/imx6ull/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
obj-m := $(MODULE_NAME).o

all:
	make -C $(KER_DIR) M=$(CUR_DIR) modules

clean:
	make -C $(KER_DIR) M=$(CUR_DIR) clean

distclean:
	rm -f *.ko *.o *.mod *.mod.c *.symvers *.order
	make -C $(KER_DIR) M=$(CUR_DIR) distclean

注意:MODULE_NAME:=drv 赋值等号后面不要加多余空格,极易引发编译问题。

bash 复制代码
#makefile
SRC:=app.c
OBJ:=app

修改为你的交叉编译器

bash 复制代码
CC:=arm-linux-gnueabihf-gcc

顶层总 Makefile,一键编译全部工程

bash 复制代码
all:
	$(CC) $(SRC) -o $(OBJ)

clean:
	rm $(OBJ)
```bash
makefile
all:
	make -C ./app all
	make -C ./drv all

clean:
	make -C ./app clean
	make -C ./drv clean

4.2 编译与开发板使用

在工程顶层目录执行make,同时编译用户应用和驱动模块,得到drv.ko与app可执行程序

将 drv.ko、app 拷贝到开发板。

开发板终端执行:

bash

bash 复制代码
insmod drv.ko
./app
rmmod drv

五、两种驱动开发方案对比总结

内核源码树内写驱动

需要修改内核源码内部的 Kconfig、Makefile;适合需要合入主线内核的驱动;调试繁琐,每次修改需要关注 menuconfig 配置。

内核源码树外独立工程

驱动代码完全独立,不污染内核源码;只需要一份编译完成的内核源码;修改驱动只编译自身工程,推荐日常驱动调试使用.

相关推荐
LeoZY_1 小时前
LinkScope 使用笔记:基于 OpenOCD 的通用硬件芯片调试助手
笔记·单片机·嵌入式硬件·开源软件
星空真迷人4 小时前
嵌入式鸿蒙并非精简版,核心究竟是什么?
stm32·单片机·嵌入式硬件·物联网·华为·harmonyos·iot
hongmai6668884 小时前
GK7205V300芯片深度解析:H.265低功耗视觉主控,助力安防监控高效选型
arm开发·嵌入式硬件·物联网·智能家居·h.265·risc-v
2301_801434985 小时前
STM32F407移植TinyUSB
stm32·单片机·嵌入式硬件
Dlrb12116 小时前
Linux驱动---Linux 中断系统及其上与下半部的介绍与阻塞IO实现按键检测
linux·嵌入式硬件·imx6ull·中断·阻塞io
国产HT1621B6 小时前
YL4056 NTC 外围电阻 R3/R4 计算:从公式推导到工程选型
科技·单片机·嵌入式硬件
唔好理总之好犀利8 小时前
TVS管分析:MKS烧保险丝异常分析
stm32·单片机·嵌入式硬件
Dr.kangder9 小时前
嵌入式处理器虚拟化仿真技术(九)——SimpleScalar原理与应用
嵌入式硬件·架构·嵌入式·虚拟化·仿真
郭涤生9 小时前
ARM大小核架构调度原理与RK3588嵌入式配置复习
arm开发·架构·rk3588