在Android上运行Linux发行版

定制可以在 Android 手机/平板上运行的Linux发行版镜像,使用 chroot

在很久以前,我曾发现过一个App,叫 LinuxOnAndroid, 也在以前的米1上面玩过,那时候这个东西的确能够运行很多Linux发行版。可惜,后来这个项目停止维护了,里面的镜像都已经太老旧以至于更新一下都可能失败。后来换了米2,更新了 Android 5.0, 由于它要求 ARM PIELinuxOnAndroid 的镜像里面并没有开启这个,所以我认为它会失败而很久没有折腾。直到前天,在 ##Orz 大水群里面,有人告诉我在 chroot 环境里面是不会被这个限制影响的,因为这个限制是在Android的 linker 里面而不是内核里面,一旦切换根目录就会被替换掉。

正好,我也长期苦于在手机上没有好办法使用 git, 以及难以调试 nodejs Python 之类的东西,所以趁着中秋假期借鉴LinuxOnAndroid的经验自己跑起来一个Linux。

准备

由于我不可能把所有发行版都装一次,也不可能在各种手机上都测试一遍,所以我只能以如下的环境为例

  • armv7h 设备 (如MSM8974)
  • Android 5.x 和 busybox 完整支持 以及 root 权限
  • 至少 4GiB 的sdcard(内置存储)空闲空间
  • 至少 2GiB 的总运行内存
  • 自备终端模拟器和SSH客户端(如JuiceSSH)
  • 以安装和配置 ArchLinuxARM 为例

我们的目标是

  • 将目标Linux发行版安装至一个虚拟分区内
  • 能够在chroot下正常工作
  • 能够访问网络/sdcard
  • 能够通过ssh访问

注意,本文中的终端模拟器下执行的命令请全部在root权限下执行。(SSH下的命令请注意看说明)

如果你在过程中遇到了问题,您可以参考本文最后处的FAQ

Bootstrap

任何 Linux 发行版的安装,都要解决一个先有鸡还是先有蛋的问题,这个过程就是 bootstrap, 只不过在某些发行版的安装过程中被自动化了。在Android上安装,同样要经过这个过程。

首先,你得在你的sdcard(内置存储)上找一个空的目录以便管理文件,我们认为它的路径是 /sdcard/linux. 那么现在,打开终端模拟器,创建好这个目录。然后,运行下面的指令来创建一个4GB容量的空文件作为我们的虚拟磁盘

dd if=/dev/zero of=/sdcard/linux/root.img bs=1048576 count=4096

这条命令创建了一个包含4096块、每块大小是1M的文件,也就是一个4G的空文件 root.img, 接下来我们需要对它进行格式化以准备使用

mke2fs -t ext4 -F /sdcard/linux/root.img

这样我们就在这个空文件上创建了一个 ext4 文件系统,接下来我们把它挂载到 /sdcard/linux/root

mkdir /sdcard/linux/root
mount -t ext4 -o loop /sdcard/linux/root.img /sdcard/linux/root

然后我们需要下载 ArchLinuxARM 的bootstrap包。由于我们身处中国,从官网软件源下载速度并不是很快,所以我推荐大家使用USTC的镜像下载

http://mirrors.ustc.edu.cn/archlinuxarm/os/

打开这个URL,在里面找到你需要的bootstrap压缩包。比如说我的设备是 MSM8974 平台,那我就要下载 ArchLinuxARM-armv7-latest.tar.gz, 如果是其他平台则要把armv7换成其他的平台名称,比如说64位的手机应该下载 ArchLinuxARM-aarch64-generic-latest.tar.gz

我们假设这个文件下载后存储在 /sdcard/Downloads/ArchLinux.tar.gz

现在,把它拷贝进刚刚挂载出来的那个分区,并解压。

cp /sdcard/Downloads/ArchLinux.tar.gz /sdcard/linux/root/
cd /sdcard/linux/root
tar xzvf *.tar.gz

