LibXZR 的小本本 - 技术向/整活
type: channel
link: https://blog.xzr.moe/category/tech_playing/
items: 用最复杂的方式为 WSL2 配置静态 IP
items: 借助 uprobe 实现对应用帧生成时间的监听
items: 一种代替 pstore 的 panic 日志转储方案
items: 在本地生成禁用校验的 vbmeta 镜像
items: 小记 AOSP 自动亮度框架与算法
items: 把虚拟机变成一个网络磁盘以实现文件互通
items: 来造一条i7-9750h的性能—功耗曲线？
items: 来写个抓linux版本更新的telegram bot？
items: 记一下这几天关于QTI EAS的一点研究
items: 解决一加sm8250平台数据流量下难以休眠

用最复杂的方式为 WSL2 配置静态 IP
type: item
date: 2023-04-14T16:23:00+00:00
description: 前言此方法已经过气，请参阅 WSL2 2.0 更新。网上为 WSL2 配置静态 IP 的方案有非常多，其中比较方便的一是其自带的桥接，二是这里的提前创建网络法。然而，桥接实在是不太适合外部网络经常变化的环境（比如笔记本），而提前创建网络的方法则显得有些太过于 hacky 了：ICS 仿佛是 Windows 不愿暴露在外的“隐藏功能”，而且我也完全不知道他那网络的 GUID 是哪里来的，会不会存在向后兼容的问题。所以，有没有什么相对“正常”一点的方法，能够为 WSL2 创建一个类似 VMware NAT 模式的网络，既做到固定网段，又做到不受外部网络环境影响呢？这篇文章研究的就是这个问题。步骤先说步骤，最后再慢慢解释。首先，打开设备管理器，选中你的电脑名，点击上方的操作，选择“添加过时硬件”：选择手动安装：选择网络适配器：厂商选择 Microsoft ，找到其下的“Microsoft KM-TEST 环回适配器”：完成后，你应该就可以在“网络连接”中看到它了，可以随便改个名字啥的（无所谓）：接下来，打开 Hyper-V 的虚拟交换机管理器，创建一个外部网络，将其桥接到刚刚创建的环回适配器上：完成上一步后，“网络连接”中应该会出现一个新的名为“vEthernet(<你的外部网络名>)”的网络适配器，点开它的属性，为它设置一个 IPv4 地址，这个地址就是以后的宿主机 IP 了：接下来，需要下载 DHCP Server ，这是一个不需要安装（甚至不需要管理员权限）的 portable 软件。这个软件分为两个部分，一个是 dhcpwiz.exe 用于创建配置文件，另一个是 dhcpsrv.exe 用于提供 DHCP 服务。将下载的压缩包解压到某个位置后双击 dhcpwiz.exe 启动“配置器”。启动后，选择刚刚创建的外部网络，点击下一页：这一页无需配置，可以直接跳过：在这一页中，你可以配置 IP 池、租期等信息，别忘了点开 Advanced ，并在其中配置 DNS 和网关地址，网关地址需要被配置为宿主机 IP，同样的， IP 池也得和宿主机 IP 在同一网段 ：↑ 对于上面这一步，如果你只是想要固定 IP 段，像我这样配置即可。但是如果你想要固定 WSL2 的 IP 地址，那么需要将 IP 池进行收缩，收缩到只有一个地址时，WSL2 的 IP 也就固定下来了 xD。在这一页中，将对刚刚设置的内容进行保存。如果你之前没有配置过，直接点击 Write INI file 即可，如果之前配置过，则需要勾选 Overwrite existing file 来确保配置能够成功覆盖：接下来这一页，因为我不想给管理员权限，于是什么都不干，直接点击完成：↑ 至于开机启动，接下来自有办法。那么配置过程就到此结束了，接下来点开 dhcpsrv.exe 来在我们的网络上启动 DHCP：↑ 勾选 Don't show this window next time ，然后点击 Continue as tray app 让其最小化到托盘，此时 DHCP 服务已经在后台默默运行了。接下来我们在不给管理员权限、不安装服务的前提下，为它配置一个超干净的开机启动。打开“运行”，输入 shell:startup ，此时资源管理器会弹出 “启动” 窗口，我们只需要在其中为 dhcpsrv.exe 创建一个快捷方式即可。此时，网络本身算是配置好了，那就打开 .wslconfig 把 WSL2 接入进去吧：C:\Users\<用户名>\.wslconfig[wsl2] networkingMode=bridged vmSwitch=vNAT #这个要对应到上面在 Hyper-V 中创建的外部网络名称好了，现在 wsl --shutdown 然后重新打开 WSL2 ，看一看自己的 IP 地址，诶，是不是在目标网段了？甚至还就是右下角弹出的 DHCP Server 分配的那个：现在，宿主机和 WSL2 之间已经可以正常通信了，但是，WSL2 还不能访问外网，这是因为现在的网关是宿主机，而宿主机却并没有转发这些数据包的能力。接下来，我们要为宿主机配置这种能力，上 NAT ！这一步，很遗憾的，我没有找到可以用的 GUI ，只能上 Powershell 了，记得给管理员权限：New-NetNat -Name NAT -InternalIPInterfaceAddressPrefix 192.168.114.0/24这里指定的内部网段要与上面分配的网段相对应。好了，现在在 WSL2 中 ping 一下，网已经通了：如果它没有通，那么你就得检查一下防火墙了，比如我用的卡巴斯基就得把这个虚拟网络设置为“本地网络”或者“受信任网络”才能正常使用 NAT 。完成了，我们得到了一个类似 VMware …

