#0 说在前面
#0.5 战斗准备
开始战斗前的物质准备:
- 硬件:一台多核性能不是太差的电脑
- Linux环境:单独安装或WSL均可
- Clang编译器:推荐最新Neutron Clang
- AOSP GCC交叉编译器:默认最新(在这里找最新标签,标签格式为“android-xx.x_rxx”)
- git相关使用方法(多分支管理、rebase、cherry-pick等)
以及3个小提示!
- 为了方便后期进行cherry pick等操作,强烈建议在条件允许的情况下,以
git clone
的方式将内核源码复制到本地,而非通过下载内核源码的压缩包。 - 即使源码只能以压缩包形式获取,也建议通过
git init
命令将其初始化为一个git仓库。 - 为了方便我们在工作时敲代码或校对或解决冲突或写出规范commit,推荐使用VSCode。
当然,最重要的,是一颗沉静得下去的耐心。
#1 寻找可编译成功的内核源码
此处“可编译成功”指编译过程中不会因为源码的bug而中止,且编译后的内核可以正常开机。
确定哪些源码能用
首先我们打开手机设置,在“关于本机”中确认内核的大版本号——Linux的版本号(即数字部分)为x.y.z结构,但它并不遵守语义化规则。其中x.y在本系列文章中规定为“大版本号”或“主线版本号”,z规定为“小版本号”或“基线版本号”。
举个栗子,有个手机内核版本为5.15.123-android13-8-00008-g3ca6a2912c7e-ab11087001
,则可以确定其大版本号为5.15。
大版本大于等于5.10的内核为支持GKI 2.0的内核,以下为方便简称GKI内核;大版本号小于5.10的内核不支持GKI或仅支持GKI 1.0,以下简称非GKI内核。
首先,不管是什么情况下,首选手机厂商提供的内核源码。
- 小米&红米:MiCode/Xiaomi_Kernel_OpenSource (GitHub)
- 一加:OnePlusOSS (GitHub)
- oppo:oppo-source (Github)
- 真我:realme-kernel-opensource (GitHub)
- 华为:Huawei Open Source Release Center
- vivo:vivo Open Source
但是部分厂商在开源时移除或修改了部分源码,使编译无法顺利完成,或编译后无法引导系统,则需要我们另寻仓库。对于大版本为5.15和5.10的高通机型,可以直接选择来自高通的源码。
- 高通内核仓库:clo/la/kernel - CodeLinaro
对于其它GKI设备,也可以使用谷歌官方的通用源码,不过问题就是如此会失去SoC厂商和手机厂商的优化。
当然,还有一个全版本通用的方法——在GitHub上找寻设备的第三方源码。不过这样找到的源码仓库,其性能和续航表现全部依赖于维护者本人的技术水平。
第三方内核的搜索关键词是:“kernel_<手机厂商名>_<设备代号>/<SoC代号>
"。比如我自己使用的搭载骁龙855的红米K20 Pro,手机的代号为raphael,SoC的代号为sm8150,就需要搜索“kernel_xiaomi_raphael”或“kernel_xiaomi_sm8150”。
不过对于非GKI设备的第三方内核,这些内核中有的只能供类原生(即OSS vendor的系统)使用,有的只能供官方系统(即官方vendor的系统)使用,一般会标注在简介内,根据自己的需要去选择。
开始编译!
接下来就是内核编译的过程,网上教程很多,这里暂不赘述。看到控制台输出这个,就说明编译成功~
至于如何刷入内核,可以参考:[基础教程2]如何使用AnyKernel 3打包内核
#2 了解commit规范
安卓内核既然也是Linux内核的一种,那么其commit规范也会受到约束。(虽然你不遵守规范也没人追究)一般情况下,一条标准的Linux内核的Git commit消息应当如下所示。
[scope]: [subject]
[description]
(可选,对于个人项目,确实写不出来或没必要可以不写
但如果要贡献给Linux或AOSP上游,则要越详尽越好)
[signature]
请注意,对我们而言,规范的commit信息的最主要的意义在于让我们在后期的优化时读懂别人在干什么,同时也要让别人读懂我们在干嘛。
scope
此处scope即作用域,表示修改的地方,如某个特定的功能。若无法确定,则一般情况下取作出的修改所在的目录即可,且一般不超过两层。如上图的commit仅修改了cgroup控制组,则scope为“cgroup
”。
如果有多个scope,则需要用英文逗号隔开,如linux@c6f53ed的scope就是“mm, memcg
”。
如果修改的是编译配置(保存在arch/<架构>/configs
目录),则scope应为对应的defconfig,如kerneltoast/android_kernel_google_zumapro@3bd1027的scope就是“zumapro_defconfig
”,而该机型的架构为“arm64”,代号刚好为“zumapro”。
如果修改覆盖的范围较广,或修改内容涉及很多方面,则scope应为"treewide:
",如kernel_common@dab2144。
如果该commit是从上游(即主线或更高版本的基线)移植下来的,即反向移植(backport),则scope前应加上“UPSTREAM:
”,如kernel_common@0c24e4d。
如果该commit的内容来源于非Linux官方仓库,则scope前应加上“FROMGIT:
”,如kernel_common@489cecd。
subject
此处subject可理解为简述,即将所做的更改用一句简短的话表达出来,记得首字母大写2333~
signature
由于我们只是进行个人项目的开发,故signature部分仅保留署名(即sign-off)部分即可。在VSCode的设置中搜索“sign off”,打开“Always Sign Off”即可在每次提交时自动署名。
如果该commit还有他人参与,别忘了在最末尾加上个Co-authored-by
。
#3 自定义内核本地版本号
既然知道了commit规范,那么我们先来改改本地版本号练练手吧!
本地版本号说白了就是给内核版本加后缀,使其具有辨识度,这也是很多第三方安卓内核所会做的。咱可以从手机设置里查阅,或和Linux发行版一样通过以下命令查询内核版本:
u0_a240@localhost ~ > uname -r
4.14.336-perf-g60753789a2
在这个例子中,“-perf-g60753789a2
”就是内核的本地版本号。
而这个后缀可以通过.config
或defconfig
(文件位置见scope处)这2个文件中的1个进行修改,它们都是编译的配置文件。像编译时间或内核版次等经常变动的字串,建议在.config
文件中修改,以防止原defconfig被git追踪带来的麻烦;而像内核名称之类的固定内容则建议在defconfig中修改。
注:此处.config
仅指在编译前执行make xxx_defconfig
后在根目录产生的.config
配置文件。
直接查找文件中的CONFIG_LOCALVERSION
条目,有就修改,没有就加上,记得值用英语的双引号包起来就行。
# defconfig 部分配置
CONFIG_LOCALVERSION="-Miracle"
# uname -r 命令运行结果
4.14.336-Miracle
而在CONFIG_LOCALVERSION
配置下边还有一个CONFIG_LOCALVERSION_AUTO
,这个条目开启后可以在编译开始时检测当前版本控制工具(如git、svn等),通过它追踪编译时的版本(即该commit所对应的独一无二的hash值)并将版本写入到内核版本中,喜欢就加上,不喜欢就关掉,例如我使用以下defconfig
配置:
# 假设现在使用 git 进行版本控制,当前 commit 为 1d7e6ba801...
# 并且本地还有未提交的变更
# defconfig 部分配置
CONFIG_LOCALVERSION="-Miracle"
CONFIG_LOCALVERSION_AUTO=y
# uname -r 命令运行结果
4.14.336-Miracle-g1d7e6ba801-dirty
接下来,提交你对defconfig的修改,大功告成!恭喜你已经自己做出了第一个commit!
#4 集成KernelSU
KernelSU需要通过KPROBES
以驱动的形式集成进内核中,所以我们需要事先在defconfig
中启用它。
# 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
接下来分别修改drivers/staging目录下的Makefile
和Kconfig
,添加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,在编译时选择启用KernelSU,大功告成!
#5 合并上游代码
绝大多数情况下,同一个设备的Android内核因驱动迁移难度大,只能局限在一个LTS大版本内,所以我们通常需要将该大版本下对应的Linux上游合并进来,以使环境更安全、并获取更新的从新版内核特性。这就是“更新基线”或“合并上游”。
绝大多数情况下,只要保证大版本一致,进行合并后几乎不会遇到无法引导的情况。
首先,为了防止可能的故障带来的回退麻烦,需要checkout到一个新的分支,咱一般建议名为baseline(-xxx)
,表示该分支用于合并上游基线(即大版本内)变动。
git checkout -b baseline-5.15
然后,给当前的本地仓库添加谷歌的通用内核仓库为远程仓库,并且拉取你内核所在大版本(非GKI)或KMI版本(GKI,KMI版本是什么?)的对应的基线分支。
# 由于谷歌代码库在大陆访问不便,此处使用清华大学TUNA的AOSP镜像源
git remote add aosp https://mirrors.tuna.tsinghua.edu.cn/git/AOSP/kernel/common.git
# 假设有个手机的内核是5.15.123-android13-8-00008-g3ca6a2912c7e-ab11087001
# 所以需要拉取的分支是android13-5.15-lts
git pull aosp android13-5.15-lts
这里列举几个大版本的基线分支:
- 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+:androidxx-y.z-lts(此处星号需要与你手机GKI内核的KMI版本匹配)
然后就是把它们合并进你源码项目!
# 首先将其合并到本地的baseline分支上
git merge aosp/android13-5.15-lts
# 进行编译测试
......
# 如果测试通过且开机后功能正常,再合并进主分支(假设为dev)
git checkout dev
git merge baseline-5.15
如果合并时出现冲突,建议通过VSCode解决冲突,一般情况下全部选择传入即可。合并时的信息无需遵循commit规范,只用默认的“Merge 'xxx' into xxx”即可。
#5 合并SoC厂商上游
由于其它SoC不直接开源或直接不开源上游,此处只讨论高通的情况。
这是一个可选项,但合并高通的内核源码也能让内核的驱动得到更新、调度得到优化。最重要的是,通过更新的设备树和bpf等,能使内核支持的Android版本得到提升。不过需要注意,高通从6.1(即骁龙8Gen3)开始就不再向在CodeLinaro上开放内核源码,
首先将高通的内核源码拉取到本地并合并,这里仍然以搭载骁龙855的4.14内核的红米K20 Pro举例。
# 添加高通的5.15通用内核仓库
git remote add qcom https://git.codelinaro.org/clo/la/kernel/msm-5.15.git
# 拉取高通代码到本地
# 此处说明:文中提到的标签会随时间而变动,大家可以通过搜索“LA.UM.”(非GKI)
# 或KERNEL.PLATFORM(GKI)来查找最新可用的标签;标签中“r”后的数字越大,或
# 同“r”数字时标签中的五位数字越大,被高通维护的版本越新;最后“QSSI”后的
# 数字暂且可理解为支持的安卓版本。
git pull qcom KERNEL.PLATFORM.2.1.r5-09200-kernel.0
git merge qcom/KERNEL.PLATFORM.2.1.r5-09200-kernel.0
一般情况下,只要合并AOSP上游完成后,再合并高通上游,是不会出现冲突问题的,可放心食用。
#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内核模块”。
就拿经常被第三方内核拿来做ZRAM压缩的zstd举个栗子吧,它存放在lib/zstd
目录下,而且绝大多数情况下不受Linux版本的影响。首先找一个zstd更新了的内核仓库,比如ferstar/xiaomi_xaga_kernel,先pull下来,同时再打开对应的目录的commit历史记录,只要不是AOSP或Linux官方的commit,均为优化或更新项。
按照cherry pick的方法一个一个pick就行,更新功能就这么简单。理论上来说,现在你的zstd肯肯定定能够正常编译成功了!其它相关的库大致也是这个样子。
更新内核部分驱动
高通将内核源码开源到CodeLinaro的同时,也开源了配套的Wi-Fi等驱动的源码,咱可以通过更新驱动来提高各部件工作的稳定性和性能。
🚧 施工中......
最后祝各位一路顺利!
好耶,终于写完第一篇了!(躺)(叹气)
感谢
感谢,期待继续更新!🤗