驱动(二)Linux 系统移植、驱动开发框架

一、Linux 驱动基础

1. 驱动分类及核心特点

驱动类型 数据处理方式 典型应用场景 关键特性
字符设备驱动 按单个字符顺序读写 串口、键盘、鼠标、LED 支持open/read/write等基本文件操作,无缓存或简单缓存
块设备驱动 按固定大小 "块"(通常 512 字节)读写 硬盘、eMMC、SD 卡、U 盘 支持随机访问,需缓存管理,通过文件系统间接访问
网络设备驱动 按网络帧(数据包)传输 以太网、WiFi、CAN 总线 不依赖文件系统,需对接 TCP/IP、CAN 等协议栈,通过socket接口访问

2. 驱动编译的两种方式(静态 vs 动态)

驱动编译的核心差异在于是否将驱动集成到内核镜像,或以独立模块形式加载,具体流程和区别如下:

(1)静态编译(内置到内核)

核心逻辑 :将驱动代码直接编译进 Linux 内核镜像(如zImage),内核启动时自动加载驱动。操作步骤

  1. 准备驱动代码 :在 Linux 内核源码的对应目录(如drivers/char/)编写驱动文件(例:hello.c)。

  2. 修改 Kconfig :在驱动目录的Kconfig文件中新增配置项,用于menuconfig图形化选择,示例:

    kconfig

    复制代码
    config HELLO_DRIVER
        tristate "Hello World Character Driver"
        help
            This is a test character driver for Linux.
  3. 图形化配置 :执行make ARCH=arm CROSS_COMPILE=xxx- menuconfig,在对应菜单中选中HELLO_DRIVER(勾选为*,表示静态编译)。

  4. 更新配置文件 :选中后,内核会自动在.config文件中添加CONFIG_HELLO_DRIVER=yy表示静态内置)。

  5. 修改 Makefile :在驱动目录的Makefile中添加编译规则,将驱动文件关联到配置项:

    makefile

    复制代码
    obj-$(CONFIG_HELLO_DRIVER) += hello.o
  6. 编译内核 :执行make ARCH=arm CROSS_COMPILE=xxx- all -jN,驱动会随内核一起生成到zImage中。

(2)动态编译(模块编译)

核心逻辑 :驱动代码编译为独立的内核模块文件(.ko),不内置到内核,可在系统运行时动态加载 / 卸载。操作步骤

  1. 准备驱动代码 :同静态编译,编写hello.c(需包含模块加载 / 卸载函数,如module_init()/module_exit())。
  2. 修改 Kconfig :同静态编译,配置项类型为tristate(支持静态 / 动态 / 不编译)。
  3. 图形化配置 :执行make menuconfig,将HELLO_DRIVER设为M(表示动态模块),.config中会生成CONFIG_HELLO_DRIVER=m
  4. 修改 Makefile :同静态编译,编译规则不变(obj-$(CONFIG_XXX)自动适配m模式)。
  5. 编译模块 :执行make ARCH=arm CROSS_COMPILE=xxx- modules,内核仅编译标记为M的驱动,生成hello.ko文件。
  6. 模块操作
    • 加载:insmod hello.ko(临时加载,重启失效)
    • 卸载:rmmod hello(需确保驱动未被使用)
    • 查看:lsmod(查看已加载的模块)

二、Linux 驱动工程搭建(目录划分规范)

为保证工程可维护性,建议按 "功能模块 + 通用文件" 划分目录,典型结构如下:

plaintext

复制代码
hello_driver/          # 驱动工程根目录
├── doc/               # 文档目录:存放驱动说明、接口文档、测试报告
├── driver/            # 驱动核心代码目录
│   ├── hello/         # 具体驱动模块(如hello驱动)
│   │   ├── hello.c    # 驱动核心逻辑(初始化、文件操作、中断处理等)
│   │   ├── hello.h    # 驱动头文件(宏定义、结构体、函数声明)
│   │   ├── Kconfig    # 该模块的配置文件
│   │   └── Makefile   # 该模块的编译规则
│   └── common/        # 通用驱动组件(如GPIO封装、中断封装)
├── include/           # 全局头文件目录:存放工程通用头文件(如平台定义、工具函数声明)
├── test/              # 测试程序目录:存放驱动测试代码(如`test_hello.c`,用于验证驱动功能)
├── scripts/           # 脚本目录:存放编译脚本(如`build.sh`)、烧录脚本(如`load_driver.sh`)
├── Makefile           # 工程顶层Makefile:调用内核Makefile,指定交叉编译工具链、架构等
└── README.md          # 工程说明:编译步骤、测试方法、注意事项

三、Linux 系统移植与驱动核心问题(重点)

1. Linux 系统移植需要哪些文件?各文件作用?

Linux 系统移植的核心是构建 "Bootloader + 内核 + 根文件系统" 三部分,所需关键文件及作用如下:

