• 首页
  • 加入
  • RSS
  • 欢迎来到 deepin 星球

    这里是一个订阅源聚合站点,其汇集了向 deepin 社区 进行贡献的贡献者们与 deepin 相关的博客文章

    Monday, March 18, 2024

    技术背景

    顾名思义,内存压缩就是压缩内存,节省内存空间。相信对于搞技术的人来说,压缩这个词并不陌生,一想到这个词,我们首先想到的是压缩可以降低占用空间,使同样的空间可以存放更多的东西。 内存无论多大,总是会有不够用的时候,或者说我们总是想在尽量低的成本控制下,达到系统最优,这个时候还是有必要引入诸如内存压缩的功能来优化系统内存占用。当系统内存紧张的时候,会将文件页丢弃或回写回磁盘(如果是脏页),还可能会触发LMK杀进程进行内存回收。这些被回收的内存如果再次使用都需要重新从磁盘读取,而这个过程涉及到较多的IO操作。 就目前的技术而言,IO的速度远远慢于这RAM操作速度。因此,如果频繁地做IO操作,不仅影响flash使用寿命,还严重影响系统性能。内存压缩是一种让IO过程平滑过渡的做法, 即尽量减少由于内存紧张导致的IO,提升性能。

    image

    图:内存管理大体框架(内存压缩技术处于内存回收memory reclaim部分中)

    主流内存压缩技术

    目前linux内核主流的内存压缩技术主要有3种:zSwap, zRAM, zCache。下面我们依次对几种方式进行一个简要的说明

    zSwap

    zSwap是在memory与flash之间的一层“cache”,当内存需要swap出去磁盘的时候,先通过压缩放到zSwap中去,zSwap空间按需增长。达到一定程度后则会按照LRU的顺序(前提是使用的内存分配方法需要支持LRU)将就最旧的page解压写入磁盘swap device,之后将当前的page压缩写入zSwap。

    zswap本身存在一些缺陷或问题:

    1. 如果开启当zswap满交换出backing store的功能, 由于需要将zswap里的内存按LRU顺序解压再swap out, 这就要求内存分配器支持LRU功能。
    2. 如果不开启当zswap满交换出backing store的功能, 和zRam是类似的。

    zRram

    zRram即压缩的内存, 使用内存模拟block device的做法。实际不会写到块设备中去,只会压缩后写到模拟的块设备中,其实也就是还是在RAM中,只是通过压缩了。由于压缩和解压缩的速度远比读写IO好,因此在移动终端设备广泛被应用。zRam是基于RAM的block device, 一般swap priority会比较高。只有当其满,系统才会考虑其他的swap devices。当然这个优先级用户可以配置。

    zRram本身存在一些缺陷或问题:

    1. zRam大小是可灵活配置的, 那是不是配置越大越好呢? 如果不是,配置多大是最合适的呢?
    2. 使用zRam可能会在低内存场景由于频繁的内存压缩导致kswapd进程占CPU高, 怎样改善?
    3. 增大了zRam配置,对系统内存碎片是否有影响?

    要利用好zRam功能, 并不是简单地配置了就OK了, 还需要对各种场景和问题都做好处理, 才能发挥最优的效果。

    zCache

    zCache是oracle提出的一种实现文件页压缩技术,也是memory与block dev之间的一层“cache”,与zswap比较接近,但zcache目前压缩的是文件页,而zSwap和zRAM压缩是匿名页。

    zcache本身存在一些缺陷或问题:

    1. 有些文件页可能本身是压缩的内容, 这时可能无法再进行压缩了;
    2. zCache目前无法使用zsmalloc, 如果使用zbud,压缩率较低;
    3. 使用的zbud/z3fold分配的内存是不可移动的, 需要关注内存碎片问题;

    内存压缩主流的内存分配器

    Zsmalloc

    zsmalloc是为ZRAM设计的一种内存分配器。内核已经有slub了,为什么还需要zsmalloc内存分配器?这是由内存压缩的场景和特点决定的。zsmalloc内存分配器期望在低内存的场景也能很好地工作,事实上,当需要压缩内存进行zsmalloc内存分配时,内存一般都比较紧张且内存碎片都比较严重了。如果使用slub分配, 很可能由于高阶内存分配不到而失败。另外,slub也可能导致内存碎片浪费比较严重,最坏情况下,当对象大小略大于PAGE_SIZE/2时,每个内存页接近一半的内存将被浪费。

    实测发现,anon pages的平均压缩比大约在1:3左右,所以compressed anon page size很多在1.2K左右。如果是Slub,为了分配大量1.2K的内存,可能内存浪费严重。zsmalloc分配器尝试将多个相同大小的对象存放在组合页(称为zspage)中,这个组合页不要求物理连续,从而提高内存的使用率。

    image

    需要注意的是, 当前zsmalloc不支持LRU功能, 旧版本内核分配的不可移动的页, 对内存碎片影响严重, 但最新版本内核已经是支持分配可移动类型内存了。

    Zbud

    zbud是一个专门为存储压缩page而设计的内存分配器。用于将2个objects存到1个单独的page中。zbud是可以支持LRU的, 但分配的内存是不可移动的。

    Z3fold

    z3fold是一个较新的内存分配器, 与zbud不同的是, 将3个objects存到1个单独的page中,也就是zbud内存利用率极限是1:2, z3fold极限是1:3。同样z3fold是可以支持LRU的, 但分配的内存是不可移动的。

    内存压缩技术与内存分配器组合

    结合上面zSwap / zRam /zCache的介绍, 与zsmalloc/zbud/z3fold分别怎样组合最合适呢? 下面总结了一下, 具体原因可以看上面介绍的时候各类型的特点。

    对比项zsmalloczbudz3fold
    zSwap(有实际swap device)×(不可用)√(可用)√(最佳)
    zSwap(无实际swap device)√(最佳)√(可用)√(可用)
    zRam√(最佳)√(可用)√(可用)
    zCache×(不可用)√(可用)√(最佳)

    zRAM技术原理

    zRam内存压缩技术是目前移动终端广泛使用的内存压缩技术。

    软件框架

    下图展示了内存管理大体的框架, 内存压缩技术处于内存回收memory reclaim部分中。

    image

    再具体到zRam, 它的软件架构可以分为3部分:数据流操作,内存压缩算法 ,zram驱动。 image

    实现原理

    Zram内存压缩技术本质上就是以时间换空间。通过CPU压缩、解压缩的开销换取更大的可用内存空间。

    我们主要描述清楚下面这2个问题: 1.什么时候会进行内存压缩? 2.进行内存压缩/解压缩的流程是怎样的?

    进行内存压缩的时机:

    1. Kswapd场景:kswapd是内核内存回收线程, 当内存watermark低于low水线时会被唤醒工作, 其到内存watermark不小于high水线。
    2. Direct reclaim场景:内存分配过程进入slowpath, 进行直接行内存回收。 image

    下面是基于4.4内核理出的内存压缩、解压缩流程。 内存回收过程路径进行内存压缩。会将非活跃链表的页进行shrink, 如果是匿名页会进行pageout, 由此进行内存压缩存放到ZRAM中, 调用路径如下:

    image

    在匿名页换出到swap设备后, 访问页时, 产生页访问错误, 当发现“页表项不为空, 但页不在内存中”, 该页就是已换到swap区中,由此会开始将该页从swap区中重新读取, 如果是ZRAM, 则是解压缩的过程。调用路径如下: image

    内存压缩算法

    目前比较主流的内存算法主要为LZ0, LZ4, ZSTD等。下面截取了几种算法在x86机器上的表现。各算法有各自特点, 有以压缩率高的, 有压缩/解压快的等, 具体要结合需求场景选择使用。

    image

    zRAM技术应用

    本节描述一下在使用ZRAM常遇到的一些使用或配置,调试的方法。

    如何配置开启zRAM

    配置内存压缩算法

    下面例子配置压缩算法为lz4

    echo lz4 > /sys/block/zram0/comp_algorithm
    

    配置ZRAM大小

    下面例子配置zram大小为2GB

    echo 2147483648 > /sys/block/zram0/disksize
    

    使能zram

    mkswap /dev/zram0

    swapon /dev/zram0

    zRAM块设备个数设定

    如果是编译为内核模块,那么可以在内核模块加载的时候,添加参数:insmod zram.ko num_devices=4

    也可直接修改内核源代码,代码地址为: /drivers/block/zram/zram_drv.c

    /* Module params (documentation at end) */
    static unsigned int num_devices = 1;
    

    修改num_devices为你想要的zram个数即可

    压缩流的最大个数设定

    这个是 3.15 版本及以后的 kernel 新加入的功能,3.15 版本之前的 zram 压缩都是使用一个压缩流(缓存 buffer 和算法私有部分)实现,每个写(压缩)操作都会独享压缩流,但是单压缩流如果出现数据奔溃或者卡住的现象,所有的写(压缩)操作将一直处于等待状态,这样效率非常低;而多压缩流的架构会让写(压缩)操作可以并行去执行,大大提高了压缩的效率和稳定性。

    查看压缩流个数:默认是1,可以直接向proc文件写入,也可以直接更改代码方式来改变默认压缩流个数

    cat /sys/block/zram0/max_comp_streams
    

    设定压缩流个数:

    echo 3 > /sys/block/zram0/max_comp_streams
    

    其他参数

    NameAccessDescription
    disksizeRW显示和设置该块设备的内存大小
    initstateRO显示设备的初始化状态
    resetWO重置设备
    num_readsRO读数据的个数
    failed_readsRO读数据失败的个数
    num_writeRO写数据的个数
    failed_writesRO写数据失败的个数
    invalid_ioRO非页面大小对齐的I/O请求的个数
    max_comp_streamsRW最大可能同时执行压缩操作的个数
    comp_algorithmRW显示和设置压缩算法
    notify_freeRO空闲内存的通知个数
    zero_pagesRO写入该块设备的全为的页面的个数
    orig_data_sizeRO保存在该块设备中没有被压缩的数据的大小
    compr_data_sizeRO保存在该块设备中已被压缩的数据的大小
    mem_used_totalRO分配给该块设备的总内存大小
    mem_used_maxRW该块设备已用的内存大小,可以写 1 重置这个计数参数到当前真实的统计值
    mem_limitRWzram 可以用来保存压缩数据的最大内存
    pages_compactedRO在压缩过程中可用的空闲页面的个数
    compactWO触发内存压缩

    swappiness含义简述

    swappiness参数是内核倾向于回收匿名页到swap(使用的ZRAM就是swap设备)的积极程度, 原生内核范围是0~100, 参数值越大, 表示回收匿名页到swap的比例就越大。如果配置为0, 表示仅回收文件页,不回收匿名页。默认值为60。可以通过节点“/proc/sys/vm/swappiness”配置。

    zRam相关的技术指标

    zRAM大小及剩余空间

    Proc/meminfo 中可以查看相关信息 SwapTotal:swap 总大小, 如果配置为ZRAM, 这里就是ZRAM总大小 SwapFree:swap 剩余大小, 如果配置为ZRAM, 这里就是ZRAM剩余大小

    当然, 节点 /sys/block/zram0/disksize 是最直接的。

    zRAM压缩率

    /sys/block/zram/mm_stat 中有压缩前后的大小数据, 由此可以计算出实际的压缩率 orig_data_size:压缩前数据大小, 单位为bytes compr_data_size :压缩后数据大小, 单位为bytes

    换出/换入swap区的总量

    proc/vmstat 中中有相关信息 pswpin:换入总量, 单位为page pswout:换出总量, 单位为page

    zRam相关优化

    上面提到zRam的一些缺陷, 怎么去改善呢?

    1. zRam大小是可灵活配置的, 那是不是配置越大越好呢? 如果不是配置多大是最合适的呢? zRam大小的配置比较灵活, 如果zRam配置过大, 后台缓存了应用过多, 这也是有可能会影响前台应用使用的流畅度。另外, zRam配置越大, 也需要关注系统的内存碎片化情。因此zRam并不是配置越大越好,具体的大小需要根据内存总大小及系统负载情况考虑及实测而定。

    2. 使用zRam,可能会存在低内存场景由于频繁的内存压缩导致kswapd进程占CPU高, 怎样改善? zRam本质就是以时间换空间, 在低内存的情况下, 肯定会比较频繁地回收内存, 这时kswapd进程是比较活跃的, 再加上通过压缩内存, 会更加消耗CPU资源。改善这种情况方法也比较多, 比如, 可以使用更优的压缩算法, 区别使用场景, 后台不影响用户使用的场景异步进行深度内存压缩, 与用户体验相关的场景同步适当减少内存压缩, 通过增加文件页的回收比例加快内存回收等等。

    3. 增大了zRam配置,对系统内存碎片是否有影响? 使用zRam是有可能导致系统内存碎片变得更严重的, 特别是zsmalloc分配不支持可移动内存类型的时候。新版的内核zsmalloc已经支持可移动类型分配的, 但由于增大了zRam,结合android手机的使用特点, 仍然会有可能导致系统内存碎片较严重的情况,因些内存碎片问题也是需要重点关注的。解决系统内存碎片的方法也比较多, 可以结合具体的原因及场景进行优化。

    参考资料

    Linux内存压缩浅析之原理

    zRAM内存压缩技术原理与应用

    Sunday, January 28, 2024

    deepin v23 beta3 将于 2024 年 1 月 31 日发布(注:因存在部分临时设计变动,延期发布,仍计划在当周发布),这里为大家简要描述本次更新中,DDE 所涉及的变更,以及我们的进一步计划。另需注意,本文章倾向于对 DDE 项目整体的技术内容进行描述,面向 DDE 开发者和对 DDE 开发感兴趣的读者,并非面向最终用户的特性概览文章。

    变化较大的默认组件

    相比 deepin v23 beta2 而言,在于 deepin v23 beta3 发布的 DDE 中,有两个项目得到了较大规模的重构,并随 beta3 默认提供给各位用户。这两个项目分别是:

    • dde-application-manager (>= 1.2)
    • dde-launchpad (取代 dde-launcher)

    dde-application-manager 承载了 DDE 下应用程序的启动与管理等职责,在早期版本中存在诸多架构不合理以及实现问题,影响到了 dde-launcher、dde-dock、dde-grand-search 等项目启动应用与管理应用功能的正常使用。于是我们对 dde-application-manager 进行了大规模的重构,也重新设计了此项目的对外 D-Bus 接口。在 beta3 中,你将不再会遇到类如应用频繁集体闪退的问题,对应用 desktop 文件的支持变得更加完整。在未来,也会对实时调整应用缩放等功能预留支持的空间。

    dde-launchpad 则是对 dde-launcher 的完整重写。一方面,dde-launcher 对旧的 dde-application-manager 具有很高的偶合度,另一方面,旧的 dde-launcher 架构使得维护此组件变得非常困难。dde-launchpad 是目前 DDE 的首个基于 QML 的组件,且基于最新的 Qt 6。得益于 QML 技术,launchpad 将会有效利用 GPU 绘制界面,使交互体验更加流畅。不过由于 dde-launchpad 是完全重写版本,故尚有部分功能未在当前的 dde-launchpad 中提供。

    技术预览组件

    非技术用户请慎重启用技术预览功能

    deepin v23 beta2 时,我们提供了一个需要用户手动安装的 dcc-insider-plugin 插件,称为技术预览插件。这个组件旨在帮助用户方便的测试 deepin 未来版本中计划提供但仍不稳定的系统组件。在 beta2 时,技术预览组件仅包含了 dde-launchpad 一项。而在 beta3 中,dde-launchpad 走出了技术预览阶段,而有更多的组件进入了技术预览阶段:

    • treeland
    • dde-shell
    • deepin-im

    treeland 是 deepin 的下一代 wayland 窗口合成器,且可以同时提供会话管理功能。由于 treeland 会替换 deepin-kwin 并同样会接管 lightdm,故切换 treeland 时需要格外留意。

    切换到 treeland 后,将会进入的 DDE 会话也会与原本有差异。treeland 会话中会使用 dde-shell 而非原本的 dde-dock、dde-widgets 等组件。尽管 dde-shell 可以独立使用,但目前 dde-shell 是作为与 treeland 共同配合进入技术预览阶段的桌面组件。dde-shell 提供了与 wayland 会话下的桌面环境的相关功能集成,今后的 dock 等组件也均会作为 dde-shell 的插件存在,以便提供更深度的集成。

    deepin-im 是新的输入法组件,作为目前各种底层输入法框架的抽象层存在,以便用户更方便的管理输入法而无需关注底层的配置细节。deepin-im 并不与 treeland 绑定,可以单独启用。

    后续计划

    在未来,首要计划即使 dde-shell 可以走出技术预览阶段并配合 treeland 达成真正稳定可用的 wayland 会话,这意味着 treeland 与 dde-shell 相关的项目还有比较多的相关工作需要持续进行。也是我们的主要努力方向。另外,尽管 dde-shell 相关的 API 可能仍会存在一些变动,但我们计划在随后提供一些必要的文档来帮助开发者更好的了解 dde-shell 的目的与作用,以便帮助开发者参与到项目之中来。如果你对这相关的话题感兴趣,欢迎加入 DDE SIG 的 Matrix 群聊 (#dde:matrix.org) 之中来。

    另外,由于 DDE 在 beta2 与 beta3 的变化较大,我们也计划提供一篇移植注意事项博客。笔者也是 DDE 移植 SIG 的成员,于是对应的文章计划会发布到 DDE 移植小组。如果您感兴趣,请考虑订阅 <planet.deepin.org> 的 RSS 更新。如果你本身在参与 DDE 的移植工作,那么也欢迎你加入 DDE 移植小组(#dde-port:deepin.orghttps://t.me/ddeport)。

    最后,感谢你读到这里。如有任何问题,欢迎在我们的开发者群(#deepin-community:deepin.org)进行讨论。

    Thursday, December 7, 2023

    简述

    在上一篇浅析Linux电源配置之后,我们一直在深入探索如何进一步优化我们系统的续航和性能表现,今天它来了:

    TLP 是适用于 Linux 的功能丰富的命令行实用程序,无需深入研究技术细节即可节省笔记本电脑电池电量。之前我们的系统使用的laptopmode,但是相较于TLP还有有部分劣势:比如tlp脚本是被动唤醒,可以以较小的开销完成电源管理相关内容。而且TLP文档支持非常完善,所以可以方便用户自行调整相关配置。以下是TLP官方文档内容的和我自己的理解的结合,各位系统用户可以结合自己的实际情况diy自己的电源策略文件,也可以将好的电源配置在deepin 论坛中分享。

    工作原理

    • TLP 基本上所做的是调整影响功耗的内核设置,内核态的配置文件存储在RAM中,所以并不具备持久性。TLP将配置存储在用户态中,在内核启动时对其进行配置
    • TLP 处理的大多数内核设置都作为 sysfs 节点导出到用户空间,即 /sys/ 下的文件。tlp-stat 的输出将显示路径。
    • TLP 提供两组独立的设置,称为配置文件,一组用于电池 (BAT),另一组用于交流操作。这意味着 TLP 不仅在启动时,而且在每次电源更改时都必须应用适当的配置文件(可以据此实现AC BT切换电源调度状态)

    TLP触发事件(信号)

    • 充电器插入(交流供电):应用AC配置文件
    • 充电器已拔下(电池供电): 应用BAT配置文件
    • 已插入 USB 设备:激活设备的 USB 自动挂起模式(可以在配置文件设置例外或拒绝连接)
    • 系统启动(boot):应用与当前电源 AC/BAT 相对应的设置配置文件。应用充电阈值并根据您的个人设置切换蓝牙、Wi-Fi 和 WWAN 设备(在默认配置中禁用)
    • 系统关机 (power off):保存或切换蓝牙、Wi-Fi 和 WWAN 设备状态,并根据您的个人设置禁用 USB 自动挂起(在默认配置中禁用)
    • 系统重启: 相当于关机再启动
    • 系统挂起到 ACPI 睡眠状态 S0ix(空闲待机)、S3(挂起到 RAM)或 S4(挂起到磁盘):保存蓝牙、Wi-Fi 和 WWAN 设备状态,并根据您的个人设置关闭可移动光盘驱动器的电源(在默认配置中禁用)。
    • 系统从 ACPI 睡眠状态 S0ix(空闲待机)、S3(挂起到 RAM)或 S4(挂起到磁盘)恢复: 应用与当前电源 AC/BAT 相对应的设置配置文件。恢复充电阈值以及蓝牙、Wi-Fi 和 WWAN 设备状态,具体取决于您的个人设置(在默认配置中禁用)。
    • LAN、Wi-Fi、WWAN 连接/断开连接或笔记本电脑插接/未插接:根据您的个人设置启用或禁用内置蓝牙、Wi-Fi 和 WWAN 设备(在默认配置中禁用)

    除了上述事件之外,TLP 不会对设置进行动态或自适应更改 特别是,TLP 绝不会因 CPU 负载、电池电量或其他原因而调整设置(如果我们需要去实现这一部分,则可以,则可以通过添加一个信号的方式来实现)

    安装

    sudo apt install tlp

    使用

    启动

    安装后TLP将在系统启动的时候自动启动,如果你不想重启系统,可以使用sudo tlp start来启动tlp,也可以使用此命令来应用更改。

    状态

    tlp-stat -s TLP是bash脚本,所以不存于daemon进程

    命令行

    TLP:

    sudo tlp bat 应用电池配置文件并进入手动模式 手动模式意味着对电源的更改将被忽略,直到下一次重新启动或发出 tlp start 以恢复自动模式

    sudo tlp ac应用交流配置文件并进入手动模式

    sudo tlp usb 对所有的ubs设备应用自动挂起

    sudo tlp bayoff 关闭 MediaBay/Ultrabay 中的光驱电源

    sudo tlp setcharge [<START_CHARGE_THRESH> <STOP_CHARGE_THRESH>] [BAT0|BAT1|BAT<x>|CMB0|CMB1] 可以设定对指定电池开始充电百分比和结束充电的百分比,以达到养护电池的目的(如果不带参数 会重置电池管理方案)(命令只能暂时更改,如果需要持久化更改 需要修改配置文件)

    sudo tlp fullcharge [BAT0|BAT1|BAT<x>|CMB0|CMB1] 设定电池充满

    tlp diskid 显示已经配置驱动器的磁盘ID

    以下部分为ThinkPad专属

    sudo tlp chargeonce [BAT0|BAT1] 将电池充电至停止充电阈值一次,这个阈值是使用setcharge设置的

    sudo tlp discharge [BAT0|BAT1] 让电池在交流电源下完全放电

    sudo tlp recalibrate [BAT0|BAT1]校准电池

    TLP-RDW

    sudo tlp-rdw [ enable | disable ] 启用或关闭无线电管理功能

    bluetooth [ on | off | toggle ]
    nfc [ on | off | toggle ]
    wifi [ on | off | toggle ]
    wwan [ on | off | toggle ]
    

    启用、禁用、切换或检查内置蓝牙、NFC、Wi-Fi 和 WWAN(3G/UMTS、4G/LTE 或 5G)无线电的状态,如果不带参数则为当前硬件状态(硬件需要支持rfkill)

    TLP-STAT

    sudo tlp-stat 查看TLP配置信息,系统信息和内核省电设置以及电池数据

    sudo tlp-stat [-b /--battery] 查看电池信息,部分电池加-v参数可以查看电压

    sudo tlp-stat [-c /--config]查看配置信息

    sudo tlp-stat --cdiff 查看默认配置和用户配置之间的差异

    sudo tlp-stat [-d /--disk] 查看硬盘配置信息

    sudo tlp-stat [-e/ --pcie] 查看Pcie配置信息

    配置

    TLP最重要的就是其配置文件,可以说,TLP是否节电的关键。 TLP 使用两个根据电源自动应用的设置配置文件:

    • _AC结尾的参数在连接交流电源的时候生效
    • _BAT结尾的参数在使用电池的时候有效
    • 既不以 _AC 结尾也不以 _BAT 结尾的参数适用于这两个配置文件

    配置文件

    按指定顺序从以下文件中读取设置:

    • Intrinsic defaults 固有默认值(这个配置为TLP自带,不可被更改)
    • /etc/tlp.d/*.conf:插入式自定义片段,按词法(字母顺序)顺序读取,不过建议可以使用一般配置命名方法(00_xxxx.conf)
    • /etc/tlp.conf:用户配置

    如果多个参数相同,但在同一文件中也存在相同的参数,则最后一个匹配项优先,这也意味着,/etc/tlp.conf 中的参数将覆盖其他任何内容,因为它是最后读取的 默认的/etc/tlp.conf 中的所有参数都被禁用,删除前导 # 以激活您的更改 /etc/tlp.d/ 目录中的配置文件由用户创建: * 文件名必须以 .conf 结尾,否则文件将被忽略 * 00-template.conf 作为示例提供

    参数默认值

    配置中有两种参数,一种是具有默认值的,会在本文档中说明,并且在/etc/tlp.conf中有Default前缀。还有一种没有默认值的。

    参数语法

    配置文件由参数和注释行组成。

    参数行

    PARAMETER=value
    

    如果value包含空格,则需要使用双引号

    key="111 1111 1111"
    

    注释行

    #开头,在1.6版本后可以在参数行后接#作为注释

    禁用功能

    • 没有默认值:使用注释或者删除即可
    • 有默认值: 赋空值即可 eg:key=""

    使用+=追加配置

    和bash的环境变量一样,支持使用+=作为追加配置

    使用root权限编辑配置文件,在保存更改后可以使用重启,拔插ac电源或者使用sudo tlp start命令激活配置

    配置详解

    基础操作

    参数名称默认参数值描述
    TLP_ENABLE1设置为0可禁用TLP(需要重新启动)。未配置时的默认值:1
    TLP_WARN_LEVEL/控制如何发出有关无效设置的警告: 0 - 禁用 1 - 向系统日志/日志报告后台任务(启动、恢复、电源更改) 2 - 外壳命令向终端报告(标准) 3 - 1和2的组合。未配置时的默认值:3
    TLP_DEFAULT_MODE/定义TLP的默认操作模式(AC或BAT),以防无法检测到电源。仅涉及某些台式机和嵌入式硬件。
    TLP_PERSISTENT_DEFAULT0选择如何确定操作模式: 0 – 根据实际电源应用设置配置文件(默认) 1 – 始终使用TLP_DEFAULT_MODE设置。未配置时的默认值:0
    TLP_PS_IGNORE/确定工作模式时要忽略的电源等级:(用作错误检测到操作模式 AC 或 BAT 的笔记本电脑的解决方法) AC BAT USB - 仅限版本 1.4 及更高版本。仅限版本 1.4 及更高版本:输入多个类,以空格分隔。

    音频

    参数名称默认参数值描述
    SOUND_POWER_SAVE_ON_AC/BAT1设置为0可禁用音频省电模式(需要重新启动)。未配置时的默认值:1(AC),1(BAT)- 版本 1.4 及更高版本,0(AC),1(BAT)- 版本 1.3。
    SOUND_POWER_SAVE_CONTROLLERYY – 关闭控制器和声音芯片的电源 N – 控制器保持活动状态。未配置时的默认值:Y。

    注释: SOUND_POWER_SAVE_ON_AC/BAT 指的是SOUND_POWER_SAVE_ON_ACSOUND_POWER_SAVE_ON_BAT

    电池保养

    参数名称参数值描述
    START_CHARGE_THRESH_BAT<x>75电池充电水平低于该水平,连接充电器时将开始充电。
    STOP_CHARGE_THRESH_BAT<x>80电池充电水平,超过该水平,充电器连接时充电将停止。

    这些参数用于设置笔记本电脑主/内部电池(BAT0)和辅助电池(BAT1)的充电阈值。启动充电阈值表示在连接充电器时,电池充电水平低于该值时将开始充电。停止充电阈值表示在充电器连接时,电池充电水平超过该值时将停止充电。这些阈值始终具有较低的可用电池容量,因此默认情况下禁用这些设置,并且必须通过删除前导 # 来显式启用这些设置。

    光驱

    参数名称默认参数值描述
    BAY_POWEROFF_ON_AC/BAT0控制光驱在交流电源和电池供电时是否关闭电源。 1:保持光驱开启状态 0:关闭光驱电源
    BAY_DEVICEsr0指定光驱设备。

    硬盘

    参数名称默认参数值描述
    DISK_DEVICES“nvme0n1 sda”定义参数作用的磁盘设备。多个设备用空白分隔。
    DISK_APM_LEVEL_ON_AC/BAT“254 254”(AC) “128 128” (BAT)设置“高级电源管理级别”。可能的值介于1和255之间。 1 – 最大省电/最低性能 – 重要提示:此设置可能会导致磁盘驱动器磨损增加,因为读写磁头卸载过多 128 – 省电和磨损之间的折衷(电池的 TLP 标准设置) 192 – 防止某些 HDD 的磁头过度卸载 254 – 最小省电/最大性能(交流电的 TLP 标准设置) 255 – 禁用 APM(某些磁盘型号不支持) keep – 用于跳过特定磁盘的此设置的特殊值(同义词:_
    DISK_APM_CLASS_DENYLIST“usb ieee1394”从高级电源管理(APM)中排除磁盘类。可能的值:sata、ata、usb、ieee1394。默认为“usb ieee1394”。
    DISK_SPINDOWN_TIMEOUT_ON_AC/BAT“0 0”磁盘空闲时主轴电机停止的超时值。有效设置:0(已禁用)、1..240(5秒到20分钟)、241..251(30分钟到5.5小时)。
    DISK_IOSCHED“keep keep”两个参数为 多队列 (blk-mq) 调度器:mq-deadlinenonekyberbfqkeep 单队列调度程序:deadlinecfqbfqnoopkeep 如果未配置,默认情况下所有磁盘将使用内核的默认调度程序。
    SATA_LINKPWR_ON_AC/BAT“med_power_with_dipm”设置SATA链路的电源管理模式。可能的值包括:max_performance、medium_power、med_power_with_dipm、min_power。默认为med_power_with_dipm。
    SATA_LINKPWR_DENYLIST“host1”从AHCI链路电源管理(ALPM)中排除SATA磁盘的主机列表。默认为空。
    AHCI_RUNTIME_PM_ON_AC“on”控制NVMe、SATA、ATA和USB磁盘以及SATA端口的运行时电源管理。可能的值包括:auto(启用)、on(禁用)
    AHCI_RUNTIME_PM_ON_BAT“auto”同上
    AHCI_RUNTIME_PM_TIMEOUT15磁盘或端口挂起前的不活动时间(秒)。仅在激活AHCI_RUNTIME_PM_ON_AC/BAT时有效。默认为15。

    注释:DISK_IOSCHED 如果使用是NVME设备时,最好使用无IO调度程序来减少CPU开销(none和noop)

    文件系统

    参数名称默认参数值描述
    DISK_IDLE_SECS_ON_AC/BAT0 (AC), 2 (battery)笔记本电脑模式等待磁盘空闲的秒数,然后再次将脏缓存块从 RAM 同步到磁盘。值大于0将激活内核笔记本电脑模式。请勿更改此设置。
    MAX_LOST_WORK_SECS_ON_AC/BAT15 (AC), 60 (battery)将文件系统缓冲区中未保存的数据写入磁盘的超时时间(秒)。

    图形显卡

    参数名称默认参数值描述
    INTEL_GPU_MIN_FREQ_ON_AC/BAT0设置 Intel GPU 的最小频率。可能的值取决于硬件。通过运行 tlp-stat -g 命令查看可用频率。
    INTEL_GPU_MAX_FREQ_ON_AC/BAT0设置 Intel GPU 的最大频率。可能的值取决于硬件。通过运行 tlp-stat -g命令查看可用频率。
    INTEL_GPU_BOOST_FREQ_ON_AC/BAT0设置 Intel GPU 的睿频频率。可能的值取决于硬件。通过运行 tlp-stat -g 命令查看可用频率。
    RADEON_DPM_PERF_LEVEL_ON_AC/BATauto控制 AMD GPU 的动态电源管理(DPM)性能级别。支持 amdgpu(仅限 TLP 版本 1.4 及更高版本)和 radeon 驱动程序。可能的值包括 auto、low、high。默认值:auto。
    RADEON_DPM_STATE_ON_AC/BATperformance (AC), battery (BAT)控制 AMD GPU 的电源管理方法。可能的值包括 battery、balanced、performance。默认值:performance(AC)、battery(BAT)。
    RADEON_POWER_PROFILE_ON_AC/BATdefault控制 AMD GPU 的时钟。仅在旧版 ATI 硬件上受 radeon 驱动程序支持(DPM 不可用)。可能的值包括 low、mid、high、auto、default。默认值:default。

    这些参数允许用户调整 Intel GPU 和 AMD GPU 在交流电和电池模式下的性能和电源管理行为。在配置这些参数时,建议参考硬件规格和运行 tlp-stat -g 查看可用频率。

    kernel

    参数名称默认参数值描述
    NMI_WATCHDOG0激活内核 NMI 看门狗定时器。设置为 0 表示禁用,有助于节省电源。设置为 1 表示启用,对于内核调试和看门狗守护程序是相关的。

    不建议关闭watchdog 否则可能导致内核崩溃后无法自动重启和内核调试

    网络

    参数名称默认参数值描述
    WIFI_PWR_ON_AC/BAToff (AC),设置 Wi-Fi 的电源保存模式。可能的值包括 off(禁用)和 on(启用)。默认值:off(AC)、on(BAT)。
    on (BAT)提示:支持已弃用的配置值 1=off/5=on,以实现向后兼容性。
    WOL_DISABLEY控制是否禁用 Wake-on-LAN(LAN 唤醒)。可能的值包括 Y(禁用)和 N(不禁用,保持 BIOS 默认)。默认值:Y。 注意:更改为 WOL_DISABLE=N 后,需要重新启动才能使新设置生效(或在 shell 中使用 sudo ethtool -s wol g)。

    这些参数允许用户配置Wi-Fi的电源保存模式和控制Wake-on-LAN(LAN唤醒)功能。

    平台

    参数名称默认参数值描述
    PLATFORM_PROFILE_ON_AC/BATperformance选择平台配置文件以控制系统的功率/性能级别、散热和风扇速度的运行特性。可能的值包括 performance、balanced、low-power。默认值:performance(AC)、low-power(BAT)。
    MEM_SLEEP_ON_AC/BATs2idle选择系统挂起模式。可能的值包括 s2idle(空闲待机)和 deep(挂起到 RAM)。注意:更改挂起模式可能导致系统不稳定和数据丢失。请使用 tlp-stat -s 检查系统上不同模式的可用性。如果不确定,请坚持使用系统默认值。

    其实如果能使用S3休眠那就更好,不过现在很多厂商并不支持S3,所以如果能用S2那就用S2吧。

    处理器

    参数名称默认参数值描述
    CPU_DRIVER_OPMODE_ON_AC/BATactive (amd-pstate), active (intel_pstate)选择 CPU 缩放驱动程序操作模式。配置取决于活动驱动程序:对于 amd-pstate(Active 模式),可能的值为 active 和 passive;对于 intel_pstate(Active 模式),可能的值为 active、passive 和 guided。
    CPU_SCALING_GOVERNOR_ON_AC/BATpowersave选择用于自动频率缩放的 CPU 缩放调节器。配置取决于活动驱动程序。可能的值包括 performance、powersave、conservative、ondemand、userspace 和 schedutil。默认值:powersave(AC)、powersave(BAT)。
    CPU_SCALING_MIN/MAX_FREQ_ON_AC/BAT0, 9999999设置可用于缩放调控器的最小/最大频率。可能的值取决于您的 CPU。请查阅tlp-stat -p的输出以获取可用频率。
    CPU_ENERGY_PERF_POLICY_ON_AC/BATbalance_performance设置 CPU 能耗/性能策略。可能的值包括 performance、balance_performance、default、balance_power 和 power。默认值:balance_performance(AC)、balance_power(BAT)。
    CPU_MIN/MAX_PERF_ON_AC/BAT0, 100定义 Intel CPU 的最小/最大 P 状态,表示为总可用处理器性能的百分比。建议仅用于限制 CPU 的功耗。可能的值在 0 到 100 之间。默认值:0 到 100(AC)、0 到 30(BAT)。
    CPU_BOOST_ON_AC/BAT1配置 CPU “turbo boost”(Intel)或“turbo core”(AMD)功能。可能的值为 0(禁用)和 1(允许)。请注意,值为 1 不会激活提升,只是允许它。默认值:1(AC)、0(BAT)。
    CPU_HWP_DYN_BOOST_ON_AC/BAT1配置 Intel CPU HWP 动态提升功能。可能的值为 0(禁用)和 1(启用)。要求 Intel Core i 第 6 代(“Skylake”)或更新的 CPU,在活动模式下具有 intel_pstate 扩展驱动程序。默认值:1(AC)、0(BAT)。

    这些参数允许用户配置 CPU 的性能和功耗特性,包括缩放驱动程序操作模式、调节器、频率范围、能耗/性能策略、P 状态范围、提升功能以及 HWP 动态提升功能。

    部分电脑的BIOS会干预PState 所以需要检查自己的CPU是否支持

    无线设备

    参数名称默认参数值描述
    RESTORE_DEVICE_STATE_ON_STARTUP0在启动时从上次关机中恢复无线电设备状态。可能的值为 0(禁用)和 1(启用)。默认值:0。
    DEVICES_TO_DISABLE_ON_STARTUP""在启动时禁用内置无线电设备。可能的值包括 bluetooth、wifi 和 wwan,多个设备用空白分隔。
    DEVICES_TO_ENABLE_ON_STARTUP""在启动时启用内置无线电设备。可能的值与上述相同,用于启用在默认情况下禁用的设备。
    DEVICES_TO_ENABLE_ON_AC""插入交流电源时启用内置无线电设备。可能的值与上述相同。
    DEVICES_TO_DISABLE_ON_BAT""在更改为电池电源时禁用内置无线电设备,无论其连接状态如何。可能的值与上述相同。
    DEVICES_TO_DISABLE_ON_BAT_NOT_IN_USE""在更改为电池电源时禁用未连接的内置无线电设备。可能的值与上述相同。

    这些参数允许用户配置在系统启动、关闭或更改电源状态时如何处理内置的蓝牙、Wi-Fi 和 WWAN 设备。可通过设置禁用或启用这些设备,以及在何种条件下执行这些操作。

    无线配置向导(自动化配置)

    参数名称参考参数值描述
    DEVICES_TO_DISABLE_ON_LAN_CONNECT“wifi wwan”当建立 LAN 连接时,禁用蓝牙、Wi-Fi 和 WWAN 设备。多个设备用空白分隔。
    DEVICES_TO_DISABLE_ON_WIFI_CONNECT“wwan”当建立 Wi-Fi 连接时,禁用 WWAN 设备。
    DEVICES_TO_DISABLE_ON_WWAN_CONNECT“wifi”当建立 WWAN 连接时,禁用 Wi-Fi 设备。
    DEVICES_TO_ENABLE_ON_LAN_DISCONNECT“wifi wwan”当断开 LAN 连接时,启用蓝牙、Wi-Fi 和 WWAN 设备。多个设备用空白分隔。
    DEVICES_TO_ENABLE_ON_WIFI_DISCONNECT""当断开 Wi-Fi 连接时,启用所有设备。
    DEVICES_TO_ENABLE_ON_WWAN_DISCONNECT""当断开 WWAN 连接时,启用所有设备。
    DEVICES_TO_ENABLE_ON_DOCK""在对接后,启用所有设备。
    DEVICES_TO_DISABLE_ON_DOCK""在对接后,禁用所有设备。
    DEVICES_TO_ENABLE_ON_UNDOCK“wifi”在取消对接后,启用 Wi-Fi 设备。
    DEVICES_TO_DISABLE_ON_UNDOCK""在取消对接后,禁用所有设备。

    这些参数允许用户配置在特定事件触发时如何处理内置的蓝牙、Wi-Fi 和 WWAN 设备。用户可以根据 LAN、Wi-Fi 或 WWAN 的连接状态、对接或取消对接等事件来启用或禁用这些设备。

    PCIE电源配置

    参数名称默认参数值描述
    RUNTIME_PM_ON_ACon控制 PCIe 设备的运行时电源管理。可能的值:auto(启用)或 on(禁用)。未配置时的默认值:on(AC)。
    RUNTIME_PM_ON_BATauto控制 PCIe 设备的运行时电源管理。可能的值:auto(启用)或 on(禁用)。未配置时的默认值: auto(BAT)。
    RUNTIME_PM_DENYLIST""从运行时电源管理中排除列出的 PCIe 设备地址。使用 lspci 查找地址。
    RUNTIME_PM_DRIVER_DENYLIST“mei_me nouveau radeon”从运行时电源管理中排除分配给所列驱动程序的 PCIe 设备。使用 tlp-stat -e 查找驱动程序。
    RUNTIME_PM_ENABLE""为列表中的 PCI(e) 设备地址永久启用(自动)运行时 PM。这优先于所有先前的运行时 PM 设置。使用 lspci 获取地址。
    RUNTIME_PM_DISABLE""为列表中的 PCI(e) 设备地址永久禁用(on)运行时 PM。与 RUNTIME_PM_ENABLE 类似,不过是禁用。使用 lspci 获取地址。
    PCIE_ASPM_ON_ACdefault设置 PCIe ASPM 省电模式。可能的值:default(推荐)、performance(性能)、powersave(省电)和 powersupersave(PowerSuperSave,超级省电)。未配置时的默认值:default。
    PCIE_ASPM_ON_BATdefault设置 PCIe ASPM 省电模式。可能的值:default(推荐)、performance(性能)、powersave(省电)和 powersupersave(PowerSuperSave,超级省电)。未配置时的默认值:default。

    这些参数允许用户配置与 PCIe 设备相关的运行时电源管理和 ASPM 等功能。用户可以根据电源来源、设备地址、驱动程序等来调整这些设置,以实现更好的功耗管理。(建议不要对nvidia驱动进行调整,可能会引发意外)

    USB

    参数名称默认参数值描述
    USB_AUTOSUSPEND1在启动时和插入时为 USB 设备设置自动挂起模式。可能的值:1(启用)或 0(禁用)。未配置时的默认值:1。
    USB_DENYLIST""从自动挂起模式中排除 USB 设备 ID。使用 tlp-stat -u 查找 ID。多个 ID 用空格分隔。
    USB_EXCLUDE_AUDIO1从自动挂起模式中排除音频设备:1(排除)或 0(不排除)。未配置时的默认值:1。
    USB_EXCLUDE_BTUSB0从自动挂起模式中排除蓝牙设备:1(排除)或 0(不排除)。未配置时的默认值:0。
    USB_EXCLUDE_PHONE0将智能手机从自动挂起模式中排除以启用充电:1(排除)或 0(不排除)。未配置时的默认值:0。
    USB_EXCLUDE_PRINTER1从自动挂起模式中排除打印机:1(排除)或 0(不排除)。未配置时的默认值:1。
    USB_EXCLUDE_WWAN0从自动挂起模式中排除内置 WWAN 设备:1(排除)或 0(不排除)。未配置时的默认值:0。
    USB_ALLOWLIST""为已被上述任何设置排除的 USB 设备 ID 重新启用自动挂起模式。使用 tlp-stat -u 查找 ID。多个 ID 用空格分隔。
    USB_AUTOSUSPEND_DISABLE_ON_SHUTDOWN0在系统关闭时禁用 USB 自动挂起模式:1(启用)或 0(禁用)。未配置时的默认值:0。

    Trace Mode

    TLP_DEBUG="arg bat disk lock nm path pm ps rf run sysfs udev usb"

    结语

    我们对于系统的优化不仅于此,现阶段tlp的配置策略仅对于部分有能力的用户公开,后续经过充分的测试和调优之后,会提供几份默认的配置给普通用户使用。并将来将这些配置文件GUI化,集成于深度定制项目中,为用户提供更为方便直观的操作体验。

    从这一阶段对于电源优化的探索可以看出,deepin系统的电源管理方案优化不仅是为了解决用户反馈的问题,更是一种对用户需求的回应和尊重。在未来,deepin系统将继续秉持用户至上的原则,不断提升系统的性能和用户体验,为广大用户提供更加优秀的操作系统产品。

    Thursday, November 30, 2023

    2023 年 11 月 21 日,Qt Group 在上海成功举办了“Qt 全球峰会 2023 中国站”,本次大会上发布了关于 Qt 开放框架与开发工具的战略,集中展示了最新的产品、解决方案,阐述了它们在优化软件跨平台开发全流程、提升研发团队跨职能协作效率以及加速产品迭代速度等方面的强大优势,同时大会上也分享了对于人工智能如何影响软件开发和测试的思考。

    pic1

    Qt 作为 Linux 上最重要的开发工具之一,为中国信创产业的发展和创新提供了更多的可能性和选择。Qt Group 中国区负责人许晟在开幕致辞中表示,“Qt 中国作为 Qt Group 在中国的本地化团队,不仅通过源代码交付帮助本地企业实现软件的自主创新,还基于强大的跨平台能力支持针对本地操作系统和主流芯片的开发,为中国信创产业的发展和创新提供了更多的可能性和选择。

    pic2

    值得一提的是,deepin 已为 Qt社区贡献 100000+ 行代码,在贡献者中名列前茅。此外,deepin 社区也基于 Qt 开发了一整套简单且实用的通用开发框架 Development ToolKit(简称DTK),DTK 处于deepin系统中的核心位置。DDE 中超过30+组件,如浏览器、音乐、邮件等40余款原生应用全部使用 DTK 开发。为此,deepin(深度)社区在 deepin 开发者平台上专门提供了在 deepin 上进行 Qt 开发的文档指导。

    2023 年 11 月 18 日,deepin(深度)社区在北京798艺术中心举办了第十三届深度开发者与用户大会(Deepin Developer&User Conference,简称DDUC),在本次大会上,deepin社区也是公开了近一年在 DTK 和 Qt 这块的建设成果,目前 DTK 已正式适配 Qt6(6.4.2),实现全面升级,相关介绍可以查看《deepin(深度)宣布 deepin DTK 已完成基于 Qt6 的全面升级!》

    pic3

    在本次 QtWS23 峰会上,Qt Group 资深开发工程师齐亮介绍了Qt Wayland 的最新进展。近些年 Linux 桌面发行版迁移到 Wayland 的趋势愈发明显,大有替代 X11 之势。Ubuntu 目前已经默认 Wayland 会话,GNOME 在 Wayland 支持这块一直走在 Linux 发行版的前列,且动作非常激进,目前 GNOME 桌面已经对外宣布将移除对 X.Org 会话支持,默认使用 Wayland。X11 诞生于 1984 年,其设计符合当时的硬件环境、用户需求。随着技术的发展,出现了很多问题,特别是在桌面环境里面,需要 Xorg、窗口管理器、桌面环境组件三者之间进行复杂的交互、协作,导致演进困难。同时 X11 对于新特性的支持较差,Hi-DPI 体验糟糕,并且完全不支持 HDR。

    事实上,在 2023 年5 月 17 日,deepin(深度)社区在推出 deepin V23 Beta 版的同时也已经支持了 Wayland 桌面环境。在 V23 beta 版本中,DDE 试验性的开启了 Wayland 的支持,允许用户在 Wayland 协议下的桌面工作环境启动 。在 deepin V23 中支持 Wayland,是 DDE 的一个非常重要的特性,也是今后 deepin 团队的工作重点,以实现 X11 版本的完全替代,提升 DDE 的优质体验,在 Wayland 桌面环境领域达到领先水平。

    DDE(deepin desktop environment)是deepin(深度)社区自主开发的美观易用、极简操作的桌面环境,主要由桌面、启动器、任务栏、控制中心、窗口管理器等组成,系统中预装了深度特色应用,它既能让您体验到丰富多彩的娱乐生活,也可以满足您的日常工作需要。

    在此背景下,deepin 团队在今年 DDUC 大会了宣布将推出 Treeland 作为今后 DDE 所有功能开发的核心。Treeland 的底层基于 wlroots,并与 Qt Quick 进行了绑定,可同时兼顾两者的优点,wlroots 是 Wayland 生态中发展最迅速的开发库之一,具有功能丰富、演进速度快等优势,将其与 Qt Quick 结合则可以弥补 wlroots 在 GUI 能力方面的欠缺,极大的降低 Wayland 合成器的开发难度,实现 Vulkan、OpenGL ES2、软件渲染等多种渲染方式的无缝切换。

    pic4

    DDE 的新架构,将桌面环境各技术领域的组件进行了统一设计,允许桌面环境开发人员对其进行完全掌控,可轻松实现设备共享、多端无缝协同等高级功能。

    pic5

    未来,deepin(深度)社区将继续与最新技术保持同步,持续推进 DTK 的改进优化,也期待更多感兴趣的朋友加入到 deepin 开源社区中来,讨论更多内容,为推动生态发展贡献力量。

    Wednesday, November 8, 2023

    本文简述了deepin v23系统使用5G WWAN网卡连接互联网的方式,仅仅作为一个临时解决方案和技术验证使用,后期会在系统中内置此功能。

    第一步:安装modemmanager

    modemmanager是一个由freedesktop托管的项目,旨在在linux设备上运行调制解调器以让linux设备获得蜂窝无线网络连接的能力,所以我们第一步骤就是安装此软件。目前deepin已经支持此软件最新版本:

    sudo apt install modemmanager
    

    同时,你需要确保你的内核模块已经正确加载了你的WWAN驱动,你可以通过使用lspci -vvv查看详细信息

    在安装了modemmanager后,你可以通过mmcli 命令使用命令行与其交互,使用mmcli —help-all来获取全部的帮助选项。

    第二步:使用mmcli连接5G网络

    使用mmcli -L 获取你的wwan卡信息,返回的信息有三个内容,分别为:DBus地址、设备类型 、设备ID,其中DBus地址的最后一位为设备编号,你可以使用mmcli --modem=<设备编号>的方式查看设备详细支持信息。

    如果你的SIM卡设置了PIN锁,则需要在连接网络之前使用mmcli --modem=0 --sim=0 --pin=**** 的方式连接,而后我们就可以启动相关设备了:

    mmcli --modem=<设备编号> --enable
    

    然后你需要使用simple connect连接网络

    mmcli -m <设备编号> --simple-connect='apn=<apn名>,ip-type=ipv4v6'
    

    比如我的连接方式为

    mmcli -m 4 --simple-connect='apn=ctnet,ip-type=ipv4v6'
    

    此时你再使用mmcli -m <设备编号> 查看信息的时候 可以查看Bearer的信息,Bearer的DBus的最后一位为其编号。使用命令查看Bearer的信息:

    mmcli -m <设备编号> -b <Bearer编号>
    

    如果你看到Bearer相关信息,就几乎接近成功了:

      ------------------------------------
      General            |           path: /org/freedesktop/ModemManager1/Bearer/0
                         |           type: default
      ------------------------------------
      Status             |      connected: yes
                         |      suspended: no
                         |    multiplexed: no
                         |      interface: wwan0
                         |     ip timeout: 20
      ------------------------------------
      Properties         |            apn: ctnet
                         |        roaming: allowed
                         |        ip type: ipv4
                         |   allowed-auth: none, pap, chap, mschap, mschapv2, eap
                         |           user: ctnet@mycdma.cn
                         |       password: vnet.mobi
      ------------------------------------
      IPv4 configuration |         method: static
                         |        address: 10.122.58.19
                         |         prefix: 8
                         |        gateway: 10.122.58.17
                         |            dns: 202.103.24.68, 202.103.44.150
                         |            mtu: 1420
      ------------------------------------
      Statistics         |     start date: 2023-11-07T05:40:08Z
                         |       duration: 1260
                         |   uplink-speed: 1250000000
                         | downlink-speed: 4670000000
                         |       attempts: 1
                         | total-duration: 1260
    
    

    第三步:使用nmcli打开连接

    networkmanager对mm是有做支持,在你完成上述步骤之后,可以通过ip a命令查看,可以看到一个被down掉的接口,我们使用nmcli查看其详细信息:

    nmcli device show
    

    你就可以 看到一个以wwan开头的设备:

    GENERAL.DEVICE:                         wwan0mbim0
    GENERAL.TYPE:                           gsm
    GENERAL.HWADDR:                         (未知)
    GENERAL.MTU:                            1420
    GENERAL.STATE:                          100(已连接)
    GENERAL.CONNECTION:                     wwan0mbim0
    GENERAL.CON-PATH:                       /org/freedesktop/NetworkManager/ActiveConnection/3
    IP4.ADDRESS[1]:                         10.122.58.19/8
    IP4.GATEWAY:                            10.122.58.17
    IP4.ROUTE[1]:                           dst = 10.0.0.0/8, nh = 0.0.0.0, mt = 700
    IP4.ROUTE[2]:                           dst = 0.0.0.0/0, nh = 10.122.58.17, mt = 700
    IP4.DNS[1]:                             202.103.24.68
    IP4.DNS[2]:                             202.103.44.150
    IP6.GATEWAY:                            --
    
    

    使用下述命令打开此设备

    nmcli d connect <设备名>
    

    上述设备名就是以wwan开头的设备

    然后你就可以使用5G网络连接了

    Saturday, November 4, 2023

    介绍

    xtrace是一个用于跟踪分析X11图形协议通信的工具,它可以监控和记录X11服务器上的各种场景,以帮助开发人员诊断和调试与图形界面相关的问题。作为一款强大的工具,xtrace可用于逆向工程、调试分析、性能分析等领域。在Linux X11系统中,xtrace能够记录一个程序在运行时所发起的X11协议请求和XServer发送给程序的事件,以及这些调用的参数。这对于在不阅读源码情况排查程序中的问题、理解程序行为、分析性能瓶颈以及进行协议审计都非常有用。笔者在工作过程中使用该工具深度剖析过腾讯会议、simplescreenrecorder等应用程序的实现,在没有阅读代码的前提下可以获得软件录屏的工作流程,配合阅读常规的X11录屏代码,可分析出其部分工功能异常原因,这些经验在xwayland适配X11应用程序截图录屏项目中通过实战解决了一系列问题。

    本文主要介绍的是X11协议的监视工具,希望您在读完本篇文章后可以对如何监控X11协议有比较深刻的认知,在工作中经常会碰到一些X11应用程序运行时功能异常,在没有应用程序源码的情况下通过xtrace进行调试是一个不错的选择,希望在阅读完这篇文章后,能丰富您的调试技巧。祝您阅读愉快!

    xtrace的安装和使用非常简单,打开终端像如下输入命令即可启动工具:

    // UOS上安装xtrace,deepin上没有可以在http://snapshot.debian.org/上下载
    sudo apt install xtrace
    
    // 运行之后协议电报内容会存储在/tmp/dde-calendar.xtrace中
    xtrace -n -o /tmp/dde-calendar.xtrace dde-calendar	
    
    命令行选项含义或用途
    –display, -d用于ssh远程调试,例如:xtrace -d :0 dde-calendar
    –outfile, -o用于将通信内容转存到磁盘,例如:xtrace -o /tmp/x.log dde-calendar
    –stopwhendone, -s进程退出后停止xtrace,例如:xtrace -s dde-calendar

    工作原理

    如图1所示,X11其主要有两种通信方式,在同一个计算机内主要使用unix domain socket进行通信;在不同计算机之间使用TCP/IP通信。只需要“截获”通信消息,将其转为易于读取的格式输出即可达到监控目的。这是xtrace工作的基本原理。

    图1. 不同客户端使用同一个XServer显示器框架图

    如下图2是X11窗口创建的基本流程,因为篇幅限制,图中只绘制了基础的请求和事件,但X11协议远比图中绘制的复杂,笔者在此只介绍一下基础的协议交互模型,方便在读者在查看xtrace追踪日志时有基础的认知。

    图2. X11协议交互流程

    总之xtrace是Xorg X Server自带的一个工具,通过分析xtrace的输出,你可以了解X Server是如何处理客户端应用程序的请求,以及可能的问题所在。xtrace 工具的作用:

    • 调试X问题:如果你遇到了与图形界面相关的问题,例如窗口无法正常显示、图形卡驱动问题等,你可以使用xtrace来捕获X Server的活动,帮助你找出问题所在。
    • 性能分析:xtrace可以记录X Server内部的操作,帮助你分析系统的图形性能,找出潜在的瓶颈。
    • 理解X协议交互:X Server与客户端应用程序之间的通信是通过X协议进行的。xtrace可以捕获这些通信,帮助你理解应用程序与X Server之间的交互方式。
    • 功能逆向:对于依赖X11协议的软件无法查看源码,可以通过观察通信协议,逆向其部分核心功能的实现 请注意,xtrace的输出可能会非常详细,因此在使用时需要注意过滤和分析输出,以便关注于你感兴趣的信息。

    X11在文件系统中暴露了通信用的socket套接字文件,这使得第三方应用程序可以监控套接字文件从而“窥视"X11客户端和服务端之间的通信,这一技术点是xtrace工作的主要基础。

    // X11 socket套接字文件在文件系统中的位置
    ls /tmp/.X11-unix
    X0  X9
    

    如下日志所示xtrace工作的本质就是不断地获取wrote和received的数据然后将其解析为易于阅读的描述语言打印出来。

    // 全量日志
    001:>:received 32 bytes
    001:>:09dc:32: Reply to InternAtom: atom=0x250("_NET_KDE_COMPOSITE_TOGGLING")
    001:>:wrote 32 bytes
    001:>:received 32 bytes
    001:>:09dc: Event XKEYBOARD-XkbEvent(85) type=2 time=0x018d8071 device=0x03 not-yet-supported=0x10,0x00,0x00,0x10,0x01,0x00,0x00,0x00,0x00,0x01,0x90,0x10,0x10,0x10,0x90,0x00,0x01,0x90,0x11,0x00,0x00,0x87,0x05;
    001:>:wrote 32 bytes
    001:>:received 32 bytes
    001:>:09dc: Event XKEYBOARD-XkbEvent(85) type=2 time=0x018d8072 device=0x03 not-yet-supported=0x10,0x00,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x10,0x10,0x10,0x10,0x00,0x01,0x90,0x11,0x00,0x00,0x87,0x05;
    001:>:wrote 32 bytes
    001:>:received 32 bytes
    001:>:09dc: Event XKEYBOARD-XkbEvent(85) type=2 time=0x018d8073 device=0x03 not-yet-supported=0x10,0x00,0x00,0x10,0x01,0x00,0x00,0x00,0x00,0x01,0x90,0x10,0x10,0x10,0x90,0x00,0x01,0x90,0x11,0x00,0x00,0x87,0x05;
    

    其工作流程大致如下:

    • 通过套接字地址族AF_INET判断是tcp通信还是本地domain socket通信,然后通过generateSocketName或者calculateTCPport拿到addr相关的信息
    • 进入mainqueue死循环读取socket通信数据
    • 通过parse_server(c)翻译Event事件通信,然后打印输出
    • parse_client(c)翻译Requst请求通信,然后打印输出

    相关的代码调用堆栈如下:

    Breakpoint 1, startline (c=0x4f8270, d=TO_SERVER, format=0x417a71 "%04x:%3u: Request(%hhu): %s ") at parse.c:52
    52              if( (print_timestamps || print_reltimestamps)
    (gdb) bt
    #0  startline (c=0x4f8270, d=TO_SERVER, format=0x417a71 "%04x:%3u: Request(%hhu): %s ") at parse.c:52
    #1  0x000000000040b5f5 in print_client_request (c=0x4f8270, bigrequest=false) at parse.c:1692
    #2  0x000000000040cc2d in parse_client (c=0x4f8270) at parse.c:1996
    #3  0x0000000000403b4a in mainqueue (listener=4) at main.c:406
    #4  0x00000000004045d4 in main (argc=4, argv=0x7fffffffdef8) at main.c:706
    (gdb) c
    Continuing.
    
    Breakpoint 1, startline (c=0x4f8270, d=TO_CLIENT, format=0x417b32 "%04x:%u: Reply to %s: ") at parse.c:52
    52              if( (print_timestamps || print_reltimestamps)
    (gdb) bt
    #0  startline (c=0x4f8270, d=TO_CLIENT, format=0x417b32 "%04x:%u: Reply to %s: ") at parse.c:52
    #1  0x000000000040c1f3 in print_server_reply (c=0x4f8270) at parse.c:1859
    #2  0x000000000040d0e5 in parse_server (c=0x4f8270) at parse.c:2064
    #3  0x00000000004035ed in mainqueue (listener=4) at main.c:336
    #4  0x00000000004045d4 in main (argc=4, argv=0x7fffffffdef8) at main.c:706
    
    // connect socket
    Breakpoint 2, generateSocketName (addr=0x7fffffffdd30, display=9) at x11common.c:114
    114             snprintf(addr->sun_path,sizeof(addr->sun_path),"/tmp/.X11-unix/X%d",display);
    (gdb) p display 
    $5 = 9
    (gdb) c
    Continuing.
    [Detaching after fork from child process 7682]
    Got connection from unknown(local)
    
    Breakpoint 2, generateSocketName (addr=0x7fffffffd9d0, display=0) at x11common.c:114
    114             snprintf(addr->sun_path,sizeof(addr->sun_path),"/tmp/.X11-unix/X%d",display);
    (gdb) p display 
    $6 = 0
    (gdb) bt
    #0  generateSocketName (addr=0x7fffffffd9d0, display=0) at x11common.c:114
    #1  0x0000000000404c92 in connectToServer (displayname=0x7fffffffe363 ":0", family=1, hostname=0x0, display=0) at x11client.c:77
    #2  0x0000000000402766 in acceptConnection (listener=3) at main.c:95
    #3  0x0000000000403edd in mainqueue (listener=3) at main.c:452
    #4  0x00000000004045d4 in main (argc=2, argv=0x7fffffffdf18) at main.c:706
    (gdb) 
    

    协议分析

    xtrace通过拦截X11协议通信来进行分析。它捕获传输到X服务器的请求以及服务器对这些请求的响应,将他们解析化以日志输出的形式打印出来。所以本质来说协议分析指的是X11协议交互分析,需要对 X11相关的协议做到非常了解,即每个协议有什么功能,在xcb中是如何处理,在xserver中又是如何处理。受限于篇幅,笔者在此不会阐述所有的协议分析,而是拿我们平时常用的一些软件做一些分析和介绍。

    日志流关键字含义或用途
    Present-Request(148,1)客户端用于GLX等送显
    Request(1): CreateWindow请求创建X窗口,shm、glx都有
    GLX-Request(152,3): glXCreateContext请求创建GLX上下文,可以用来判断客户端是否GLX应用
    MIT-SHM-Request(130,3): PutImageX11 shm客户端请求更新图像
    Event XKEYBOARD-XkbEvent(85)xserver键盘事件传递给X客户端
    Event Generic(35) XInputExtension鼠标事件,后面带着ButtonPress、ButtonRelease、Motion等
    Request(36): GrabServergrab请求
    Request(37): UngrabServer解除grab请求
    DeleteProperty请求删除X11窗口的一些属性
    ChangeProperty改变X11窗口的一些属性
    PropertyNotify窗口属性改变发送事件通知客户端
    MIT-SHM-Request(130,4): GetImageshm方式获取屏幕图像
    Request(62): CopyArea复制屏幕一部分区域图像(离屏)
    Request(53): CreatePixmap创建图像(离屏)

    上述表格中只是介绍了常见部分的协议,X协议非常的丰富,完整的模块如下所示:

    • XCB BigRequests API 发送和接受超过请求长度(65535字节)限制的数据
    • XCB Composite API 支持窗口合成,将多个窗口的内容合成最终的显示图像
    • XCB Damage API 用于跟踪窗口或者绘图上下文的可视区域的改变
    • XCB DPMS API 用于管理显示器的电源管理功能,控制显示器电源模式
    • XCB DRI2 API 支持直接渲染和硬件加速
    • XCB DRI3 API 支持直接渲染和硬件加速,对DRI2的扩展和改进
    • XCB Glx API 提供GLX接口创建OpenGL上下文,进行图形渲染和交互
    • XCB Present API 实现高性能的图像呈现在屏幕上
    • XCB RandR API 管理显示器的分辨率、屏幕方向和显示器布局等
    • XCB Record API 记录X服务器的事件流,以便进行调试、分析等
    • XCB Render API xrender绘图
    • XCB ScreenSaver API 管理屏幕保护程序的行为和状态
    • XCB Shape API 用于创建和操作不规则窗口的形状
    • XCB Shm API 应用程序能够通过共享内存的方式高效的传输图像
    • XCB Sync API 实现同步操作和时间戳的管理,确保预期的时序
    • XCB XCMisc API 提供额外的杂项函数和功能,获取一些服务器信息
    • XCB Core API 核心部分,提供了基本通信功能和操作
    • XCB Xevie API 拦截和处理X服务器上的事件流
    • XCB XF86Dri API 直接渲染,直接访问图形硬件,提高图形性能和效率
    • XCB XFixes API 增强功能:光标、窗口形状、窗口属性、窗口位置
    • XCB Xinerama API 用于管理多个显示器的配置和操作
    • XCB Input API 处理输入事件(如键盘、鼠标、触摸屏等)交互
    • XCB xkb API 配置与操作键盘相关的设置,键盘布局和状态
    • XCB XPrint API 直接从应用程序打印文档、图像和其他内容
    • XCB API xcb基础功能
    • XCB SELinux API 在应用程序中管理selinux安全策略和执行安全操作
    • XCB Test API 测试协议,常用于远程控制
    • XCB Xv API xvideo相关,用于视频渲染、视频加速、获取视频信息
    • XCB XvMC API 在GPU上执行视频解码和运动补偿功能 笔者在工作中也有时对xtrace日志无法分析到有用信息,此时会查看XCB帮助文档,通过分析对比查找到相关的xcb函数,从而逐渐熟悉X11协议。

    总结

    总体来说xtrace是一个有用的工具,对于笔者来说经常会接触生态软件的图形显示问题,对于少部分软件开发商不愿意提供代码和问题复现最小demo,此时其软件对于笔者来说是一个黑盒,当问题边界靠近X相关的技术时,笔者会使用xtrace去详细分析该软件的详细功能,往往这可以在底层剖析软件的显示工作方式,配合系统上相关的组建库代码,可以方便地处理客户的紧急问题。 笔者编写这篇文档是希望可以鼓励更多的同事使用xtrace,这个工具可以帮助你熟悉X11的工具原理,同时也可以理解像qt、gtk等UI库的底层实现,在定位系统复制粘贴、图形显示、拖拽、窗口相关的问题时可以提供更加底层的日志,方便更加精准地定位问题根因!

    参考资料

    Wednesday, November 1, 2023

    介绍

    随着Linux图形发展的需求增长,需要更多的图形功能和性能,这促成了Direct Rending Infastructure(DRI)和Direct Rendering Manager(DRM)的出现,DRM是一个内核级别的子系统,提供了对图形设备的管理和访问控制,它允许用户空间的图形库和应用程序直接访问GPU进行渲染,而无需操作系统介入,它的主要作用如下:

    • 图形硬件管理:DRM负责管理图形硬件资源,如显存、显卡以及显示设备。它允许内核与这些硬件设备进行通信和控制。
    • 图形加速:提供对硬件加速功能的支持,例如3D渲染和视频加速。这使得图形渲染更加高效和流畅。
    • 多显示器支持:允许多个显示器的管理和配置,包括扩展桌面、镜像模式等。
    • 用户空间接口:提供了用户空间图形库(如Mesa 3D等)和应用程序与内核中DRM子系统进行通信的接口。
    • 支持不同的图形协议:DRM在支持X Window System(X11)的同时,也能与新一代图形协议,如Wayland,进行整合。

    这种更好的性能、3D加速和对硬件的更直接访问使得它可以支持X Window System和Wayland的图形输出和管理。其现状如下:

    • 持续发展:随着硬件技术和图形需求的不断演进,DRM在Linux内核中也在不断发展。新的功能和改进不断加入,以满足新一代图形硬件和应用程序的需求。
    • 多厂商支持:DRM的发展得到了多家硬件厂商的支持,这包括AMD、Intel、NVIDIA等,它们为Linux内核开发并贡献了各自硬件的DRM驱动程序。
    • 支持新技术:DRM也在逐步支持新的图形技术,例如,对于低功耗图形处理的优化、支持更高分辨率、HDR显示以及机器学习和AI加速等方面的改进。
    • Wayland和DRM集成:Wayland作为新一代图形显示协议,与DRM更为紧密地整合,提供更为直接和高效的图形渲染和显示方式。

    总体而言,现如今很多Linux发行版上图形服务器后端都是对接的drm实现图形输出和相关控制,DRM在Linux图形子系统中扮演着关键角色,随着技术和需求的不断发展,它持续进化以适应新的硬件和应用场景,为Linux系统提供了强大的图形渲染支持。

    图1. DRM时间线

    DRM剖析

    Framebuffer && DRM

    在谈drm之前必须先了解一下它的前辈Framebuffer,Framebuffer是指一块内存区域,用户存储显示设备上每个像素的颜色信息。在早期的计算机系统中,操作系统直接将图形数据写入这块显存区域,这被称为直接显存访问(Direct Framebuffer Access),这种方式简单、直接,但随着图形复杂程度增加和硬件发展,它变得难以满足高分辨率和复杂图形的需求,随着事件的推移,由DRM提供了更多先进的功能,它追加能替代了Framebuffer,并称为linux图形系统的主要组成部分(图形框架演变如下图2)。然而Frambuffer仍然在某些特定场景下有其用户之地,尤其是在一些嵌入式系统和特定的硬件上。 总的来说,Framebuffer到DRM的发展代表了图形处理在Linux系统中的进步和演进,从最初的直接访问到高级的硬件加速和更加强大的图形功能。drm为显示硬件的适配和图形的发展贡献了很大的力量。

    图2. Framebuffer到DRM

    drm-plane

    drm_plane本质是对显示控制器中scanout硬件的抽象。简单来说,给定一个plane,可以让其与一个framebuffer关联表示进行scanout的数据,同时控制scanout时进行的额外操作,比如colorspace的改变,旋转、拉伸、偏移等操作。drm_plane是与硬件强相关的,显示控制器支持的plane是固定的,其支持的功能也是由硬件决定的。所有的drm_plane必为三种类型之一:

    • Primary - 主plane,一般控制整个显示器的输出。CRTC必须要有一个这样的plane。
    • Curosr - 表示鼠标光标图层(可选),一般启用的话称其为开启硬件光标,代码如下,流程如下图3。
    • Overlay - 叠加plane,可以在主plane上叠加一层输出,可选。
    ....
    if (cursor != NULL && drm_connector_is_cursor_visible(conn)) {
    	struct wlr_drm_fb *cursor_fb = get_next_cursor_fb(conn);
    	if (cursor_fb == NULL) {
    		wlr_drm_conn_log(conn, WLR_DEBUG, "Failed to acquire cursor FB");
    		return false;
    	}
    
    	drmModeFB *drm_fb = drmModeGetFB(drm->fd, cursor_fb->id);
    	if (drm_fb == NULL) {
    		wlr_drm_conn_log_errno(conn, WLR_DEBUG, "Failed to get cursor "
    			"BO handle: drmModeGetFB failed");
    		return false;
    	}
    	uint32_t cursor_handle = drm_fb->handle;
    	uint32_t cursor_width = drm_fb->width;
    	uint32_t cursor_height = drm_fb->height;
    	drmModeFreeFB(drm_fb);
        // 设置硬件光标,更新光标图像buffer
    	int ret = drmModeSetCursor(drm->fd, crtc->id, cursor_handle,
    			cursor_width, cursor_height);
    	int set_cursor_errno = errno;
    	if (drmCloseBufferHandle(drm->fd, cursor_handle) != 0) {
    		wlr_log_errno(WLR_ERROR, "drmCloseBufferHandle failed");
    	}
    	if (ret != 0) {
    		wlr_drm_conn_log(conn, WLR_DEBUG, "drmModeSetCursor failed: %s",
    			strerror(set_cursor_errno));
    		return false;
    	}
    
        // 移动硬件光标位置
    	if (drmModeMoveCursor(drm->fd,
    			crtc->id, conn->cursor_x, conn->cursor_y) != 0) {
    		wlr_drm_conn_log_errno(conn, WLR_ERROR, "drmModeMoveCursor failed");
    		return false;
    	}
    } else {
    	if (drmModeSetCursor(drm->fd, crtc->id, 0, 0, 0)) {
    		wlr_drm_conn_log_errno(conn, WLR_DEBUG, "drmModeSetCursor failed");
    		return false;
    	}
    }
    ....
    

    图3. drm-plane框架图

    图4. 多drm-plane合成流程图

    在笔者看来,drm-plane与layer的概念类似,只是更加偏向硬件底层概念,开发者可以通过控制显示相关的参数将plane投显到显示器上任何区域(参考图5),这种映射机制提供了非常方便地偏移、缩放方法,如果在这一层上实现移动和缩放等动画,效率将会比使用OpenGL等API制作高,将频繁变化的图像抽象工作在一个单独的drm-plane也可以达到提高性能和省电的目的。

    名字描述
    SRC_X当前framebuffer crop区域的起始偏移x坐标
    SRC_Y当前framebuffer crop区域的起始偏移y坐标
    SRC_W当前framebuffer crop区域的宽度
    SRC_H当前framebuffer crop区域的高度
    CRTC_X屏幕显示区域的起始偏移x坐标
    CRTC_Y屏幕显示区域的起始偏移y坐标
    CRTC_W屏幕显示区域的宽度
    CRTC_H屏幕显示区域的高度

    表1. drm-plane部分属性表

    图5. drm-plane送显参数控制

    crtc(Cathode Ray Tube Controller)

    CRTC 是显示控制器中的一个重要部分,主要用于管理显示设备的扫描和刷新。CRTC 负责生成视频信号的定时和同步,控制屏幕上像素的扫描和刷新,以确保正确的图像显示。它管理像素的输出到屏幕上的确切位置,以及刷新率、分辨率等显示参数。在现代的图形处理中,CRTC通常由图形处理单元(GPU)或显示控制器中的专用部分来控制,以确保正确的图像输出。

    图6. crtc框架图

    drm legacy链路通过调用drmModePageFlip请求crtc更新显示器图像,其代码如下,流程如图6所示:

    static bool legacy_crtc_commit(struct wlr_drm_connector *conn,
    		const struct wlr_drm_connector_state *state,
    		uint32_t flags, bool test_only) {
    	....
    
    	if (flags & DRM_MODE_PAGE_FLIP_EVENT) {
    		uint32_t page_flags = DRM_MODE_PAGE_FLIP_EVENT;
    		if (flags & DRM_MODE_PAGE_FLIP_ASYNC) {
    			page_flags |= DRM_MODE_PAGE_FLIP_ASYNC;
    		}
    
    		if (drmModePageFlip(drm->fd, crtc->id, fb_id,
    				page_flags, drm)) {
    			wlr_drm_conn_log_errno(conn, WLR_ERROR, "drmModePageFlip failed");
    			return false;
    		}
    	}
    
    	return true;
    }
    

    图7. buffer更新流程图

    encoder

    编码器从CRTC获取像素数据,并将其转换为适合任何连接器的格式。在某些设备上,CRTC可以将数据发送到多个编码器。在这种情况下,两个编码器将从同一扫描输出缓冲区接收数据,从而在连接到每个编码器的连接器上产生“克隆”显示配置。 在Linux系统中,DRM(Direct Rendering Manager)是用于处理图形显示的子系统,负责在用户空间和图形硬件之间提供接口。DRM编码器在Linux中扮演了关键角色,主要用于对图形和视频内容进行编码、解码、处理和显示。在DRM框架中,编码器的作用包括但不限于:

    • 视频压缩和解压缩: 编码器负责对视频内容进行压缩,以减小数据量并更有效地传输和存储视频流。解码器用于解压缩视频内容,以便图形硬件能够正确地渲染和显示内容。
    • 图形处理和渲染: DRM编码器能够处理图形和视频流,进行渲染和合成,然后将图像数据发送到显示设备以在屏幕上显示。
    • 硬件加速: DRM编码器还可以利用硬件加速功能,以便更快地处理图形和视频内容。这有助于提高性能和效率,特别是对于高分辨率视频或图形内容的处理。
    • 支持多种编解码标准: DRM编码器能够支持多种视频编解码标准,如H.264、H.265等,确保对不同格式的视频流进行正确的处理。 在Linux系统中,DRM编码器与图形驱动程序、图形处理单元(GPU)和显示设备等硬件密切相关。它允许Linux系统管理和控制图形硬件,处理视频流并在屏幕上显示图像,同时也与DRM系统中的访问控制和安全性机制集成,确保受保护内容的安全传输和显示。

    图8. encoder框架图

    connector

    DRM connectors是用于管理和描述图形设备的连接器或端口。它们提供了对显示设备的连接和属性描述。在图形系统中,这些连接器可以代表诸如HDMI、DisplayPort、DVI等物理连接接口,而它们也可以描述内部连接,例如LCD panels。DRM connectors 在 Linux 中的作用包括:

    • 显示设备连接描述: DRM connectors 提供了关于显示设备的物理连接信息,比如类型(HDMI、VGA、DisplayPort等)、连接状态以及支持的分辨率和刷新率等信息。
    • 动态连接管理: 它们可以监测连接和断开事件。当显示设备插入或拔出时,DRM connectors 可以检测到这些变化,并通知系统相应的变化。这使系统能够动态调整图形配置以适应新的连接或断开状态。
    • 多显示器支持: DRM connectors 允许系统管理多个显示器的连接和配置。这使得可以同时使用多个显示器或监视器,或者在不同的显示设备上显示不同的内容。
    • 显示属性查询和配置: 通过 DRM connectors,可以查询连接器支持的分辨率、刷新率以及其他显示属性。这使系统可以调整和配置图形设备,以最佳方式显示图形内容。

    总体来说,DRM connectors 在 Linux 中的作用是管理显示设备的物理连接、状态和属性,使系统能够动态适应不同的显示设备、支持多个显示器,并提供相应的配置信息,以便正确地显示图形内容。

    图9. connector框架图

    framebuffer

    framebuffer是一个重要概念。Framebuffers是用于存储屏幕上每个像素的颜色和其他相关信息的内存区域。在DRM中,framebuffer提供了一个抽象接口,允许用户空间程序访问和操作显存,以便渲染图形数据。其作用包括:

    • 图形数据存储: Framebuffers提供一个区域,用于存储图形数据,包括每个像素的颜色、透明度等信息。这些数据构成了屏幕上显示的图像。
    • 直接访问屏幕数据: 通过framebuffers,用户空间程序或操作系统内核能够直接访问和操作图形数据,而无需经过额外的复杂处理。
    • 硬件抽象层: Framebuffers提供了一个硬件无关的抽象接口,这意味着不同的图形设备和硬件都可以通过相同的接口进行访问和操作。
    • 显示图像: Framebuffers存储了最终用于在屏幕上显示的图像数据。图形数据在 framebuffer中进行组织和处理,然后通过显示控制器输出到屏幕。

    在DRM 中,framebuffers通常与CRTC和显示控制器等组件一起工作。framebuffers提供的抽象层允许操作系统或应用程序以统一的方式对图形数据进行处理和管理,无论具体的硬件设备是什么样的。总之,framebuffers 在 Linux 的 DRM 中提供了一个接口和数据结构,用于存储、操作和管理图形数据,使其能够在屏幕上正确地显示图像。

    图10. framebuffer内存结构图

    drm调试工具

    drm_info

    用于转储有关 DRM 设备信息的小实用程序

    drm_info
    
    Node: /dev/dri/card1
    ├───Driver: i915 (Intel Graphics) version 1.6.0 (20201103)
    │   ├───DRM_CLIENT_CAP_STEREO_3D supported
    │   ├───DRM_CLIENT_CAP_UNIVERSAL_PLANES supported
    │   ├───DRM_CLIENT_CAP_ATOMIC supported
    │   ├───DRM_CLIENT_CAP_ASPECT_RATIO supported
    │   ├───DRM_CLIENT_CAP_WRITEBACK_CONNECTORS supported
    │   ├───DRM_CAP_DUMB_BUFFER = 1
    │   ├───DRM_CAP_VBLANK_HIGH_CRTC = 1
    │   ├───DRM_CAP_DUMB_PREFERRED_DEPTH = 24
    │   ├───DRM_CAP_DUMB_PREFER_SHADOW = 1
    │   ├───DRM_CAP_PRIME = 3
    │   ├───DRM_CAP_TIMESTAMP_MONOTONIC = 1
    │   ├───DRM_CAP_ASYNC_PAGE_FLIP = 1
    │   ├───DRM_CAP_CURSOR_WIDTH = 256
    │   ├───DRM_CAP_CURSOR_HEIGHT = 256
    │   ├───DRM_CAP_ADDFB2_MODIFIERS = 1
    │   ├───DRM_CAP_PAGE_FLIP_TARGET = 0
    │   ├───DRM_CAP_CRTC_IN_VBLANK_EVENT = 1
    │   ├───DRM_CAP_SYNCOBJ = 1
    │   └───DRM_CAP_SYNCOBJ_TIMELINE = 1
    ├───Device: PCI 8086:46aa Intel Corporation Alder Lake-UP4 GT2 [Iris Xe Graphics]
    │   └───Available nodes: primary, render
    ├───Framebuffer size
    │   ├───Width: [0, 16384]
    │   └───Height: [0, 16384]
    ├───Connectors
    │   ├───Connector 0
    │   │   ├───Object ID: 236
    │   │   ├───Type: eDP
    │   │   ├───Status: connected
    │   │   ├───Physical size: 290x180 mm
    │   │   ├───Subpixel: unknown
    │   │   ├───Encoders: {0}
    │   │   ├───Modes
    │   │   │   └───2880x1800@60.00 preferred driver phsync nvsync 
    │   │   └───Properties
    │   │       ├───"EDID" (immutable): blob = 266
    │   │       ├───"DPMS": enum {On, Standby, Suspend, Off} = On
    │   │       ├───"link-status": enum {Good, Bad} = Good
    │   │       ├───"non-desktop" (immutable): range [0, 1] = 0
    │   │       ├───"TILE" (immutable): blob = 0
    │   │       ├───"CRTC_ID" (atomic): object CRTC = 80
    │   │       ├───"scaling mode": enum {Full, Center, Full aspect} = Full aspect
    │   │       ├───"panel orientation" (immutable): enum {Normal, Upside Down, Left Side Up, Right Side Up} = Normal
    │   │       ├───"Broadcast RGB": enum {Automatic, Full, Limited 16:235} = Automatic
    │   │       ├───"max bpc": range [6, 12] = 10
    │   │       ├───"Colorspace": enum {Default, BT709_YCC, XVYCC_601, XVYCC_709, SYCC_601, opYCC_601, opRGB, BT2020_CYCC, BT2020_RGB, BT2020_YCC, DCI-P3_RGB_D65, RGB_WIDE_FIXED, RGB_WIDE_FLOAT, BT601_YCC} = Default
    │   │       ├───"HDR_OUTPUT_METADATA": blob = 0
    │   │       └───"vrr_capable" (immutable): range [0, 1] = 0
    │   ├───Connector 1
    │   │   ├───Object ID: 245
    │   │   ├───Type: DisplayPort
    │   │   ├───Status: disconnected
    │   │   ├───Encoders: {1}
    │   │   └───Properties
    │   │       ├───"EDID" (immutable): blob = 0
    │   │       ├───"DPMS": enum {On, Standby, Suspend, Off} = Off
    │   │       ├───"link-status": enum {Good, Bad} = Good
    │   │       ├───"non-desktop" (immutable): range [0, 1] = 0
    │   │       ├───"TILE" (immutable): blob = 0
    │   │       ├───"CRTC_ID" (atomic): object CRTC = 0
    │   │       ├───"subconnector" (immutable): enum {Unknown, VGA, DVI-D, HDMI, DP, Wireless, Native} = Unknown
    │   │       ├───"audio": enum {force-dvi, off, auto, on} = auto
    │   │       ├───"Broadcast RGB": enum {Automatic, Full, Limited 16:235} = Automatic
    │   │       ├───"max bpc": range [6, 12] = 12
    │   │       ├───"Colorspace": enum {Default, BT709_YCC, XVYCC_601, XVYCC_709, SYCC_601, opYCC_601, opRGB, BT2020_CYCC, BT2020_RGB, BT2020_YCC, DCI-P3_RGB_D65, RGB_WIDE_FIXED, RGB_WIDE_FLOAT, BT601_YCC} = Default
    │   │       ├───"HDR_OUTPUT_METADATA": blob = 0
    │   │       ├───"vrr_capable" (immutable): range [0, 1] = 0
    │   │       ├───"Content Protection": enum {Undesired, Desired, Enabled} = Undesired
    │   │       └───"HDCP Content Type": enum {HDCP Type0, HDCP Type1} = HDCP Type0
    │   └───Connector 2
    │       ├───Object ID: 258
    │       ├───Type: DisplayPort
    │       ├───Status: disconnected
    │       ├───Encoders: {6}
    │       └───Properties
    │           ├───"EDID" (immutable): blob = 0
    │           ├───"DPMS": enum {On, Standby, Suspend, Off} = Off
    │           ├───"link-status": enum {Good, Bad} = Good
    │           ├───"non-desktop" (immutable): range [0, 1] = 0
    │           ├───"TILE" (immutable): blob = 0
    │           ├───"CRTC_ID" (atomic): object CRTC = 0
    │           ├───"subconnector" (immutable): enum {Unknown, VGA, DVI-D, HDMI, DP, Wireless, Native} = Unknown
    │           ├───"audio": enum {force-dvi, off, auto, on} = auto
    │           ├───"Broadcast RGB": enum {Automatic, Full, Limited 16:235} = Automatic
    │           ├───"max bpc": range [6, 12] = 12
    │           ├───"Colorspace": enum {Default, BT709_YCC, XVYCC_601, XVYCC_709, SYCC_601, opYCC_601, opRGB, BT2020_CYCC, BT2020_RGB, BT2020_YCC, DCI-P3_RGB_D65, RGB_WIDE_FIXED, RGB_WIDE_FLOAT, BT601_YCC} = Default
    │           ├───"HDR_OUTPUT_METADATA": blob = 0
    │           ├───"vrr_capable" (immutable): range [0, 1] = 0
    │           ├───"Content Protection": enum {Undesired, Desired, Enabled} = Undesired
    │           └───"HDCP Content Type": enum {HDCP Type0, HDCP Type1} = HDCP Type0
    ├───Encoders
    │   ├───Encoder 0
    │   │   ├───Object ID: 235
    │   │   ├───Type: TMDS
    │   │   ├───CRTCS: {0, 1, 2, 3}
    │   │   └───Clones: {0}
    │   ├───Encoder 1
    │   │   ├───Object ID: 244
    │   │   ├───Type: TMDS
    │   │   ├───CRTCS: {0, 1, 2, 3}
    │   │   └───Clones: {1}
    
    .........
    
    ├───CRTCs
    │   ├───CRTC 0
    │   │   ├───Object ID: 80
    │   │   ├───Legacy info
    │   │   │   ├───Mode: 2880x1800@60.00 preferred driver phsync nvsync 
    │   │   │   └───Gamma size: 256
    │   │   └───Properties
    │   │       ├───"ACTIVE" (atomic): range [0, 1] = 1
    │   │       ├───"MODE_ID" (atomic): blob = 271
    │   │       │   └───2880x1800@60.00 preferred driver phsync nvsync 
    │   │       ├───"OUT_FENCE_PTR" (atomic): range [0, UINT64_MAX] = 0
    │   │       ├───"VRR_ENABLED": range [0, 1] = 0
    │   │       ├───"SCALING_FILTER": enum {Default, Nearest Neighbor} = Default
    │   │       ├───"DEGAMMA_LUT": blob = 0
    │   │       ├───"DEGAMMA_LUT_SIZE" (immutable): range [0, UINT32_MAX] = 129
    │   │       ├───"CTM": blob = 0
    │   │       ├───"GAMMA_LUT": blob = 270
    │   │       └───"GAMMA_LUT_SIZE" (immutable): range [0, UINT32_MAX] = 1024
    
    ........
    
    
    └───Planes
        ├───Plane 0
        │   ├───Object ID: 31
        │   ├───CRTCs: {0}
        │   ├───Legacy info
        │   │   ├───FB ID: 237
        │   │   │   ├───Object ID: 237
        │   │   │   ├───Size: 2880x1800
        │   │   │   ├───Format: ARGB2101010 (0x30335241)
        │   │   │   ├───Modifier: I915_FORMAT_MOD_Y_TILED (0x100000000000002)
        │   │   │   └───Planes:
        │   │   │       └───Plane 0: offset = 0, pitch = 11520 bytes
        │   │   └───Formats:
        │   │       ├───C8 (0x20203843)
        │   │       ├───RGB565 (0x36314752)
        │   │       ├───XRGB8888 (0x34325258)
        │   │       ├───XBGR8888 (0x34324258)
        │   │       ├───ARGB8888 (0x34325241)
        │   │       ├───ABGR8888 (0x34324241)
        
        .....
    

    drm_monitor

    用于监控 KMS 状态的 CLI 工具

    drm_monitor -d /dev/dri/card1
    CRTC 80: seq=30095325875 ns=3076787603594 delta_ns=16668012 Hz=59.995157
    CRTC 131: seq=0 ns=0 delta_ns=0 Hz=0.000000
    CRTC 182: seq=0 ns=0 delta_ns=0 Hz=0.000000
    CRTC 233: seq=0 ns=0 delta_ns=0 Hz=0.000000
    

    参考资料

    Tuesday, October 31, 2023

    一、背景

    近期,我们收到用户反馈,在使用 deepin 系统过程中遇到了 CPU 功耗过高导致的设备发热、续航较差情况,而用户在这些负载场景下,CPU 的占用往往不高。为了解决这个痛点,统信软件开源社区中心特别成立专项计划,对于 deepin 的电源进行专项优化,本文旨在针对此问题根因进行分析于说明。

    在对电源进行专项优化之前,我们首先对 deepin 系统进行了深入的调查和分析,以了解其在负载场景下的实际运行情况。经过对 CPU 使用率和功耗的监测,我们发现了一个令人惊讶的事实:尽管在高负载场景下 CPU 的占用率不高,但其功耗却持续升高,最终导致设备发热并影响续航。也就是说,我们前期做的省电优化工作,不仅无效,还起了反作用(具体情况作者将在下文作出仔细说明)。

    二、问题

    1. 内核

    最开始发现问题的地方在内核。有用户将我们的内核和 ubuntu 的内核进行对比后发现,虽然我们的系统和 ubuntu 系统的性能差不多,但是在发热和续航上,我们较 ubuntu 落后较多。有用户在 deepin 系统上使用 ubuntu 和其他开源 linux 发行版的配置文件分别编译内核,发现 deepin 的主要问题存在于发热控制之上。我们的测试同事高度重视这一社区反馈,遂对社区用户反馈的问题进行复现,佐证了这一现象。

    对于这种问题,我们立即联系了内核研发部的同事,并邀请部分对内核配置有一定研究的社区用户共同参与。在大家的合力排查下,我们发现,deepin v23 中提供的 HWE 内核存在部分 debug 和无用的内核选项被开启的情况,并且部分节电功能实际未能获得启用,这些都在一定程度上导致了 deepin v23 的续航表现不佳。

    2. 系统

    在系统层面,我重新审视了 dde-daemon 提供的电源调度模块,并且对比内核文档提供的文件接口,分析我们用户使用的电源模式,发现其中存在可以优化的空间。这将是本文着重讲解的内容之一。

    三、前置知识

    1.ACPI

    ACPI 是 Advanced Configuration and Power Interface 的缩写,是一种计算机硬件和操作系统之间交换能源相关信息的接口规范。它定义了计算机硬件的能源相关信息,如电源供应器状态、设备功耗、设备功率因数等。ACPI 是操作系统控制计算机硬件能源管理的标准,同时也是硬件厂商和操作系统之间通信的标准。

    在 deepin 系统中,ACPI 负责处理计算机硬件的能源管理,它与 deepin 系统的电源管理模块进行交互,以实现对计算机硬件的能源管理。

    在分析系统层面的问题时,我们需要了解 ACPI 和电源管理模块的作用和功能,以及它们是如何协同工作的。在本文中,我们将会详细讲解 ACPI 的工作原理以及 deepin 系统中的电源调度模块工作模式,并提出可行优化建议。

    首先,让我们了解一下 ACPI 的工作原理。当计算机硬件发生电源变化时,ACPI 会收集硬件信息,并向操作系统发送电源请求。操作系统收到电源请求后,会根据用户配置自动调整各个硬件的电源策略。而 deepin 系统的电源模块则是帮助用户生成配置来调整 ACPI 的行为。所以在这一方面,我们能做的就是向 ACPI 提供合理的电源策略,在保证性能的同时,降低设备温度并提升续航表现。

    2.平台电源配置

    平台电源配置提供了三种可选模式:performance(性能模式),balance(平衡模式),low-power(节能模式)。一般情况下,用户使用平衡模式就可以。在台式机和 mini 主机类(对于功耗和发热没有任何要求)设备上默认提供性能模式,在笔记本等移动设备上默认提供平衡模式。默认不提供节能模式,因为某些 ACPI 设备在节能模式工作过程中可能出现“睡死现象”,所以为了避免此问题,默认不提供 low-power 节电模式。

    3.CPU 电源配置

    /sys/devices/system/cpu/cpufreq目录下有许多文件名为policy<x>(x 代表核心编号),这些文件对应着你电脑上的 CPU 核心,而 CPU 的电源调度细节就在这些文件夹里面。在policy<x>目录下有一个文件`scaling_driver``,使用 cat 或其他方式访问它,得到的结果就是我们当前使用的调度器:

    • intel_cpufreq / acpi_cpufreq : 使用 scaling freq 调度
    • intel_pastate : 使用 Intel Pstate 调度
    • amd-pstate : 使用 AMD Pstate 调度

     scalling freq 调度

    这是最传统的 CPU 调度方式,你可以在 policy文件夹下的 scaling_available_governors 获取可选电源模式:

    英文中文含义
    performance性能模式最极致的性能表现,最火热的 CPU 温度,最短的续航。
    powersave节能模式为绿色地球出一份力。
    balance平衡模式性能和续航兼顾。小孩子才做选择,我全都要。
    schedutil平衡模式平衡模式的一种,使用不同算法进行调度。
    ondemand平衡模式平衡模式的一种,根据当前 CPU 负载动态调整频率。当负载大于阈值时调整到最高频率,其他情况按负载比例计算频率。
    conservative平衡模式平衡模式的一种,根据当前 CPU 负载动态调整频率。当负载大于最大阈值时步进递增频率,当负载小于最低阈值时步进递减。
    userspace用户模式以用户指定的频率运行 CPU,可通过/sys/devices/system/cpu/cpuX/cpufreq/scaling_setspeed 进行配置

    你可能好奇,为啥这里有这么多平衡模式,其实这些平衡模式的作用都是是一样的:平衡性能和续航,不过使用的算法可能不同,这里我不做详细说明,我在网络上找到一些详细资料可以参考,有兴趣的朋友可自行查阅:

     Intel Pstate

    这是 Intel 近几代 CPU 独享的 moment,内核开启 intel pstate 后(V23 内核默认开启)你会发现在 policy文件夹下多了几个文件:

    我们只需要关注:

    • energy_performance_available_perference : 可用的 pstate 电源调度
    • energy_performance_perference:当前选定的 pstate 电源调度,可以更改此文件内容来更改电源调度
    • 在 Intel Pstate 中出现了两个新的调度方案:
    • balance_performance : 平衡偏性能,平时工作频率不高,在负载增大时能快速响应
    • balance_power : 平衡偏节能,电源策略较为保守 在部分电脑上还有 default 方案,此方案就是经过 pstate 优化过的 balance 策略。具体 PState 使用的黑魔法以及主动模式和被动模式的调度策略,可以参照内核文档进行分析。

    AMD PState

    这是 AMD ZEN2 以上用户,以及支持 kernel 6.4.x 用户独享的 moment。其实 AMD 在 6.1 内核已经做了 PState 的支持,不过是被动模式。

    • (Actvie Mode)主动模式

      Active Mode 仅在内核版本大于 6.4 以上,且内核选项打开 AMD PState 时可用。可能需要在 grub 内加入启动参数以打开此功能:amd_pstate=active,也可以修改文件实现 Active Mode 的电源策略和 Intel PStatewi 类似。

    • (Passive Mode)被动模式

      Passtive Mode 仅在内核大于 6.1 以上,且内核选项打开 AMD PState 时可用。可能需要在 grub 加入启动参数开启此功能:amd_pstate=passive,也可修改文件实现。

      Passive Mode 提供两种电源模式,在/sys/device/system/cpu/cpufreq/scaling_governor文件进行调整:

      • performance 使用 platform_profile 进行配置,调度积极性较高
      • scheutils 在/sys/device/system/cpu/cpufreq/schedutil/rate_limit_us文件中调整调度粒度(两次调度的间隔时间)和 ACPI 的 scheutils 类/sys/device/system/cpu/cpufreq/scaling_governor
    • (Guided Mode)引导模式

      Guided Mode 仅在内核大于 6.1 以上,且内核选项打开 AMD PState 时可用。可能需要在 grub 加入启动参数开启此功能:amd_pstate=guided,也可修改文件实现。这就类似汽车的自动挡,驱动程序请求最低和最大性能级别,平台自动选择此范围内适合当前工作负荷的性能级别。

    4.GPU 电源管理部分

     AMD GPU

    如果是 AMD GPU 则需要更改两个文件(使用 tee 命令进行写入):

    • /sys/class/drm/card0/device/power_dpm_state(这是一个遗留接口,目的是向后兼容)
    • performance 高性能模式
    • balance 平衡模式
    • battery 节能模式
    • /sys/class/drm/card0/device/power_dpm_force_performance_level

    以下设置来自 AMD 官方驱动文档:

    https://dri.freedesktop.org/docs/drm/gpu/amdgpu.html#power-dpm-force-performance-level

    drm/amdgpu AMDgpu driver — The Linux Kernel  documentation

    power_dpm_force_performance_level:

    AMD GPU 驱动程序提供了一个 sysfs API,用于调整某些与功率相关的参数。文件 power-dpm-force-performance-level 将用于执行此操作。它接受以下参数:

    • auto:当选择 auto 时,设备将尝试针对驱动中的当前条件动态选择最佳功率曲线
    • low:当选择低时,GPU 被强制到最低功率状态
    • high:当选择高时,GPU 被强制到最高功率状态
    • manual:当选择手动时,用户可以通过 sysfs pp_dpm_mclk、pp_dpm_sclk 和 pp_dpm_pcie 文件手动调整每个时钟域启用的电源状态,并通过 pp_power_profile_mode sysfs 文件调整电源状态转换方式。
    • profile_standard 固定时钟级别分析模式。此模式将时钟设置为固定级别,该级别因 ASIC 而异。这对于分析特定工作负载很有用(不常用)。
    • profile_min_sclk 最小 sclk 分析模式。此模式将 sclk 强制设置为最低级别。这对于分析最小功耗的场景很有用(不常用)。
    • profile_min_mclk 最小 mclk 分析模式。此模式将 mclk 强制设置为最低级别。这对于分析最小功耗的场景很有用(不常用)。
    • profile_peak 峰值分析模式。此模式将所有时钟(mclk、sclk、pcie)设置为最高级别。这对于分析最大性能的场景很有用(不常用)。

    测试:

    • LOW 模式的跑分

    LOW 模式的跑分

    • auto 模式的跑分

    auto 模式的跑分

    • high 模式的跑分

    high 模式的跑分

     Intel GPU

    intel GPU 使用的 i915 驱动,并不希望你对其做出调整,因为其驱动自带的电源策略已经足够聪明。不过你也可以通过 intel 提供的 intel-gpu-tools 进行调整和获取信息。

    sudo apt install intel-gpu-tools

    然后使用

    sudo intel_gpu_frequency

    来获取当前频率(当前使用的是 Intel A750)

    可以看到 intel 的显卡驱动是在 600 MHz 到 2400 MHz 之间动态调整(如上图)

    测试笔记本下 intel 核显跑分如下 图片

    Nvidia

    由于 nvidia 驱动不开源,所以在系统层面无法对其做控制。

    四、应用

    应用级别的省电,应该就是在保证用户使用流畅度的情前提下下节省性能。之前也有用户提出过,是否能参照 vivo 的 origin3 os 的不公平调度算法来实现优化。毕竟安卓系统的底层也是 linux,理论上实现难度不大。

    Cgroups,全称 Control Groups,是 Linux 内核提供的一种资源管理机制,用于对进程分组并对其资源进行限制和隔离。Cgroups 可以用于限制进程的 CPU、内存、磁盘、网络等资源,也可以用于限制进程的优先级和 IO 权限。利用其提供的能力,我们很容易实现类似不公平调度算法(我们新的 AM 天然支持 Cgroups 的操作),但是我还有一些顾虑:

    • 不同于手机操作系统,计算机操作系统是多任务并行的,在多数窗口管理器下,我们并没有一个明显的前台应用,此时使用不公平调度可能存在隐患;
    • 容易引发人机对抗。在我的观念里面,计算机是为人服务的,那么用户的意志必定是第一优先级,所以我们不应改变用户的行为,如果使用不平衡调度和用户预期不一致,会极大降低用户体验;
    • 使用前后台区分应用,可能导致开销和收益比下降,性价比不高。linux 桌面不像安卓设备有明显前后台,那么用户频繁切换应用的操作将导致我们的调度器频繁切换调度,使得开销过大。 我认为最佳的解决方案是:提供能力,但不提供方案。我们可以提供基于 Cgroups 方式修改应用组的优先级,然后让用户自己选择什么应用优先级更高,什么应用优先级低,以实现调度(比如在 dock 上右键选择优先级)或提供一套配置以供用户自由选择。

    如果一个电脑需要使用不平衡调度来保证使用流畅性,可能这并不是一个操作系统能解决的问题,而更应该考虑硬件是否需要更换,以保证多任务使用的流畅性。

    附录——常用的调试测试工具

    1. S-tui

    可以看到 CPU 频率变化,配合 stress 可以对 cpu 进行压力测试。 图片

    2. intel-gpu-tools

    可以使用 intel_gpu_frequency 来获取和调整 i965 的驱动频率。

    3. glmark2

    GPU 跑分软件。

    4. stress-ng

    CPU 压力测试软件。

    5. powertop

    电源测试软件,可以看到电源的功耗和使用情况。

    Sunday, September 24, 2023

    随笔

    在给Wireshark做完编译、调整、上架之后,我打算到论坛里看看大家对开源应用有没有什么疑惑的地方,没想到大家现在对开源的关注度这么高了。在看到最近一些闹得比较沸沸扬扬的话题之后,我也对我现在开源应用适配工作的很多地方产生了疑惑,我这样跟开源还有关系吗?

    重新适配开源应用的意义

    在一些实际的场景中,其实有很多"重复造轮子"的情况,即明确存在其他开源项目可用来替代的情况下仍然自己重头造一个项目来实现相同/类似的功能。比如各大桌面环境都有自己的一套文管:DDE的dde-file-manager、KDE的Dolphin,但严格意义上他们不是重复造轮子,而是更好地融入桌面环境中。 另外一种情况就是我现在做的适配开源应用,我的作品很大一部分可以在系统的应用商店中找到。但细心的小伙伴可能会发现:

    • “诶,这个应用不是系统仓库里有吗?为什么还要消耗资源去重复适配?”

    • “Debian 12的这个应用已经去到3.6.1了,为什么商店里的还是3.6.0?”

    • “为什么软件官方已经提供了deepin可以直接安装的包,应用商店还要另起一个新包名互不兼容?”

      想到这几个问题,大家是不是血压都上来了?这不就是妥妥重复劳动吗?别急,听我慢慢道出里面的小细节。我愿总结为两个核要义:平易、近人。

    易用性

    首先问大家一个问题,抛开命令真爱粉不谈,你认为用apt安装包方便还是在应用商店看着截图、应用描述、评论等等来一站式点击"安装"方便? 虽然命令也可以直接安装各种系统仓内的各种包,但对于新手用户个人认为还是有一定难度的。而且不使用应用商店来下载,很有可能就与应用商店各种特性擦肩而过了,比如定期的应用推荐、应用版本迭代的changelog。

    用户侧体验优化

    不知道大家有没有体验最近Wireshark-4.0.8,有使用过的朋友应该会发现我们很贴心的准备了两个desktop启动方式,一个是普通模式一个是root模式,中间的取舍故事今天就不多赘述了,大家有兴趣可以后面单独拎出来。 至于为什么会设置这么两个模式?其实我在适配开源项目前一般都会预览大家在应用商店里的反馈,这次的举动主要是由于Wireshark用普通user权限可以打开程序,但只是能看个壳子,只有root权限打开才是完整的功能。而且大家也反馈了比较多这个情况,我在评估一段之间、测试可行性之后给大家带来了"两个desktop+提示窗+双语支持"的独家体验。 “两个desktop+提示窗"是为了让大家方便以不同的模式打开,“提示窗"则是根据不同模式给大家展示相关提示信息,“双语支持"则是根据系统语言来设置中文和其他语言下分别显示中文提示和英文提示,一定程度上兼顾海外友人的体验。

    image

    我观察了下,Wireshark编译之后的原版desktop文件是不提供这种级别的优化的;而现在deepin应用商店里的版本实实在在可以做到了这个层次的优化,亲近人心。 大家可以把细节打在评论上。

    生态多样性

    严格意义上,每个包只有包名是相对唯一的,这也是因为大部分包管理器都会通过包名来检查该包的情况。但对于同一个应用特别是开源项目而言,它允许被多次修改分发,除了用版本号用以区分,也并没有强制要求每个人维护的包名都遵循同一个。 但为什么Debian等主流发行版一般包名都会随着版本迭代而保留使用,一般并不轻易修改包名?个人认为有以下几个可能:

    • 一般主流发行版仓库中某个重要项目/库为了保证长期稳定性和可维护性,一般都由同一个维护者负责维护,所以包名不会发生明显改动。

    • 包名变动过大,相关联的其他包/库均需要重新调整依赖关系,不太利于用户体验和仓库维护管理。

    • 接第一个情况,即便每个人都有编译开源项目的自由,但大家都比较自觉不会向系统仓中投递已经确切存在的包。

      除此之外,如果你同时在应用商店和外部安装一个包名一致的应用,则只会保留版本较新的一个。或者说,如果你想要保留本地安装的版本,但你在应用商店里对其进行升级,那此时你本地的包就会被替换掉了。

    稳定性测试

    其实在开源应用上架到应用商店前都会有测试流程,大家也很难保障哪一个版本会有致命问题,或者哪一个版本会丧失预期特性。所以我们在选包之后,一般是先编译一遍,没有致命问题我们才进行其他操作,尽最大努力保驾护航。

    适配开源应用的难度

    要是你觉得我们就是单纯把一些官方提供的二进制归档文件甚至是直接可用的deb包转化来上架应用商店,比如Visual Studio Code。那我很坦白告诉你,就是单纯几个字,“解包–运行–重新打包”。 但如果你是看了上文内容的话,就知道事情并不简单了。不只是从源码开始编译,核心在于如何与终端用户、各大开发者站在统一战线。当然,这里指的并不只是不断去满足用户的需要,这种可能会比较适合C端方向。我更希望的,是帮助用户在专业性比较强的问题面前能够提供满意的答卷,比如这次Wireshark,其实大部分用户真的不一定知道需要使用root来运行,所以我们做了优化。 应用/软件包只是一个普通产出物,不可替代的其实是整个适配过程中与其他人产生差异化的用户思维。

    结语

    曾经,我觉得我适配出来的开源应用只上架应用商店确实是玷污了开源,而且我并不是对程序Core本身做出了代码性贡献,这些优化充其量可能也就是普通建议而已。但后来,能看到大家对开源的支持,我也释怀了。 我希望以后有机会可以分享我在编译中发生的故事,同时也很支持使用其他发行版的小伙伴可以使用我在deepin上编译、优化调整之后的开源应用。 我也希望在开源这片蓝天里,我不是孤军奋战。

    Friday, September 22, 2023

    前不久深度科技旗下deepin社区发布了自己的 IDE:deepin-IDE,得到了全网用户尤其是开源社区用户的广泛关注,目前在 GitHub(https://github.com/linuxdeepin/deepin-unioncode)仓库的 star 数量已经达到 600 多个,说明大家的热情还是很高涨的。

    为了从技术层面给大家的热情做一个反馈,本文试着将 deepin-IDE 内部的一些实现方法进行分享,希望能够解答友友们的疑惑并得到积极的反馈。

    本篇挑了大家关心的“调试”部分进行分享。需要说明的是,deepin-IDE 的调试功能是选用 DAP(Debug Adapter Protocol )调试适配协议实现的,所以整体架构是围绕该协议搭建的,至于 DAP 具体是什么,让我们带着问号往下看。

    什么是 DAP 协议

    DAP 即调试适配协议( Debug Adapter Protocol ),顾名思义,它是用来对多种调试器进行抽象统一的适配层,将原有 IDE 和调试工具直接交互的模式更改为和 DAP 进行交互。该模式可以让 IDE 集成多种调试器变得更简单,且灵活性更好。

    IDE 中的调试功能有许多小功能组成,包括单步执行、断点、查看变量值等,常规的实现方式是在每个 IDE 中去实现这些逻辑,且因为调试工具的接口不同,还需要为每个调试工具做一些适配工作,这将导致大量且重复的工作,如下图所示:

    调试适配器协议背后的想法是标准化一个抽象协议,用于开发工具如何与具体调试器通信。这个思想和 LSP(Language Server Protocol)和 BSP(Build Server Protocol)类似,都是通过协议去统一相同功能在不同工具之间的差异性。其所处位置如下图所示,其中左边为不同的开发工具,右边为不能同的调试器,不同于开发工具和调试器直接交互的方式,DAP 将这些交互统一了起来,让开发工具和调试工具都面向 DAP 编程。

    上图中的交互是通过协议进行,所以不会像通过 API 的方式存在语言限制,可以更好的适应调试器的集成。

    DAP 如何工作

    以下部分解释了开发工具(例如 IDE 或编辑器)和调试适配器之间的交互,包括具体的协议格式说明、交互流程等。

    调试会话

    开发工具有两种基础的方式和调试器进行交互,分别是:

    【单会话模式】

    在这种模式下,开发工具启动一个调试适配器作为一个单独的进程并且通过标准的std接口进行通信。在调试会话的结束时调试适配器就终止,对于当前的调试会话,开发工具往往需要实现多个调试适配。

    【多会话模式】

    在这种模式下,开发工具不会启动调试适配器,而是假定它已经在运行并且会在特定端口上侦听连接尝试,对于每个调试会话,开发工具在特定端口上启动一个新的通信会话并在会话结束时断开连接。

    在与调试适配器建立连接后,开发工具和调试适配器之间通过基础协议进行通信。

    基础协议

    基础协议由两部分组成,包括头和内容(类似于 HTTP),头部和内容部分通过“\r\n”进行分割:

    【协议头】

    协议头部分由字段组成, 每个头字段由一个键和一个值组成,用‘:’(一个冒号和一个空格)分隔, 每个头字段都以“\r\n“结尾。由于最后一个协议头字段和整个协议头本身都以 \r\n 终止,并且由于协议头是强制性的,所以消息的内容部分总是在(并唯一标识)两个 \r\n 序列之前。当前只支持一个协议头字段:

    头字段名值类型描述
    Content-Length数字这个字段是必须的,用来记录内容字段的长度,单位是字节。

    协议头部分使用的是“ASCII”编码。

    【内容部分】

    内容部分包含了实际要传输的数据,这些数据用 JSON 格式来描述请求、响应和事件。内容部分用的是 utf-8 编码

    为了有个具体的认识,这里举个简单的例子。在调试过程中,开发人员经常会使用到下一步操作,在 DAP 中其协议为:

    Content-Length: 119\r\n
    \r\n
    {
        "seq": 153,
        "type": "request",
        "command": "next",
        "arguments": {
            "threadId": 3
        }
    }
    

    类型是“请求”,命令是下一步,参数部分可以携带多个,这里是用的线程Id。 这个协议看着挺简单的,是吧?接下来就讲讲如何使用它。

    使用方法

    详细的使用方法这里就不涉及,因为用一个时序图就可以说明:

    可以看到,初始化、请求、响应等必要的步骤都在图中。其中调试适配器可以理解为调试器的抽象,调试功能的最终执行者是由对应语言的调试工具实现的。

    在 deepin-IDE 中的实现

    在 deepin-IDE 中,调试功能的实现是结合 cppdap + debugmanager 实现的。

    cppdap 是一款基于 C++ 开发的 SDK,基本实现了 DAP 的全量协议。 deepin-IDE 的客户端和服务端都是应用的该 SDK 进行开发,据此可以实现以下功能:

    1.通信功能,包括服务端的 TCP 监听,客户端的 TCP 连接等;

    2.DAP 协议的封装,并实现协议的串行化和解串行化;

    3.提供注册回调功能,从而可以在回调内处理各种事件、请求等;

    它的层级结构如下:

    cppdap 可以减少客户端和服务端不少工作量,也统一了两边的协议数据。而 debugmanager 可以理解为调试器的抽象,包含所有必要的调试要素。整体结构如下:

    左边是客户端,右边是服务端,内部实现如下:

    客户端实现

    客户端包含了两个个主要功能,一个是和 DAP 服务端进行交互,发送调试命令或处理返回的数据;另一个是将DAP 数据转换后显示到用户界面,并响应界面发送的事件。概括起来就包含业务模块、事件模块、DAP 模块和界面4个部分。

    业务模块

    • 业务模块包含了插件类、调试参数、调试管理类等,其中插件类负责插件加载、初始化、获取上下文等,调试管理类用来组合事件、DAP、界面几个模块。 事件模块

    • 事件模块包含两个子模块,分别是事件发送和事件接收,比如页面跳转事件、添加\移除断点事件等。 DAP 模块

    DAP 模块基于 cppdap 开发,采用层级结构,底层是原始 DAP 协议封装,中间层是针对业务做的进一步封装,简化了向外提供的接口,最上层是对整个调试功能的整合,包括数据缓存、界面元素、命令收发。

    • 界面部分 界面模块包含堆栈界面、变量界面、断点列表、异步对话框等,用于 DAP 的数据展示。
    • 如上图所示,灰色部分为 DAP 客户端的界面呈现。

    服务端实现

    服务端的功能分为两个部分,一个是基于 cppdap 实现命令的收发,另一个是与 gdb 交互,实现调试程序的启动、暂停、退出等一系列动作。

    DAP

    • 和客户端一样,服务端也是基于cppdap实现的通信和协议封装和解析。 调试工具

    • 和调试工具的交互是通过进程调用的方式实现,接收进程输出得到返回信息。如果调试工具本身支持 DAP 协议,则可以直接交互。

    至此,本次的分享就到这儿啦!不知道你对 deepin-IDE 中的调试功能有所了解了吗?

    温馨提示,deepin-IDE 还包含很多有意思的功能,如果大家感兴趣可以积极反馈,后续有机会再进行分享。

    参考文档

    debug-adapter-protocol

    deepin-IDE 使用手册

    内容来源:deepin社区

    内容作者:deepin-mozart、toberyan

    转载请注明出处