接下来我们要切换软件源到 USTC. 使用busybox里面的vim或vi编辑文件 /sdcard/linux/root/etc/pacman.d/mirrorlist, 将原来的 Server = xxxx 那一行注释掉 (前面加个#),然后在最前面重新添加一行

Server = https://mirrors.ustc.edu.cn/archlinuxarm/$arch/$repo

然后需要配置 DNS. 先用rm命令删除原有的 /sdcard/linux/root/etc/resolv.conf, 然后重新建立这个文件并编辑,键入DNS服务器配置

nameserver 8.8.8.8
nameserver 8.8.4.4

完成以后,我们就基本上完成了 bootstrap 过程,可以准备 chroot 进去了,但在这之前,我们得先把运行需要的目录挂载到虚拟根目录里面去

mount -o bind /dev /sdcard/linux/root/dev
mount -o bind /sys /sdcard/linux/root/sys
mount -o bind /proc /sdcard/linux/root/proc
mount -t tmpfs tmpfs /sdcard/linux/root/tmp
ln -s /proc/self/fd /dev/fd

最后一条指令是为了修复在子系统内执行一些包管理器会需要 /dev/fd 而出现的问题。如果不加这一条语句,那么在之后我们配置 pacaur 之类的从AUR安装软件的工具时会出现错误。

接下来,我们可以 chroot 进入刚刚创建好的基本系统了

chroot /sdcard/linux/root /bin/bash

配置基本系统

现在我们已经进入了这个基本系统,你可以看到命令提示符已经发生了变化。首先,我们最好先做一次全系统升级

pacman -Syu

然后安装一些工具

pacman -S base-devel vim sudo

接下来配置一个新用户,我们假设这个用户的名称是peter

useradd -m peter
passwd peter

运行第二行命令以后请键入为这个用户设置的密码。接下来用刚刚安装的 vim 编辑 /etc/sudoers, 添加这样一行

peter ALL=(ALL) ALL

这样 peter 就有了sudo权限。别忘了给 root 用户也设置一个密码,而且最好是强密码(反正平时用不到)。

然后我们准备启动 sshd 服务。先生成 ssh host key, 再启动sshd

ssh-keygen -A
/bin/sshd

这样你已经在后台启动了一个 ssh 服务。现在你可以关闭这个终端模拟器了,我们将使用 SSH 登入。

打开SSH客户端,新建一个链接,服务端IP写本地(127.0.0.1), 端口为默认的22, 使用刚刚新增的用户和其对应的密码登录(不要使用root)

配置网络

如果是在其他的Linux发行版上配置chroot环境,那么这个教程到这里就可以结束了。但是很可惜的是,我们使用的是 Android. Android 魔改的内核导致只有特定的用户组才能访问网络。所以我们在chroot的环境里面仍然需要配置对应的用户组才能在非root下正常使用网络。

sudo groupadd -g 3001 android_bt
sudo groupadd -g 3002 android_bt-net
sudo groupadd -g 3003 android_inet
sudo groupadd -g 3004 android_net-raw
sudo gpasswd -a peter android_bt
sudo gpasswd -a peter android_bt-net
sudo gpasswd -a peter android_inet
sudo gpasswd -a peter android_net-raw

然后退出ssh重新登录即可。

配置SD卡(内置存储)访问

由于我们创建的虚拟磁盘只有4G,可能不够存储使用,所以我们需要在子系统内访问SD卡。但是,Android同样是禁止普通用户直接访问SD卡的。所以,我们同样要进行相应的处理。

先打开一个终端模拟器,把SD卡挂载到虚拟根目录内

mkdir /sdcard/linux/root/mnt/sdcard
mount -o bind /sdcard /sdcard/linux/root/mnt/sdcard

然后关闭这个终端。

接下来一样要创建用户组,在ssh里面执行

sudo groupadd -g 1015 sdcard-rw
sudo groupadd -g 1028 sdcard-r
sudo gpasswd -a peter sdcard-rw
sudo gpasswd -a peter sdcard-r

然后尝试 ls /mnt/sdcard, 应该已经可以使用了。

配置区域和语言

在chroot出来的环境里面,我们没有 systemd 和任何其他初始化系统支持,所以不能自动设置语言。我们必须手动配置。

在ssh内编辑 /etc/locale.gen, 去掉 en_US.UTF-8 一行前面的注释符号,然后运行

locale-gen

接着编辑 ~/.bashrc (如果你使用了其他的shell那么请编辑这个shell的初始化脚本),添加这些到尾部

export LC=en_US.UTF-8
export LC_ALL=en_US.UTF-8

然后重新登录ssh即可。

配置启动脚本

这些挂载出来的虚拟分区在重启手机后会被取消挂载,如果每次都手动重新挂载和启动sshd会非常麻烦。所以,我们可以创建一个shell脚本,自动挂载分区并启动sshd

#!/system/bin/sh

mount -t ext4 -o loop /sdcard/linux/root.img /sdcard/linux/root

mount -o bind /sys /sdcard/linux/root/sys
mount -o bind /proc /sdcard/linux/root/proc
mount -o bind /dev /sdcard/linux/root/dev
mount -t tmpfs tmpfs /sdcard/linux/root/tmp

mount -o bind /sdcard /sdcard/linux/root/mnt/sdcard

ln -s /proc/self/fd /dev/fd

chroot /sdcard/linux/root /bin/sshd

这个脚本会将运行所需分区全部挂载上,并在 chroot 环境内启动一个sshd,这样你就可以直接使用ssh客户端连接到内部的系统了。这样,每次手机重启以后,你只需要开终端模拟器执行一下这个shell脚本就可以了。

以后

现在,一个Linux发行版已经在你的Android上运行起来了。你可以尽情地调戏,只要记住这是ARM平台而非和PC一样的平台。你甚至可以在里面运行一个VNC服务器,用 VNC 客户端连接,可以运行一个桌面环境,等等。

另外,我非常推荐修改ssh设置把ssh服务端的监听IP改成 127.0.0.1, 以防止别有用心之人。

如果需要从AUR上安装软件,你需要 pacaur 之类的命令,但是注意不要通过添加 pacman 软件源的方式安装,因为那些软件源都不支持ARM平台。要安装这类的工具,你得先从 AUR 上下载它们的 PKGBUILD, 然后放在当前目录里执行 makepkg -i 进行编译安装。

FAQs

Q: 如何使用初始化系统如systemd来管理服务?

A: 并不能使用,建议使用supervisord来替代。使用supervisord以后,可以在启动脚本里把执行sshd的代码改成执行supervisord的。

Q: SSH失败,提示PTY allocation failed on channel 0?

A: 尝试执行以下代码

umount /dev/pts
mount -t devpts devpts /dev/pts

后记

我在这个Linux环境里面运行了 node.jsPython 3.4,性能表现非常不错。但是,里面运行的 jekyll 较之PC却慢了很多,不过至少我终于可以用手机预览我的新博客文章了。在chroot里面,除了不能换内核以外,几乎可以把它当成一个完整的Linux发行版。加之 ArchLinuxARM 的支持非常好,你可以做任何PC上的 ArchLinux 能做到的事情,包括编译安装软件等等。

后来我在里面测试了 git, 几乎一切正常,但是在修改配置时,比如 git remote add, 如果当前目录在SD卡上,则会报错。此时,你应该在命令前加上一个 sudo

其他的,我也不想再一一赘述,总而言之,这就是一个全功能的Linux环境。


You'll only receive email when Typeblog publishes a new post

More from Typeblog