Android驱动开发实战:从内核模块到I2C设备驱动案例
简介这份资源面向具备一定Linux内核基础的Android驱动开发学习者聚焦驱动程序从编写到加载的完整实践路径帮助理解设备识别、设备节点创建、中断处理、读写操作、设备控制与电源管理等关键环节。包内共11个文件以c源码、h头文件、o目标文件为主辅以Android.mk、Makefile、Kconfig、modules.order及cmd编译命令记录覆盖用户空间测试程序与内核驱动模块两部分压缩包约12KB结构紧凑便于对照编译流程与模块组织方式。资源以触摸屏驱动为典型案例展示初始化与注销、中断解析、input事件上报及调试日志输出等实现思路适合作为驱动入门练手与内核模块编译加载的参考素材。目前已有865人学习下载可帮助开发者快速建立Android驱动开发的整体认知并积累可复用的代码骨架与排错经验。1. Android 编写驱动程序案例从应用到内核的那条窄路很多人第一次听到「Android 编写驱动程序案例」脑子里浮现的是写个 App 调调传感器。真到项目里才发现App 跑在用户态传感器、GPIO、I2C 设备全在内核态中间隔着 HAL、JNI、sysfs 好几层。我做过一个模拟项目要在某定制 Android 平板上驱动一颗自定义的 I2C 温湿度芯片App 只负责读值显示真正的活全在驱动里。这个案例的价值在于它把「Android 驱动开发」从抽象概念拉回到一条可复现的路径——写内核模块、编进内核或动态加载、通过设备节点暴露给上层、再用一个最小 App 验证。适合有 C 基础、懂一点 Linux 设备模型、但没在 Android 上完整跑通过驱动的开发者。下面按我实际踩过的顺序讲。2. 先搞清楚 Android 驱动和普通 Linux 驱动的差别2.1 内核同源但构建和权限体系不同Android 用的也是 Linux 内核所以字符设备、platform 驱动、设备树这些概念完全通用。差别在三个地方一是内核构建Android 有自己的内核源码树和编译脚本通常用build/build.sh或make配合交叉工具链不是桌面 Linux 那套make modules二是权限Android 的 SELinux 默认 enforcing驱动创建的设备节点如果没打标签App 根本打不开三是上层接口Android 更倾向用 HAL 把驱动能力包装成 HIDL/AIDL 服务而不是让 App 直接 open/dev/xxx。我一般建议新手先用一个字符设备驱动跑通「加载→创建设备节点→用户态读写」这条链路再考虑接 HAL。因为 HAL 那层一旦出问题排查成本翻倍。2.2 选字符设备还是 platform 驱动字符设备适合「数据流」型的外设比如传感器读值、自定义串口协议。platform 驱动适合挂在 SoC 总线上的设备配合设备树描述寄存器地址和中断。模拟项目里那颗 I2C 芯片我最终用的是 I2C 客户端驱动 字符设备接口的混合方式probe 里注册 I2C 设备同时创建一个字符设备节点给上层读。选型判断很简单设备有没有固定寄存器地址、有没有中断线有就走 platform/I2C 框架只是逻辑上的数据通道字符设备就够。别一上来就上 misc 设备misc 虽然省事但主设备号固定 10多个自定义设备容易混。2.3 最小可运行的内核模块骨架先看一个能编译、能加载、能打印的骨架这是后面所有功能的地基。#include linux/module.h #include linux/kernel.h #include linux/init.h static int __init demo_drv_init(void) { pr_info(demo_drv: init\n); return 0; } static void __exit demo_drv_exit(void) { pr_info(demo_drv: exit\n); } module_init(demo_drv_init); module_exit(demo_drv_exit); MODULE_LICENSE(GPL); MODULE_AUTHOR(demo); MODULE_DESCRIPTION(minimal android kernel module);逻辑说明module_init和module_exit是模块入口出口pr_info打到内核日志用dmesg看。参数说明MODULE_LICENSE(GPL)必须写否则内核会标记污染某些符号无法使用。编译时用 Android 内核树里的 Makefile把obj-m demo_drv.o加进去再用交叉编译器make ARCHarm64 CROSS_COMPILEaarch64-linux-android- modules。产物是.ko文件insmod加载。这一步跑通说明工具链和内核版本对上了后面才有的谈。3. 从字符设备到设备节点把驱动暴露给上层3.1 注册字符设备并创建设备节点骨架跑通后加字符设备接口。核心是alloc_chrdev_region、cdev_init、cdev_add再配合class_create和device_create自动生成/dev节点。#include linux/fs.h #include linux/cdev.h #include linux/device.h static dev_t dev_num; static struct cdev demo_cdev; static struct class *demo_class; static int demo_open(struct inode *inode, struct file *filp) { pr_info(demo_drv: open\n); return 0; } static ssize_t demo_read(struct file *filp, char __user *buf, size_t len, loff_t *off) { char kbuf[16] hello; if (*off 0) return 0; if (copy_to_user(buf, kbuf, 5)) return -EFAULT; *off 5; return 5; } static const struct file_operations demo_fops { .owner THIS_MODULE, .open demo_open, .read demo_read, }; static int __init demo_drv_init(void) { alloc_chrdev_region(dev_num, 0, 1, demo_drv); cdev_init(demo_cdev, demo_fops); cdev_add(demo_cdev, dev_num, 1); demo_class class_create(THIS_MODULE, demo_class); device_create(demo_class, NULL, dev_num, NULL, demo_dev); pr_info(demo_drv: init major%d minor%d\n, MAJOR(dev_num), MINOR(dev_num)); return 0; }逻辑说明alloc_chrdev_region动态申请主次设备号cdev_add把 file_operations 挂上去device_create在/dev下生成demo_dev。参数说明class_create第二个参数是类名device_create最后一个参数是节点名App 打开的就是/dev/demo_dev。copy_to_user是内核往用户态拷数据的标准方式不能直接赋值。3.2 用 ndk 编译一个最小验证 App驱动加载后写个 App 读节点验证。用 Android NDK 编译一个可执行文件推到设备上跑比写完整 App 快。#include stdio.h #include fcntl.h #include unistd.h int main(void) { int fd open(/dev/demo_dev, O_RDONLY); if (fd 0) { perror(open); return -1; } char buf[32] {0}; int n read(fd, buf, sizeof(buf) - 1); printf(read %d bytes: %s\n, n, buf); close(fd); return 0; }逻辑说明标准文件操作open触发驱动的demo_openread触发demo_read。参数说明O_RDONLY只读如果驱动只实现了 read别用O_RDWR。编译用aarch64-linux-android-clang demo_test.c -o demo_testadb push到/data/local/tmpadb shell里执行。3.3 SELinux 标签不配对App 打不开节点这是最容易翻车的一步。现象是 App 报Permission denied但ls -l /dev/demo_dev权限是 666。原因是 SELinux 上下文不对。解决分两步先在file_contexts里给节点打标签比如/dev/demo_dev u:object_r:demo_device:s0再在demo.te里允许 App 域访问这个标签allow appdomain demo_device:chr_file { read write open };。改完重新编译 sepolicy 刷进去。如果只是调试可以临时setenforce 0验证是不是 SELinux 问题但量产绝不能这么干。我一般先用dmesg | grep avc看拒绝日志日志里会直接告诉你哪个域访问哪个标签被拒照着加规则就行。4. 接上真实硬件I2C 设备驱动的 probe 与读写4.1 设备树里描述 I2C 从设备模拟项目那颗温湿度芯片挂在 I2C1 上地址 0x48。设备树里加节点i2c1 { status okay; demo_sensor: demo_sensor48 { compatible demo,demo-sensor; reg 0x48; }; };逻辑说明compatible是驱动匹配的关键字驱动里of_match_table要写一样的字符串。参数说明reg是从设备地址status okay打开 I2C1 控制器。改完设备树要重新编译 dtb 并刷入这一步经常被忽略导致 probe 根本不触发。4.2 probe 里注册字符设备并读寄存器#include linux/i2c.h static int demo_sensor_probe(struct i2c_client *client, const struct i2c_device_id *id) { u8 reg 0x00; u8 val 0; struct i2c_msg msgs[2] { { .addr client-addr, .flags 0, .len 1, .buf reg }, { .addr client-addr, .flags I2C_M_RD, .len 1, .buf val }, }; if (i2c_transfer(client-adapter, msgs, 2) ! 2) { dev_err(client-dev, i2c read failed\n); return -EIO; } dev_info(client-dev, sensor reg00x%02x\n, val); return 0; } static const struct of_device_id demo_sensor_of_match[] { { .compatible demo,demo-sensor }, {}, }; MODULE_DEVICE_TABLE(of, demo_sensor_of_match); static struct i2c_driver demo_sensor_driver { .driver { .name demo_sensor, .of_match_table demo_sensor_of_match, }, .probe demo_sensor_probe, }; module_i2c_driver(demo_sensor_driver);逻辑说明i2c_transfer一次发两个消息第一个写寄存器地址第二个读数据这是 I2C 读寄存器的标准套路。参数说明I2C_M_RD表示读方向client-addr是设备树里reg解析出来的地址。module_i2c_driver宏替代了 init/exit注册和注销自动完成。4.3 用 i2c-tools 先验证硬件通不通写驱动前先用i2cdetect -y 1看 0x48 在不在总线上。如果扫不到别急着改驱动先查硬件供电和上拉电阻。我见过有人驱动调了两天最后发现是芯片没焊好。i2cget -y 1 0x48 0x00能直接读寄存器确认硬件正常再写驱动能省大量时间。5. 避坑与排查那些让驱动「玄学」失败的细节5.1 现象insmod 报 unknown symbol原因模块用了内核没导出的符号或者内核版本和编译模块的头文件不一致。解决用modinfo xxx.ko看 vermagic和uname -r对比不一致就重新用当前内核树编译。如果符号确实没导出改用EXPORT_SYMBOL的内核接口或者把驱动编进内核而不是模块。5.2 现象probe 不执行dmesg 无任何输出原因compatible字符串和设备树不匹配或者设备树没刷进去。解决cat /proc/device-tree/.../compatible确认实际值和驱动里逐字符对比。注意大小写和连字符demo,demo-sensor和demo,demo_sensor是两回事。5.3 现象App 读节点返回 EFAULT原因copy_to_user的 buf 是内核栈上的局部数组但长度算错或者*off没更新导致重复读。解决检查copy_to_user返回值非 0 就是失败*off每次读后要累加否则 read 会一直返回同一段数据。我一般会在 read 里加pr_info打印 len 和 off一眼看出问题。5.4 现象驱动加载后系统卡死或重启原因在 probe 里做了耗时操作或者访问了未映射的寄存器地址。解决probe 里别用msleep大延时别直接解引用物理地址用ioremap。如果设备树寄存器地址写错访问会触发总线错误直接 panic。用devm_系列函数管理资源能减少释放遗漏。5.5 现象SELinux 拒绝日志刷屏但功能正常原因驱动创建了多个节点或 sysfs 属性只给部分打了标签。解决dmesg | grep avc逐条看把涉及的标签和域都加规则。别图省事setenforce 0量产固件里 enforcing 是默认临时关掉只会让问题延后爆发。6. 进阶用 sysfs 暴露参数并用脚本做回归验证驱动跑通后调试参数不该每次改代码重编。用 sysfs 把关键参数暴露出来配合脚本做回归是让方案可维护的关键。6.1 用 DEVICE_ATTR 创建可读写属性static int sample_rate 10; static ssize_t sample_rate_show(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, %d\n, sample_rate); } static ssize_t sample_rate_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int val; if (kstrtoint(buf, 10, val) || val 0 || val 1000) return -EINVAL; sample_rate val; return count; } static DEVICE_ATTR_RW(sample_rate); /* 在 probe 末尾 */ device_create_file(client-dev, dev_attr_sample_rate);逻辑说明DEVICE_ATTR_RW生成 show/store 两个回调device_create_file在/sys/bus/i2c/devices/1-0048/下生成sample_rate文件。参数说明kstrtoint做字符串转整数并校验范围越界返回-EINVAL。用户态echo 50 sample_rate就能改采样率不用重编驱动。6.2 用 shell 脚本做上电回归#!/system/bin/sh DEV/sys/bus/i2c/devices/1-0048/sample_rate for v in 1 10 100 1000; do echo $v $DEV || { echo set $v failed; exit 1; } got$(cat $DEV) [ $got $v ] || { echo mismatch: set $v got $got; exit 1; } done echo all pass逻辑说明遍历边界值写入后读回比对任何一步失败就退出并打印。参数说明1和1000是边界10、100是常规值。这个脚本放在/data/local/tmp每次改驱动后跑一遍能快速发现属性读写回归。6.3 验证方法从 dmesg 到 ftrace功能验证靠 App 和脚本性能验证靠 ftrace。echo function /sys/kernel/debug/tracing/current_tracer再echo demo_sensor_probe set_ftrace_filter触发一次 probe 后cat trace能看到函数调用耗时。如果 probe 超过几十毫秒就要查是不是 I2C 速率太低或寄存器访问次数太多。我一般把 I2C 速率从 100k 提到 400kprobe 时间能降一半但前提是硬件支持。最后说个习惯每改一版驱动先dmesg -c清日志加载后立刻看输出别等系统跑一会儿再回头找。驱动这行日志就是黑匣子后悔药就是提前打够 print。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

