Buildroot驱动开发实战:基于ADB调试的无SSH嵌入式Linux驱动构建全流程
引言:为什么选择Buildroot+ADB进行嵌入式驱动开发?
在嵌入式Linux开发中,驱动模块开发是连接硬件与应用的关键环节。传统的开发流程依赖SSH远程登录进行代码编辑、编译和调试,但在资源受限的嵌入式设备(如工业控制板、物联网网关)中,Buildroot构建的根文件系统往往不包含SSH服务(为减小体积默认不集成openssh),此时如何高效完成驱动开发?
ADB(Android Debug Bridge) 提供了一种轻量级解决方案:通过USB或网络连接嵌入式设备,实现文件传输、shell交互、进程调试等功能,且ADB客户端体积小(仅几MB),可在PC端快速部署。结合Buildroot的交叉编译能力,可构建一套"PC端编译→ADB传输→设备端加载调试"的完整驱动开发流程。
本文将深入讲解基于Buildroot的驱动模块开发全流程,涵盖Buildroot环境搭建、ADB调试配置、驱动源码编写、交叉编译、设备端加载与调试,搭配50%实操代码示例(含Buildroot定制、驱动模块、Makefile、ADB脚本),并结合嵌入式开发最佳实践,带你从零构建一套稳定的驱动开发环境。
第一章:Buildroot与ADB开发环境搭建
1,1 Buildroot概述与核心优势
1,1,1 什么是Buildroot?
Buildroot是一个嵌入式Linux系统构建工具,通过交叉编译生成定制化的根文件系统、内核、引导程序(如U-Boot)和应用程序。其核心优势在于:
• 极致轻量:可生成仅几MB的根文件系统(默认不含SSH、GUI等非必需组件);
• 高度定制:通过Kconfig菜单配置内核特性、软件包、文件系统格式(ext4/squashfs等);
• 交叉编译一体化:统一管理编译器(如gcc-arm-linux-gnueabihf)、依赖库和构建流程;
• 快速迭代:增量编译机制,修改配置后仅需重新构建变化部分,编译时间短(小型项目<30分钟)。
1,1,2 Buildroot vs Yocto:为何选择Buildroot?
特性 Buildroot Yocto Project
上手难度 低(Kconfig菜单直观) 高(需学习BitBake语法和Layer机制)
构建速度 快(Makefile驱动,并行编译友好) 慢(BitBake任务调度复杂)
输出体积 小(默认精简配置,最小几MB) 大(默认包含较多工具链和库)
社区生态 专注嵌入式,文档简洁 生态丰富,支持复杂产品定制
适用场景 资源受限设备、快速原型验证 复杂产品(如车载系统、智能电视)
对于驱动开发场景,Buildroot的轻量、快速、易定制特性更适配:我们只需保留内核、交叉编译器、ADB工具,无需冗余组件,可显著缩短开发链路。
1,2 Buildroot环境搭建与定制(以ARM64为例)
1,2,1 开发环境准备
• PC系统:Ubuntu 22,04 LTS(推荐,对Buildroot支持最佳);
• 硬件要求:8GB+内存,100GB+磁盘空间(用于存储源码和编译产物);
• 目标设备:ARM64架构嵌入式板卡(如树莓派4B、NXP i,MX8、瑞芯微RK3568),需支持USB OTG或以太网;
• 工具链依赖:提前安装PC端编译工具:
sudo apt update && sudo apt install -y \
build-essential git wget unzip python3 python3-pip \
libncurses5-dev libncursesw5-dev zlib1g-dev gawk flex bison \
rsync bc cpio libssl-dev liblz4-tool # Buildroot构建依赖
1,2,2 下载与配置Buildroot
步骤1:获取Buildroot源码
git clone https://git,buildroot,net/buildroot # 官方仓库(最新版)
# 或下载稳定版(推荐初学者):
wget https://buildroot,org/downloads/buildroot-2023,02,6,tar,gz
tar -zxvf buildroot-2023,02,6,tar,gz && cd buildroot-2023,02,6
步骤2:配置Buildroot(定制根文件系统)
Buildroot通过make menuconfig进入Kconfig配置界面,需重点配置以下选项(以ARM64设备为例):
make menuconfig
核心配置项详解:
1, 目标架构配置(Target options)
• Target Architecture → AArch64 (little-endian)(选择ARM64架构);
• Target Architecture Variant → 根据CPU型号选择(如Cortex-A53/A55/A76,不确定选cortex-a53通用配置);
• Target ABI → aarch64(64位ABI);
• Endianness → Little Endian(小端模式,嵌入式设备主流)。
2, 工具链配置(Toolchain)
• Toolchain type → External toolchain(使用预编译工具链,避免从源码编译GCC,节省时间);
• Toolchain → Linaro AArch64 2023,03(选择较新版本,兼容性好);
• Toolchain origin → Pre-installed toolchain(若PC已安装Linaro工具链)或Download and install toolchain(Buildroot自动下载);
• External toolchain gcc version → 12,x(与Linaro版本匹配);
• External toolchain kernel headers series → 6,1,x(内核头文件版本,需与目标内核匹配);
• External toolchain C library → glibc(嵌入式设备推荐glibc,兼容性优于musl)。
3, 内核配置(Kernel)
• Kernel → 勾选Linux kernel(必须,驱动需依赖内核头文件编译);
• Kernel version → Custom version(自定义内核版本,如6,1,30,需与设备实际运行内核版本一致!);
• Kernel configuration → Using a custom config file(使用设备厂商提供的内核配置,或基于默认配置修改);
• Kernel binary format → zImage(压缩内核镜像,适合嵌入式存储)。
4, 根文件系统配置(Filesystem images)
• Root filesystem type → ext4(常用可写文件系统,方便调试);
• ext4 root filesystem → 勾选Create an ext4 root filesystem image;
• exact size → 256M(根据需求调整,最小可设为32M)。
5, 添加ADB工具(Target packages → Shell and utilities)
Buildroot默认不含ADB,需手动添加:
• 进入Target packages → Shell and utilities → android-tools → 勾选adb(ADB客户端);
• (可选)勾选fastboot(用于刷写设备固件)。
6, 其他精简配置(减小根文件系统体积)
• Target packages → Debugging, profiling and benchmark → 取消勾选gdb、strace(调试阶段可临时开启);
• Target packages → Libraries → Compression and decompression → 仅保留zlib(驱动模块可能依赖);
• System configuration → Root password → 设置一个密码(如root),避免登录时无密码风险。
步骤3:保存配置并退出
配置完成后,选择
1,2,3 编译Buildroot生成根文件系统
首次编译耗时较长(取决于网络和CPU性能,约1-3小时),后续增量编译仅需几分钟:
make -j$(nproc) # -j$(nproc):使用全部CPU核心并行编译
编译产物路径:
• 内核镜像:output/images/Image(ARM64的zImage);
• 根文件系统:output/images/rootfs,ext4;
• 交叉编译器:output/host/bin/aarch64-linux-gnu-*(如aarch64-linux-gnu-gcc)。
1,3 ADB环境配置与设备连接
1,3,1 ADB工具安装与验证(PC端)
Buildroot编译时已生成ADB客户端(output/host/bin/adb),将其添加到系统PATH:
echo 'export PATH=$PATH:/path/to/buildroot-2023,02,6/output/host/bin' >> ~/,bashrc
source ~/,bashrc # 立即生效
adb version # 验证安装,输出类似:Android Debug Bridge version 1,0,41
1,3,2 嵌入式设备端ADB服务配置
目标设备需运行ADB守护进程(adbd),但Buildroot默认根文件系统不含adbd(需手动添加)。有两种方案:
方案1:使用Buildroot构建含adbd的根文件系统(推荐)
重新配置Buildroot,添加adbd服务:
make menuconfig
进入Target packages → Shell and utilities → android-tools → 勾选adbd(ADB守护进程),保存配置后重新编译:
make -j$(nproc) # 仅重新构建变化的包,耗时较短
编译完成后,根文件系统rootfs,ext4中包含/usr/bin/adbd,需在设备启动时自动运行adbd(通过/etc/init,d/S99adbd脚本)。
方案2:设备端手动部署adbd(临时调试)
若设备已运行Linux(如Debian/Ubuntu),可通过包管理器安装adbd:
# 设备端执行(需联网)
apt update && apt install -y android-tools-adbd
1,3,3 设备端启动adbd服务
方式1:通过init,d脚本启动(Buildroot构建的系统)
创建/etc/init,d/S99adbd启动脚本:
#!/bin/sh
# Start adbd daemon
case "$1" in
start)
echo "Starting adbd,,,"
/usr/bin/adbd & # 后台运行adbd
;;
stop)
echo "Stopping adbd,,,"
killall adbd
;;
restart)
$0 stop
$0 start
;;
echo "Usage: $0 {start|stop|restart}"
exit 1
;;
esac
exit 0
赋予执行权限并启动:
chmod +x /etc/init,d/S99adbd
/etc/init,d/S99adbd start
方式2:手动启动(临时调试)
# 设备端执行(需root权限)
adbd & # 后台运行adbd,默认监听USB(ADB over USB)
# 若需网络ADB,添加参数:adbd tcp:5555 &
1,3,4 ADB连接设备与验证
步骤1:连接设备(USB或网络)
• USB连接(推荐,稳定性高):用USB线连接PC与设备的OTG口,设备端确保adbd监听USB;
• 网络连接(适合无USB接口的设备):设备端执行adbd tcp:5555 &,PC端执行adb connect 设备IP:5555(如adb connect 192,168,1,100:5555)。
步骤2:列出设备(验证连接)
PC端执行:
adb devices
成功输出:
List of devices attached
ABCDEF1234567890 device # 设备序列号,状态为device表示连接正常
若显示unauthorized,需在设备端同意USB调试授权(部分设备需手动确认弹窗)。
步骤3:进入设备Shell
adb shell # 直接进入设备root shell(Buildroot默认root用户无密码)
# 或指定用户:adb shell -u shell(普通用户)
Shell操作示例:
# 查看内核版本(需与Buildroot配置的内核版本一致)
uname -r # 输出:6,1,30
# 查看根文件系统挂载情况
mount | grep rootfs # 输出:/dev/mmcblk0p2 on / type ext4 (rw,relatime)
# 查看ADB进程
ps aux | grep adbd # 输出:root 123 0,0 0,1 1234 567 ? S 10:00 0:00 /usr/bin/adbd
第二章:Linux驱动模块基础与源码编写
2,1 Linux驱动模块核心概念
2,1,1 驱动模块的两种加载方式
Linux驱动模块可动态加载(无需重启系统)或静态编译进内核:
• 动态加载:通过insmod/modprobe加载,ko文件,通过rmmod卸载,适合调试阶段;
• 静态编译:将驱动源码直接编译进内核镜像(zImage),开机自动加载,适合量产阶段。
本文聚焦动态加载模块开发(灵活度高,便于ADB调试)。
2,1,2 驱动模块的关键数据结构与函数
• struct module:内核模块描述符,存储模块名称、版本、作者等信息(由MODULE_*宏填充);
• module_init(fn):指定模块加载入口函数(fn在insmod时执行);
• module_exit(fn):指定模块卸载入口函数(fn在rmmod时执行);
• printk:内核态日志函数(类似用户态printf,日志级别0-7,通过dmesg查看);
• register_chrdev:字符设备注册函数(本文以字符设备为例,适合简单外设驱动)。
2,2 驱动模块源码编写(含ADB调试辅助功能)
以下实现一个简单的字符设备驱动,支持以下功能:
• 模块加载/卸载时在dmesg打印日志;
• 创建设备节点/dev/demo_drv,支持用户态通过read/write读写数据;
• 内置ADB调试辅助:模块加载时自动将日志重定向到/var/log/demo_drv,log(通过ADB可拉取该文件分析)。
2,2,1 驱动源码(demo_drv,c)
#include
#include
#include
#include
#include
#include
#include
#include
/* -------------------------- 模块信息(必选) -------------------------- */
MODULE_LICENSE("GPL"); /* 许可证(必须,否则加载时报"tainted kernel"警告) */
MODULE_AUTHOR("Embedded Developer"); /* 作者 */
MODULE_DESCRIPTION("Buildroot ADB Debug Demo Driver"); /* 描述 */
MODULE_VERSION("1,0"); /* 版本 */
/* -------------------------- 设备参数定义 -------------------------- */
#define DEMO_DRV_NAME "demo_drv" /* 设备名称 */
#define DEMO_DRV_MINOR 0 /* 次设备号起始值 */
#define DEMO_DRV_COUNT 1 /* 设备数量 */
#define LOG_FILE_PATH "/var/log/demo_drv,log" /* 日志文件路径(ADB可访问) */
#define BUF_SIZE 1024 /* 内核缓冲区大小 */
static int major; /* 主设备号(动态分配) */
static struct cdev demo_cdev; /* 字符设备结构体 */
static struct class *demo_class; /* 设备类(用于自动创建设备节点) */
static char *kernel_buf; /* 内核缓冲区(存储用户数据) */
static struct file *log_file; /* 日志文件指针(内核态写文件需谨慎,此处简化实现) */
/* -------------------------- 字符设备操作方法 -------------------------- */
/**
* @brief 打开设备(用户态open("/dev/demo_drv", O_RDWR)时触发)
* @param inode: 设备inode节点
* @param filp: 文件指针
* @return 0成功,-1失败
*/
static int demo_open(struct inode *inode, struct file *filp) {
printk(KERN_INFO "[demo_drv] Device opened (count: %d)\n", module_refcount(THIS_MODULE));
filp->private_data = kernel_buf; /* 将内核缓冲区关联到文件私有数据 */
return 0;
}
/**
* @brief 释放设备(用户态close("/dev/demo_drv")时触发)
* @param inode: 设备inode节点
* @param filp: 文件指针
* @return 0成功
*/
static int demo_release(struct inode *inode, struct file *filp) {
printk(KERN_INFO "[demo_drv] Device released\n");
return 0;
}
/**
* @brief 读设备(用户态read("/dev/demo_drv", buf, len)时触发)
* @param filp: 文件指针
* @param buf: 用户态缓冲区(数据拷贝目标)
* @param count: 请求读取字节数
* @param ppos: 文件偏移量
* @return 实际读取字节数(成功),负数(失败)
*/
static ssize_t demo_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) {
int ret;
char *data = filp->private_data; /* 获取内核缓冲区 */
int data_len = strlen(data); /* 缓冲区有效数据长度(简化处理,实际需跟踪写入位置) */
/* 限制读取长度不超过数据长度和缓冲区大小 */
count = min(count, (size_t)data_len);
if (count == 0) {
printk(KERN_DEBUG "[demo_drv] Read: no data available\n");
return ;。enym.cn/kt;0;
}
/* 内核态→用户态数据拷贝(需检查返回值) */
ret = copy_to_user(buf, data, count);
if (ret != 0) {
printk(KERN_ERR "[demo_drv] Read: copy_to_user failed (ret=%d)\n", ret);
return -EFAULT; /* 返回坏地址错误 */
}
printk(KERN_INFO "[demo_drv] Read %zu bytes: %s\n", count, data);
return count;
}
/**
* @brief 写设备(用户态write("/dev/demo_drv", buf, len)时触发)
* @param filp: 文件指针
* @param buf: 用户态缓冲区(数据拷贝源)
* @param count: 请求写入字节数
* @param ppos: 文件偏移量
* @return 实际写入字节数(成功),负数(失败)
*/
static ssize_t demo_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos) {
int ret;
char *data = filp->private_data; /* 获取内核缓冲区 */
/* 限制写入长度不超过缓冲区大小 */
count = min(count, (size_t)(BUF_SIZE - 1)); /* 留1字节存'\0' */
if (count;。enym.cn/qs;= 0) {
printk(KERN_ERR "[demo_drv] Write: buffer full\n");
return -ENOMEM; /* 返回内存不足错误 */
}
/* 用户态→内核态数据拷贝(需检查返回值) */
ret = copy_from_user(data, buf, count);
if (ret != 0) {
printk(KERN_ERR "[demo_drv] Write: copy_from_user failed (ret=%d)\n", ret);
return -EFAULT;
}
data[count] = '\0'; /* 手动添加字符串结束符 */
printk(KERN_INFO "[demo_drv] Written %zu bytes: %s\n", count, data);
return count;
}
/* 文件操作结构体(关联上述方法) */
static const struct file_operations demo_fops = {
,owner = THIS_MODULE, /* 模块自身(防止模块被意外卸载) */
,open = demo_open,
,release = demo_release,
,read = demo_read,
,write = demo;。enym.cn/sy;write,
/* 可选:,unlocked_ioctl = demo_ioctl(若需实现ioctl命令) */
};
/* -------------------------- 模块加载与卸载 -------------------------- */
/**
* @brief 模块加载函数(insmod时执行)
*/
static int __init demo_drv_init(void) {
int ret;
dev_t dev_no; /* 设备号(主设备号+次设备号) */
printk(KERN_INFO "[demo_drv] Initializing module,,,\n");
/* 1, 分配内核缓冲区 */
kernel_buf = kmalloc(BUF_SIZE, GFP_KERNEL);
if (!kernel_buf) {
printk(KERN_ERR "[demo_drv] Failed to allocate kernel buffer\n");
ret = -ENOMEM;
goto err_kmalloc;
}
strncpy(kernel_buf, "Hello from Buildroot Driver!", BUF_SIZE - 1); /* 初始化缓冲区 */
kernel_buf[BUF_SIZE - 1] = '\0';
/* 2, 动态分配主设备号 */
ret = alloc_chrdev_region(&dev_no, DEMO_DRV_MINOR, DEMO_DRV_COUNT, DEMO_DRV_NAME);
if (ret < 0) {
printk(KERN_ERR "[demo_drv] Failed to allocate char device region (ret=%d)\n", ret);
goto err;。enym.cn/oq;alloc_chrdev;
}
major = MAJOR(dev_no); /* 提取主设备号 */
printk(KERN_INFO "[demo_drv] Allocated major number: %d\n", major);
/* 3, 初始化字符设备并添加到内核 */
cdev_init(&demo_cdev, &demo_fops);
demo_cdev,owner = THIS_MODULE;
ret = cdev_add(&demo_cdev, dev_no, DEMO_DRV_COUNT);
if (ret < 0) {
printk(KERN_ERR "[demo_drv] Failed to add cdev (ret=%d)\n", ret);
goto err_cdev_add;
}
/* 4, 创建设备类(/sys/class/demo_drv,udev会自动创建设备节点) */
demo_class = class_create(THIS_MODULE, DEMO_DRV_NAME);
if (IS_ERR(demo_class)) {
ret = PTR_ERR(demo_class);
printk(KERN_ERR "[demo_drv] Failed to create device class (ret=%d)\n", ret);
goto err_class_create;
}
/* 5, 创建设备节点(/dev/demo_drv) */
device_create(demo_class,;。enym.cn/bg; NULL, dev_no, NULL, DEMO_DRV_NAME);
printk(KERN_INFO "[demo_drv] Device node created: /dev/%s\n", DEMO_DRV_NAME);
/* 6, ADB调试辅助:创建日志文件(简化实现,实际需通过内核态文件操作API) */
/* 注意:内核态直接写文件风险较高,此处仅通过printk输出日志路径,实际调试依赖dmesg */
printk(KERN_INFO "[demo_drv] ADB log path: %s (pull via 'adb pull /var/log/demo_drv,log')\n", LOG_FILE_PATH);
printk(KERN_INFO "[demo_drv] Module loaded successfully (major=%d)\n", major);
return 0;
/* 错误处理:逆序释放资源 */
err_class_create:
cdev_del(&demo_cdev);
err_cdev_add:
unregister_chrdev_region(dev_no, DEMO_DRV_COUNT);
err_alloc;。enym.cn/uy;chrdev:
kfree(kernel_buf);
err_kmalloc:
return ret;
}
/**
* @brief 模块卸载函数(rmmod时执行)
*/
static void __exit demo_drv_exit(void) {
dev_t dev_no = MKDEV(major, DEMO_DRV_MINOR); /* 构建设备号 */
printk(KERN_INFO "[demo_drv] Exiting module,,,\n");
/* 1, 销毁设备节点和设备类 */
device_destroy(demo_class, dev_no);
class_destroy(demo_class);
/* 2, 删除字符设备 */
cdev_del(&demo_cdev);
/* 3, 释放设备号 */
unregister_chrdev_region(dev_no, DEMO_DRV_COUNT);
/* 4, 释放内核缓冲区 */
kfree(kernel_buf);
printk(KERN_INFO "[demo_drv] Module unloaded successfully\n");
}
/* 注册模块加载/卸载函数 */
module_init(demo_drv_init);
module_exit(demo_drv_exit);
2,2,2 驱动模块Makefile编写(交叉编译关键)
驱动模块需通过交叉编译器编译(目标架构为ARM64,需用aarch64-linux-gnueabi-gcc,而非PC的gcc)。创建Makefile:
# -------------------------- 交叉编译配置(需与Buildroot工具链匹配) --------------------------
# Buildroot交叉编译器路径(根据实际路径修改)
CROSS_COMPILE ?= /path/to/buildroot-2023,02,6/output/host/bin/aarch64-linux-gnu-
# 内核源码路径(需与Buildroot配置的内核版本一致,且已配置编译过)
KERNEL_DIR ?= /path/to/buildroot-2023,02,6/output/build/linux-6,1,30/
# -------------------------- 模块编译参数 --------------------------
obj-m += demo_drv,o # 指定编译为模块(demo_drv,c → demo_drv,ko)
module_name;。enym.cn/vr;demo_drv
kernel_module_dir = $(KERNEL_DIR)
# -------------------------- 编译规则 --------------------------
all:
# 调用内核Makefile编译模块(指定ARCH和CROSS_COMPILE)
$(MAKE) -C $(kernel_module_dir) ARCH=arm64 CROSS_COMPILE=$(CROSS_COMPILE) M=$(PWD) modules
# -------------------------- 清理规则 --------------------------
clean:
$(MAKE) -C $(kernel_module_dir) ARCH=arm64 CROSS_COMPILE=$(CROSS_COMPILE) M=$(PWD) clean
rm -rf *,o *,ko *,mod,c *,mod,o *,;。enym.cn/yl;symvers *,order ,*,cmd ,tmp_versions
# -------------------------- 辅助规则(ADB部署) --------------------------
# 推送模块到设备(需先连接ADB)
push: $(module_name),ko
adb push $(module_name),ko /lib/modules/$(shell uname -r)/ # 推送到内核模块目录
adb shell "depmod -a" # 更新模块依赖(部分系统需要)
# 加载模块(需root权限)
load:
adb shell "insmod /lib/modules/$(shell uname -r)/$(module_name),ko"
# 卸载模块
unload:
adb shell "rmmod $(module_name)"
# 查看模块日志
log:
adb shell "dmesg | grep demo_drv | tail -20"
# 查看设备节点
node:
adb shell "ls -l /dev/$(module_name)"
,PHONY: all clean push load unload log node
Makefile关键参数说明:
• CROSS_COMPILE:交叉编译器前缀(Buildroot工具链路径需替换为实际编译输出的路径,如/home/user/buildroot/output/host/bin/aarch64-linux-gnu-);
• KERNEL_DIR:内核源码路径(Buildroot编译内核时会将源码解压到output/build/linux-xxx/,需确保该路径存在且内核已编译过——至少执行过make kernel-menuconfig和make linux-rebuild);
• M=$(PWD):告诉内核Makefile模块源码在当前目录;
• ARCH=arm64:指定目标架构为ARM64。
第三章:驱动模块交叉编译与ADB部署调试
3,1 内核源码准备与编译(驱动编译前提)
驱动模块编译依赖内核头文件和内核构建系统,需确保KERNEL_DIR指定的内核已编译过(至少编译过一次,生成必要的,o文件和头文件)。
3,1,1 编译内核(Buildroot环境下)
若之前未编译内核,执行以下命令:
cd /path/to/buildroot-2023,02,6
make linux-rebuild # 仅重新编译内核(比完整make更快)
编译完成后,KERNEL_DIR(如output/build/linux-6,1,30/)下会生成:
• include/:内核头文件(驱动编译必需);
• ,config:内核配置文件(需与驱动模块兼容,如启用了CONFIG_MODULES才能加载模块)。
3,1,2 验证内核配置(关键选项)
驱动模块编译需内核开启以下选项(通过make linux-menuconfig检查):
make linux-menuconfig # 进入内核配置界面
确保以下选项已勾选:
• Enable loadable module support → Module unloading(支持模块卸载);
• Enable loadable module support → Forced module loading(允许强制加载模块);
• Device Drivers → Character devices → Enable dynamic device number allocation(动态分配设备号,本文驱动依赖此选项)。
3,2 驱动模块交叉编译(PC端执行)
3,2,1 修正Makefile路径
修改Makefile中的CROSS_COMPILE和KERNEL_DIR为实际路径:
# 示例(替换为你的Buildroot路径)
CROSS_COMPILE ?= /home/user/buildroot-2023,02,6/output/host/bin/aarch64-linux-gnu-
KERNEL_DIR ?= /home/user/buildroot-2023,02,6/output/build/linux-6,1,30/
3,2,2 执行编译
在驱动源码目录(demo_drv,c和Makefile所在目录)执行:
make clean # 清除旧编译产物
make all # 开始交叉编译
编译成功标志:
• 生成demo_drv,ko文件(ARM64架构的驱动模块);
• 无error输出,最后显示:
CC [M] /path/to/demo_drv,o
LD [M] /path/to/demo_drv,ko
3,2,3 编译问题排查
• 错误1:/bin/sh: 1: aarch64-linux-gnu-gcc: not found
原因:CROSS_COMPILE路径错误或未安装交叉编译器。
解决:检查CROSS_COMPILE是否指向Buildroot的output/host/bin/目录,确保该目录下存在aarch64-linux-gnu-gcc。
• 错误2:kernel headers not found
原因:KERNEL_DIR路径错误,或内核未编译。
解决:确认KERNEL_DIR指向Buildroot编译生成的内核源码目录,执行make linux-rebuild编译内核。
• 错误3:unknown type name 'struct cdev'
原因:内核配置未启用字符设备支持。
解决:通过make linux-menuconfig开启Device Drivers → Character devices相关选项,重新编译内核。
3,3 ADB部署与驱动模块调试
3,3,1 推送模块到设备(ADB)
编译生成demo_drv,ko后,通过ADB推送到设备的模块目录:
make push # 执行Makefile中的push规则(需先连接ADB设备)
手动推送(等效于make push):
adb push demo_drv,ko /lib/modules/$(adb shell "uname -r")/ # 动态获取设备内核版本
adb shell "depmod -a" # 更新模块依赖(部分系统需要,否则modprobe找不到模块)
3,3,2 加载与卸载模块(ADB Shell)
加载模块:
adb shell "insmod /lib/modules/$(adb shell "uname -r")/demo_drv,ko"
# 或通过Makefile自动加载:make load
验证加载结果:
# 查看模块是否加载
adb shell "lsmod | grep demo_drv"
# 输出:demo_drv 16384 0(模块名、大小、引用计数)
# 查看设备节点是否创建
adb shell "ls -l /dev/demo_drv"
# 输出:crw------- 1 root root 240, 0 Jan 1 00:00 /dev/demo_drv(240为主设备号,0为次设备号)
# 查看内核日志(关键调试手段)
adb shell "dmesg | grep demo_drv"
# 预期输出:
# [ 1234,567890] [demo_drv] Initializing module,,,
# [ 1234,568001] [demo_drv] Allocated major number: 240
# [ 1234,568123] [demo_drv] Device node created: /dev/demo_drv
# [ 1234,568234] [demo_drv] Module loaded successfully (major=240)
卸载模块:
adb shell "rmmod demo_drv"
# 或执行Makefile规则:make unload
# 验证卸载结果
adb shell "lsmod | grep demo_drv" # 无输出(模块已卸载)
adb shell "dmesg | grep demo_drv" # 查看卸载日志
# 预期输出:[ 5678,901234] [demo_drv] Exiting module,,,
# [ 5678,901345] [demo_drv] Module unloaded successfully
3,3,3 驱动功能测试(用户态读写)
通过ADB Shell在设备端直接测试驱动的read/write功能:
步骤1:写入数据到驱动
# 向/dev/demo_drv写入字符串"Test write from ADB"
adb shell "echo 'Test write from ADB' > /dev/demo_drv"
步骤2:查看驱动日志(验证write回调)
adb shell "dmesg | grep demo_drv | tail -1"
# 预期输出:[ 6789,012345] [demo_drv] Written 20 bytes: Test write from ADB
步骤3:从驱动读取数据
adb shell "cat /dev/demo_drv"
# 预期输出:Test write from ADB(与写入内容一致)
步骤4:查看驱动日志(验证read回调)
adb shell "dmesg | grep demo_drv | tail -1"
# 预期输出:[ 7890,123456] [demo_drv] Read 20 bytes: Test write from ADB
3,3,4 常见问题与调试技巧
问题现象 可能原因 调试方法
insmod: ERROR: could not insert module demo_drv,ko: Invalid module format 模块与内核版本不匹配(编译模块的内核版本≠设备运行内核版本) 1, 检查uname -r输出与KERNEL_DIR的内核版本是否一致;2, 重新编译内核和模块
dmesg无驱动日志 模块未加载成功,或printk级别过高 1, 检查lsmod是否有模块;2, 降低printk级别(如用KERN_ERR替代KERN_DEBUG);3, 执行dmesg -n 8(临时显示所有级别日志)
/dev/demo_drv设备节点不存在 设备类创建失败,或udev未运行 1, 检查demo_drv_init中class_create和device_create是否返回错误;2, 手动创建设备节点:mknod /dev/demo_drv c 240 0(240为主设备号)
copy_to_user/copy_from_user失败 用户态缓冲区无效(如空指针、越界) 1, 检查用户态read/write的buf参数是否有效;2, 在驱动中添加BUG_ON(!buf)断言排查
第四章:高级实践:Buildroot定制ADB调试环境与驱动自动化
4,1 Buildroot定制ADB调试工具集
为进一步简化开发流程,可通过Buildroot定制包含ADB客户端、驱动调试工具(如insmod/rmmod/dmesg增强版)、日志收集脚本的根文件系统。
4,1,1 添加ADB增强工具(adbd/adb/fastboot)
在Buildroot配置中确保已勾选android-tools下的adb、adbd、fastboot,并添加usbutils(USB设备识别):
make menuconfig
# Target packages → Shell and utilities → usbutils(勾选)
# Target packages → Shell and utilities → android-tools → adb, adbd, fastboot(确保已勾选)
4,1,2 添加驱动调试脚本(/usr/bin/drv_debug,sh)
创建board/
#!/bin/sh
# 驱动调试辅助脚本(ADB调用示例:adb shell drv_debug,sh demo_drv)
DRV_NAME=$1
LOG_FILE="/var/log/${DRV_NAME},log"
if [ -z "$DRV_NAME" ]; then
echo "Usage: $0
echo "Example: $0 demo_drv"
exit 1
fi
echo "=== Driver Debug Info: $DRV_NAME ==="
echo "1, Module status:"
lsmod | grep "$DRV_NAME" || echo " Not loaded"
echo -e "\n2, Device node:"
ls -l "/dev/$DRV_NAME" 2>/dev/null || echo " Node not found"
echo -e "\n3, Recent kernel logs (last 10 lines):"
dmesg | grep "$DRV_NAME" |;。enym.cn/ee; tail -10
echo -e "\n4, Log file content ($LOG_FILE):"
if [ -f "$LOG_FILE" ]; then
cat "$LOG_FILE"
else
echo " Log file not found (check if driver writes to it)"
fi
赋予执行权限:
chmod +x board/
重新编译Buildroot后,设备端可通过drv_debug,sh demo_drv一键查看驱动状态。
4,2 驱动模块自动化测试(ADB+Shell脚本)
编写PC端Shell脚本,实现模块编译→推送→加载→测试→卸载全流程自动化:
test_drv,sh:
#!/bin/bash
# Buildroot驱动模块自动化测试脚本(ADB+交叉编译)
set -e # 遇到错误立即退出
# -------------------------- 配置参数(根据实际情况修改) --------------------------
BUILDROOT_PATH="/home/user/buildroot-2023,02,6"
DRIVER_SRC_DIR="/home/user/demo_drv" # 驱动源码目录
DRIVER_NAME="demo;。enym.cn/tr;drv"
CROSS_COMPILE="${BUILDROOT_PATH}/output/host/bin/aarch64-linux-gnu-"
KERNEL_DIR="${BUILDROOT_PATH}/output/build/linux-6,1,30/"
ADB_DEVICE=$(adb devices | grep -w "device" | awk '{print $1}' | head -1)
# -------------------------- 检查环境 --------------------------
if [ -z "$ADB_DEVICE" ]; then
echo "Error: No ADB device connected (run 'adb devices' to check)"
exit 1
fi
echo "=== Starting ${DRIVER_NAME} automated test (device: ${ADB_DEVICE}) ==="
# -------------------------- 步骤1:交叉编译驱动 --------------------------
echo -e "\n[1/5] Cross-compiling driver,,,"
cd "${DRIVER_SRC_DIR}"
make clean
make all CROSS_COMPILE="${CROSS_COMPILE}" KERNEL_DIR="${KERNEL_DIR}"
if [ ! -f "${DRIVER_NAME},ko" ]; then
echo "Error: Driver module ${DRIVER_NAME},ko not generated"
exit 1
fi
echo "✓ Driver compiled successfully: ${DRIVER_NAME},ko"
# -------------------------- 步骤2:推送模块到设备 --------------------------
echo -e "\n[2/5] Pushing module to device,,,"
adb push "${DRIVER_NAME},ko" "/lib/modules/$(adb shell "uname -r")/"
adb shell "depmod -a"
echo "✓ Module pushed to /lib/modules/$(adb shell "uname -r")/"
# -------------------------- 步骤3:卸载旧模块(若存在) --------------------------
echo -e "\n[3/5] Unloading old module (if exists),,,"
adb shell "rmmod ${DRIVER_NAME} 2>/dev/null || true" # 忽略"模块未加载"错误
echo "✓ Old module unloaded (if any)"
# -------------------------- 步骤4:加载模块并验证 --------------------------
echo -e "\n[4/5] Loading module and verifying,,,"
adb shell "insmod /lib/modules/$(adb shell "uname -r")/${DRIVER_NAME},ko"
# 检查模块是否加载
sleep 1 # 等待模块初始化完成
if ! adb shell "lsmod | grep -q ${DRIVER_NAME}"; then
echo "Error: Failed to load module (check 'dmesg' for details)"
adb shell "dmesg | grep ${DRIVER_NAME}"
exit 1
fi
echo "✓ Module loaded successfully"
# 检查设备节点
if ! adb shell "test -c /dev/${DRIVER_NAME}"; then
echo "Error: Device node /dev/${DRIVER_NAME} not created"
exit 1
fi
echo "✓ Device node /dev/${DRIVER_NAME} created"
# -------------------------- 步骤5:功能测试(读写验证) --------------------------
echo -e "\n[5/5] Testing driver read/write functionality,,,"
# 测试写入
TEST_DATA="Automated test: $(date)"
echo "Writing test data: ${TEST_DATA}"
adb shell "echo '${TEST_DATA}' > /dev/${DRIVER_NAME}"
# 验证写入日志
if ! adb shell "dmesg | grep -q 'Written,*${TEST_DATA}'"; then
echo "Error: Write operation failed (check 'dmesg')"
adb shell "dmesg | grep ${DRIVER_NAME}"
exit 1
fi
echo "✓ Write test passed"
# 测试读取
READ_DATA=$(adb shell "cat /dev/${DRIVER_NAME}")
if [ "$READ_DATA" != "$TEST_DATA" ]; then
echo "Error: Read data mismatch (expected: '${TEST_DATA}', got: '${READ_DATA}')"
exit 1
fi
echo "✓ Read test passed (data: ${READ_DATA})"
# -------------------------- 清理(可选) --------------------------
read -p "Press Enter to unload module and exit, or Ctrl+C to keep loaded,,,"
adb shell "rmmod ${DRIVER_NAME}"
echo "✓ Module unloaded"
echo -e "\n=== Automated test completed successfully ==="
使用方法:
chmod +x test_drv,sh
,/test_drv,sh # 一键执行全流程测试
结语:Buildroot+ADB驱动开发的优势与展望
本文通过环境搭建→驱动编写→交叉编译→ADB调试的全流程实战,验证了Buildroot+ADB在嵌入式驱动开发中的可行性:
• 轻量高效:Buildroot生成的根文件系统仅几十MB,ADB客户端体积小,适合资源受限设备;
• 灵活调试:ADB提供文件传输、shell交互、日志查看能力,弥补了无SSH的短板;
• 全流程可控:从内核配置到驱动编译,均通过脚本和Makefile固化,确保环境一致性。
未来,随着Buildroot对RISC-V架构的支持完善,以及ADB over TCP/IP的稳定性提升,这套方案将进一步拓展到更多嵌入式场景(如边缘计算、工业物联网)。对于开发者而言,掌握Buildroot+ADB驱动开发技能,不仅能应对无SSH环境的调试挑战,更能深入理解嵌入式Linux系统的"精简、定制、高效"本质。
附录:
• Buildroot官方文档:https://buildroot,org/docs,html
• Linux内核驱动开发指南:https://www,kernel,org/doc/html/latest/index,html
• ADB命令大全:https://developer,android,com/studio/command-line/adb
• 示例代码仓库:https://gitee,com/embedded-dev/buildroot-adb-drv-demo(含完整驱动源码、Makefile、Buildroot配置)
上一篇:u盘里误删的文件去哪了