借助 uprobe 实现对应用帧生成时间的监听
type: item
date: 2023-03-28T08:56:00+00:00
description: English前言前段时间有个概念很火，它被称为 FAS (Frame time Aware Scheduling) ，即根据帧生成时间而非处理器负载来调控处理器频率。这种操作能够在不影响帧率的前提下尽可能的降低处理器频率，从而改善游戏的能效。不少手机厂商也采取了类似的思路来改善游戏功耗，如小米的 FEAS 。接下来，我想要明确一下接下来会用到的概念。首先是“帧时间”和“帧生成时间”，这两者看着类似但实际上是完全不同的。“帧时间”是两次显示内容改变之间的间隔，“帧生成时间”则是生成这一帧花费的时间。以上面手搓的这张图为例，这张图描述了简化的帧渲染——显示流程(1)。上图中，横向为时间轴，绿色竖线代表在该时刻发生了 vsync 信号，红色部分代表新的帧正在生成。红色标号代表第 n 帧正在生成，蓝色标号代表第 n 帧正显示在屏幕上。在 vsync 信号发生时，之前已经生成完毕的帧会被显示在屏幕上，而新一帧的生成也会开始。我们来详细解释一下上面这张图：首先，第 1 帧开始生成，这一帧的生成很快就完成了，但是必须等到 vsync 信号到来时才会被显示在屏幕上，于是它凭空等待了一会儿。vsync 信号到来，第 1 帧被显示在了屏幕上，此时第 2 帧的生成开始。第 2 帧的生成也很快就完成了，但必须继续等待下一个 vsync 信号。vsync 信号到来，第 2 帧被显示在了屏幕上，此时第 3 帧的生成开始。第 3 帧的生成看起来有点慢。vsync 信号到来，但是此时第 3 帧的生成还没有完成，于是屏幕显示的内容没有改变，第 2 帧继续显示在屏幕上。此时，我们便可以认为发生了丢帧。vsync 信号到来，这下第 3 帧已经生成好了，于是它被显示在了屏幕上，同时第 4 帧的生成开始了。通过上面的这些解释，我想你应该能够更好的区分“帧时间”和“帧生成时间”了。在上面的图中，被蓝色括号包住的部分即为第 n 帧的“帧时间”，被红色括号包住的部分即为第 n 帧的“帧生成时间”。可以看到，“帧生成时间”能够直接反应当前性能，假如某一帧的“帧生成时间”超过了 vsync 间隔（即屏幕刷新间隔），丢帧势必发生。在丢帧已经开始发生时，“帧时间”也在一定程度上能够反应性能。对于上面所说的 FAS ，最佳的实践方式是：使“帧生成时间”尽量从左侧逼近（小于等于）“屏幕刷新间隔”。但是，实际上，这并不是一件容易做到的事情，原因在于“帧生成时间”的数据实在是有些难以获取。一般而言，想要获取“帧生成时间”，是要对应用进程下手的，毕竟帧的渲染流程主要发生在对应的应用进程中(2)。“帧时间”则可以直接从 surfaceflinger 中获取，相对来说就会方便许多。于是，现在常见的 FAS 实现，如 Scene FAS ，采用的都是使用“帧时间”来替代“帧生成时间”的方案，比如控制处理器频率使得“帧时间”尽量稳定。对于游戏类应用，这种方式其实就已经能够起到不错的效果了，因为游戏画面的帧率往往是稳定且连续的，但是对于日常应用，这可是要翻车的。日常应用中，我们可能会遇到许多画面完全静止的情况，比如在阅读时。在这种情况下，是不存在帧的刷新的(3)，那么此时的“帧时间”将会变得非常大，但是，这能够说明此时因为性能不足而丢帧吗？并不能，因为此时是压根没有新的帧，而不是因为性能不足而丢帧。“帧时间”存在的这种缺陷使得 FAS 思想迟迟难以被使用到日常应用中。这篇文章所展示的内容将会帮助解决这一困境，但也只能解决基于 libhwui 的普通 Android 应用的“帧生成时间”获取问题，对于采用自己渲染引擎的游戏类应用，到目前为止，使用“帧时间”代替“帧生成时间”仍然是最好的妥协方案。注释：(1) 没错，这真的简化了很多东西，但拿来帮助理解我觉得完全没有问题。在真实情况下，由于 GPU 的异步性，还存在三重缓冲的问题，而且 vsync-sf 和 vsync-app 信号发生的时间也不一定是同步的，这些信号的发生时间甚至可能不是固定的，详见 (3) 。(2) 我曾经想过在 surfaceflinger 中把分发 vsync 信号到应用 queuebuffer() 之间的间隔作为“帧生成时间”，但是没有成功，因为这对于在内核中操作来说太不友好了，需要大量的逆向和面向寄存器编程工作。但是，对于第三方 ROM ，这也许是一个可行的思路。不过鉴于应用在收到 vsync 后不一定会更新帧，那就还得弄清楚这个 queuebuffer() 属于哪一次 vsync 信号，还是有些难度👀。(3) surfaceflinger 自身使用的（vsync-sf）和向应用程序分发的（vsync-app） vsync 信号并不直接来自于 hwcomposer 。surfaceflinger 自身维护了一个软件 vsync…