电表网关如何实现协议自适应、抗干扰与边缘计算一体化

电表网关如何实现协议自适应、抗干扰与边缘计算一体化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 3:27:38 阅读更多 →
扫码枪网口TCP通讯demo及源码:从连通到稳定解析

扫码枪网口TCP通讯demo及源码:从连通到稳定解析

简介:这份资源是面向工业自动化与上位机开发者的扫码枪网口TCP通讯示例,重点解决基恩士扫码枪与计算机之间稳定数据交互的问题。项目以计算机作为服务端、扫码枪作为客户端,涵盖连接建立、指令下发触发扫码、条码数据回传等完整流程&#xff…

2026/10/11 3:27:38 阅读更多 →
K近邻优化实战:从特征工程到距离度量与搜索加速

K近邻优化实战:从特征工程到距离度量与搜索加速

1. 先认清K近邻的短板:这个算法优化的是什么1.1 K-nearest算法的分类流程和三个痛点先说个大家可能都经历过的场景:拿到一个分类任务,老板让先跑个baseline,很多人第一反应就是"直接上一个K-nearest算法"(也…

2026/10/11 3:27:38 阅读更多 →

最新新闻

服务器初始化步骤简述

服务器初始化步骤简述

服务器初始化完成硬件上电,通过控制台/IPMI登录操作系统,获取服务器操作终端。配置网络配置网卡IP、网关、DNS,测试内网、外网连通。为yum下载、时间同步等后续操作提供网络基础。配置Yum源替换为国内镜像源,清理并生成yum缓存&am…

2026/10/11 4:25:11 阅读更多 →
DESIGN.md 完整指南:一份 Markdown 设计系统文件,让 AI 生成视觉一致的 UI

DESIGN.md 完整指南:一份 Markdown 设计系统文件,让 AI 生成视觉一致的 UI

DESIGN.md 完整指南:一份 Markdown 设计系统文件,让 AI 生成视觉一致的 UI 【免费下载链接】awesome-design-md A collection of DESIGN.md files analysis by popular brand design systems. Drop one into your project and let coding agents generat…

2026/10/11 4:25:11 阅读更多 →
Open Generative AI 快速上手:420+ 模型的免费 AI 图像与视频生成工作室,无订阅费

Open Generative AI 快速上手:420+ 模型的免费 AI 图像与视频生成工作室,无订阅费

Open Generative AI 快速上手:420 模型的免费 AI 图像与视频生成工作室,无订阅费 【免费下载链接】Open-Generative-AI Unrestricted Open-source alternative to AI video platforms — Free AI image & video generation studio with 600 models (…

2026/10/11 4:25:11 阅读更多 →
Gradle报错No tests found排查指南:原因与解决方案

Gradle报错No tests found排查指南:原因与解决方案

直接说结论:Execution failed for task :xxxx-api:test. > No tests found for given includes:这行报错,八成不是你的测试代码写挂了,而是 Gradle 在 test 任务阶段压根没发现任何它认为需要测试的类。这个报错在 Java/Kotlin 多模块项目…

2026/10/11 4:25:11 阅读更多 →
PS5通用优化指南:容量、散热、存档与远程游玩全攻略

PS5通用优化指南:容量、散热、存档与远程游玩全攻略

最近帮身边好几个人折腾了PS5,发现一个挺有意思的现象:不管是首发入的首批机器,还是后来买的改款,甚至是收来的二手,大家遇到的问题几乎一模一样——硬盘不够用、系统选项看不懂、主机烫得能煎鸡蛋,还有存档…

2026/10/11 4:25:11 阅读更多 →
nodejs 获取客服端ip,以及获取ip一直都是127.0.0.1的问题

nodejs 获取客服端ip,以及获取ip一直都是127.0.0.1的问题

个人博客(vue3 nodejs mysql )http://36.151.145.67:30000/ 一、问题描述 在做登录日志的时候想要获取客户端的ip, 网上查了一下 通过 req.headers[x-forwarded-for] || req.connection.remoteAddress; 获取, 结果获取了之后不管是开发环境&#xf…

2026/10/11 4:24:11 阅读更多 →

日新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 5:23:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 10:38:42 阅读更多 →