Ubuntu 下编译goldfish内核并使用模拟器运行
前言
要学习Android Framework开发,通常需要下载AOSP(Android Open Source Project)项目。AOSP包含了Android操作系统的全部源代码,但并不包含内核部分。若需进一步学习驱动开发,则需另行下载内核源码。
需要特别说明的是,Android系统使用的内核并非原生Linux Kernel,而是基于Linux内核进行了深度定制,加入了进程间通信(Binder)、低内存管理等Android专属特性。内核有很多版本,我们可以选择谷歌官方维护的Android通用内核(Android Common Kernel, ACK),也可根据实际设备情况选用厂商开源的内核(需注意部分设备可能要求解锁Bootloader)。
简单起见,本文选用goldfish内核作为学习对象。该内核是Android模拟器专用版本,特别适合在虚拟环境中进行驱动开发测试。
值得注意的是,虽然Android基于Linux内核构建,但是AOSP是不需要和内核编译到一起的,而是在设备启动时,Bootloader 负责选择并加载内核,然后再启动 AOSP。 本文所用环境
- VMware + Ubuntu 22.04
- Android ndk 16
1 下载及编译
1.1 内核下载
AOSP项目比较大,我们这里只获取 Goldfish 内核,而不下载整个 AOSP,clone完成需要手动checkout 出源码分支。 Android模拟器内核更新的比较慢,通常用的都是相对稳定、较老的内核:
- 3.18:长期使用的老版本(早期 AOSP)
- 4.4 / 4.9:主流 LTS 支持版本(支持 Android 9/10)
- 4.14:进入 Android 10/11 时期
- 5.4:对应 Android 12/13(只维护了一个分支)
bash
git clone https://android.googlesource.com/kernel/goldfish
# 如果不支持科学上网,可以使用国内的源
git clone https://aosp.tuna.tsinghua.edu.cn/android/kernel/goldfish.git
# 查看所有远程分支
git branch -r
# 切换到其中一个分支
git checkout android-goldfish-4.14-gchips
1.2 环境配置
安装gcc 内核构建过程分为两个部分,ndk中的交叉编译工具链(x86_64-linux-android-gcc)负责内核本体的交叉编译,同时内核构建脚本还需要一个本地主机的 gcc 来编译 host 工具(如 fixdep 等),因此需要安装gcc。
bash
sudo apt install gcc
下载交叉编译器 goldfish内核默认使用 GCC 编译,较高的版本才支持clang编译,可以通过goldfish/Documentation/process/changes.rst
查看要求编译器的最低版本。
这里我们使用Android ndk提供的编译器来编译,打开Android studio 下载ndk,由于 goldfish 默认使用gcc编译器,而自 NDK r18 版本起,Android 官方推荐使用 Clang 作为默认的编译器,gcc被移除,因此我们选择下载r18以下的旧版本。
1.3编译
配置编译脚本 使用touch
命令新建build.sh
配置编译脚本文件,输入以下内容:
bash
#!/bin/bash
# 设置目标架构为 x86_64(模拟器内核一般是 x86 或 x86_64)
export ARCH=x86_64
# 设置子架构,通常与 ARCH 一致
export SUBARCH=x86_64
# 设置交叉编译工具链前缀(这是 Android NDK 中的编译器前缀)
export CROSS_COMPILE=x86_64-linux-android-
# 设置你的 NDK 路径,这个路径需要根据你的实际 NDK 安装位置修改
export NDK_PATH=/home/zhg/Workplace/Android/Sdk/ndk/16.1.4479499
# 指定 NDK 工具链的 bin 目录,里面包含交叉编译器等工具
export TOOLCHAIN=$NDK_PATH/toolchains/x86_64-4.9/prebuilt/linux-x86_64/bin
# 把工具链路径加入系统环境变量 PATH,方便在命令行中直接调用编译器
export PATH=$TOOLCHAIN:$PATH
# 使用默认配置文件 x86_64_ranchu_defconfig 生成 .config 文件
# ranchu 是 Android Emulator 使用的虚拟平台(QEMU 后端)
make x86_64_ranchu_defconfig
# 开始编译内核,-j$(nproc) 会使用所有可用 CPU 核心进行并行编译,加快速度
make -j$(nproc)
开始编译 执行 sh build.sh
开始编译。编译过程比较简单,以下是一些可能出现的报错:
secclass_map 需要更新 错误信息:
bash
In file included from scripts/selinux/mdp/mdp.c:49:
./security/selinux/include/classmap.h:247:2: error: #error New address family defined, please update secclass_map.
247 | #error New address family defined, please update secclass_map.
| ^~~~~
In file included from scripts/selinux/genheaders/genheaders.c:18:
./security/selinux/include/classmap.h:247:2: error: #error New address family defined, please update secclass_map.
247 | #error New address family defined, please update secclass_map.
| ^~~~~
make[3]: *** [scripts/Makefile.host:102:scripts/selinux/mdp/mdp] 错误 1
make[2]: *** [scripts/Makefile.build:671:scripts/selinux/mdp] 错误 2
make[2]: *** 正在等待未完成的任务....
解决方法:打开security/selinux/include/classmap.h
在secclass_map
结构体增加 添加新的地址族对应项。 或者临时解决:执行 grep -r "PF_MAX" /usr/include/
查找PF_MAX的值,修改if语句:
c
#undef PF_MAX
#define PF_MAX 44
#if PF_MAX > 44
#error New address family defined, please update secclass_map.
#endif
报错 multiple definition of 'yylloc'重复定义 错误信息:
bash
/usr/bin/ld: scripts/dtc/dtc-parser.tab.o:(.bss+0x10): multiple definition of yylloc'; scripts/dtc/dtc-lexer.lex.o:(.bss+0x0): first defined here
collect2: error: ld returned 1 exit status
解决方法:使用grep -rw "YYLTYPE yylloc;"
命令搜索这个变量,找出到除了dtc-lexer.lex.c 外的(scripts/dtc/dtc-lexer.l
、dtc-lexer.lex.c_shipped
)所有YYLTYPE yylloc
改成extern YYLTYPE yylloc
完成 当终端输出以下信息代表内核映像 bzImage 已成功构建。表示bzImage已经生成,位于arch/x86/boot/bzImage。bzImage 是经过压缩的内核映像文件,用于启动系统。
bash
Kernel: arch/x86/boot/bzImage is ready (#1)
2 使用avd启动加载内核
启动内核映像通常依赖于使用模拟器或在物理硬件。以上编译的goldfish内核镜像bzImage可以用模拟器( Android Emulator)来运行。
2.1 创建模拟器
运行Android Stuido并打开Virtual Device Manager
创建一个模拟器,由于我们编译的Goldfish 内核是针对 x86_64 架构的,因此模拟器镜像也选择x86_64的,Android版本也要与内核版本兼容,我们前面选择的是内核版本 4.14,与之对应的是Android10(android-29)。具体可以从这里(Which Android runs which Linux kernel?)和AOSP官网查到Android内核支持表。
如果提示Your CPU does not support required features (VT-x or SVM).
,则需要启用虚拟化技术,打开VMware的虚拟化的设置:
- 关闭当前虚拟机,依次选择虚拟机->设置->硬件tab下的处理器选项,勾选右侧的
虚拟化Intel VT-x/EPT或AMD-V/RVI
。 - 如果仍然报错:关闭有冲突的windows虚拟化功能,打开Windows主机的"启用或关闭windows功能",关闭所有有关的功能:包括Hyper-V、Windows虚拟机监控程序平台、适用于Linux的Windows子系统、虚拟机平台等。
2.2 使用命令运行模拟器
为了能在终端使用模拟器的命令,需要配置Android sdk环境变量:
bash
export ANDROID_HOME=//home/zhg/Workplace/Android/Sdk
export PATH=$ANDROID_HOME/emulator:$ANDROID_HOME/platform-tools:$PATH
启动 AVD,<avd_name>
是上面在Android studio中创建avd时的名称。
bash
# 通过 -kernel 命令行参数指定内核
emulator -avd <avd_name> -kernel /path/to/your/bzImage -verbose
执行命令后,可能会出现在虚拟机上运行模拟器(禁止套娃)导致性能下降之类的风险提示,可以忽略。
2.3 查看模拟器内核版本
等模拟器运行起来后,在模拟器的设置->关于模拟器页面->Android版本
中可以看到内核版本信息,也可以使用命令adb shell uname -a
查看,根据版本号和时间可以确定运行的是我们刚才编译的内核。
2.4 查看内核日志
查看内核控件日志,logcat只能查看用户空间的日志,dmesg可以查看全部日志。 Android 8.0(Oreo)及以上版本,访问 dmesg需要root权限
bash
adb root # 获取 root 权限
adb shell dmesg
# adb shell dmesg | grep xxx
以上就是关于goldfish内核的编译及运行,下一篇我们将使用goldfish内核来编写一个简单的驱动来学习相关知识。
参考链接
1\] [学习 Binder 的预备知识3 ------ linux 驱动开发入门](https://juejin.cn/post/7235309338097123388 "https://juejin.cn/post/7235309338097123388")