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(只维护了一个分支)
1
2
3
4
5
6
7
8
9
10
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。

1
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配置编译脚本文件,输入以下内容:

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
#!/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 需要更新
错误信息:

1
2
3
4
5
6
7
8
9
10
11
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.hsecclass_map结构体增加 添加新的地址族对应项。
或者临时解决:执行 grep -r "PF_MAX" /usr/include/查找PF_MAX的值,修改if语句:

1
2
3
4
5
6
7
#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’重复定义
错误信息:

1
2
/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.ldtc-lexer.lex.c_shipped)所有YYLTYPE yylloc改成extern YYLTYPE yylloc

完成
当终端输出以下信息代表内核映像 bzImage 已成功构建。表示bzImage已经生成,位于arch/x86/boot/bzImage。bzImage 是经过压缩的内核映像文件,用于启动系统。

1
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的虚拟化的设置:

  1. 关闭当前虚拟机,依次选择虚拟机->设置->硬件tab下的处理器选项,勾选右侧的虚拟化Intel VT-x/EPT或AMD-V/RVI
  2. 如果仍然报错:关闭有冲突的windows虚拟化功能,打开Windows主机的“启用或关闭windows功能”,关闭所有有关的功能:包括Hyper-V、Windows虚拟机监控程序平台、适用于Linux的Windows子系统、虚拟机平台等。

2.2 使用命令运行模拟器

为了能在终端使用模拟器的命令,需要配置Android sdk环境变量:

1
2
export ANDROID_HOME=//home/zhg/Workplace/Android/Sdk
export PATH=$ANDROID_HOME/emulator:$ANDROID_HOME/platform-tools:$PATH

启动 AVD,<avd_name>是上面在Android studio中创建avd时的名称。

1
2
# 通过 -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权限

1
2
3
adb root  # 获取 root 权限
adb shell dmesg
# adb shell dmesg | grep xxx

以上就是关于goldfish内核的编译及运行,下一篇我们将使用goldfish内核来编写一个简单的驱动来学习相关知识。

参考链接

[1] 学习 Binder 的预备知识3 —— linux 驱动开发入门