一种代替 pstore 的 panic 日志转储方案
type: item
date: 2023-03-26T07:56:00+00:00
description: 前言我手里有两台一加 8250 平台的机子，一台小 8 ，一台 8T 。照理说这两台机子的硬件上的区别应该不大，但是这台 8T 从出厂系统开始，我就没有成功获得过一次 pstore ，warm reset 之后开机看 pmic 的日志还是 cold boot，非常的诡异。相反，这台小 8 的 pstore 则一直工作正常。我甚至非常怀疑这台 8T 的 pmic 固件是不是有什么猫病。无奈，小 8 是主力机，没法天天拿来调试。于是糊了个在 panic 时转储日志到本地文件 / 指定分区的方案，也算是对 pstore 的一个小替代吧。源码https://github.com/libxzr/kernel-playground/commits/playground/logstore是基于高通 4.19 平台的，别的平台可能需要移植一下。细节单纯转储日志还是很简单的，利用 kmsg_dump 相关的 api 把日志从 buffer 里拿出来，再转储到文件里即可。这里的关键点是在于，该在何时转储日志。panic 的流程大概可以概括为以下几个阶段：进入 panic() 函数。在本 CPU 上禁用中断与抢占。打印 panic 信息与（本 CPU 的）调用栈。关闭其它 CPU。调用各个模块注册的 panic notifier。调用各个模块注册的 kmsg dumper。准备重启 / halt 住。在越是后面的阶段转储日志，得到的日志越是详细。比如在回调各个模块的 panic notifier 和 kmsg dumper 时，它们就很有可能往日志中增加额外的内容。我们所熟悉的 pstore 就是注册在 kmsg dumper 中的。但是，这一套流程中有一个对转储影响巨大的因素，那就是“关闭其它 CPU”（smp_send_stop()）。这里的关闭可不是温柔的 cpu_down()，而是直接发送 IPI 强行下线其它的处理器（ARM64）。要知道，对于正常的热插拔流程，在关闭 CPU 之前至少要把运行在它上面的任务迁移走，还要通知中断控制器不再给这个 CPU 分发中断。但是，panic 时真的是什么都没做就把 CPU 强行下线了，所有上面没有来得及迁移的东西都直接被毁灭了。这一操作其实是导致 panic 时设备直接 freeze 住的直接原因——别的 CPU 啥也不能执行，当前 CPU 被独占只能执行 panic 代码，自然而然设备看起来就像死机了一样。对于 pstore ，这一操作影响不大，反正它只需要写入指定的内存区域罢了。但是，对于我们这种正儿八经的日志转储，这种操作可能就是毁灭性的。就不说 vfs 了，光光是最底层的存储器驱动就可能依赖于一堆中断和 kthread / queue_work ，这些原本可能跑在别的 CPU 上的东西都被摧毁了，它该怎么转储日志？所以呐，想要和 pstore 在同一阶段转储日志已经变得非常不靠谱。对于我们这种非常复杂的转储方式来说，最好的选择就是在 down 掉别的 CPU 之前。此时我们虽然不能得到完整的 panic 日志（比如别的 CPU 的调用栈），但是对于得到 panic 的原因以及本 CPU 的调用栈已经非常足够了。此时我们得到的一个典型日志是长这样的：[ 19.122168] Unable to handle kernel NULL pointer dereference at virtual address 0000000000000000 [ 19.122175] Mem abort info: [ 19.122177] ESR = 0x96000046 [ 19.122179] Exception class = DABT (current EL), IL = 32 bits [ 19.122181] SET = 0, FnV = 0 [ 19.122182] EA = 0, S1PTW = 0 [ 19.122183] Data abort info: [ 19.122185] ISV = 0, ISS = 0x00000046 [ 19.122187] CM = 0, WnR = 1 [ 19.122189] user pgtable: 4k pages, 39-bit VAs, pgdp = 000000003746e1fd [ 19.122191] [0000000000000000] pgd=000000015722b003, pud=000000015722b003, pmd=0000000000000000 [ 19.122195] Internal error: Oops: 96000046 [#1] PREEMPT SMP [ 19.122198] Process Scre…