组件 关键文件 / 目录 作用说明
Bootloader(以 UBoot 为例) U-Boot 源码目录、u-boot.bin 1. 硬件初始化(如 CPU、内存、GPIO、时钟);2. 加载内核到内存;3. 传递启动参数(bootargs)给内核;4. 提供命令行交互(如烧录、分区管理)
Linux 内核 zImage/uImage.config、设备树(*.dtb 1. zImage:压缩后的内核镜像,是系统运行的核心;2. .config:内核配置文件,决定内核功能和驱动;3. *.dtb:设备树文件,描述硬件信息(如外设地址、中断号),替代传统的 "板级代码"
根文件系统 rootfs目录(包含bin/sbin/etc等) 1. 提供系统运行所需的文件和目录结构;2. 包含初始化脚本(如init)、系统命令(如ls/cd)、库文件(lib);3. 存储用户数据和应用程序
交叉编译工具链 arm-linux-gnueabihf-gcc 为 ARM 架构编译 UBoot、内核、根文件系统和驱动的工具集

2. Linux 系统的启动流程(从上电到应用运行)

完整启动流程分为 4 个阶段,按顺序如下:

  1. **硬件上电初始化(固化代码)**上电后,CPU 先执行芯片内部固化的 "启动代码"(如 i.MX 的 ROM Code),完成最基础的硬件检查(如内存是否存在),然后从指定启动介质(如 eMMC、SD 卡)加载 Bootloader。

  2. Bootloader(UBoot)阶段

    • 初始化硬件:配置 CPU 时钟、内存控制器、GPIO、串口等(让硬件处于可工作状态)。
    • 加载内核和设备树:将存储介质中的zImage(内核)和*.dtb(设备树)加载到内存指定地址(如内核加载到0x80800000)。
    • 传递启动参数:通过bootargs将根文件系统路径、串口配置等参数传递给内核,然后跳转到内核入口地址,启动内核。
  3. Linux 内核阶段

    • 内核初始化:解压内核、初始化进程管理、内存管理、文件系统(如ext4)、驱动框架。
    • 设备树解析:解析*.dtb文件,识别硬件设备(如 UART、GPIO),并加载对应的驱动。
    • 启动 init 进程:内核初始化完成后,启动第一个用户空间进程init(PID=1),init进程负责后续的系统初始化。
  4. 根文件系统与用户空间阶段

    • init进程执行初始化脚本(如/etc/init.d/rcS):挂载其他分区(如/tmp)、启动网络服务、加载驱动模块、启动应用程序。
    • 进入用户交互:若配置了登录功能,会启动终端(如串口终端),等待用户登录,最终进入 Shell 命令行或自动启动应用。

3. UBoot 中bootcmdbootargs的含义?

(1)bootcmd:UBoot 的 "自动启动命令"
  • 作用 :UBoot 启动后,若未手动打断(如按键盘按键),会自动执行bootcmd中定义的命令,用于加载内核和设备树。

  • 典型示例 (针对 eMMC 启动):

    bash

    复制代码
    setenv bootcmd 'mmc dev 0; mmc read 0x80800000 0x1000 0x8000; mmc read 0x83000000 0x9000 0x1000; bootz 0x80800000 - 0x83000000'

    命令拆解:

    • mmc dev 0:选择第 0 个 eMMC 设备;
    • mmc read 0x80800000 0x1000 0x8000:将 eMMC 中地址0x1000开始、长度0x8000zImage读入内存0x80800000
    • mmc read 0x83000000 0x9000 0x1000:将 eMMC 中地址0x9000开始、长度0x1000的设备树(*.dtb)读入内存0x83000000
    • bootz 0x80800000 - 0x83000000:启动内核(bootz是压缩内核启动命令,-表示无 ramdisk,最后是设备树地址)。
(2)bootargs:UBoot 传递给 Linux 内核的 "启动参数"
  • 作用 :内核启动时会解析bootargs,获取根文件系统路径、串口配置、硬件参数等,决定系统的运行方式。

  • 典型示例

    bash

    复制代码
    setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk0p2 rootfstype=ext4 rw init=/linuxrc'

    参数拆解:

    • console=ttymxc0,115200:指定系统控制台为ttymxc0(第 1 个串口),波特率 115200;
    • root=/dev/mmcblk0p2:指定根文件系统位于mmcblk0p2(第 0 个 eMMC 的第 2 个分区);
    • rootfstype=ext4:根文件系统类型为ext4
    • rw:根文件系统以 "可读可写" 模式挂载;
    • init=/linuxrc:指定用户空间的第一个init进程路径为/linuxrc(通常是busybox的链接)。

4. UBoot 如何编译?UBoot 的功能?

(1)UBoot 编译步骤(以 ARM 架构为例)
  1. 准备交叉编译工具链 :确保arm-linux-gnueabihf-gcc已安装并添加到PATH(可通过echo $PATH验证)。

  2. 获取 UBoot 源码 :从官方(https://www.denx.de/wiki/U-Boot)或开发板厂商(如正点原子)获取适配的 UBoot 源码。

  3. 加载默认配置 :执行厂商提供的板级配置文件(位于configs/目录),示例(正点原子 IMX6ULL 开发板):

    bash

    复制代码
    make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_alientek_emmc_defconfig
  4. 图形化配置(可选) :若需修改 UBoot 功能(如添加命令、支持外设),执行:

    bash

    复制代码
    make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
  5. 编译 UBoot :指定并行任务数(-jN,N 为 CPU 核心数的 1-2 倍),加速编译:

    bash

    复制代码
    make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16
  6. 获取编译产物 :编译完成后,在源码根目录生成u-boot.bin(UBoot 镜像,需烧录到启动介质)、u-boot.elf(调试用)等文件。

(2)UBoot 的核心功能

UBoot 的定位是 "硬件初始化与内核加载工具",核心功能分为 5 类:

  1. 硬件初始化:上电后初始化 CPU、内存(DDR)、时钟、GPIO、串口、存储设备(eMMC/SD)等,为内核运行准备硬件环境。
  2. 内核加载与启动 :从存储介质(eMMC、SD、U 盘、网络)加载内核(zImage)和设备树(*.dtb)到内存,传递bootargs后启动内核。
  3. 命令行交互 :提供丰富的命令集,用于调试和管理,如:
    • 存储操作:mmc(eMMC/SD)、usb(U 盘)、nand(NAND Flash);
    • 内存操作:md(查看内存)、mw(修改内存)、cp(内存拷贝);
    • 网络操作:ping(网络测试)、tftp(从 TFTP 服务器下载文件);
    • 环境变量操作:setenv(设置环境变量)、saveenv(保存环境变量到存储介质)。
  4. 系统烧录 :支持将 UBoot、内核、根文件系统烧录到 eMMC、NAND Flash 等存储设备(如mmc write命令)。
  5. 故障排查:提供串口打印、内存查看、硬件状态检测等功能,帮助定位硬件或系统启动问题。

5. 内核如何编译?如何裁剪?

(1)Linux 内核编译步骤(以 ARM 架构为例)
  1. 准备环境 :安装交叉编译工具链(如arm-linux-gnueabihf-gcc)、内核源码(从https://www.kernel.org/或厂商获取)。

  2. 清理编译环境 :若之前编译过,执行distclean彻底清理残留文件:

    bash

    复制代码
    make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
  3. 加载默认配置 :执行板级默认配置文件(位于arch/arm/configs/目录),示例:

    bash

    复制代码
    make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_alientek_emmc_defconfig
  4. 图形化裁剪(可选) :执行menuconfig调整内核配置(裁剪功能 / 驱动):

    bash

    复制代码
    make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
  5. 编译内核 :执行all编译内核镜像、设备树和模块(-jN加速):

    bash

    复制代码
    make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16
  6. 获取产物

    • 内核镜像:arch/arm/boot/zImage(压缩内核,用于启动);
    • 设备树:arch/arm/boot/dts/xxx.dtb(xxx 为开发板对应的设备树文件名);
    • 驱动模块:若有动态编译的驱动,生成*.ko文件(位于对应驱动目录)。
(2)内核裁剪方法(通过menuconfig

内核裁剪的核心是 "保留必需功能,删除无用功能",以减小内核体积、降低资源占用,步骤如下:

  1. 进入裁剪界面 :执行make ARCH=arm CROSS_COMPILE=xxx- menuconfig,进入图形化配置界面。
  2. 裁剪原则
    • 移除无用硬件驱动 :如无老旧硬件(软驱、并口)、特定网卡或声卡驱动,检查Device Drivers子菜单,禁用无关选项。
    • 精简文件系统支持 :保留必需的文件系统(如ext4squashfs),移除BtrfsReiserFS等。
    • 禁用调试和性能分析 :关闭Kernel hackingDebugging相关选项,禁用ProfilingTracers
    • 优化网络功能 :移除不用的网络协议(如Amateur RadioIrDA),精简防火墙(Netfilter)规则,仅保留必需模块。
    • 模块化非核心功能 :将不常用功能编译为模块(M),而非内置(Y),如某些文件系统或驱动。
  3. 使用搜索功能 :在menuconfig界面中,可以通过按 "/" 键来进行全局搜索,快速定位到需要裁剪或配置的选项。
  4. 保存配置 :完成裁剪后,按 "Esc" 键退出配置界面,选择保存配置,配置结果将保存到.config文件中。
相关推荐
liu****4 小时前
负载均衡式的在线OJ项目编写(四)
运维·c++·负载均衡·个人开发
帅锅锅0074 小时前
Vim工具使用总结
linux
景晁4 小时前
(自用)vim的高级命令
linux·编辑器·vim
Stanf up5 小时前
Linux动静态库
linux
2501_920047035 小时前
docker相关进程的作用
运维·docker·容器
iconball5 小时前
个人用云计算学习笔记 --14( Linux 逻辑卷管理、Linux 交换空间管理)
linux·运维·笔记·学习·云计算
峰顶听歌的鲸鱼6 小时前
32.Linux NFS 服务
linux·运维·服务器·笔记·学习方法
NobitaLab6 小时前
vpp开启nat,分片包丢包问题分析与解决
linux
Ting-yu6 小时前
零基础学Docker(5)--容器数据卷
运维·docker·容器