Buildroot驱动开发实战:基于ADB调试的无SSH嵌入式Linux驱动构建全流程
创始人
2026-01-19 19:25:55

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:保存配置并退出

配置完成后,选择保存为,config文件,退出menuconfig。

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 /* 模块相关宏(MODULE_LICENSE、MODULE_AUTHOR等) */

#include /* 内核函数(printk、container_of等) */

#include /* 文件系统相关(struct file_operations、register_chrdev等) */

#include /* 设备模型(struct class、device_create等) */

#include /* 字符设备(struct cdev) */

#include /* 用户态-内核态数据拷贝(copy_to_user、copy_from_user) */

#include /* 内核内存分配(kmalloc、kfree) */

#include /* 字符串操作(strncpy、strlen) */

/* -------------------------- 模块信息(必选) -------------------------- */

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///rootfs-overlay/usr/bin/drv_debug,sh(Buildroot的rootfs-overlay机制,可直接覆盖根文件系统文件):

#!/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///rootfs-overlay/usr/bin/drv_debug,sh

重新编译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配置)

相关内容

热门资讯

亚太企业2026年AI投资将增... 亚太地区企业正在加大人工智能投资力度,一项新研究发现,96%的组织计划在2026年将AI投资平均增加...
光束驱动AI计算实现超级计算机... 张量运算是一种支撑现代技术特别是人工智能的高级数学形式。这些运算远超人们日常遇到的简单计算。可以将其...
马斯克兑现承诺,开源X推荐算法... 新智元报道 编辑:定慧 【新智元导读】马斯克兑现承诺,X平台全新推荐算法正式开源!这套由Grok驱...
原创 新... # 新一代丰田锋兰达都市 SUV:都市先锋的理想座驾品鉴 在城市的喧嚣与繁华中,一款真正契合都市生活...
原创 马... 2026 年 1 月,马斯克在德州超级工厂的长谈抛出重磅消息,从现在算起大约 2000 天,也就是五...