【小白向】从零开始的Android内核折腾(一)——起步

#0 说在前面

本系列文章以非C语言开发者的角度编写,后续正在更新中,会加入各种合并、性能优化、backport上游新特性等内容,敬请期待!

对于咱折腾佬来说,第三方内核带来的高刷新率、优秀的调度给使用体验带来的提升是显著的。同时,如果要刷入高于最后一个官方维护的系统,或部分非官方系统的话,就需要自己去编译一个内核用于引导系统。

无语的是,明明这么多人都在搞第三方内核,全网却找不到任何一篇关于折腾Android内核的教学或指导类文章。在自己的激情驱使之下,无奈只得自己摸黑前行,偶尔去打扰下这个领域的大佬。在长时间没有任何参考文献的摸索之后,终于有了这系列的文章,供各位喜欢折腾的咕咕们参考,这也是本系列文章的意义——让喜欢折腾的人都能体验到折腾的乐趣

注:本文咕咕以自己正在使用的红米K20 Pro(Linux 4.14.y)为例进行介绍。

#0.5 战斗准备

开始战斗前的物质准备:

  • 硬件:一台多核性能不是太差的电脑
  • Linux环境:推荐最新发行版Ubuntu,尽量别用可能出现玄学问题的WSL
  • Clang编译器:推荐最新Neutron Clang
  • AOSP GCC交叉编译器:默认最新(在这里找最新标签,标签格式为“android-**.*_r*”)
  • git相关使用方法(多分支管理、rebase、cherry-pick等)

