Android 驱动编译背景
Android 内核驱动是Android中负责硬件的连接和操控,确保硬件与操作系统的交互,这些驱动是内核的拓展组件,通过提供统一的驱动操作接口供用户层使用。
Android内核驱动主要分为两种类型:Android专用驱动和Android使用的设备驱动(基于Linux)。其中,Android专用驱动包括如Ashmem(匿名共享内存驱动)、Logger(轻量级的日志驱动)、Binder驱动(基于OpenBinder驱动,为Android平台提供IPC进程间通信的支持)等。而设备驱动则主要基于Linux的设备驱动模型,负责管理各种硬件设备,如USB驱动、物理内存驱动等。
在Android系统中,一个传统的驱动架构通常包括kernel驱动、HAL(硬件抽象层)驱动、JNI(Java Native Interface)层、service层等。程序运行时的调用关系通常是从apk(应用程序包)开始,通过service层、JNI层、HAL层,最终到达kernel驱动层。
在开发 Android 驱动程序的过程中,如何对开发好的驱动程序进行编译。
对于Linux驱动有两种编译和运行方式:
- 将驱动编译进Linux 内核中,当Linux 内核启动的时候就会自动运行驱动程序。
- 将驱动编译成模块(Linux 中模块拓展名为.ko), 在 Linux 内核启动以后使用相应命令加载驱动模块。
- 内核模块是Linux 内核向外部提供的一个插口
- 内核模块是具有独立功能的程序,可以被单独编译,但不能单独运行,在运行时被链接到内核作为内核一部分在内核空间运行。
- 内核模块便于 驱动和文件系统的二次开发
所以想要驱动在Android 系统中运行,有两种编译方式:
- 驱动编译到内核源码树中,直接编译到内核中。
- 驱动编译到内核模块,或者随着内核源码树一起编译成ko 文件。
这里将介绍如何把字符设备驱动源程序在内核源码中编译成 .ko 文件,然后加载到pixel 6P 中。主要分为两部分
- 第一步:将字符设备驱动源代码放到内核 /common/driver/char/ 目录中
- 第二步:编辑Kconfig 和 Makefile 文件,Kconfig 文件 是在编译前执行配置命令 make menuconfi 时用到的;Makefile 在执行编译命令make 用到的。
驱动源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| android_hello.c
#include <linux/module.h> #include <linux/fs.h> #include <linux/cdev.h>
static int major = 300; # 主设备号 static int minor = 0; static dev_t devno; static struct cdev cdev;
static int hello_open(struct inode *inode, struct file *filep) { printk("hello open\n"); return 0; } static struct file_operations hello_ops= { .open = hello_open, }; static int hello_init(void) { int ret;
printk("hello_init\n"); printk("ohhhhhhhhh HelloWorld driver!"); devno = MKDEV(major, minor); # 定义设备号宏 ret = register_chrdev_region(devno, 1, "HelloWorld"); 注册字符设备 if(ret < 0) { printk("register_chrdev_region faile\n"); return ret; } cdev_init(&cdev, &hello_ops); ret = cdev_add(&cdev, devno, 1); if(ret < 0) { printk("cdev_add faile \n"); return ret; } return 0; } static void hello_exit(void) { cdev_del(&cdev); unregister_chrdev_region(devno, 1); printk("hello_exit\n"); } MODULE_LICENSE("GPL"); module_init(hello_init); module_exit(hello_exit);
|
编译过程
这里我下载的是common-android13-5.10的通用内核源码,这里在编译驱动之前要将这个common-android13-5.10 先编译出来,然后将boot.img 刷入到pixel6P 中,避免内核版本不一致,导致后面编译出来的驱动加载不起来,另外,单独对 pixel 6P boot 分区刷编译的内核镜像要注意的是,编译出来的内核镜像版本不能低于pixel 6P 原来的内核版本,否则会成砖,要重新刷入工厂镜像。
因为我测试过common-android13-5.10编译出来的boot.img 刷到pixel6P中,然后驱动可以执行,但是再pixel 5 中,都是aarch64 架构的,就无法加载了,显示ELF格式错误。
1 2 3 4 5 6 7 8
| Pixel 5 redfin:/data/local/tmp # insmod android_hello.ko insmod: failed to load android_hello.ko: Exec format error 1|redfin:/data/local/tmp # file android_hello.ko android_hello.ko: ELF relocatable, 64-bit LSB arm64, static, BuildID=5d50bec4bbc8b8d26924f0630567fb9a4b0e347c, not stripped redfin:/data/local/tmp # uname -a Linux localhost 4.19.252-g005154455a52-ab9276409 #1 SMP PREEMPT Wed Nov 9 11:59:07 UTC 2022 aarch64 Toybox redfin:/data/local/tmp #
|
在pixel6P中
1 2 3 4 5 6
| raven:/data/local/tmp # file android_hello.ko android_hello.ko: ELF relocatable, 64-bit LSB arm64, static, BuildID=5d50bec4bbc8b8d26924f0630567fb9a4b0e347c, not stripped raven:/data/local/tmp # insmod android_hello.ko insmod: failed to load android_hello.ko: File exists 1|raven:/data/local/tmp # uname -a Linux localhost 5.10.205-android13-4-00002-g6bc28fdfeec3-dirty #1 SMP PREEMPT Tue Mar 12 21:27:16 UTC 2024 aarch64 Toybox
|
排除内核源码版本的问题后,将代码放到对应的 内核源码目录内,

然后在 /drivers/char/Kconfig 中添加配置。
1 2 3 4 5 6 7
| Kconfig config ANDROID_HELLO tristate "MY testAndroid_hello" default y help qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq endmenu
|
如果驱动程序单独放在一个文件夹内,那么要在该文件夹内单独编写Kconfig, 然后在上一级目录的Kconfig中加入Kconfig 的路径,便于menuconfig 找得到你的Kconfig。
类似于如下所示

然后在/drivers/char/Makefile 中添加你要编译的对象。
1 2 3
| Makefile obj-m += android_hello.o 或者obj-$(CONFIG_ANDROID_HELLO) += android_hello.o
|
然后执行在 /common-android13-5.10/common 中执行 make menuconfig 命令,
修改 “MY testAndroid_hello” 为 M , 因为 M 表示程序会自动到你所指定的dir目录中查找模块源码,将其编译,生成KO 文件,Y 表示直接编译内核。
在char driver 中可以看到我们定义的内核模块

然后 /common-android13-5.10/ 下执行
BUILD_CONFIG=common/build.config.gki.aarch64 build/build.sh
开始编译
可以看到 交叉编译链 将 android_hello.o 编译为 module。

编译到这一块会中断,显示如下的 “ERROR: modules list out of data”

虽然中断了,但不影响ko编译出来

如果想解决整个ERROR, 在build.sh 文件中可以看到整个判断

可以删除“exit 1”,或者只需要
cp /root/aosp/common-android13-5.10/out/android13-5.10/common/modules.order common/android/gki_aarch64_modules
再次编译就可以成功通过编译了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 部分编译log + set +x ======================================================== Checking ======================================================== Comparing + gki_modules_list=/root/aosp/common-android13-5.10/common/android/gki_system_dlkm_modules ++ sed /root//common-android13-5.10//android/ ++ tr + KMI_STRICT_MODE_OBJECTS='vmlinux ' + /root/aosp/common-android13-5.10/build/abi/compare_to_symbol_list /root//common-android13-5.10//android13-5.10//Module.symvers //aosp//out//common/ + set ======================================================== Installing INSTALL /char/ 编译出来的驱动 DEPMOD ======================================================== Generating ======================================================== Generating ========================================================
|
adb push 到手机上

cat /proc/devices 查看

总结
编译驱动多看看Android 内核源码中驱动编译的流程,这里我没有刷面具,而是直接在编译内核镜像的时候,把KernelSU 编译到内核中,这样可以不用单独刷面具拿root。后面有时间再用交叉编译链来单独编译android 能用的ko 驱动模块。