一、驱动的编写
静态加载 , 驱动在zImage 中
1.编写驱动 .c
2.编译代码
.config Kconfig Makefile
3.通过 make menuconfig 勾选 需要编译的内容 <*>
4.zImage 中,会包含咱们的驱动的代码。 zImage 会变大。
5.需要开发板重新启动,新增的代码才能生效。
动态加载 ,生成ko文件,独立与zImage
1.编写驱动 .c
2.编译代码
修改Makefile obj-m += led.o
make modules产生ko文件
3.使用insmod 命令 , 手动加载(Linux 已经成功运行后)。 根据实际需要加载。不是zImage的一部分 。
rmode ,卸载模块。更加灵活。
字符设备重要的宏:
|--------------------------|-------------------------------------|
| 函数名 | 功能 |
| module_init | 驱动模块加载入口 |
| module_exit | 驱动模块卸载入口 |
| MODULE_LICENSE(_license) | MODULE_LICENSE("GPL") , |
| printk(char*s, ....); | 内核中,类似printf函数,不要输出 浮点数。这个等级需要配置文 件 |
| pr_info(fmt, ...) | 带输出等级的输出,一般信息 |
| pr_err(fmt, ...); | 带输出等级的输出 错误信息 |
在源码目录下编写自己的驱动
源码最顶层有个.config ,会决定那些文件被编译。 在源码目录中,每层都有Kconfig, Markfile . 最顶层层Make ,会调用子目录中的Makefile
drivers/char 下新建 mydevtest.c
cpp
#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;
} s
tatic void __exit mymodele_exit(void)
{
pr_info("mymodule_init byebye\n");
return;
} m
odule_init(mymodule_init);
module_exit(mymodele_exit);
如果需要编译这个文件。需要按以下步骤
1.在drivers/char 目录下创建.c文件
2.修改Kconfig
在Kconfig 中加入
cpp
config MYMODULE #MYMODULE 这个是驱动控制变量名 在Makefile中要用
tristate "mymodule inner kernel test" # 三态, Y,M N , bool 是 Y N
# depends on ,依赖
default y #选择的默认状态
help #说明信息
driver test kconfig makefile
3.修改Makefile
4.make menuconfig ,进行勾选
5.make modules ->生成ko 文件。 复制到nfs/rootfs 下
6.模块的加载
cpp
insmod myhell.ko
ls /sys/module
lsmod #查看加载了那些模块
rmmod #卸载模块
源码目录外编写驱动 (推荐)
工程的构建,在linux源码之外的地方编写代码。
目录的结构
cpp
├── app
│ ├── app.c
│ └── Makefile
├── drv
│ ├── .clangd #clangd匹配脚本
│ ├── compile_commands.json -> /home/linux/imx6ull/linux-imxrel_imx_4.1.15_2.1.0_ga_alientek/compile_commands.json # 自动补齐文件
│ ├── drv.c
│ └── Makefile
└──
Makefile
drv的Makefile
MODULE_NAME:= drv 注意drv后面不能有空格,已经翻车多次。
先决条件, linux源码需要已经编译过了,才行。
make -C 进入目录
M= ,设置模块的路径, 咱们的模块路径和源码路径是不同的。
cpp
MODULE_NAME:= drv
KER_DIR := /home/linux/imx6ull/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
CUR_DIR := $(shell pwd)
obj-m += $(MODULE_NAME).o
all:
make -C $(KER_DIR) M=$(CUR_DIR) modules
cp $(MODULE_NAME).ko ~/nfs/rootfs
clean:
make -C $(KER_DIR) M=$(CUR_DIR) clean
distclean:
rm $(MODULE_NAME).ko
make -C $(KER_DIR) M=$(CUR_DIR) distclean
app的Makefile
cpp
SRC:=app.c
OBJ:=app
CC:=arm-linux-gnueabihf-gcc
all:
$(CC) $(SRC) -o $(OBJ)
clean:
rm $(OBJ)
顶层makefile
cpp
all:
make -C ./app all
make -C ./drv all
clean:
make -C ./app clean
make -C ./drv clean
二、字符设备驱动框架
linux 系统中驱动的分类:
- 字符设备驱动 gpio,i2c,spi,ad,uart,lcd,camera , 按照字节操作,有顺序要求。数据吞吐量小。比重大,占到95% 以上。
- 块设备驱动 存储设备的驱动程序, emmc, sd, flash 类型设备。 随机数据访问,按照固定大小字节读写,数据吞吐量大。
- 网络设备驱动 ,支持网络协议栈设备 。 wifi, blue, zigbee, can。

字符设备驱动编写的步骤
1 申请设备号,主次设备号 MKDEV, alloc_chrdev_region
2 注册设备号 register_chrdev_region , alloc_chrdev_region
3 初始化字符设备结构体 cdev_init(自动关联fops), cdev_add(驱动注册到内核),cdev_alloc (手动关联)
4 准备 file_operation , 注册 应用层对应的open,close,read, write函数。给函数指针赋值。
相关函数
1.cdev操作函数接口:
|------------|------------|
| 函数名 | 功能 |
| cdev_init | 初始化字符设备结构体 |
| cdev_alloc | 申请字符设备结构体 |
| cdev_add | 添加字符设备 |
| cdev_del | 删除字符设备 |
2.字符设备操作接口:
|--------------------------|---------------------------------------------------|
| 函数名 | 功能 |
| register_chrdev_region | 静态注册已知设备号范围(需提前确定主设备号) |
| alloc_chrdev_region | 动态分配设备号,并注册(内核自动分配主设备号) |
| unregister_chrdev_region | 释放注册的设备号 |
| register_chrdev | 注册设备号并关联file_operations结构体(早期内核使用, 2.6版本 以后尽量不使用) |
| unregister_chrdev | 注册注册的设备号(早期内核使用, 2.6版本以后尽量不使用) |
当创建设备类型,cat /proc/devices
出现设备类后,没有 具体的设备 。需要手动创建节点
创建具体的设备,主设备号 248 ,次设备号0
mknod /dev/led0 c 248 0
3.file_operation 结构体