以及开始前的相关知识准备:

  • Linux的版本号(即数字部分)为x.y.z结构,但它并不遵守语义化规则。其中x.y在本系列文章中规定为“大版本号”或“主线版本号”,z规定为“小版本号”或“基线版本号”。
  • 为节约成本,多数厂商会选择将系统内核停留在某一个长期提供支持(LTS)的主线版本上,而由于安卓内核的驱动限制,我们后期进行操作时也只能更新基线,如果您会移植的话也犯不着来看这篇没啥营养的小文章了(逃
  • 谷歌在5.4版本开始引入GKI 1.0,但效果并不理想,我们文中所说的“GKI内核”指5.10及以上版本的GKI 2.0内核,而“非GKI内核”则指5.4及以下版本的内核。

以及一些操作上的小提示!

  • 为了方便后期进行backport等操作,强烈建议在条件允许的情况下,以git clone的方式将内核源码复制到本地,而非通过下载内核源码的压缩包。
  • 即使源码只能以压缩包形式获取,也建议通过git init命令将其初始化为一个git仓库。

当然,最重要的,是一颗沉静得下去的耐心

以魔法为你祈祷,愿各位魔法使的内核折腾之旅一路顺风!

#1 寻找可编译成功的内核源码

此处“可编译成功”指编译过程中不会因为源码的bug而中止,且编译后的内核可以正常开机。

在绝大多数情况下,咕咕推荐在手机厂商官方提供的内核源码上进行定制化。但由于部分厂商(目前仅见小米)在开源时故意移除或修改了部分源码,使编译无法顺利完成,或编译后无法引导系统。虽然在今后我们会有能力修复,但咱作为新手魔法使,就只能找一些第三方的可编译成功的内核作为咱修改的基础。

官方内核源码获取/仓库地址:

如果需要第三方内核源码仓库,建议以“kernel_<手机厂商名>_<设备代号>“在各大开源网站搜索。比如我自己使用的红米K20 Pro,就需要搜索“android_kernel_xiaomi_raphael”或“kernel_xiaomi_raphael”。不过这些内核有的只能供类原生(即开源vendor的系统)使用,有的只能供官方系统(即官方vendor的系统)使用,一般会标注在简介内,根据自己的需要去选择即可。

接下来就是内核编译的过程,网上教程很多,这里暂不赘述。看到控制台输出这个,就说明编译成功!

本图仅作参考,咱一般编译的Android内核是面向ARM64设备的,绝大部分内核编译产物是一个Image.gz-dtb文件

至于如何刷入内核,可以参考:[基础教程2]如何使用AnyKernel 3打包内核

#2 自定义内核扩展版本

说白了就是给内核版本加后缀,使其具有辨识度,这也是很多第三方安卓内核所会做的。咱可以从手机设置里查阅,或和Linux发行版一样通过以下命令查询内核版本:

u0_a240@localhost ~ > uname -r
4.14.340-Miracle-r3-20240329-1447

在这个例子中,-Miracle-r3-20240329-1447就是我们自己添加的后缀。

而这个后缀可以通过Makefile.configarch/arm64/configs/<你设备的代号>_defconfig这3个文件中的1个进行修改,它们都是编译时的配置文件。绝大多数情况下,咕咕仅推荐各位改动靠后的两者。

注:此处.config仅指在编译前执行make xxx_defconfig后在根目录产生的.config配置文件。

在这之前,咱需要了解下Android内核的commit信息规范,以规范的commit信息去提交更改,能使开发和查阅工作更加轻松。

  • 所有commit需要签出(sign-off),VSCode的git栏右上角菜单中有“提交***(已签署)”就能实现这个功能;如果使用git的cli,就需要在git commit命令后加上--signoff
  • 所有commit信息均需以英语编写,在提交修改影响内核工作的参数之类的commit时需要尽可能填写commit的详细信息。
  • 修改defconfig的内容时应加上defconfig: 前缀。
  • 从Linux上游引入内容时应加上UPSTREAM: 前缀。
  • 从非Linux的git项目引入内容时应加上FROMGIT: 前缀。
  • 修改arch中某一架构目录的内容时,直接以改架构的名字为前缀(如修改了arch/arm64/kernel中某一文件,以arm64: 为前缀)
  • 修改某个特定功能时,要以该功能(通常是功能较大时这么写)或改动的文件的路径(一般最多写两级)为前缀。比如修改mm/writeback中的文件时,commit信息应该为mm/writeback: Xxxxmm: writeback: Xxx;修改fs/erofs中的文件时,commit信息应该为erofs: Xxxfs: erofs: Xxx
  • 修改涉及多个功能或不同目录的文件时,用treewide: 前缀。
# 通过 Makefile 修改(不建议)

# SPDX-License-Identifier: GPL-2.0
VERSION = 4
PATCHLEVEL = 14
SUBLEVEL = 340
EXTRAVERSION = <你的内核后缀>
NAME = Petit Gorille

至于.configdefconfig,直接查找文件中的CONFIG_LOCALVERSION条目,有就改掉,没有就添上。而像编译时间或内核版次等经常变动的字串,建议在.config文件中修改,以防止原defconfig被git追踪带来的麻烦。

而在CONFIG_LOCALVERSION配置下边还有一个CONFIG_LOCALVERSION_AUTO,这个条目开启后可以在编译开始时检测当前版本控制工具(如git、svn等),通过它追踪编译时的版本(即该commit所对应的独一无二的hash值)并将版本写入到内核版本中。例如我使用以下defconfig配置:

# 假设现在使用 git 进行版本控制,当前 commit 为 1d7e6ba801...
# ,并且本地有未提交的变更

# arch/arm64/configs/???_defconfig 部分配置
CONFIG_LOCALVERSION="-Miracle"
CONFIG_LOCALVERSION_AUTO=y

# uname -r 命令运行结果
4.14.340-Miracle-g1d7e6ba801-dirty

#3 集成KernelSU

KernelSU需要通过KPROBES以驱动的形式集成进内核中,所以咱需要事先在defconfig中启用它。

# arch/arm64/configs/???_defconfig 部分配置
# 如果已经有了的配置项请勿再写一次
CONFIG_KPROBES=y
CONFIG_HAVE_KPROBES=y
CONFIG_KPROBE_EVENTS=y

为了方便后期更新,以及托管平台的管理,我们不采用KernelSU提供的一键集成脚本,而是直接clone到本地。进入drivers/staging目录(要在外层也行,看你心情)克隆KernelSU。

cd drivers/staging
git clone https://github.com/tiann/KernelSU kernelsu

并将KernelSU设为本内核项目的子模块,在根目录创建.gitmodules,添加:

[submodule "drivers/staging/kernelsu"]
	path = drivers/staging/kernelsu
	url = https://github.com/tiann/KernelSU.git

接下来分别修改该目录下的MakefileKconfig,添加KernelSU条目即可。(绿色背景色代码为需要在对应文件中添加的内容)

diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig
index cc9d9fa0e1e38..ecfd48f4fa93 100644
--- a/drivers/staging/Kconfig
+++ b/drivers/staging/Kconfig
@@ -102,4 +102,6 @@
source "drivers/staging/wfx/Kconfig"

+source "drivers/staging/kernelsu/kernel/Kconfig"
+
endif # STAGING


diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile
index cc9d9fa0e1e38..ecfd48f4fa93 100644
--- a/drivers/staging/Makefile
+++ b/drivers/staging/Makefile
@@ -41,3 +41,4 @@
obj-$(CONFIG_QLGE)		+= qlge/
obj-$(CONFIG_WFX)		+= wfx/
+obj-y					+= kernelsu/kernel/

最后,提交commit

git add drivers/staging/Makefile drivers/staging/Kconfig drivers/staging/kernelsu .gitmodules
git commit -m "driver: staging: initialize support for kernelsu" --signoff

在编译时选择启用KernelSU,大功告成!

#4 合并Linux上游

绝大多数情况下,同一个设备的Android内核因驱动迁移难度大,只能局限在一个LTS大版本内,所以我们通常需要将该大版本下对应的Linux上游合并进来,以使环境更安全、并获取更新的从新版内核特性。这就是“更新基线”,或者说“合并上游”。

绝大多数情况下,只要保证原分支上的内核适配的是你的设备(对于非GKI设备),进行合并后几乎不会遇到无法引导的情况。

首先,为了防止可能的故障带来的回退麻烦,需要checkout到一个新的分支,咱一般建议名为baseline(-xxx),表示该分支用于合并上游基线(即大版本内)变动。

git checkout -b baseline-4.14

然后,给当前的本地仓库添加谷歌的通用内核仓库为远程仓库,并且拉取你内核所在大版本或KMI版本(KMI版本是什么?)的对应的基线分支。

# 由于谷歌代码库在大陆访问不便,此处使用清华大学TUNA的AOSP镜像源
git remote add aosp https://mirrors.tuna.tsinghua.edu.cn/git/AOSP/kernel/common.git
# 由于咱手机在4.14基线上,所以拉取4.14的分支
git pull aosp deprecated/android-4.14-stable

这里列举几个大版本的基线分支:

  • 4.4:deprecated/android-4.4-p
  • 4.9:deprecated/android-4.9-q
  • 4.14:deprecated/android-4.14-stable
  • 4.19:android-4.19-stable
  • 5.4+:android**-*.**-lts(此处星号需要与你手机GKI内核的KMI版本匹配)

然后就是把它们合并进你源码项目!

# 首先将其合并到本地的baseline分支上
git merge aosp/deprecated/android-4.14-stable
# 进行编译测试
......
# 如果测试通过且开机后功能正常,再合并进主分支(假设为dev)
git checkout dev
git merge baseline-4.14

如果合并时出现冲突,建议通过VSCode解决冲突,比终端方便得多(确信)一般情况下全部选择传入即可。

如果你的内核大版本已经停止了支持(EoL),Oracle公司的项目kernel-lts还可以给它强行续命一段时间(通常为一年)。比如咕咕的手机的4.14内核已经EoL了,咱就可以通过合并该项目的上游来获得延长支持。具体的规则和要合并的分支都可以在其官方项目中查阅。

但是在合并前,为了防止出现潜在的问题,还是建议先合并到谷歌最后的上游版本之后,再合并kernel-lts。

# 添加kernel-lts的远程仓库
git remote add elts https://github.com/openela/kernel-lts
# 拉取4.14的分支
git pull elts linux-4.14.y
# 合并进已经到最新谷歌上游的4.14源码中
git merge elts/linux-4.14.y

#5 合并SoC厂商上游

由于其它SoC不直接或直接不开源上游,此处只讨论高通的情况。

这是一个可选项,但合并高通的内核源码也能让内核的驱动得到更新、调度得到优化。最重要的是,通过更新的设备树,能使内核支持的Android版本得到提升

首先将高通的内核源码拉取到本地并合并,这里仍然以搭载骁龙855的4.14内核的红米K20 Pro举例。

# 添加高通的4.14通用内核仓库
git remote add qcom https://git.codelinaro.org/clo/la/kernel/msm-4.14.git
# 拉取高通代码到本地
# 此处说明:文中提到的标签会随时间而变动,大家可以通过搜索LA.UM.来查找最新可用的标签;
# 标签中的五位数字越大,被高通维护的版本越新;最后的数字暂且可理解为支持的安卓版本
git pull qcom LA.UM.9.1.r1-16000-SMxxx0.QSSI14.0
git merge qcom/LA.UM.9.1.r1-16000-SMxxx0.QSSI14.0

当然,冲突肯定是存在的,大家只能自己解决冲突问题咯~

#6 如何优化?

由于专业知识的欠缺,所以咱把“性能优化”拎成几个小的方面:

  • 简易的配置项修改(这也是我们为数不多能够自己参与的优化项目)
  • “援引”他人的优化类目
  • 用更先进的模块替换原内核的旧模块

手动调整部分配置项目

ZRAM

简而言之,ZRAM技术是通过压缩算法(一般是LZ4)将内存中指定的数据进行压缩,其实质是以牺牲CPU性能来换取内存

ZRAM的配置及实现源码在drivers/block/zram/zram_drv.c,然而对于基础知识尚且不足的我们,能够修改的只有ZRAM的大小。但是请注意,ZRAM的大小不要超过实际内存大小,否则可能会导致意外发生(逃

ZRAM大小的数据通常在上述文件中if (!disksize)的上一行。

@@ -1725,7 +1725,7 @@
int err;

-disksize = memparse(buf, NULL);
+disksize = (u64)6144 * SZ_1M;
if (!disksize)
    return -EINVAL;

像这样就是将ZRAM固定为6GB。

Swappiness

swap即交换分区,它是手机存储上开辟的一小块空间,在内存使用量达到一定值时临时充当内存的作用。而swappiness就是调整swap分区启用阈值的参数,咱可以在mm/vmscan.c中通过vm_swappiness进行修改。

@@ -191,7 +191,7 @@
 * From 0 .. 200.  Higher means more swappy.
 */
-int vm_swappiness = 60;
+int vm_swappiness = 160;

static void;

“援引”他人优化类目

优化的核心是借鉴。——鲁迅根本没说过这句话

毕竟咱专业知识不足,使用他人造的轮子并非可耻之事。那么我们应如何使用他人的优化项目呢?

这就要提到我们后续进行绝大多数优化工作的工具——cherry pick——它能从其它分支中pick出你设定的commit到你所在分支上,我们可以通过它来拣选他人的优化commit并应用到自己的内核上。

首先将他人的内核仓库设置为远程仓库,pull下来即可

# 假设存在一个内核项目,主开发分支为 dev-14,优化 commit 为 129ffd0 到 ec6caa5
git remote add miracle https://github.com/Project-Miracle/android_kernel_xiaomi_raphael
git pull miracle/dev-14
git cherry-pick 129ffd0...ec6caa5 -x

此时我们就可以看到,我们自己当前所在的分支上多出了几个commit,它们就是从另外那个分支上cherry pick下来的。

替换原内核的部分“模块”

注:此“模块”指实现特定功能的一系列单一功能的代码,并非“Linux内核模块”。

首先讨论“库”的问题——内核依赖于一些第三方库才能正常运行,它们的源码一般存放在lib内。然而在我们的获取到的内核源码中,它们的版本一般较旧或存在优化空间。

接下来咱以更新内核自带的zstd库举个栗子,它的相关源码存放在lib/zstd目录下。

像这样的结构就是旧的zstd版本,当然你也可以通过查询commit记录去判断是否为旧版zstd。至于怎么去更新它,咱一般有两种方式。

第一种方案是按照官方说明手动更新:

# 复制 zstd 源码
git clone https://github.com/Facebook/zstd --depth=1
# 进入为内核生成 zstd 代码的脚本的目录
cd zstd/contrib/linux-kernel
# 将 zstd 代码导出给内核
make import linux=/path/to/kernel
# 提交更改,截止本教程编写时间,zstd 最新版本为 1.5.6
git add *
git commit -m "lib: zstd: Upgrade to 1.5.6" --signoff

当然,光是导入新版代码是不行的,还需要引入一些更改。这里咱随便找一个有新版zstd的内核的源码,从上边cherry-pick一些更改。具体的commit信息可以在这里找到,我们需要的只是和zstd相关的。

git remote add reference https://github.com/Project-Miracle/android_kernel_microsoft_wsa
git pull reference A13
git cherry-pick 2a438230b363988c08f6c2126827c4a904972115 511169d5d25e96dcc274060ed450733bd35b30ca b5c2b50bd8e5d16a583071e9d360a3870161cbdf

如果仍然遇到了问题的话,那就看看有没有其它修复问题的commit可以pick。也可以reset掉前4个提交,看看第二种方法——全部pick别人的。

# 假设你已经像方法一那样添加并拉取好了远程仓库
# 由于部分未知原因,需要以1.4.10为界分两次pick代码,否则会导致失败
git cherry-pick 3479cf97ca9519c3f1642f9a3764e4c9690dd093...148dbd842d898ce6ec547b1147660e75d66986fd
git cherry-pick a95304934a9d490fdc63104ddb62f0a40c6d3fe7...a2b463b4f0d277b30b2f489eecb93554b9ba5328
# 然后再pick修复commit
git cherry-pick 2a438230b363988c08f6c2126827c4a904972115

理论上来说,现在你的zstd模块肯肯定定能够正常编译成功了!其它相关的库大致也是这个样子。

更新内核部分驱动

高通将内核源码开源到CodeLinaro的同时,也开源了配套的Wi-Fi等驱动的源码,咱可以通过更新驱动来提高各部件工作的稳定性和性能。

🚧 施工中……

最后祝各位魔法使们一路顺利~

好耶,终于写完第一篇了!(躺)(叹气)

除特殊声明转载之外,本文由博主 云萧是个咕咕怪 原创,依据 CC BY-NC-SA 4.0 许可协议授权,转载请注明出处。(*◦˙▽˙◦)
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