操统实验日志 第一章 序章
简述
+操统实验日志 第一章 序章
简述
在一切开始之前,请允许我先简要地介绍一下关于这个实验的一切
它是关于什么的
@@ -506,7 +506,7 @@ btf.addGlobalFn('pjaxSend', () => {目前为止,环境已经基本配置完成了
接下来就让我们开始愉快的操作系统实验之旅吧!
From 3709e6198b889b37d7d0c22a022bbc7cbb0b9b73 Mon Sep 17 00:00:00 2001 From: Linloir <3145078758@qq.com> Date: Sun, 20 Oct 2024 15:46:52 +0000 Subject: [PATCH] Auto deploy pages --- 2022/07/15/os-journal-vol-1/index.html | 8 +- 2022/07/15/os-journal-vol-2/index.html | 8 +- 2022/07/19/os-journal-vol-3/index.html | 8 +- 2022/08/20/os-journal-vol-4/index.html | 11 +- 2024/10/11/reborn/index.html | 8 +- 2024/10/12/blog-from-scratch/index.html | 21 +- 2024/10/13/blog-mig/index.html | 8 +- 2024/10/13/host-git-at-home/index.html | 65 ++-- 2024/10/13/micro-posts/index.html | 8 +- 2024/10/15/tencent-new-start/index.html | 8 +- 2024/10/20/jar-via-adb/index.html | 404 ++++++++++++++++++++++++ archives/2022/07/index.html | 10 +- archives/2022/08/index.html | 10 +- archives/2022/index.html | 10 +- archives/2024/10/index.html | 10 +- archives/2024/index.html | 10 +- archives/index.html | 10 +- archives/page/2/index.html | 319 +++++++++++++++++++ categories/index.html | 12 +- categories/技术/index.html | 10 +- categories/杂思/index.html | 10 +- categories/生活/index.html | 10 +- index.html | 10 +- links/index.html | 15 +- page/2/index.html | 349 ++++++++++++++++++++ tags/index.html | 12 +- tags/博客/index.html | 10 +- tags/大学/index.html | 10 +- tags/安卓/index.html | 319 +++++++++++++++++++ tags/工作/index.html | 10 +- tags/操作系统/index.html | 10 +- tags/生活/index.html | 10 +- tags/瞎捣鼓/index.html | 10 +- tags/短文/index.html | 10 +- tags/综合/index.html | 319 +++++++++++++++++++ tags/自言自语/index.html | 10 +- 36 files changed, 1892 insertions(+), 190 deletions(-) create mode 100644 2024/10/20/jar-via-adb/index.html create mode 100644 archives/page/2/index.html create mode 100644 page/2/index.html create mode 100644 tags/安卓/index.html create mode 100644 tags/综合/index.html diff --git a/2022/07/15/os-journal-vol-1/index.html b/2022/07/15/os-journal-vol-1/index.html index 85113ba..9c2e643 100644 --- a/2022/07/15/os-journal-vol-1/index.html +++ b/2022/07/15/os-journal-vol-1/index.html @@ -7,7 +7,7 @@ - + @@ -166,7 +166,7 @@ isHome: false, isHighlightShrink: false, isToc: true, - postUpdate: '2024-10-15 13:17:40' + postUpdate: '2024-10-20 23:46:30' }
在一切开始之前,请允许我先简要地介绍一下关于这个实验的一切
目前为止,环境已经基本配置完成了
接下来就让我们开始愉快的操作系统实验之旅吧!
本章的序将会首先介绍操作系统是如何运行起来的,并在此基础上介绍实现一个完备的操作系统实验需要实现哪些方面,以及这些部分的先后顺序和依赖关系
由于这份文档我并不打算作为一份完备的教程文档来编写,因此语言方面的介绍会相对简略或是跳过,对应的详细介绍可以参考学校的同步教程
在本章的后半部分,将会介绍MBR和中断的相关知识,记录如何编写MBR、测试使用BIOS启动MBR引导程序并通过中断输出字符串进行测试
@@ -953,7 +953,7 @@ BIOS从0x7C00处开始执行代码,其并不区分内存中存储
下一章中,将会编写bootloader,从mbr中加载bootloader并且启动,最后在bootloader中让CPU进入保护模式在本章的第一部分中,将会介绍读取硬盘的CHS方式、LBA方式以及如何通过in、out指令读写硬盘,之后会将上一章输出Hello World!的代码移植到BootLoader中,并且从MBR中加载并跳转到编写的BootLoader执行
在第二部分中,会回顾保护模式的概念并介绍进入保护模式的四个步骤,并在开启保护模式之后输出第二个Hello World
完成
至此,就完成了本章的全部任务,赶紧使用make clean build run来测试代码的运行情况吧!
在本章节的第一部分中,将会简要介绍在下一章中将要编写的KernelLoader,以及在开始着手进行它的编写之前所需要完成的,包括各种驱动、文件系统接口等在内的诸多准备工作。
在第一部分之后,我决定按照KernelLoader中的函数调用顺序,逐节完成KernelLoader中所需要的所有准备工作,因此在第二部分中,将会首先记录如何在项目中使用C语言和汇编混合编程,包括C语言是如何进行函数调用的,以及内联汇编中NASM向AT&T迁移语法所需要注意的问题。有了这部分基础知识,就可以进行第三部分编写一些常用的驱动,并从我个人的角度讲讲为什么要这么做,它对后续的代码编写能够起到哪些帮助。
在之后的第四部分中,会进行有关文件系统的知识的详述,并且带领大家阅读微软关于FAT文件系统的文档,根据文档完成FAT文件系统接口的设计和实现。
@@ -1836,13 +1836,16 @@ VirtualPageIndex &= VirtualPageAddress >> n \nonumber \\其中的成员函数实现如下:
1 | PageTable* PageTable::from(const PageTableEntry& entry) { |
勇者奖章
恭喜你,读完了所有日志中最长最复杂的一篇,后面的实验之路将会因这一章的努力而愈发平坦。
时隔两年,终于借着重新配置家里网络环境的契机,重新搭建了这个博客。
+时隔两年,终于借着重新配置家里网络环境的契机,重新搭建了这个博客。
原先关于操作系统的文章正在慢慢搬迁,应该很快就能恢复了~
再一次启用关于自己的博客,感觉心里良多感慨。还记得上一次搭博客时的自己,刚来到计算机学院,对着网上的保姆教程在腾讯云的小机器上搭了 git 仓库、配置了宝塔面板、DNS 解析。
那时的自己对 TLS、证书、Git、反代、CDN、Docker 这些东西都还是那么陌生,以至于教程之外的东西完全不敢去碰,哪怕是在宝塔面板上配一个 Let’s Encrypt 的证书都要折腾好久,也没有去研究 hexo deploy 到底 deploy 了什么到服务端,只觉得能跑便是好事,这也就导致了后来的删库跑路事件——本地的博客仓库被主动删除,等到发现服务器上是没有 Markdown 源文件的时候已经太迟,由于没有了源文件,写新的博客势必会导致旧的 html 被覆盖,又因为文章实在太长迟迟没有动手迁移,原先的数万字长文就这样被冻在了旧的博客里长达两年。
不夸张地说,看到熟悉的页面再一次出现在浏览器中的时候,内心有许多感慨,大概就像离家的游子多年后重新推开家门时那样吧。拂去把手上的灰尘,推开门回到曾经熟悉的地方,所有的东西都还在原本的地方等着自己,仿佛从来没有离开过那样。博客大概就是我内心无处安放的杂思的归宿吧,我想,如今它们终于又能安家了。
这一次回来,不知道能够持续多久,但我希望,能够长一些、再长一些。至于内容,我也不打算维持早年纯技术的导向了,我更多地想让这个博客成为我存在的痕迹,让多年后的自己看到曾今的文章能够会想起当年的纠结、焦虑、喜悦或是激动,能够从这里,看到我。
总之,欢迎回家。
-简单来说,方案分为了几个主要的部分:
+简单来说,方案分为了几个主要的部分:
其中红色线条为 HTTP 流量,蓝色线条为 DDNS-GO 流量,紫色线条为本地或 v6 直连的 ssh TCP 流量

在配博客之前,我是先配好了 Nas 上的 Gitea 服务,可以参考 在 NAS 上部署自己的 Gitea 服务,无需公网服务器 这一篇博客来准备基本的网络环境和 Gitea 服务。
+在配博客之前,我是先配好了 Nas 上的 Gitea 服务,可以参考 在 NAS 上部署自己的 Gitea 服务,无需公网服务器 这一篇博客来准备基本的网络环境和 Gitea 服务。
(也就是说,我是先搭好了 Gitea,然后实在不知道能拿干点什么,才决定把博客迁移回来的。有点为了醋包饺子的感觉哈哈,不过现在博客全部内容都运行在自己本地感觉还是颇有成就感的)
-待后面补充~
-最开始想的迁移方案是使用 skip-render 标记 html,但始终觉得不够优雅,因为导航栏、个人信息、头图之类的内容时常都会变,如果 skip-render 那永远都会是当时那个版本的页面,甚至可能超链接都是失效的,除了能显示原本的博文之外其实体验应该是相当差的——横竖感觉就是很突兀嘛!
最开始想的迁移方案是使用 skip-render 标记 html,但始终觉得不够优雅,因为导航栏、个人信息、头图之类的内容时常都会变,如果 skip-render 那永远都会是当时那个版本的页面,甚至可能超链接都是失效的,除了能显示原本的博文之外其实体验应该是相当差的——横竖感觉就是很突兀嘛!
直到今天突然意识到,hexo 渲染 markdown 为 html 文本肯定会分为三个大部分:
然后再把原先的图片复制到现在的 img 目录下,批量改一手路径,Done!
谁能想到这个卡了我两年的问题,竟然能 15 分钟就搞定了!!!
过于激动,遂特写此文记录一下,真是拍大腿啊!!!
-简单来说,方案包含了以下几个主要部分:
+简单来说,方案包含了以下几个主要部分:
大四毕业上班之后,便不再和父母一起住了,自然而然地就想改善一下家里的网络环境,也正好实践一下刚考完研还热乎的计算机网络知识,而不至于像之前重启一下路由器马上就能听到 “怎么没网了” 这样的投诉。
-做的第一件事就是选一个宽带运营商。由于我自己两个号码都是中国联通的,一张是米粉卡 5r / mo 月租 + 1r / GiB (3r 不限量) 日租流量包的套餐,另一张曾经也是米粉卡,后来手贱改成了 19r / mo 月租 + 3GiB 流量 + 1r / GiB 日租流量包的大王卡套餐,一直都觉得第二张卡套餐太亏,于是想趁着宽带的机会把这张卡和宽带绑在一起共用一个套餐 (事实证明其实没什么用,因为现在宽带都改成最低消费模式了,也就是只要满足卡的每月最低消费就能有宽带,如果没有达到最低消费就直接扣中间的差额。所以其实这么看还是米粉卡最划算,相当于每月不限量流量才 95r,宽带 139r 低消只用再补 40r,所以有便宜套餐千万不要随便改啊),就选了联通 139r / mo 的 1000M 宽带的套餐。
+大四毕业上班之后,便不再和父母一起住了,自然而然地就想改善一下家里的网络环境,也正好实践一下刚考完研还热乎的计算机网络知识,而不至于像之前重启一下路由器马上就能听到 “怎么没网了” 这样的投诉。
+做的第一件事就是选一个宽带运营商。由于我自己两个号码都是中国联通的,一张是米粉卡 5r / mo 月租 + 1r / GiB (3r 不限量) 日租流量包的套餐,另一张曾经也是米粉卡,后来手贱改成了 19r / mo 月租 + 3GiB 流量 + 1r / GiB 日租流量包的大王卡套餐,一直都觉得第二张卡套餐太亏,于是想趁着宽带的机会把这张卡和宽带绑在一起共用一个套餐 (事实证明其实没什么用,因为现在宽带都改成最低消费模式了,也就是只要满足卡的每月最低消费就能有宽带,如果没有达到最低消费就直接扣中间的差额。所以其实这么看还是米粉卡最划算,相当于每月不限量流量才 95r,宽带 139r 低消只用再补 40r,所以有便宜套餐千万不要随便改啊),就选了联通 139r / mo 的 1000M 宽带的套餐。
办套餐的时候最关心的就是:
在办的时候专门确认了前三点,确认是不会有公网 IPv4 但是有公网 IPv6,上行带宽差不多 150M,想了想感觉也够了,遂同意,签合同,上门安装,便也有了后文。
-这个 139r 的联通套餐实际上是他们的 FTTR 解决方案,会带一个主路由和一个副路由,中间用隐形光纤连接,主路由给的是 2.5G 网口版本,这点好评,不过官方的配网方案感觉应该是 AC + AP 模式,光猫必须作为网关,然后路由器估计也得用 AP 模式不然感觉应该路由器连接的设备和官方的 AP 连接的设备就不在一个子网了。
+这个 139r 的联通套餐实际上是他们的 FTTR 解决方案,会带一个主路由和一个副路由,中间用隐形光纤连接,主路由给的是 2.5G 网口版本,这点好评,不过官方的配网方案感觉应该是 AC + AP 模式,光猫必须作为网关,然后路由器估计也得用 AP 模式不然感觉应该路由器连接的设备和官方的 AP 连接的设备就不在一个子网了。
当即就觉得这样不行,还是得该桥接,代价就是官方的 AP 就浪费了 (虽然我感觉好像是不是如果把 AP 连上后面交换机的光口应该也能用?不知道官方的 AP 里面是什么逻辑),但反正也是不要钱的东西,浪费就浪费了,于是初步规划了一下家里的网络拓扑如下:

以我浅薄的计算机网络知识理解一下光猫桥接:
-在默认情况下,光猫作为家里的网关路由,光猫出口端连接的是运营商网络,具有运营商分配的 IP;光猫另一端连接家庭网络的其他设备,运行在 192.168.1.0/24 子网 A (随便命一个名字方便区分) 下,并且光猫使用固定 IP 192.168.1.1 接入这个子网。
-之后,如果路由器选择路由模式,则路由器与光猫连接的一端接入子网 A 中,并具有光猫分配的 IP,此时所有设备和路由器连接,并以路由器作为它们的网关,此时,这些设备和路由器向内的端口同处一个子网 B 中,一般也是 192.168.1.0/24 这个网段,路由器以 IP 192.168.1.1 接入这个子网。
+以我浅薄的计算机网络知识理解一下光猫桥接:
+在默认情况下,光猫作为家里的网关路由,光猫出口端连接的是运营商网络,具有运营商分配的 IP;光猫另一端连接家庭网络的其他设备,运行在 192.168.1.0/24 子网 A (随便命一个名字方便区分) 下,并且光猫使用固定 IP 192.168.1.1 接入这个子网。
+之后,如果路由器选择路由模式,则路由器与光猫连接的一端接入子网 A 中,并具有光猫分配的 IP,此时所有设备和路由器连接,并以路由器作为它们的网关,此时,这些设备和路由器向内的端口同处一个子网 B 中,一般也是 192.168.1.0/24 这个网段,路由器以 IP 192.168.1.1 接入这个子网。
相反,如果路由器选择 AP 模式,则路由器可以理解为一个无线交换机,其不分割子网,所有设备和光猫内侧端口处于同一个子网 A 中,此时使用 IP 192.168.1.1 则可以访问到光猫管理页面。
如果将光猫桥接,则可以理解为,光猫以一种子网设备的形式连接到运营商和家里真实网关设备之间的子网上,也就是可以想象成如下的网络拓扑结构:

也就是,光猫自己充当这个交换机的角色,并以一个固定 IP 192.168.1.1 接入到广播域 A 中。路由器和入户网线都与光猫充当的交换机连接,路由器实际与运营商建立拨号连接并获得运营商提供的 IP,充当广播域 B 的网关设备。
为什么要强调光猫以固定 IP 接入广播域 A 中呢,因为从拓扑图中也可以看到,一旦光猫开启了桥接模式,并且所有的设备都与路由器连接,在广播域 B 中的设备将无法访问到广播域 A 中的光猫,想要访问光猫只有在路由器中建立路由表转发 192.168.1.1 的包到广播域 A 中或是直接将设备接入广播域 A 中才能通过 192.168.1.1 访问到光猫。
-其实改光猫桥接很简单,网上针对不同的光猫型号和运营商都有很丰富的教程了,反倒是超管密码的获取才是最难的一步,这就要考验和安装人员拉扯的话术了。
+其实改光猫桥接很简单,网上针对不同的光猫型号和运营商都有很丰富的教程了,反倒是超管密码的获取才是最难的一步,这就要考验和安装人员拉扯的话术了。
还记得当时和安装小哥说能不能改成桥接,小哥说现在公司规定他们是不可以这么操作的,也就是说以前明着要求小哥改桥接的路子就不太行得通了。后来想了一下,问小哥之后要是网络出了什么问题是不是要找他上门帮我弄,小哥说是的,我又问了价格,说这种不收钱的,我就顺水推舟说要不给我个超管的密码,我自己也会配网络,如果有什么问题我自己弄一下就好了也不麻烦他上门一趟,还没有费用拿,小哥很高兴就给我写了个小纸条留了密码,还嘱咐说这个密码是动态的,过几个月就失效了,要配得快点,失效了可以再问他要。
果然沟通还是要拿捏住让双方都获利还不明显违规的点哈哈,给我密码对他来说节省了跑一趟的时间和精力,减少了投诉率,对我来说又能够达成目的,还不违背运营商禁止小哥配桥接的要求,问就是我自己淘宝买的超管密码。
-光猫桥接搞定以后,就是把路由器连接上光猫然后配置拨号连接了。
+光猫桥接搞定以后,就是把路由器连接上光猫然后配置拨号连接了。
拨号的账号可以在光猫的页面看到,密码则可以问运营商人工客服,一般默认就是账号的后 6 位,IPv6 可以复用 IPv4 线路进行拨号,如下图所示:

由于需要允许内网设备与公网建立连接,需要关闭 IPv6 防火墙功能,其他厂商是否需要关闭取决于厂商如何定义它们的防火墙。TP-Link 对 IPv6 防火墙的描述为:“IPv6 防火墙能有效将外网和内网进行隔离,避免家庭局域网完全暴露给外网,保障用户的网络安全。”
同时,地址获取协议和前缀授权就都选择自动,DNS 服务器我选择了 Cloudflare 的 1.1.1.1 中的配置。

针对局域网内的设备,可以根据运营商分配的数量灵活选择 SLAAC 和 DHCPv6。由于抠门的深圳联通只给了 /64 地址,相当于只有直接连接到路由器的设备可以使用 SLAAC 分配地址,下级设备无法继续使用 SLAAC 分配,索性我就直接关闭了 SLAAC 能力改用 DHCPv6。
+针对局域网内的设备,可以根据运营商分配的数量灵活选择 SLAAC 和 DHCPv6。由于抠门的深圳联通只给了 /64 地址,相当于只有直接连接到路由器的设备可以使用 SLAAC 分配地址,下级设备无法继续使用 SLAAC 分配,索性我就直接关闭了 SLAAC 能力改用 DHCPv6。

路由有了 IPv6 之后,接上机柜里的 MacMini,就可以使用 IPv6 访问到这台机器了,先拿个 python http 服务试试看通不通:
+路由有了 IPv6 之后,接上机柜里的 MacMini,就可以使用 IPv6 访问到这台机器了,先拿个 python http 服务试试看通不通:
1 | cd ~/some_dir |
IPv6 地址可以使用 ipconfig (Windows) 或是 ip addr (Linux/Mac) 命令查看到,一般是查看 无线局域网适配器 WLAN 或是 以太网适配器 (Windows) 或是 eth0 (Linux),en0 (Mac) 下面的 IPv6 或是 inet6 标识的地址。还会有很多标有 临时 或是 deprecated 的地址,那些是系统为了防止网站跟踪 IP 地址,轮替生成的用于主动建立连接时使用的地址,这些地址有效期较短并且时常变化,不适用于监听传入的连接。
IPv6 地址可以使用 ipconfig (Windows) 或是 ip addr (Linux/Mac) 命令查看到,一般是查看 无线局域网适配器 WLAN 或是 以太网适配器 (Windows) 或是 eth0 (Linux),en0 (Mac) 下面的 IPv6 或是 inet6 标识的地址。还会有很多标有 临时 或是 deprecated 的地址,那些是系统为了防止网站跟踪 IP 地址,轮替生成的用于主动建立连接时使用的地址,这些地址有效期较短并且时常变化,不适用于监听传入的连接。

浏览器直接 http://[这里填 IPv6 地址]:端口号 即可测试能不能正常访问了,不放心可以流量访问确定链路是通的。
接下来就该把这个地址解析成域名了。因为 IPv6 始终不是静态的,因此需要动态地轮询设备的 IPv6 地址然后更新 DNS 服务商上的解析内容。
-参考 ddns-go 文档即可快速在系统中安装 ddns-go。没有选择 Docker 主要是看到可以使用 brew 安装,甚至感觉比 docker 还简单。
+参考 ddns-go 文档即可快速在系统中安装 ddns-go。没有选择 Docker 主要是看到可以使用 brew 安装,甚至感觉比 docker 还简单。
1 | brew install ddns-go |
这样就启动了控制面板在 23333 端口的 ddns-go 服务,配置文件默认保存在 ~/.ddns_go_config.yaml 中,这个我个人懒得改,想改看文档可以 -c 指定路径
这样就启动了控制面板在 23333 端口的 ddns-go 服务,配置文件默认保存在 ~/.ddns_go_config.yaml 中,这个我个人懒得改,想改看文档可以 -c 指定路径
然后就是 http://192.168.X.X:XXXXX 登陆进去面板配置了,这里需要注意的是需要局域网登录进去配置,如果是非局域网地址会拒绝连接。
Webhook 通知我选择了 Server 酱,每天免费 5 条消息也够用,Webhook 格式配置如下:
1 | https://sctapi.ftqq.com/<my_token>.send?title=检测到 DDNS 设备 IP 变化&desp=MacMini の IP 已变更为 #{ipv6Addr}, 域名更新 #{ipv6Result} |
搞完以后就可以浏览器进 域名:端口号 看看服务生没生效啦,在这一步测试的时候,记得在 Cloudflare 中关闭 Proxied 模式,后面会具体讲到原因。
这个时候已经可以在 IPv6 的网络下通过 域名:端口号 访问到服务了,但是这时候还有两个令人头疼的问题:
这个时候已经可以在 IPv6 的网络下通过 域名:端口号 访问到服务了,但是这时候还有两个令人头疼的问题:
开启 Proxy 十分简单,直接在 DNS 记录里打开 Proxied 开关即可。
-不过,由于 Cloudflare 只支持有限的端口转发,并且只支持基于 http/https 协议的流量转发,因此适用面相对不那么广,但是家用部署网站服务还是绰绰有余了。具体允许的端口号见 Cloudflare 文档。
+不过,由于 Cloudflare 只支持有限的端口转发,并且只支持基于 http/https 协议的流量转发,因此适用面相对不那么广,但是家用部署网站服务还是绰绰有余了。具体允许的端口号见 Cloudflare 文档。
经过测试,中国联通封了 80,443,2096 端口,回源我采用的 2095 端口,这个在 Cloudflare 控制台 - 规则 - Origin Rules 可以创建回源规则针对特定域名指定。
待后续翔实~
-感觉如果博客只写长文的话,好像很快就会疲乏,其实很多时候想说的内容就是一两句话,即便硬是写成了长文,又觉得好像啰嗦了。
+感觉如果博客只写长文的话,好像很快就会疲乏,其实很多时候想说的内容就是一两句话,即便硬是写成了长文,又觉得好像啰嗦了。
看到 Hexo - Butterfly 有提供一个 “说说” 的页面可以用 .yml 格式来存一些说说文档,但是仔细一看发现好像不会自动分页,这样一来图片一多感觉加载就会变成彻底的灾难…
不知道为什么在静态编译的时候没有做成本地分页的格式呢… 就像文章那样,其实在编译阶段就可以分散到不同的 index.html 去了,好可惜,也许以后有空会想办法看看能不能改吧…
还有很多云存储的方案,但感觉把自己的内容放在云上,总感觉会比较担心数据安全和以后的迁移成本,纯本地的话哪怕一天发两条十年也不过才不到上万条数据,一个 .yml 就带走了,哎可惜没分页终究还是不打算去用。
想来想去,就把短博客也当作正常的文章一样的显示在主页吧,不过会在标题前面加上 “短文” 的标记和对应的 tag,也方便浏览的时候来做区分好了
今天收到了转正邮件,正式标志着一个新的人生阶段的开始。
+今天收到了转正邮件,正式标志着一个新的人生阶段的开始。
对自己的期望就是,不要忘记做技术的初心,在新的阶段能有所成长,有所收获。
Po 一张在鹅厂的第一个关爱里程碑~

本方案本质上是使用了安卓提供的 app_process 命令,在将 Java 代码正确地打包为需要的 .jar 或是 .dex 文件后,通过 app_process 启动对应的入口函数来实现 adb 执行 Java 代码的能力。
对于目标 .jar 或是 .dex 文件,有两种不同的编译方案:
.dex 文件方式:<homedir>\AppData\Local\Android\Sdk),找到 cmdline-tools\latest\bin\d8.bat (这里可以将 <homedir>\AppData\Local\Android\Sdkcmdline-tools\latest\bin 添加到环境变量中方便后面调用 d8 命令),如果没有,可以在 Android Studio 更新 Commandline Tools 或是在 build-tools\<version> 下找到一个能用的d8 [options] <source jar> 命令编译出 .dex 文件,例如 d8 server.jar,会在当前工作目录下生成 classes.dex.jar 文件方式:app/src/main/java 下) 以及添加依赖app-debug.apk).apk 后缀改为 .jar得到 .jar 或是 .dex 文件以后,使用 adb [-s serial] push <source file> <target> 命令推送到手机端
最后,在 shell 环境执行 app_process call 起 Java 程序:
app_process 启动命令为 app_process -Djava.class.path=<path to jar> [other java options] <working directory (preferred /)> com.your.package.Class [args] ...,也即对于打包好的 server.jar 文件,执行命令可能为 adb shell app_process -Djava.class.path=/data/local/tmp/server.jar / com.linloir.server.Main --port 23456
起因是想要做一个检查安卓设备网络连通性的能力,最初想的就是 adb ping 直接秒了,甚至还可以复杂点用 adb ip route get 1.1.1.1 拿到网关以后分别去 ping 网关和外网地址,从而判断设备到网关的连通性和网关到外网的连通性
但是很快遇到了一个很诡异的现象:某些设备连续 ping 同一个地址 (即 A 先 ping 网关,然后 B ping 网关,最后 C ping 网关,串行执行) 时,会有一些设备出现 80% 以上的丢包甚至直接完全无法 ping 通的情况,并且当某台设备 ping 不通的同时,再开一个 shell 让它 ping 另外一个地址 (例如外网地址),又是完全正常的,并且同一时间之前卡住的还是完全卡住的状态。反复研究发现有那么一些手机互相就像有羁绊一样,只要挨个 ping 相同地址就基本后一台铁挂,由于时间有限同时能力也有限,暂时没有深究了 (如果有后续就再补一篇文章)
+于是,就因为这个 ping 这样谎报军情的现象,导致最后实在没办法再采用原本的方案,被迫不得不去找一个新的、能够兼容所有主流安卓设备的方案
+思路很快确定了,就是做一个 HTTP 服务,给个 /ping 接口,客户端这边去发 GET 请求然后确认回包 200 即可。但是,问题就在于,如何能够在所有主流安卓设备上发 HTTP 请求然后判断结果呢?
+最初,我想到了 curl,然后发现不是所有的设备都有 curl;于是转而投奔 nc,结果发现 nc 也不是所有的设备都有;其他的命令其实基本就也不想再试了,因为就算当前手头上的设备都能支持,谁也说不好会不会新来的机器就不支持了…
这时候都感觉仿佛只能用最最丑陋的下策了 —— 装一个 apk 包然后用 adb 拉起来然后走 socket 通信去执行 HTTP 请求。但是这样的方案,在自动化工程里面几乎是不可接受的,比如,光是自动化安装包这个环节就有各种坑可以踩,就算手动装也有不小的工作量;同时,call 起 apk 的过程也可能影响前台的 app。总之,这个方案一定是利大于弊的,因此也就没有第一时间去做尝试,而是继续去寻找可行的方案
+经过同事点拨,突然想到,平常自动化测试用到的 openatx/uiautomator 这个包,它本质上就是个 python 包装的反向代理,使得电脑端通过 python API 和手机侧的 server 通信,再把请求转换成 uiautomator 的指令加以执行。那么既然 openatx/uiautomator 是这样的 server-client 架构,那它肯定在手机上也跑了个 server 服务吧,这个服务之前有注意到就是一个 u2.jar 文件,所以应该意味着,我应该也可以写一个 Java 程序然后想办法在安卓端跑起来
然后就开始了后文中试图让 Java 能在 adb shell 中跑起来的各种尝试…
+最初的思路比较直接,安卓可以说是基于 Java 的,早期的运行时虚拟机 dalvikvm 即便到了 ART 时代仍然被系统所兼容,并且 dalvikvm 命令也仍然存在于所有安卓设备上,于是便试图通过 dalvikvm -cp <dex file> com.your.package.Class 来执行代码
在 demo 验证阶段,一个简单的 Hello World 程序是可以运行的,并且所有的设备都能够正确的执行,这基本证明了两点:
+但是很快,在 HTTP 请求的代码验证中,部分设备就出现了各种各样的错误:
+java.lang.UnsatisfiedLinkError 并且直接无法执行java.lang.ClassNotFoundException 异常但是不影响执行其中,第二种异常提供了一些有用的信息:
+1 | java.lang.reflect.InvocationTargetException |
它指出,即便我在上层使用的是 java.net.URL.openConnection,下层应用的实现还是变成了 com.android.okhttp.OkUrlFactory 这种设备相关的代码,这应该就是导致在不同设备上会有不通执行结果的原因
进一步猜测,UnsatisfiedLinkError 指出部分动态链接库没有被正确地加载,考虑到对于一个普通程序而言,在启动时安卓运行时肯定会进行一些初始化操作,其中肯定有一部分链接库会在这个阶段由运行时去完成链接,而 dalvikvm 作为直接与虚拟机交互的指令,很有可能就是缺少了这样一些准备操作导致一些相关的运行时库没有被正确链接上,进而导致了 UnsatisfiedLinkError 甚至是 Segmentation Fault
依据这样的猜测,dalvikvm 应当是行不通了,势必要找到一个与安卓运行时挂钩的东西去执行 .dex 才能够避免同样的问题
目光再次转向 atx/uiautomator 工程,在 readme 中读到了 uiautomator 1.0 时代能够通过 uiautomator <jar> ... [-c class] 的方式来执行 .jar 文件中自动化测试的代码,遂猜测其也是使用这样的方式启动的 u2.jar 文件。(其实这里犯了一个很大的错误让我与真相擦肩而过,进而浪费了许多时间在这一步上,这一点将在 经验与教训 一节详述)
网上能找到的文档大致描绘了一个这样的步骤:
+android.jar 和 uiautomator.jar 依赖android create uitest-project -n <name> -t <target version that match android.jar and uiautomator.jar> -p <path to project> 创建 xmlant 构建 jar 包uiautomator <jar> ... [-c class] 命令启动很遗憾的是,当前 Android SDK 中的 android 命令早已不包含 create uitest-project 这一条指令了,并且官方早就删除了与 uiautomator 1.0 相关的文档。同时,现有的各种打包文档也都含糊不清,IDE 也还是 Eclipse,想要在当前的环境成功打包出来可以说是难上加难了。我尝试下载了旧版的 SDK、ant 以及 Java JDK8 来尝试完成这个工作,总是在各种环节出现不兼容的问题,遂最终也作罢。事后复盘想想估计就算真的成功编译了,估计是否兼容如今的安卓版本也是个大问题,并且要是别人想要复现一次这个过程也要遭受这样的苦难,实在不是一个值得尝试的思路
最终,是在一篇关于 scrcpy 源码解读 的博客中找到了一段关键的内容:
+++上面我们提到
+scrcpy-server.jar是通过app_process运行起来的,那么app_process又是什么鬼?app_process是启动 zygote 和其他 Java 程序的应用程序,它可以让虚拟机从main()方法开始执行一个 Java 程序。具体的用法可以参考app_process源码中的注释:+ +
1 "Usage: app_process [java-options] cmd-dir start-class-name [options]\n"与 APP 进程不同,通过
+app_process启动的进程可以在 root 权限和 shell 权限 (adb 默认) 下启动,也就分别拥有了调用不同 API 的能力。通常情况下 shell 权限启动的app_process只能够调用一些能够完成adb本身工作的 API,root 权限启动的 app_process 进程则拥有更多权限,甚至能够调用系统 signature 保护级别的 API 及访问整个文件系统。实际上不少
+adb命令都是对调用app_process进行了一些封装,这里举我们平时常用的am指令为例,我们在执行adb shell am的时候其实是执行了以下的脚本。通过app_process运行am.jar中com.android.commands.am.Am这个类的main()函数来完成具体操作。+ +
1
2
3
4
5
6
7
8
if [ "$1" != "instrument" ] ; then
cmd activity "$@"
else
base=/system
export CLASSPATH=$base/framework/am.jar
exec app_process $base/bin com.android.commands.am.Am "$@"
fi这里需要注意的是
+app_process只能运行原始 dex 文件,也可以接收包含classes.dex的jar包,如am.jar。这里 scrcpy 取巧直接用了编译apk的方式,再直接重命名为jar包,这样也可以被app_process运行。而且可以使用 gradle 方便的进行编译,直接在 Android Studio 中开发,调用原生 API,像源码中的IRotationWatcher.aidl也可以直接生成相应的IPC Proxy类进行调用,省力又省心。+
scrcpy-server.jar在adb下通过app_process运行起来后,默认就有了 shell 权限,像一些截屏、录屏、模拟按键点击等功能都可以直接使用,而且完全不需要任何权限的声明。
也就是说,app_process 就是我最开始想要找的,带完整运行时版本的 dalvikvm
在引用的文章中,也提到了 scrcpy 使用的一种打包方法:
+app/src/main/java 下) 以及添加依赖app-debug.apk).apk 后缀改为 .jar进一步分析,其实本质上就是借用了 Android Studio 将 Java 代码打包为 dex 文件的能力,真正使用的还是 apk 文件中的 classes*.dex 文件,通过以 .zip 打开构建的 apk 并删除其他无关文件再测试可以证实这一点。而由于 dex 本质上还是在给 Java 解释器提供 classpath,只是可能不同于 jar 文件的组织方式,因此理论上对于其他方式打包的 dex 文件,app_process 应该也是支持的
这里想到前面用到的 d8.bat 工具,其就是将 .jar 文件转换为 .dex 文件,遂进行测试,证实了另一种打包的思路:
<homedir>\AppData\Local\Android\Sdk),找到 cmdline-tools\latest\bin\d8.bat (这里可以将 <homedir>\AppData\Local\Android\Sdkcmdline-tools\latest\bin 添加到环境变量中方便后面调用 d8 命令),如果没有,可以在 Android Studio 更新 Commandline Tools 或是在 build-tools\<version> 下找到一个能用的d8 [options] <source jar> 命令编译出 .dex 文件,例如 d8 server.jar,会在当前工作目录下生成 classes.dex经过测试,其中第 4 点是必须的,原因在引用的文章中也有提到:这里需要注意的是 app_process 只能运行原始 dex 文件,也可以接收包含 classes.dex 的 jar 包,也就是说对于 app_process,.jar 可能只是被作为一个文件夹看待而不是作为实际的 classpath 看待,实际作为 classpath 的是提供的 .dex 或是 .jar 中的 .dex
由此可以看出,理论上只要能打包出带完整依赖的 jar 包,辅以 d8 的转换,就可以用来作为 app_process 的 classpath 了,只是使用 Android Studio 方案的明显优势就是对于 Android API 的天然支持和对于依赖管理和导出的便捷性吧,这里可以根据具体的需求和场景来选择
在有了 .jar 或是 .dex 后,参考 app_process 的启动参数:
1 | app_process [java-options] cmd-dir [--application | --zygote [--start-system-server]] start-class-name [options] |
即对于打包好的 server.jar 文件,执行命令可能为 adb shell app_process -Djava.class.path=/data/local/tmp/server.jar / com.linloir.server.Main --port 23456
atx/uiautomator2 的实现的时候,先入为主地就认为作者使用了 uiautomator <jar> 这种方式调用,后面注意力都放在了查找作者针对 u2.jar 的打包方式和相关代码上了,以至于在仓库里搜索 u2.jar 的时候竟然没有注意到在搜索结果中 core.py 赫然有着 command = "CLASSPATH=/data/local/tmp/u2.jar app_process / com.wetest.uia2.Main" 这一启动方式,与答案擦肩而过并且进一步在寻找 uiautomator 1.0 的打包方式上浪费了大半天的时间