在Xiaomi Pad 6 Pro上配置LXC
本文最后更新于 2024年11月1日 下午
前言
小米的HyperOS实在是让人难绷,类似窗口上方的小白点和底下占用了一些像素的白条关不掉很难受之类的槽点很多。
但是没办法,单独适配Linux和Windows又过于繁琐,于是想着直接在安卓里面chroot linux得了,可是chroot没办法使用systemd,用这样的Linux似乎有些确实意味了。
忽然想到很久以前听说的LXC,在网上搜了一下,LXC是支持Systemd的,且与原系统共享内核,无需适配主线内核,省事不少,于是乎便有了这篇随笔。
关于LXC
LXC, Linux Containers. 其概念在网上一搜就能搜到,此处不再过多介绍。
目前广泛使用的Docker
采用的也是LXC技术。
因其本质是利用linux内核的cgroups
和命名空间
机制实现与主机系统的隔来创建容器,而安卓内核通常没打开这些特性,所以其需要打开一些内核config。
使用lxc-checkconfig检查内核配置情况:
再看看服务器的:
服务器要跑docker的,云服务器提供商自然不会把这些配置落下,相比之下安卓设备并没有这样的需求,没有打开自然也是情理之中的事情了。
GKI
综上所知,要systemd需要lxc,要lxc需要config,而打开config就需要编译内核了。
现在安卓内核普遍采用GKI 2.0,即Generic Kernel Image(通用内核镜像),OEM厂商的内核一般都是直接从谷歌那边拿编译好的。
使用uname -a
可以查看当前内核信息:
谷歌的内核开源,且由谷歌维护,这么看的话,想要自己改个config然后编译就简单多了(后记:实际上修改config会导致ABI变化,从而影响启动和模块加载,折腾了两天,在github上面找到了一个现成的patch才解决),只要去找到和现在机器内核一样的commit的源码拉下来自己编译就行了,但怎么找到对应的commit呢?
其实不用,根据GKI的规范,只要KMI一样就可以,在上面的图片中:
1 |
|
5.10-android12-9
就是KMI了,只要一样就可以兼容。
当然,为了做到万无一失,我肯定会去找一样的commit拉下来编译,这就需要利用到内核信息中的g226a9632f13d
和ab11136126
部分了,这里的g
代表了该内核是由谷歌自己编译的,而ab
后面的11136126
代表的编译任务的序号:
即下面链接中数字部分:
https://ci.android.com/builds/submitted/11136126/kernel_aarch64/
只需要替换为ab
后面的数字,就可以跳转到谷歌官方的构建产物列表处,无需登录,立即就可以下载到vmlinux、Image.gz等等内核编译产出文件。当然,还有日志文件。
经过简单的搜索即可发现,该内核日期为2023-11月(但是现在已经2024年10月了,小米日常操作),branch为common-android12-5.10-2023-11
。
构建、验证GKI内核
参考谷歌的文档,可以轻松地构建内核。
创建一个screen/tmux窗口,防止ssh意外断开
1
screen -S build
创建文件夹,拉仓库的清单文件
1
2
3
4mkdir android-kernel && cd android-kernel
sudo curl https://mirrors.tuna.tsinghua.edu.cn/git/git-repo -o /usr/bin/repo
sudo chmod +x /usr/bin/repo
repo init -u https://mirrors.bfsu.edu.cn/git/AOSP/kernel/manifest -b common-android12-5.10-2023-11先别急着sync,对比一下manifest文件,这里第二个manifest是从谷歌的构建产物中下载的
1
diff --color .repo/manifests/default.xml manifest_11136126.xml
发现有很多不同的地方,这当然是不能忍的,一定要完全一直才能保证待会儿编译完成之后开机是完全没有问题的
覆盖manifest,拉源码
1
2cp manifest_11136126.xml .repo/manifests/default.xml
repo sync经过漫长的等待,也是终于把源码拉下来了,运行下面的命令构建内核,命令也是从谷歌的build_log里面扣出来的
1
LTO=thin DIST_DIR=./gki202311-out/ BUILD_CONFIG=common/build.config.gki.aarch64 build/build.sh -j16
编译完成后,在输出文件里面可以找到
Image
文件,此文件为内核文件。使用
masgikboot
重构boot中的内核文件,并使用fastboot boot
命令进行热启动,即只启动,不刷写。
在电脑上用adb把Image文件推到/sdcard,不再赘述。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23# termux
# 获取root
tsu
cd
mkdir lxctest && cd lxctest
# 查看当前系统的槽位
getprop | grep suffix
# [ro.boot.slot_suffix]: [_b]
## 此处为b槽
# 提取小米的boot分区
cp /dev/block/by-name/boot_b .
# 使用magisk解包
# 如果安装了magisk,系统会自带一个magiskboot,位置在/data/adb/magisk/magiskboot
/data/adb/magisk/magiskboot unpack boot_b
# 使用ls查看,有kernel文件和ramdisk.cpio
# 此处只需要替换kernel即可。
# 把/sdcard下面的Image文件移动过来
mv /sdcard/Image kernel
# 重新打包
/data/adb/magisk/magiskboot unpack boot_b /sdcard/new-boot.img把文件从/sdcard拉到电脑上,然后把平板重启到fastboot,再做热启动
1
2adb reboot bootloader
fastboot boot new-boot.img验证是否正常启动
目前我是没有修改任何文件的,内核按道理来说是可以直接启动的,实测也确实是如此1
uname -a
使用
uname
命令查看内核信息,发现abXXXXXX,也就是谷歌的构建编号已经消失了,此内核正常启动。
修改GKI内核
修改config会导致ABI变化,这样就会导致模块加载出现异常之类的问题,搞了半天,编译了好几次,最后在https://github.com/TapetalArray/gki-custom/blob/main/patchs/
里面找到了几个patch文件可以bypass一些强制验证+勉强修复ABI,然后就可以随意打开config了。
- 应用patch文件
1
2
3git clone https://github.com/sunflower2333/android-kernel-build --depth=1
cd common # 内核在common里面
git apply ../android-kernel-build/patches/*
需要手动加的config大致就是Namespace方面的PID_NS,USER_NS,IPC_NS,Cgroup方面的CGROUP_DEVICE,这几个都打开就行了,另外IPC_NS需要SYSVIPC才行,
DEVTMPFS也需要打开。我对比了一下上面的github项目中提供的defconfig和谷歌提供的gki_defconfig,其实差不多,除了多了上面那些配置还有一些额外的地方和
谷歌那边的不一样,我这里直接按照顺序把需要的扣过来放在谷歌这边默认的config里面了。
- 替换defconfig
1
cp android-kernel-build/android-kernel/gki_config common/arch/arm64/configs/gki_defconfig
在执行编译命令时,工具会先执行saveconfig,然后对比生成的defconfig和gki_defconfig是否一样,
如果不一样就终止了,所以顺序比较重要。如果直接把需要的CONFIG直接丢到gki_defconfig最前面,编译的时候就会炸这个,
这个时候最好还是注意一下打印出的diff信息,有时候他会直接把加的config去掉,而不是放在正确的位置上,
可以通过对比+
和-
的个数来查看,如果不一样的话就白加了。
这玩意就很反人类,谁知道要放在哪里,但是使用XXX build.sh menuconfig又会报错找不到ncurses库,翻了一下,可以用一种方法手动进入menuconfig
1 |
|
这样就可以修改config文件了,把我们需要的东西打开之后再保存回defconfig
1 |
|
输出的文件在out/android12-5.10/common/defconfig
,直接复制过去
1 |
|
然后cd 到common里面, 使用git diff命令查看修改的内容
1 |
|
结果发现除了我们在menuconfig加的东西之外,原来打开的CONFIG自己没了,这时候就要加回去,直接加到最上面就行了
1 |
|
重新运行内核编译命令,此时报错saveconfig和gki_defconfig不一样,直接用saveconfig生成的defconfig替换gki_defconfig就行了,命令和上面的一样
1 |
|
再次查看git diff,发现只有我们需要的东西打开了,其他的一个也没有少,这个时候就可以编译了。
编译
1
SKIP_MRPROPER=1 LTO=thin DIST_DIR=./gki202311-out/ BUILD_CONFIG=common/build.config.gki.aarch64 build/build.sh -j18
SKIP_MRPROPER=1
代表不执行make mrproper,也就是删除编译内核时生成的文件,但是我们这里不需要,执行这个之后每次都需要从头开始编译,慢死了。LTO=thin
代表Link Time Opimization 链接时优化的等级为Thin,一开始不知道这个破玩意,默认是full,给他开了20G的swap才编译出来,慢的要死还
费硬盘,使用thin的话8G以内就够了,甚至不需要开swap,而且速度也很快。验证
按照上面的方法重新打包,然后fastboot boot xxx
就可以启动了,我也是碰壁碰了几天成功进去系统了,开机就显示安卓设备内部损坏
,
其实啥也没有,不用管他,貌似只要改了config就会出这玩意。1
2lxc-setup-cgroups
lxc-checkconfig一片绿色,风景无限好
另外,还可以使用moby提供的脚本检查配置情况,下载下来直接运行就行了
也基本上是绿色的,完成。
LXC容器的安装、配置和使用
安装
直接在termux 里面安装lxc就行了,不过lxc在root-repo里面,需要先安装root-repo。
安装使用新版本的lxc,termux自带那个lxc太老了,github上面有个编译好的lxc-6.0的release
下载下来直接安装就行了,如果之前安装了termux的lxc需要先卸载掉。
1 |
|
创建容器
1 |
|
-n
后面接容器名字,我这里装arch就直接用arch表示了。DOWNLOAD_SERVER
可以加也可以不加,推荐不要加,用梯子拉官方的。
在获取到容器列表后,对话中依次输入发行版,发行版版本,cpu架构,就可以创建了。
启动容器
1 |
|
然后不出意外的话,就爆炸了,不出意外的话会报错can not parse mountpoint xxxxx cgruop2 none
的相关信息,这个我猜测是termux里面自带的那个lxc版本(3.1.0)太老了,不支持cgroup2,导致无法识别这个文件系统就爆炸了,必须把这个cgroup重新remount成tmpfs才行,但是改成tmpfs会导致安卓异常,应用无法打开。
安装了新版的LXC之后,这些问题全都消失了,也不需要挂载成tmpfs了,直接用默认的就行了,安卓应用也可以正常打开了。
写了个脚本完成上面的工作,不过这里我顺便直接把rootfs丢到镜像里面了,所以加上了挂载的操作,下面会讲到。
1 |
|
先把上面的loop和mount部分去掉,暂时用不到,执行这个脚本bash startup-arch.sh
,正常情况下已经没有报错了,此时使用lxc-info
命令
查看状态,arch容器的状态为RUNNING
1 |
|
进入到容器内部
终于到了最激动人心的进入环节,有两种方式可以进入容器,一种是lxc-attach
,另外一种是lxc-console
,
lxc-attach感觉类似于chroot,使用这种方式进入居然还会保留原终端的环境变量,但是我没有确认过这样的情况下
systemd能否运行;使用lxc-console的话就是直接连接到容器的tty了,感觉类似刚开机让你输密码的界面。
两者的进入方式都为:
1 |
|
关于Alarm/ArchLinuxArm:
- 默认用户: alarm
- 密码: alarm
- root默认密码: root
LXC容器的配置
如果在上一步完了一会儿就会发现,容器里面连个网都没有,sudo也是爆炸的,说什么effective uid is not zero,还说sudo被放在
了一个挂载属性为nosuid的硬盘上。我当时就去看了一下rootfs在什么鬼地方,果然不出所料啊,是直接以文件的形式存在/data/data/com.termux/files/usr/var/lib/lxc/arch/rootfs/
里面的,使用mount | grep /data/ | grep nosuid
不难
发现,/data确实是以nosuid
的属性挂载的,这就不奇怪了。
所以我直接把rootfs挪到镜像里面去了,具体操作方法如下:
配置rootfs存储到虚拟镜像
首先,创建一个空白磁盘,大小20G
1
2fallocate -l 20G linux.img
mkfs.ext4 linux.img接着,挂载它,使用losetup命令
1
2
3
4
5
6
7tsu # 获取root
cd # 回家
mkdir ArchLinux
losetup -f # 查看空闲的 loop设备
# > /dev/block/loop43
losetup -f linux.img # 设置loop设备
mount /dev/block/loop43 ArchLinux # 挂载loop设备然后,把原来的rootfs全部复制过来
1
2# 如果上面的容器还没有退出的话,使用lxc-stop -n arch停止容器
cp -a /data/data/com.termux/files/usr/var/lib/lxc/arch/rootfs ArchLinux修改容器的配置文件,然后就大功告成了
1
2
3
4
5nano /data/data/com.termux/files/usr/var/lib/lxc/arch/config
# 把原来的那个注释了,改成挂载点的路径
#lxc.rootfs.path = dir:/data/data/com.termux/files/usr/var/lib/lxc/arch/rootfs
lxc.rootfs.path = dir:/data/data/com.termux/files/home/.suroot/Archlinux可以选择把原来的rootfs直接删了,反正也用不到了。
再去执行sudo的话, sudo就变得可以使用了,很棒。
网络配置
打开/data/data/com.termux/files/usr/var/lib/lxc/arch/config
,直接在里面加上lxc.net.0.type = none
就完事了,
它会直接共享主机的所有网络和网卡。
图形化配置
测了好久,最后得出结论,没办法直接干掉安卓显示,然后替换成Linux的桌面管理器(Wayland Or Xorg),
也没办法实现加速渲染,精力和实力有限,遂放弃研究。
主要原因有如下几点:
- Xorg驱动兼容性问题:Xorg使用DRI的时候,会报一个空指针异常,通过在源码添加检测后可以正常使用,但是Input Evdev无法使用触摸/指针设备。
- libinput&udev问题: libinput依赖udev,当udev服务运行的时候所有event device都已经加载完成了,压根没法获取到现有的event device,
如果在启用udev服务连接输入设备,倒是可以扫描到,但是基本上也没啥用了,因为像键盘什么的event设备在安卓启动的时候就
已经添加了。- Weston驱动兼容性问题:weston依赖libinput,必须要在udev服务启动之后用蓝牙(安卓里面)连接个输入设备才能启动,而且启动之后
是黑屏的,我试过glxgears和vkcube都无法显示。- Kwin驱动兼容性问题:Kwin直接说
/dev/dri/card0
无法使用/不是KMS设备,懒得管它了。- Xorg 加速兼容性问题:高通在安卓使用KGSL驱动(/dev/kgsl3d0),我启用Zink之后,他会Vulkan那边找一个设备号是
226:128
的渲染后端
(也就是/dev/dri/renderD128),vulkan那边找不到,因为后端实际上是kgsl-3d0
,id号不匹配,就被代码pass掉了,mesa报错
error: ZINK: failed to choose pdev
。实际上在编译安装mesa之后vulkaninfo是可以看到vulkan信息的。- *史安卓导致的问题:如果干掉hwc,那么WIFI、蓝牙什么的全都掉线,小米的键盘输入响应也会变得十分迟缓(我也不知道为什么,安卓构思驱动),
在内核日志中键盘是实时上报的,但是在event设备中需要好几秒才能输出出来。干掉HWC/surfaceflinger还会导致拔出ADB之后,系统会直接重启,
这也是放弃夺舍的最主要原因之一- 面板/驱动的诡异问题:在Pad5/6 Pro系列的REC中,会出现花屏问题,表现为花屏大半部分(见下面的图),只剩一小部分,通过更改DRM Mode可以缓解这个问题,
在Pad6 Pro上面将DRM Mode设置为1800x2880x90vid
也就是90HZ模式可以使屏幕大部分是正常显示的,但是小部分还是花屏。。。这很难受,
也是放弃的次要原因。
lxc配置
在容器的config文件中加上这些配置:
1 |
|
编译安装mesa(可选)
arch自带的libvulkan_freedreno.so 没有对kgsl的支持,所以需要克隆mesa仓库,使用下面的参数编译安装mesa:
1 |
|
-Dfreedreno-kmds=kgsl
是关键参数。
夺舍安卓
安卓的图像管理器此时正在占用DRM控制权(IOCTL DRM SET MASTER),想要获取到DRM的控制权,
需要把安卓的图像管理器干掉:
1 |
|
然而事实狠狠的打了你一巴掌,surfaceflinger并不会占有drm,实际占用的是HWC(Hardware Composer),在高通平台上是/vendor/bin/hw/vendor.qti.hardware.display.composer-service
, 如图:
这个进程是没办法完全干掉的,最暴力的方法,循环kill:
1 |
|
在Arch中启动Xorg,报错了,通过查看日志可以知道modesetting模块空指针爆炸了,给那个变量加上空指针检查就行了,至于怎么编译安装Xorg…
我这里就不细说了,网上很多教程。这里附加一下xorg.conf
1 |
|
Xorg启动之后,运行DISPLAY=:0 glxgears
测试一下显示:
启动KDE Plasma测试
1 |
|
旋转屏幕
1 |
|
另外,如果是黑屏的话,可以试试调整屏幕亮度:
1 |
|
到这里就结束了,输入没办法使用,拔掉usb会直接重启,花屏无法解决,GUI部分放弃。
后记篇
关于这次尝试的调侃
常常看到类似Helium的项目,使用安卓内核运行Linux,然后在Linux中运行lxc容器把安卓跑起来,
本篇也是反客为主,在安卓里面把Linux跑起来,然后喧宾夺主获取显示控制权,运行Linux桌面和服务。
关于显示DRM部分
显示部分还是比较遗憾的,如果不是这个平板可能就没有花屏问题了,如果能让hwc“主动”放弃DRM控制(DRMDropMaster IOCTL),
那么拔掉USB之后安卓应该不会崩溃,听小太阳说可以通过ptrace实现附加到进程上让hwc发送IOCTL,姑且先不研究了,以后有时间再看吧。
显示那块编译安装了Xorg和Weston,Xorg在修复空指针后就可以显示了,Weston那边修了半天还是黑屏,依赖seatd,依赖libinput,
又依赖udev,改来改去气炸了,直接放弃。
关于显示加速部分
重新编译mesa之后,vulkaninfo可以拿到信息,启动Xorg之后尝试使用DISPLAY=:0 vkcube
结果报错不支持DRI3(???),令人困惑,
网上搜了半天也没找到解决方法,估计是msm的驱动问题。使用Xvfb
虚拟X11也是没有DRI3的,不过我看termux-x11那个headless的Xorg服务
好像是支持DRI3的,改天试一下玩玩。
关于Arch
有点后悔一开始选了Arch,过程中遇到了很多问题,比如mesa那个缺后端kgsl,需要自己编译,再比如扩容虚拟磁盘的时候遇到的Keyring爆炸,
很糟心,还有sunshine也没支持alarm的,本来打算在安卓远控的也放弃了。
关于桌面输入
只能用抽象形容,键盘输入可以用,指针设备都用不了,可以看到鼠标在闪烁,但是就是不动,急死我了,
不过这里附加一下Xorg的input配置,没配全,只启用了触摸板、键盘、触摸、笔,在ServerLayout
里面定义。
1 |
|