编译LineageOS以及自定义内核(LOS 21.0)

发布于 | 最后修改于 | 12 分钟
目录

由于一些需求,需要自行调整Lineage内核编译配置、打补丁。记录一下必要的知识。

说明🔗

本文由笔者在Lineage OS 21.0、Pixel 4a(Sunfish)上折腾(添加KernelSU)的经历整理而成,其它环境请视情况调整。哦对了,我用的host os是ArchLinux+Zsh。

什么LOS起源于CM之类的历史啊什么的就不写了。

和直接编译内核有什么不同?🔗

简单地说,没什么不同,只是编译环境配置方式稍有区别。在配置好工具链、源码的情况下,可以说基本没区别。但是笔者觉得想搞清楚内核编译用哪个工具链、哪个配置,还是要先学习Android编译环境(因为真的没搞清楚)。

脱离Android源码环境直接编译内核的好处:

  • 下载的源码少得多
    • Android一套源码加上编译完内核会超过150G
    • 直接编译内核加上源码工具链什么的占用硬盘大概不到30G
  • 可以按Linux那套熟悉的来操作

坏处:

  • 需要自己配置环境(比源码环境里麻烦一点)
  • 不熟悉Android源码环境可能找不到需要的编译配置/工具链

Android源代码仓库介绍🔗

简单说,Android源代码库就是许许多多git仓库按一定层次放置在一起得到的超级仓库,该仓库使用repo进行一部分管理,而具体到某一仓库的修改(如内核),则使用git进行操作。

repo构建项目工作目录所加载的<manifest>.xml描述了整体项目所使用的各个源码库的来源(仓库地址、分支等)和存放位置,以及处理一些软链接。

GKI(通用内核)推出后,部分设备的内核和Android的源码库可以分离、单独编译内核和系统。不过Lineage还是放一起的。

中国大陆内有Lineage OS的源码镜像仓库,若有需求可以选用。

按照LineageOS自动生成的文档开始工作🔗

这是相对简化的版本,毕竟笔者只是想编译内核!

推荐阅读:Android官方文档:构建系统Android官方文档:编译Android

目标平台:Pixel 4a(Sunfish)

对于ArchLinux,安装必要的工具(有些包之间可能有依赖关系懒得排查了):

pacman -Sy --needed base-devel android-tools android-udev \
  bc cpio gettext git libelf pahole perl python tar xz \
  ccache curl git-lfs libxml2 gperf imagemagick sdl_image libxslt \
  lzop pngcrush rsync schedtool squashfs-tools

此外大概还要安装AUR软件包:ncurses5-compat-libs

仅编译内核的话,上述部分软件包是不必要的。

安装repo,随后初始化仓库(请根据实际情况修改代码):

mkdir -p ~/.local/bin
export PATH=~/.local/bin:$PATH
curl https://storage.googleapis.com/git-repo-downloads/repo > ~/.local/bin/repo
chmod a+x ~/.local/bin/repo

mkdir lineageos-workspace && cd lineageos-workspace
repo init -u https://github.com/LineageOS/android.git -b lineage-21.0 --git-lfs

需要改镜像站的现在可以改了,具体看镜像站说明。搞定了之后开始同步代码:

repo sync

要下载不少数据(下载量约60G,占用空间约翻倍,因为有一半数据相当于副本),硬盘空间务必留够。

激活环境和设备配置:

# 仅支持bash、zsh,其它shell不兼容/不支持
source build/envsetup.sh
breakfast sunfish

注意上一步对于Pixel 4a平台会报错,因为需要提取私有blobs。可以从已经装有LineageOS的设备或官方完整刷机包中提取blobs。如从装有LineageOS的Pixel 4a上提取,则需要开启adb调试、root身份调试,然后在刚才的终端执行下面的操作:

cd device/google/sunfish
./extract-files.sh
croot # 回到工作空间顶级目录

然后再试着激活设备配置

breakfast sunfish

前面每一步都没有报错的话,源代码工作空间就都准备好了。

使用完整Android源码环境编译Linux kernel🔗

不妨重新开一个shell,激活一遍环境(也就是说每次新开shell都要这样):

cd lineageos-workspace
source build/envsetup.sh
breakfast sunfish

编译内核

mka kernel

然后等编译完成,最后会输出内核位置(例如out/target/product/sunfish/kernel)。

根据Android官方文档,应该可以这样启动这个内核:

fastboot boot out/target/product/sunfish/kernel

但我得到的却是这样的信息:

creating boot image...
creating boot image - 20695040 bytes
Sending 'boot.img' (20210 KB)                      OKAY [  0.216s]
Booting                                            FAILED (remote: 'Error verifying the received boot.img: Invalid Parameter')
fastboot: error: Command failed

经过一些检查,发现是内核没带上dtb,出现了一点问题。要手动合成一下:

mka kernel dtbimage  # 也可以只执行 mka dtbimage,毕竟kernel已经编译成功了
cat out/target/product/sunfish/{kernel,dtb.img} > kernel-dtb
fastboot boot kernel-dtb

也可以用下面指令编译得到完整的boot.img并测试(会多生成至少4G数据):