在本地生成禁用校验的 vbmeta 镜像
type: item
date: 2022-11-24T07:55:31+00:00
description: 前言vbmeta 分区是 Android AVB 机制的重要组成部分，其中包含了设备上部分分区的校验信息。有时候出于调试需要，我们需要修改设备上一些分区的内容，此时为了能使设备顺利启动，我们还需要额外禁用 vbmeta 分区中的校验。众所周知，我们可以通过执行 fastboot --disable-verity --disable-verification flash vbmeta vbmeta.img 来刷入一个 vbmeta 镜像，并同时禁用这个镜像中的校验。但是，fastboot 并不会修改原镜像，也没有提供导出这个禁用了校验的镜像的能力。那么有没有什么办法能够从原镜像获得禁用了校验的镜像呢？调查首先，可以看看 fastboot 是怎么做的：system/core/fastboot/fastboot.cppstatic void rewrite_vbmeta_buffer(struct fastboot_buffer* buf, bool vbmeta_in_boot) { // Buffer needs to be at least the size of the VBMeta struct which // is 256 bytes. if (buf->sz < 256) { return; } std::string data; if (!android::base::ReadFdToString(buf->fd, &data)) { die("Failed reading from vbmeta"); } uint64_t vbmeta_offset = 0; if (vbmeta_in_boot) { // Tries to locate top-level vbmeta from boot.img footer. uint64_t footer_offset = buf->sz - AVB_FOOTER_SIZE; if (0 != data.compare(footer_offset, AVB_FOOTER_MAGIC_LEN, AVB_FOOTER_MAGIC)) { die("Failed to find AVB_FOOTER at offset: %" PRId64, footer_offset); } const AvbFooter* footer = reinterpret_cast<const AvbFooter*>(data.c_str() + footer_offset); vbmeta_offset = be64toh(footer->vbmeta_offset); } // Ensures there is AVB_MAGIC at vbmeta_offset. if (0 != data.compare(vbmeta_offset, AVB_MAGIC_LEN, AVB_MAGIC)) { die("Failed to find AVB_MAGIC at offset: %" PRId64, vbmeta_offset); } fprintf(stderr, "Rewriting vbmeta struct at offset: %" PRId64 "\n", vbmeta_offset); // There's a 32-bit big endian |flags| field at offset 120 where // bit 0 corresponds to disable-verity and bit 1 corresponds to // disable-verification. // // See external/avb/libavb/avb_vbmeta_image.h for the layout of // the VBMeta struct. uint64_t flags_offset = 123 + vbmeta_offset; if (g_disable_verity) { data[flags_offset] |= 0x01; } if (g_disable_verification) { data[flags_offset] |= 0x02; } unique_fd fd(make_temporary_fd("vbmeta rewriting")); if (!android::base::WriteStringToFd(data, fd)) { die("Failed writing to modified vbmeta"); } buf->fd = std::move(fd); lseek(buf->fd.get(), 0, SEEK_SET); …

小记 AOSP 自动亮度框架与算法
type: item
date: 2022-07-29T06:03:00+00:00
description: 前言差不多是一个月前摸了摸 AOSP 自动亮度相关的东西，这篇文章主要是对当时得到的一些结论进行整理。省流助手：自动亮度的代码框架 / 自动亮度策略 / 如何学习用户偏好 / AOSP “自适应亮度”机器学习是骗局AOSP 自动亮度的代码是一个大屎山，特别是 DisplayPowerController 中的 updatePowerState() 部分，看着就让人犯恶心，因此本文主要注重于描述“某一部分代码大概干了什么事情”，而不会深入屎山去讨论“这个 temporary 变量是干嘛用的？这个 pending 又是怎么更新的？”本文基于 Android 12L 讨论。本文仅针对 AOSP，被任何 OEM 修改过的 ROM 甚至是谷歌 Pixel 的自带系统，都有可能具有与文章描述不符合的行为。把握代码框架面对屎山，我认为找到其出入口是最为重要的，顺着出入口能弄清楚不少东西，至于中间那些糊不清楚的屎就算了，只需要知道它们能跌跌撞撞的正常运行就够了。对于自动亮度，它的入口有好几个：光线传感器的读数、配置文件的读取 以及 用户的输入。而它的出口就只有一个，那就是改变屏幕亮度。传感器读数从何输入？services/core/java/com/android/server/display/AutomaticBrightnessController.java private final SensorEventListener mLightSensorListener = new SensorEventListener() { @Override public void onSensorChanged(SensorEvent event) { if (mLightSensorEnabled) { final long time = SystemClock.uptimeMillis(); final float lux = event.values[0]; handleLightSensorEvent(time, lux); } } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { // Not used. } };顺藤摸瓜顺着摸，我们能够了解到 AutomaticBrightnessController 大概干了些什么事情。 private void handleLightSensorEvent(long time, float lux) { ...... if (mAmbientLightRingBuffer.size() == 0) { ...... } ...... updateAmbientLux(time); }看起来它维护了一个环形缓冲区，这很好理解，毕竟采集到的数据肯定不能直接使用，至少也得消抖啥的吧。接着往下摸。 private void updateAmbientLux(long time) { // If the light sensor was just turned on then immediately update our initial // estimate of the current ambient light level. if (!mAmbientLuxValid) { ...... updateAutoBrightness(true /* sendUpdate */, false /* isManuallySet */); } ...... // Essentially, we calculate both a slow ambient lux, to ensure there's a true long-term // change in lighting conditions, and a fast ambient lux to determine what the new // brightness situation is since the slow lux can be quite slow to converge. // // Note that both values need to be checked for sufficient change before updating the // proposed ambient light value since the slow value might be sufficiently far enough away // from the fast value to cause a recalculation while its actual…

把虚拟机变成一个网络磁盘以实现文件互通
type: item
date: 2021-09-18T09:54:00+00:00
description: 前言曾经看到有人炫耀wsl的高度集成性——竟然能够像打开硬盘一样在Windows上打开虚拟机的文件？那可不爽死？但是wsl的性能实在是太屑了，于是，现在咱就拿Vmware也整一个本文基于Vmware + Ubuntu 20.04实现，别的组合能在原理上走通应该都一样打通网络这一步是可选的，你有千百种方法使宿主机能够连接上虚拟机，只要有一种能用就可以跳过这步而且，VMware提供的NAT本身应该是能够正确配置的，只有在遇到连接问题时才需要下方操作默认情况下，宿主机地址为192.168.*.1 （可在任务管理器查看VMnet8）（*是随机数）虚拟机地址为192.168.*.* （可通过ifconfig命令查看）只需将虚拟机网络指定为NAT模式似乎就行，甚至不需要特定虚拟网络下面介绍的是我自己使用的操作唠打通虚拟机和宿主机的网络还是挺有用的，可以实现如 虚拟机利用宿主机的网络代理 之类的骚操作要想实现不依赖外部（比如路由器）进行网络互通，可以使用Vmware提供的NAT机制配置NATVmware提供了一套虚拟网络适配器，只需要进行适当的配置，使得宿主机和虚拟机跑在同一个虚拟网的同一个子网上，那相互之间的连接肯定不是问题想要完成这配置，首先打开虚拟网络编辑器然后点击主窗口中的更改设置以使用管理员权限显示所有配置信息默认情况下，Vmware应该本身就分配了一个VMnet8作为NAT模式的虚拟网 （如果没有，就新建一个）你可以勾选使用本地DHCP服务将IP地址分配给虚拟机，如果你懒得为虚拟机配置静态IP的话（对于我这种VMnet8下只有一台虚拟机的来说，DHCP约等于静态ip，反正我就没看它变过。。）你必须勾选将主机虚拟适配器连接到此网络然后，点开NAT设置记一下网关的IP，因为配置宿主机的时候需要用到（宿主机如果不指定静态地址，经常和网关重IP，或者飘到别的子网去就离谱）网关似乎默认给的是.2，但是无所谓，如果看着不爽可以改成.1配置宿主机静态IP宿主机的静态IP，不配置似乎也行，配置起来最好，能够避免很多奇奇怪怪的问题我遇到的问题不限于，宿主机漂移到别的子网宿主机把网关IP占了导致虚拟机直接没网打开控制面板\网络和 Internet\网络连接对着VMNet8右键，属性，Internet协议版本4（TCP/IPv4）为宿主机指定一个ip并把刚刚记录的网关地址填进去，DNS自己看吧，我这里用的是阿里的一路确认退出，然后可以去任务管理器里看看宿主机在VMnet8中的地址，如果定住了，那就ok配置虚拟机网络右键虚拟机，设置，网络适配器，改成上面的那个虚拟网（VMnet8）确认即可，接下来就可以启动虚拟机，使用ifconfig命令查看ip地址，也可以自己配置一下静态ip可能的坑Windows快速启动只要Windows是快速启动的，我的VMnet必炸，两端都通不了，非常离谱。可以在控制面板\硬件和声音\电源选项\系统设置关闭配置SambaSamba是一个SMB协议的实现，允许 Windows 客户访问 Linux 系统上的目录、打印机和文件安装sudo apt install samba基本配置配置文件位于/etc/samba/smb.conf找到配置文件的Share Definitions部分，这一行下面的内容就是共享配置啦先说一下共享的结构，[]包含的内容是共享的名称，比如题图的Ubuntu它默认提供了homes的共享，可以取消注释直接用，但是，我不建议，因为，似乎存在一些问题连接后显示的homes无法打开，不显示鉴权有了凭据之后才会显示用户目录，但是凭据在哪里输入呢？也许需要手动在地址里追加一个用户名称？因此，我建议自己定义一个共享[Ubuntu] comment = libxzr-virtual-machine path = /home/libxzr browseable = yes read only = no valid users = libxzr可以直接放在conf的末尾然后，新建一个Samba用户sudo smbpasswd -a 用户名 这个用户名必须是linux下存在的用户，比如libxzr嗯，用户名也要和上面的valid users对应之后重复两次密码，账户就建立好了，可以尝试在Windows资源管理器里输入\\虚拟机ip进行连接规范化权限众所周知，在Ubuntu下新建文件夹的权限默认是775，而新建文件的默认权限是664，我们可以往配置中加入两行使得我们远程创建的东西也具有相同的权限（强迫症） directory mask = 0775 create mask = 0664规范化权限组假如你新增了一个用户，那么那个用户就可能在你的目录下创建不属于你的文件，这很令人不爽，可以通过强制覆盖用户/用户组来进行规范 force …

来造一条i7-9750h的性能—功耗曲线？
type: item
date: 2021-05-17T16:31:00+00:00
description: 前言曾经看到过有人测试了4800U的性能—功耗曲线，u1s1我确实有被震惊到，台积电7nm + zen2架构的加持下，其低频能效极为出色，身为低压处理器的4800U只需15w功耗就可以在性能上轻松秒杀牙膏厂的标压旗舰——45w的10875H，而在超过甜点频率后，4800U的能效骤降，在功耗大幅增加的同时，性能提升微乎其微。这不经让我好奇，我手里的这颗手动降压打磨版的过气9750H的性能—功耗曲线会长啥样，甜点频率又在何处？测试测试很简单呐，使用throttlestop锁定功耗，然后使用Cinebench R15跑分，记录频率与分数，三次取最高值。结果如图由于我这颗U只能做到全核4GHz，此时降压后功耗大约为60w，所以只能测到60w了。。。进行了一些简单的计算，包括能效，ipc和处理单位任务消耗的能量（使用1/分数 * 功耗进行估算）嗯，这颗过气处理器似乎并没有出现那种常常在手机处理器上出现的能效先增后降的情况，也就是说，可能无法找到一个所谓的最佳能效频率（因为频率越低能效越高...）不过没有关系，还可以拟合一下数据，看看这颗处理器的性能—功耗曲线大致是长什么样的拟合的时候为了让图像看起来更美观，我又额外测了一组空载，大约是(0, 2.5w) (真是辣鸡到爆了)数据拟合数据拟合使用Pytorch，标准的梯度下降和平方损失函数，当然，模型需要自己设计一下class Net(nn.Module): def __init__(self): super().__init__() self.k = Parameter(torch.tensor(0.0)) self.n = Parameter(torch.tensor(0.0)) self.b = Parameter(torch.tensor(0.0)) def forward(self, x): return self.k * (self.n ** x) + self.b大概长这样。思路很简单，众所周知，cpu的频率和功耗的关系通常成指数，而同频性能几乎是不变的，因此性能与功耗的关系显然也是一个指数啦~因此，我打算使用指数函数来进行拟合，再配上k和b进行放缩与平移然而，上面这样的模型是有大问题的，接下来就是踩坑时间坑一 底数拟合不动真的，指数函数很少拿来拟合，我从来没注意到这玩意儿在数学上的特殊性训练模型时遇到的第一个问题就是，底数n训练不动，训练全程似乎只有b在动，最终造成曲线极为奇怪而且损失爆表这个曲线就离谱经过一系列的探索，嗯，是这玩意儿在数学上具有一定的特殊性：底数的范围必须先被确定如果底数n初值被设为0，那么0的x次方始终为0，梯度根本无法反向传递，导致参数无法被更新 （现在的情况）如果底数的初值在0到1之间，那么参数的更新将永远无法越过1这条鸿沟，会造成下图的奇怪拟合因此，要解决这个问题，只需将底数n的初值设置在1以上即可（比如1.5），当然，取一个好点的值能够减少所需训练次数。。坑二 损失都显示不出来了又是一个很离谱的问题，在修改了底数初值之后，连b都没法训练了。。。通常，nan代表函数未能收敛，这种情况只需要通过调整到更小的学习率就可以解决，但是，似乎我怎么调都没有用。。嗯，这是一个由于没有进行特征缩放导致的问题。。我们的输入数据可都是上千的，底数是要训练到多接近于1才能拟合住啊，这都超出浮点的精度范围了吧，自然无法收敛。。解决办法很简单，在模型里面或者外面把输入数据砍一砍就是了比如把性能统一除以60，功耗统一除以10，这样两者的值都会在5左右，拟合起来自然也没啥压力了(self.k * (self.n ** (x / 300)) + self.b) * 10坑三 学习率的选择指数函数就是不一般呐，过大的学习率会导致损失来回震荡，甚至是收敛到一个大的离谱的值，也许指数函数的损失函数关于参数并非只有一个极小值？解决办法：多试试🤣最后选择使用5 * 1e-6的学习率训练200000次完成了！总算是能正常训练出来了。。#!python import torch from torch import nn, optim from torch.autograd import Variable import numpy as np import matplotlib.pyplot as plt from torch.nn.parameter import Parameter from matplotlib.font_manager import FontProperties x_train = torch.tensor([0.0, 365.0, 693.0, 801.0, 897.0, 1024.0, 1111.0, 1235.0]) y_train = torch.tensor([…

来写个抓linux版本更新的telegram bot？
type: item
date: 2021-04-18T12:29:00+00:00
description: 前言本来一直在用这个频道的推送来追linux版本更新（以即时更新内核来爽），但是上个月这个频道不知怎么的似乎死了，到现在整整一个月过去了，4.19的longterm已经更新了5个版本，可它还是一点动静也没有。那就自己写一个吧😋开工首先，要用什么来写，第一个想到的便是python，虽说别的东西也能写，但是一个小脚本配合丰富的第三方库，实现起来应该是相当简单的不过python平时基本没摸过，那就来面向搜索引擎编程吧（不过u1s1，这东西真的面向搜索引擎就能轻松写起来..）库这种小脚本主要需要用到两类库吧，一类是用来发请求的，另一类是用来解析的（虽说其实自己手动解析字符串也不是不行。。）发请求的库选定的是著名的，有45k star的 requests解析库则是另一个著名的 Beautiful Soup 4安装这些库，pip install requests bs4pip是可以用代理的，比如pip install xxx --proxy="http://127.0.0.1:1080"里面的http在某个pip更新之后，就变成必须的了，否则直接报错当然，在bash/cygwin环境下可以通过bash profile中增加https_proxy和http_proxy变量来全局代理正式开工开发环境：Windows + Notepad++先导包import requests as req from bs4 import BeautifulSouppy有一种特别的from import语句from后面的东西是包名（文件夹+文件），而import后面的东西则是函数/类as则是取个昵称，取完昵称之后原来的名字就不认了有时候也可以用import xxx.*来把函数/类导进来，但是据说这是要靠对应包的内置配置指定？然后看看要爬的是啥目标：kernel.org真正的目标： 说人话就是release这个table下面的每一行的第二列程序要做的事情大概就是，先找到release这个table，然后在里面遍历每一行，把每一行的第二列放入一个数组（列表）然后返回总的来说，这个页面的结构还是相当鲜明的（好评）发送请求https://docs.python-requests.org/en/master/api/#requests.getres = req.get("https://kernel.org")（这里我在import的时候把requests库重命名为req了）嗯，一行就搞定了，返回的是一个requests.Response对象，我将其命名为res解析请求刚刚拿到了requests.Response的一个对象，这个对象可以有许多的操作https://docs.python-requests.org/en/master/api/#requests.Response此处只需要使用其text属性，拿出返回的正文信息然后用它来构造一个BeautifulSoup对象bs = BeautifulSoup(res.text)嗯，其实这样就可以了，但是会抛出一个warning,此时只需要按照提示再加上一个参数即可（这种是叫 关键字参数？ 不过去掉那个features=也是没有任何问题的）bs = BeautifulSoup(res.text,features="html.parser")之后，就可以跟着官方文档开干了https://www.crummy.com/software/BeautifulSoup/bs4/doc.zh/按照上面所说的思路，首先找到那个release的table 根据文档，我们可以使用find方法，并指定id该方法返回符合搜索条件的第一个东西，由于我们要找的东西只有一个，因此不需要使用能返回所有结果的find_all方法（find_all返回一个包含所有结果的list迭代器（list_iterator），当作数组用for循环遍历即可）table = bs.find("table", id="releases")之后，对这个table里面的东西进行遍历（遍历每一行）根据文档，可以使用table.children来获取其子元素列表（其实也是个list_iterator）在这之前，可以先了解一下遍历出来的东西可能有哪几种类型Tag对应html里的tag (比如div、a、h1都是)NavigableString可遍历的字符串，可以理解为字符内容，比如 aaa 里面装着的aaaBeautifulSoup整个文档，当成Tag就完事了Comment文档中的注释内容，可以视作特殊类型的NavigableString然后，我们开始遍历ret = [] table = bs.find("table", id="releases") for row in table.c…

记一下这几天关于QTI EAS的一点研究
type: item
date: 2021-02-28T11:09:00+00:00
description: 前言几天前心血来潮，给内核关掉了WALT强行切换到了PELT负载追踪，但遗憾的是高通的魔改用力过猛，以至于直接切换回PELT（并修复编译）后并不能正常进行负载分配（负载迁移到大核之后难以摊开，EAS下负载全部堆在一个核心上，直到满频过载之后fall back to 传统的调度才会把负载摊开。。。）。当然，去谷歌Pixel的内核那边掏修复补丁是个办法（谷歌不走寻常路的把高通打磨了半天的WALT换回了PELT），但是，我觉得谷歌其实，emmmm也不是这么可信吧（其实是懒得解决移植的冲突，意外情况太多）。那就顺着逻辑，4.4内核上自带的是PELT，WALT是移植的，那就打磨PELT，现在WALT变成了原生支持，PELT反而需要一堆补丁来修复，那就打磨WALT吧。其实还有一个点让我突然想好好研究一下WALT的，那就是我发现PELT下小核的负载比默认WALT下的正常的多，WALT下酷安滑屏小核负载基本都在15%-20%（PELT下在40%以上），而三个中核却有较高的负载（30%以上），这是不科学的，因为能量模型应该已经告诉了内核小核最高频的能效高于大核最低频，而且此时小核性能反而更强，这种莫名的负载迁移确实让我很想调查一下。一些节点的结论懒得写过程了，摸鱼所得，毕竟这些调度算法也不是一时半会儿能看懂的，也都只知道了个大概schedtune节点sched_boost_no_override见下方sched_boostschedtune.colocate指定一个cgroup的任务是不是相关线程组，这在负载迁移和选择核心时会起作用，下面再说sysctl节点位于/proc/sys/kernelsched_boost这个其实很久以前就研究过了，不过还是记录下吧0 -> 小核优先负载 + 相关线程组负载如果超过sched_group_upmigrate则该线程组优先中核 （详细见下）1 -> 大核优先负载2 -> 指定了sched_boost_no_override的任务中核优先负载，其他任务小核优先（相关线程组会被忽略）3 -> 0的内容 + 负载缩放使频率调度更激进？sched_busy_hysteresis_enable_cpussched_busy_hyst_nssched_coloc_busy_hysteresis_enable_cpussched_coloc_busy_hyst_ns这几个是涉及cpuidle的控制，其中hysteresis_enable_cpus是指定CPU的bitmask，hyst_ns是时间，这里的时间大概是指任务执行后暂停cpuidle的时间？默认情况下powerhal会在触摸屏幕时对sched_busy_hysteresis_enable_cpus指定所有核心，对sched_busy_hyst_ns指定一个忘了是多少的时间，手指离开屏幕时复原，这个造成的效果是在中核/大核优先负载时产生明显的jitter下降，小核优先负载时效果不明显。有没有coloc的区别是在于是否针对相关线程组，指针对所有任务还是仅在相关线程组活跃时在指定的核心上进行。sched_dynamic_ravg_window_enablesched_ravg_window_nr_ticks这两个玩意儿提供了神奇的动态控制WALT window size的功能，当内核的HZ被设为250时会默认启用。注意：当HZ不为250时，就算你打开这个功能它也不会工作，所以这节点完全多余。。它默认为90Hz刷新率使用12ms的时间窗口为120Hz刷新率使用8ms时间窗口（display那边在刷新率改变时主动调用）你也可以通过sched_ravg_window_nr_ticks设置更小的时间窗口，窗口的大小算法是1000/HZ得到最小窗口大小，最小窗口大小 * sched_ravg_window_nr_ticks得到指定窗口大小注意：你只能设置更小的时间窗口，更大的时间窗口（比刷新率指定的要大）会被直接忽略（里面有个min的比较）（所以我觉得这两个节点完全没有意义）更小的时间窗口意味着更快的负载响应速度，但是也意味着轻负载更容易被判断为重负载导致不必要的任务迁移prefer_spread_on_idle决定了是否要主动把负载迁移到空闲的核心上，迁移到空闲核心意味着从（idle状态唤醒这些核心） | （阻止刚刚结束任务的核心进入idle）状态，这可能导致更高的耗电，在某些情况下能够提升性能0 -> 否1 -> 小核 是2 -> 全核 是3 -> 小核 仅在刚刚idle时是 （意味着不刻意唤醒深度idle的核心）4 -> 全核 仅在刚刚idle时是 （意味着不刻意唤醒深度idle的核心）sched_force_lb_enable这个东西本身不属于WALT，既然看过了就写…

解决一加sm8250平台数据流量下难以休眠
type: item
date: 2021-02-23T15:01:00+00:00
description: 前言这个问题由来已久，一直搁着没看，它在官方系统+官方内核上同样发生，导致一加的865设备的数据流量下的待机续航普遍较差，idle drain高达4%-5%/h，这显然是无法接受的。今天决定了要把这个问题瞧一瞧，一瞧就是半个下午+一个晚上。问题描述数据流量下待机续航极差深度睡眠时间极少（截图来自官方内核）日志排查dmesg中存在大量类似这样的日志[ 750.094062] PM: Device alarmtimer failed to suspend: error -16 [ 750.094065] PM: Some devices failed to suspend, or early wake event detected [ 753.496178] active wakeup source: alarmtimer [ 756.421102] active wakeup source: alarmtimer [ 757.386560] active wakeup source: alarmtimer [ 760.735522] active wakeup source: alarmtimer [ 763.220499] active wakeup source: alarmtimer [ 764.955187] active wakeup source: alarmtimer [ 767.227574] active wakeup source: alarmtimer [ 769.694145] active wakeup source: alarmtimer [ 771.424980] active wakeup source: alarmtimer [ 773.686699] active wakeup source: alarmtimer [ 775.367871] pm_system_irq_wakeup: 351 triggered pm8xxx_rtc_alarm另外再附上一张来自官方内核的日志截图非常类似源码排查alarmtimer 来自 kernel/time/alarmtimer.c ，这是一个负责定时器的核心驱动，Android系统层的AlarmManager就是由它在内核层实现的这个驱动能发生问题就很神奇，首先，确定阻止唤醒的原因排查一 为什么它阻止了休眠？经过一次测试，马上锁定了问题的发生点static int alarmtimer_suspend(struct device *dev) { .............. if (ktime_to_ns(min) < 2 * NSEC_PER_SEC) { __pm_wakeup_event(ws, 2 * MSEC_PER_SEC); return -EBUSY; } .............. }错误代码-16就是在这里返回的，可以看到在这里跳出的条件是ktime_to_ns(min) < 2 * NSEC_PER_SEC，结合上下文可知此处是判断下一个定时器的发生时间如果在两秒以内，则阻止休眠（就休眠这两秒没什么用，而且可能导致问题（比如rtc时钟未即时响应导致下一次唤醒时间被设置在休眠时间之前了））我也尝试了移除这段代码，原来的难以休眠变为了诡异的休眠一点点时间（大概一秒）就唤醒，循环进行排查二 诡异的定时器哪里来？定时器创建函数 alarmtimer_enqueuestatic void alarmtimer_enqueue(struct alarm_base *base, struct alarm *alarm) { if (alarm->state & ALARMTIMER_STATE_ENQUEUED) timerqueue_del(&base->timerqueue, &alarm->node); timerqueue_add(&base->timerqueue, &alarm->node); alarm->state |= ALARMTIMER_STATE_ENQUEUED; } 我尝试在里面打印出current->comm从而显示执行这段代码的任务通过比较wifi下与数据流量下的定时器日志，我发现主要区别来自一大堆kworker/xx的进程（排除了来自用户空间的可能性，反而是松了一口气，因为一加的用户空间脏的不行 + 现在的动态分区 使得修改用户空间变得非常困难）而很明显，我无法直接追踪这些kworker到底是哪里的于是我尝试使用工具调试perf 工具我尝试使用perf record -g -a sleep 10记录了10秒内的系统活动情况然后通过perf report打印出来遗憾的是，没有什么有用的东西（主要是特征不是很明显…