Linux驱动编译与加载

15 阅读2分钟

一、内核源码树

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 字符设备驱动时,需要关注的目录树:

Linux-4.9.88/
├── Makefile
├── .config
├── drivers/
│   └── char/
│       ├── Makefile
│       ├── Kconfig
│       └── led_drivers/
│           ├── led_drv.c
│           ├── Makefile
│           └── Kconfig
├── arch/
│   └── arm/
│       └── boot/
│           └── dts/
└── include/
    └── linux/

二、两种添加驱动的方法

方法一:外部模块编译

  驱动源码放在内核源码目录外面,例如:

内核源码树:/home/book/imx6ull-sdk/Linux-4.9.88
驱动源码:/home/book/driver/led/led_drv.c

我们需要在驱动源码目录下建立外部makefile,但是也需要借用内核源码的makefile进行编译(Linux 驱动模块不是普通 C 程序,它最终要加载进正在运行的 Linux 内核,并且部分函数是 Linux 内核提供的,所以编译时必须使用和目标内核一致的编译规则、头文件、配置和版本信息),例如:

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。

方法二:加入内核源码树编译

  驱动源码放在内核源码目录下,例如:

Linux-4.9.88/drivers/char/led_drivers/led_drv.c

我们需要修改以下目录,将该驱动类似于其他驱动一样加入到内核源码的目录

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 的关系

整体关系:

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:

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后会打开一个菜单界面,可以在里面选择:

[*] 编译进内核                     //[*] -> y
[M] 编译成模块                     //[M] -> m
[ ] 不编译                         //[ ] -> n

例如:在 menuconfig 中选择了 LED 驱动,然后选择了编译成模块,保存退出后,.config 文件中会出现

CONFIG_LED_DRV=m

3..config 是什么?

  .config 是 menuconfig 保存出来的最终配置文件。它记录了用户选择了哪些功能,所以 .config 是内核编译时真正参考的配置结果。 例如:

CONFIG_LED_DRV=y
表示 LED 驱动编译进内核。

CONFIG_KEY_DRV=m
表示 KEY 驱动编译成 .ko 模块。

# CONFIG_BEEP_DRV is not set
表示 BEEP 驱动不编译。

4.Makefile 是什么?

  Makefile 决定具体编译哪些源码文件。
  比如某个驱动目录下的 Makefile:

obj-$(CONFIG_LED_DRV) += led_drv.o
obj-$(CONFIG_KEY_DRV) += key_drv.o
obj-$(CONFIG_BEEP_DRV) += beep_drv.o

它会根据 .config 中的配置结果来决定编译方式,假设 .config 中是:

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 文件,以及怎么组织生成目标文件”。