一、内核源码树
bash
Linux-4.9.88/
├── Makefile # 顶层 Makefile,内核编译入口
├── Kconfig # 顶层配置菜单入口
├── .config # 保存 menuconfig 的选择结果
│
├── arch/ # 和 CPU 架构相关的代码
│ └── arm/ # ARM 架构相关代码
│ ├── configs/ # 默认配置文件
│ └── boot/
│ └── dts/ # 设备树文件
│
├── drivers/ # 各种设备驱动
│ ├── char/ # 字符设备驱动
│ ├── gpio/ # GPIO 驱动
│ ├── i2c/ # I2C 驱动
│ ├── spi/ # SPI 驱动
│ ├── net/ # 网络设备驱动
│ └── leds/ # LED 子系统驱动
│
├── include/ # 内核头文件
│ └── linux/ # 驱动常用头文件
│
├── fs/ # 文件系统
├── kernel/ # 内核核心代码
├── mm/ # 内存管理
├── net/ # 网络协议栈
└── scripts/ # 编译脚本和配置工具
写 LED 字符设备驱动时,需要关注的目录树:
arduino
Linux-4.9.88/
├── Makefile
├── .config
├── drivers/
│ └── char/
│ ├── Makefile
│ ├── Kconfig
│ └── led_drivers/
│ ├── led_drv.c
│ ├── Makefile
│ └── Kconfig
├── arch/
│ └── arm/
│ └── boot/
│ └── dts/
└── include/
└── linux/
二、两种添加驱动的方法
方法一:外部模块编译
驱动源码放在内核源码目录外面,例如:
arduino
内核源码树:/home/book/imx6ull-sdk/Linux-4.9.88
驱动源码:/home/book/driver/led/led_drv.c
我们需要在驱动源码目录下建立外部makefile,但是也需要借用内核源码的makefile进行编译,例如:
javascript
KERN_DIR = /home/book/imx6ull-sdk/Linux-4.9.88
obj-m += led_drv.o //obj-m:Linux 内核 Kbuild 编译系统中的变量
//m:编译为模块,不编译进内核
//如果不写 obj-m += led_drv.o,Linux 内核编译
//系统会进入你当前目录看 Makefile,但它不知道要
//把哪个 .c 文件编译成 .ko,所以一般不会编译你的 led_drv.c
all:
make -C $(KERN_DIR) M=$(PWD) modules //make -C $(KERN_DIR):进入内核源码KERN_DIR目录,使用内核编译系统
//M=$(PWD):告诉内核:外部模块源码在当前目录
//modules:告诉内核:只编译模块,不是整个内核
clean:
make -C $(KERN_DIR) M=$(PWD) clean
这种方式不会改内核源码树,也不需要 Kconfig。
方法二:加入内核源码树编译
驱动源码放在内核源码目录下,例如:
arduino
Linux-4.9.88/drivers/char/led_drivers/led_drv.c
我们需要修改以下目录,将该驱动类似于其他驱动一样加入到内核源码的目录
scss
drivers/char/Makefile
drivers/char/led_drivers/Makefile
drivers/char/Kconfig
drivers/char/led_drivers/Kconfig
源码放进内核源码树
修改 Kconfig 和 Makefile
这里修改drivers/char/Makefile时,通常加上obj-y(或者$(CONFIG_LED_DRV)) += led_drivers/,
它的意思不是"把文件夹编译成 .o",而是:编译当前目录时,进入 led_drivers 子目录继续读取里面的 Makefile
通过 menuconfig 选择 y/m/n
执行 make 或 make modules 自动编译
三、Kconfig、menuconfig、.config、Makefile 的关系
整体关系:
ini
Kconfig 文件
|
| 定义有哪些配置选项
| 例如:CONFIG_LED_DRV
v
make menuconfig
|
| 图形/菜单方式让用户选择 y / m / n
v
.config 文件
|
| 保存最终选择结果
| 例如:
| CONFIG_LED_DRV=y
| CONFIG_KEY_DRV=m
v
Makefile
|
| 根据 CONFIG_xxx 的值决定编译进内核还是编译成模块
v
生成内核镜像 zImage / Image 或 .ko 模块
1.Kconfig 是什么?
Kconfig 是用来定义配置菜单和配置选项的文件。
比如驱动目录中可能有一个 Kconfig:
arduino
config LED_DRV //config LED_DRV最终会变为CONFIG_LED_DRV
tristate "My LED Driver"
default n
help
This is a simple LED driver.
它的意思是:
定义了一个配置项:LED_DRV
在 menuconfig 中显示为:
My LED Driver
它可以选择:
y:编译进内核
m:编译成模块 .ko
n:不编译
2.menuconfig 是什么?
menuconfig 不是一个文件,而是一个配置界面。
当我们执行make menuconfig后会打开一个菜单界面,可以在里面选择:
css
[*] 编译进内核 //[*] -> y
[M] 编译成模块 //[M] -> m
[ ] 不编译 //[ ] -> n
例如:在 menuconfig 中选择了 LED 驱动,然后选择了编译成模块,保存退出后,.config 文件中会出现
ini
CONFIG_LED_DRV=m
3..config 是什么?
.config 是 menuconfig 保存出来的最终配置文件。它记录了用户选择了哪些功能,所以 .config 是内核编译时真正参考的配置结果。 例如:
ini
CONFIG_LED_DRV=y
表示 LED 驱动编译进内核。
CONFIG_KEY_DRV=m
表示 KEY 驱动编译成 .ko 模块。
# CONFIG_BEEP_DRV is not set
表示 BEEP 驱动不编译。
4.Makefile 是什么?
Makefile 决定具体编译哪些源码文件。
比如某个驱动目录下的 Makefile:
javascript
obj-$(CONFIG_LED_DRV) += led_drv.o
obj-$(CONFIG_KEY_DRV) += key_drv.o
obj-$(CONFIG_BEEP_DRV) += beep_drv.o
它会根据 .config 中的配置结果来决定编译方式,假设 .config 中是:
ini
CONFIG_LED_DRV=y
CONFIG_KEY_DRV=m
# CONFIG_BEEP_DRV is not set
展开的结果如下:
obj-y += led_drv.o
表示led_drv.o 编译进内核
obj-m += key_drv.o
表示key_drv.o 编译成 key_drv.ko 模块
obj- += beep_drv.o
beep_drv.o 不编译
综上所述:Kconfig / menuconfig / .config 决定"某个功能要不要编、编进内核还是编成模块";Makefile 根据这个结果,决定"具体编译哪些 .c 文件,以及怎么组织生成目标文件"。