1. Linux设备驱动课程大纲
- Linux内核模块
- 字符设备驱动
- Linux内核中并发和竞态的解决办法
- IO模型(阻塞、非阻塞、IO多路复用、信号驱动IO)
- 设备树(语法、如何编写设备树)
- GPIO子系统
- 中断子系统
- 内核定时器
- platform平台驱动
- i2c总线驱动
- spi总线驱动
- 块设备驱动
- 摄像头驱动
2. 驱动的层次种类
应用层:APP glibc 0-3G内存空间
内核层:文件 内存 网络 进程 设备管理 3-4G内存空间
内核层到硬件层:字符设备驱动、块设备驱动、网络设备驱动
硬件层:鼠标 键盘 屏幕 硬盘 LED emmc
字符设备驱动:按照字节流访问,只能顺序访问,不能无序访问
块设备驱动:按照block(512kB)访问,可以顺序访问也可以无序访问
网络设备驱动:没有设备节点,完成网络协议栈向硬件上的数据收发功能的代码
3. Linux内核模块
3.1 内核模块三要素
- 入口
在驱动的入口函数中完成资源的分配工作,在安装驱动的时候入口函数执行
- 出口
在驱动的出口函数中完成资源的释放工作,在卸载驱动的时候出口函数执行
- 许可证
内核模块必须遵从GPL开源协议
3.2 内核模块代码实列
#include <linux/init.h>
#include <linux/module.h>
// 入口
// static:限定作用域,只在当前文件可以使用
// int:函数的返回值类型
// __init: #define __init __section(".init.text")
// 告诉编译器将驱动的入口函数放在.init.text段中
// 在linux内核启动的时候从这个段中找到驱动,回调驱动的入口函数
// demo_init:入口函数的名字,习惯上:led_init,adc_init,uart_init...
// (void):没有参数
static int __init demo_init(void)
{
return 0;
}
// 出口
// static:限定作用域,只在当前文件可以使用
// void:函数的返回值类型
// __exit: #define __init __section(".exit.text")
// 告诉编译器将驱动的出口函数放在.exit.text段中
// demo_exit:入口函数的名字,习惯上:led_exit,adc_exit,uart_exit...
// (void):没有参数
static void __exit demo_exit(void)
{
}
module_init(demo_init); // 告诉内核入口函数
module_exit(demo_exit); // 告诉内核出口函数
MODULE_LICENSE("GPL"); // 许可证,开源协议
3.3 内核模块的编译
内部编译(产品阶段)
- 把驱动代码拷贝到内核源代码目录下
linux@ubuntu:~/linux-5.10.61/drivers/char$ cp ~/work/day1/01demo/demo.c .
- 修改Kconfig文件
Kconfig文件:生成make menuconfig的选项菜单的文件
472 config DEMO
473 tristate "this is demo list"
474 help
475 this is demo to test intel compile
- 执行make menukonfig选配选项
Y:将驱动编译到uImage,当内核启动的时候就会执行驱动的入口函数
M:将驱动进行模块化编译,生成一个xxx.ko文件,执行安装命令驱动入口才会被执行
N:不编译
- 将上述的配置信息保存到.config文件中
2148 CONFIG_DEMO=m
- 在Makefile中添加编译选项
在linux-5.10.61/drivers/char/Makefile中添加如下选项
obj-$(CONFIG_DEMO) +=demo.o
经过变量的替换obj-$(CONFIG_DEMO) 就变成了obj-m +=demo.o
- 编译驱动
make uImage LOADADDR=0xc2000000
编译内核
将obj-y后面的文件都编译到uImage这个内核文件中
make modules
模块化编译
将obj-m后的文件单独编译成xxx.ko模块
外部编译(开发阶段)
- 外部编译:驱动代码放在linux内核目录之外进行编译
自己编写Makefile文件
KERNELDIR:= /home/linux/linux-5.10.61 #开发板内核路径
PWD:= $(shell pwd) #当前路径
.PHONY:all
all:
make -C $(KERNELDIR) M=$(PWD) modules
@#make -C $(KERNELDIR)切换路径到内核顶层目录下,
@#读取顶层Makefile文件,对着它执行make M=$(PWD) modules
@#make M=$(PWD) modules对当前目录下的驱动进行模块化编译
.PHONY:clean
clean:
make -C $(KERNELDIR) M=$(PWD) clean
@#make -C $(KERNELDIR)切换路径到内核顶层目录下,
@#读取顶层Makefile文件,对着它执行make M=$(PWD) clean
@#make M=$(PWD) clean清除当前目录编译生成的文件
obj-m:=demo.o #指定编译的模块
- 通用的Makefile
make arch=arm modname=hello
make arch=x86 modname=hello
arch?=x86
modname?=demo
ifeq ($(arch),arm)
KERNELDIR:= /home/linux/linux-5.10.61
else
KERNELDIR:= /lib/modules/$(shell uname -r)/build/
endif
PWD:= $(shell pwd) #当前路径
.PHONY:all
all:
make -C $(KERNELDIR) M=$(PWD) modules
.PHONY:clean
clean:
make -C $(KERNELDIR) M=$(PWD) clean
obj-m:=$(modname).o #指定编译的模块
3.4 模块相关命令
sudo insmod demo.ko
安装模块
lsmod
查看模块
sudo rmmod demo
卸载模块