mka bootimage
fastboot boot out/target/product/sunfish/boot.img

boot.img比kernel-dtb多了ramdisk和一些参数。

kernel启动了就算成功了!

比较令人在意的一点:Lineage OS的内核编译配置在vendor/lineage/build/tasks/kernel.mk,对应在线仓库位置在https://github.com/LineageOS/android_vendor_lineage/blob/HEAD/build/tasks/kernel.mk

给内核打补丁——例如添加KernelSU🔗

自己编译内核不就是为了自己改么!现在终于到改的部分了!

要修改内核,需要去内核源码库。而对于sunfish,内核在kernel/google/msm-4.14/

不过Android是用repo管理项目的,那就标准一点,新建一个分支进行开发:repo start linux-ksu 我貌似搞错了什么,这不行。但是在每个子项目内创建branch是可以的。

先给内核打上特殊的印记٩( ᐛ )و:

echo "-FaintDay" > kernel/google/msm-4.14/localversion.20-kernelname

这是一个KBUILD(Linux的构建系统)自带的一个小功能,localversion。最后的内核版本会从4.14.336(加后面一串)变成4.14.336{-FaintDay}(加后面一串,不含花括号)。

开个新的干净shell进入内核源码库,准备打KernelSU补丁:

cd kernel/google/msm-4.14
# 用官方脚本打补丁
curl -LSs "https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh" | bash -
# 启用内核kprobe特性, 测试过了sunfish直接加这行就行
echo "CONFIG_KPROBES=y" >> arch/arm64/configs/sunfish_defconfig

touch ".scmversion"  # emm 还是加上这个好了

其实官方脚本就是把源码链接进内核源码树、添加了编译依赖,把集成用的依赖启用后,KSu就能正常编译了。有时手动改kernel config不够靠谱,那么就试试make menuconfig然后make savedefconfig吧!示例如下:

# 仅仅生成配置只需要host端工具链齐全,交叉编译用的工具链在这里没必要
export ARCH=arm64
export SUBARCH=arm
export O=out
cd kernel/google/msm-4.14
make sunfish_defconfig
make menuconfig
# 然后进行需要的调整

# 下面保存最小化的配置
make savedefconfig
# 更新配置
cp defconfig arch/arm64/configs/sunfish_defconfig

修改完后运行一下make mrproper打扫干净战场,然后git commit一下(避免被自动标记成dirty),再按前一节所述编译并启动内核。

使用Anykernel3打包内核安装包🔗

总不可能每次开手机都接一下电脑加载内核,那样多麻烦!可以选择直接生成一个完整的boot.img刷入,也可以仅仅编译内核、dtbo、dtb然后修改原本的boot.img。我选后者,搭配Anykernel3制作安装包。

先编译需要的文件:

mka kernel dtbimage, dtboimage

下载一份AnyKernel3,准备把它改成需要的样子。更换内核不需要修改Ramdisk,placeholder之类的可以完全删除;为了极致精简,有些用不到的binary executables也可以删。

下面假设AnyKernel3模板在ak3文件夹,和lineageos-workspace在同一位置:

cp lineageos-workspace/out/target/product/sunfish/dtb.img ak3/dtb
cp lineageos-workspace/out/target/product/sunfish/dtbo.img ak3/dtbo.img
# 内核可以直接copy,注意名称
cp lineageos-workspace/out/target/product/sunfish/kernel ak3/Image.lz4
# 也可以解压
lz4 -d lineageos-workspace/out/target/product/sunfish/kernel ak3/Image
# 编辑anykernel.sh
cat << EOF > ak3/anykernel.sh
# AnyKernel3 Ramdisk Mod Script
# osm0sis @ xda-developers

## AnyKernel setup
# begin properties
properties() { '
kernel.string=LOS 21.0 Kernel with KSu for Pixel 4a
do.devicecheck=1
do.modules=0
do.systemless=1
do.cleanup=1
do.cleanuponabort=0
device.name1=Pixel4a
device.name2=sunfish
supported.versions=
supported.patchlevels=
'; } # end properties

# shell variables
block=/dev/block/bootdevice/by-name/boot
is_slot_device=1;
ramdisk_compression=auto;

## AnyKernel methods (DO NOT CHANGE)
# import patching functions/variables - see for reference
. tools/ak3-core.sh;

## AnyKernel file attributes
# set permissions/ownership for included ramdisk files
set_perm_recursive 0 0 755 644 $ramdisk/*;
set_perm_recursive 0 0 750 750 $ramdisk/init* $ramdisk/sbin;


## AnyKernel install
dump_boot;
write_boot;
## end install
EOF

# 然后打包
cd ak3
zip -r9 ../LOS-KSU-AnyKernel3.zip * -x .git .github README.md *placeholder

可以用KernelFlasher刷入这个安装包。先用和之前一样的方式启动带有KSU的内核(也就是需要root权限),随后操作KernelFlasher在活动分区(如果是A/B分区的话)刷入AK3安装包。

额外内容:拆分内核编译环境🔗

这里我打算直接用Google提供的现成环境稍加修改。TBW(感觉自己不会再写下去了,毕竟目的已经达成了)