• HOME
  • JOIN
  • RSS
  • Wednesday, December 29, 2021

    接着上一篇文章,这篇文章主要讲解一下具体的实现。

    https://github.com/linuxdeepin/dde-session 已经包含了所有的文件和提交。

    上面文章说过,为了让 dde 使用 systemd –user 来关系服务,我提供了一组服务:

    dde-session-initialized.target
    dde-session-manager.service
    dde-session-manager.target
    dde-session-pre.target
    dde-session-restart-dbus.service
    dde-session-shutdown.service
    dde-session-shutdown.target
    dde-session.target
    dde-session-x11-services-ready.target
    dde-session-x11-services.target
    dde-session-x11.target
    org.deepin.Session.service
    

    lightdm 在认证通过以后,会启动 dde-session 进程,dde-session 会通过 systemd 的 dbus 启动 org.deepin.Session.service

    org.deepin.Session.service 中会执行 dde-session-ctl --systemd-service,启动 dde-session-x11.target.

    dde-session-x11.target 关联了所有初始化的服务,systemd 会帮助自动运行这些服务,并且最终会运行到 dde-session-manager.service 上。

    dde-session-manager.service

    [Service]
    Type=simple
    ExecStart=/usr/bin/startdde
    ExecStopPost=/usr/lib/libexec/dde-session-ctl --logout
    

    org.deepin.Session.service 中会启动一个特殊的服务,这个服务会监听 dde-session 的退出,因为总要有一个服务去监听 session 的状态,要确保 session 在服务在,session 退服务退,服务退 session 退,共存亡,这也是所有操作中最麻烦的一步。

    org.deepin.Session.service

    [Service]
    Type=dbus
    BusName=org.deepin.Session
    ExecStart=/usr/bin/dde-session --systemd-service
    ExecStop=/usr/lib/libexec/dde-session-ctl --shutdown
    

    在 dde-session-ctl 中是这么实现 shutdown 的:

    if (isShutdown) {
        // kill startdde-session or call login1
        QDBusInterface systemd("org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager");
        qInfo() << systemd.call("StartUnit", "dde-session-shutdown.target", "replace-irreversibly");
        return 0;
    }
    

    在 dde-session-ctl 中是这么实现 logout 的:

    if (parser.isSet(logout)) {
        org::deepin::Session session("org.deepin.Session", "/org/deepin/Session", QDBusConnection::sessionBus());
        session.Logout();
    
        return 0;
    }
    

    dde-session main.cpp

    // 添加启动参数,表示这是通过 systemd 启动的。
    QCommandLineOption systemd(QStringList{"d", "systemd-service", "wait for systemd services"});
    parser.addOption(systemd);
    parser.process(app);
    
    if (parser.isSet(systemd)) {
        return -1;
    }
    
    QDBusServiceWatcher *watcher = new QDBusServiceWatcher("org.deepin.Session", QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForUnregistration);
        watcher->connect(watcher, &QDBusServiceWatcher::serviceUnregistered, [=] {
            QDBusInterface systemdDBus("org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager");
            qInfo() << systemdDBus.call("StartUnit", "dde-session-shutdown.service", "replace");
            qApp->quit();
        });
        qInfo() << systemdDBus.call("StartUnit", "dde-session-x11.target", "replace");
    }
    

    dde-session-shutdown.service 只负责执行 dde-session-ctl --shutdown,用于启动 dde-session-shutdown.target

    dde-session-shutdown.service

    [Service]
    Type=oneshot
    ExecStart=/usr/lib/libexec/dde-session-ctl --logout
    

    通过 systemd 的接口启动了 dde-session-shutdown.target,从而启动了清理流程。

    dde-session-shutdown.target 没有执行任何内容,仅仅是关联了一组服务,但是是冲突这些服务,因为这是启动的时候

    dde-session-shutdown.target 中还关联了最重要的一个服务: dde-session-restart-dbus.service,这个服务是用来关闭 dbus.service 服务的,这样就可以保证 session 结束的时候,不会有 dbus 服务逃逸出去。

    dde-session-restart-dbus.service

    [Service]
    Type=notify
    ExecStart=/usr/lib/libexec/dde-session-ctl --restart-dbus
    

    在 dde-session-ctl 中是这么实现 restart-dbus 的:

    if (isRestartDBus) {
        QDBusInterface systemd("org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager");
        qInfo() << systemd.call("StopUnit", "dbus.service", "replace-irreversibly");
        return 0;
    }
    

    复盘一下整个流程,在登录界面输入密码登录以后,lightdm 会启动 dde-session 作为 session 的入口,dde-session 会注册一个 org.deepin.Session 的 dbus 服务,并且会启动关联的 systemd 服务,启动一个 dde-session --systemd-service,在这个进程里,会启动 dde-session-x11.target,从而最终启动了 dde-session-manager.service,将原本的会话入口 startdde 启动了。

    dde-session --systemd-service 会监听 org.deepin.Session 服务的存活,当服务不存在时,就会开始执行退出流程,从而启动了 dde-session-ctl --shutdown,在 dde-session-ctl 的帮助下,启动了 dde-session-shutdown.target,将所有启动的 dde 核心服务都冲突掉,从而完成了服务关闭,在最后阶段再将 dbus.service 服务停止,完成最终的清理。

    还有一种退出模式,手动启动了 dde-session-shutdown.service 服务,也会触发完成的退出流程,dde-session-shutdown.service 会利用 dde-session-ctl --logout 将会话注销,从而触发上面的执行流程,完成会话与服务的关闭。

    目前还有一些问题没有解决,没有创建独立的 systemd.slice 和 systemd.scope,这可以帮助 dde 细致的划分和管理自己的子 services,退出 dbus.service 的手段非常黑,而且 dbus 服务的程序由于图形服务也已经结束了,导致成片的 X11 Error。这些都是要解决的。

    下一步准备写几个 service,用来代替 startdde 的组件启动,比如 kwin、dde-desktop、dde-dock 和 xdg-autostart 等。

    就目前的情况来看,任重而道远。

    原文链接:https://blog.justforlxz.com/2021/12/29/Starting-sessions-with-systemd-part-2/

    接着上一篇文章 Starting sessions with systemd,这篇文章主要讲解一下具体的实现。

    https://github.com/linuxdeepin/dde-session 已经包含了所有的文件和提交。

    上面文章说过,为了让 dde 使用 systemd –user 来关系服务,我提供了一组服务:

    dde-session-initialized.target
    dde-session-manager.service
    dde-session-manager.target
    dde-session-pre.target
    dde-session-restart-dbus.service
    dde-session-shutdown.service
    dde-session-shutdown.target
    dde-session.target
    dde-session-x11-services-ready.target
    dde-session-x11-services.target
    dde-session-x11.target
    org.deepin.Session.service

    lightdm 在认证通过以后,会启动 dde-session 进程,dde-session 会通过 systemd 的 dbus 启动 org.deepin.Session.service

    org.deepin.Session.service 中会执行 dde-session-ctl --systemd-service,启动 dde-session-x11.target.

    dde-session-x11.target 关联了所有初始化的服务,systemd 会帮助自动运行这些服务,并且最终会运行到 dde-session-manager.service 上。

    dde-session-manager.service

    [Service]
    Type=simple
    ExecStart=/usr/bin/startdde
    ExecStopPost=/usr/lib/libexec/dde-session-ctl --logout

    org.deepin.Session.service 中会启动一个特殊的服务,这个服务会监听 dde-session 的退出,因为总要有一个服务去监听 session 的状态,要确保 session 在服务在,session 退服务退,服务退 session 退,共存亡,这也是所有操作中最麻烦的一步。

    org.deepin.Session.service

    [Service]
    Type=dbus
    BusName=org.deepin.Session
    ExecStart=/usr/bin/dde-session --systemd-service
    ExecStop=/usr/lib/libexec/dde-session-ctl --shutdown

    在 dde-session-ctl 中是这么实现 shutdown 的:

    if (isShutdown) {
    // kill startdde-session or call login1
    QDBusInterface systemd("org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager");
    qInfo() << systemd.call("StartUnit", "dde-session-shutdown.target", "replace-irreversibly");
    return 0;
    }

    在 dde-session-ctl 中是这么实现 logout 的:

    if (parser.isSet(logout)) {
    org::deepin::Session session("org.deepin.Session", "/org/deepin/Session", QDBusConnection::sessionBus());
    session.Logout();

    return 0;
    }

    dde-session main.cpp

    // 添加启动参数,表示这是通过 systemd 启动的。
    QCommandLineOption systemd(QStringList{"d", "systemd-service", "wait for systemd services"});
    parser.addOption(systemd);
    parser.process(app);

    if (parser.isSet(systemd)) {
    return -1;
    }

    QDBusServiceWatcher *watcher = new QDBusServiceWatcher("org.deepin.Session", QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForUnregistration);
    watcher->connect(watcher, &QDBusServiceWatcher::serviceUnregistered, [=] {
    QDBusInterface systemdDBus("org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager");
    qInfo() << systemdDBus.call("StartUnit", "dde-session-shutdown.service", "replace");
    qApp->quit();
    });
    qInfo() << systemdDBus.call("StartUnit", "dde-session-x11.target", "replace");
    }

    dde-session-shutdown.service 只负责执行 dde-session-ctl --shutdown,用于启动 dde-session-shutdown.target

    dde-session-shutdown.service

    [Service]
    Type=oneshot
    ExecStart=/usr/lib/libexec/dde-session-ctl --logout

    通过 systemd 的接口启动了 dde-session-shutdown.target,从而启动了清理流程。

    dde-session-shutdown.target 没有执行任何内容,仅仅是关联了一组服务,但是是冲突这些服务,因为这是启动的时候

    dde-session-shutdown.target 中还关联了最重要的一个服务: dde-session-restart-dbus.service,这个服务是用来关闭 dbus.service 服务的,这样就可以保证 session 结束的时候,不会有 dbus 服务逃逸出去。

    dde-session-restart-dbus.service

    [Service]
    Type=notify
    ExecStart=/usr/lib/libexec/dde-session-ctl --restart-dbus

    在 dde-session-ctl 中是这么实现 restart-dbus 的:

    if (isRestartDBus) {
    QDBusInterface systemd("org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager");
    qInfo() << systemd.call("StopUnit", "dbus.service", "replace-irreversibly");
    return 0;
    }

    复盘一下整个流程,在登录界面输入密码登录以后,lightdm 会启动 dde-session 作为 session 的入口,dde-session 会注册一个 org.deepin.Session 的 dbus 服务,并且会启动关联的 systemd 服务,启动一个 dde-session --systemd-service,在这个进程里,会启动 dde-session-x11.target,从而最终启动了 dde-session-manager.service,将原本的会话入口 startdde 启动了。

    dde-session --systemd-service 会监听 org.deepin.Session 服务的存活,当服务不存在时,就会开始执行退出流程,从而启动了 dde-session-ctl --shutdown,在 dde-session-ctl 的帮助下,启动了 dde-session-shutdown.target,将所有启动的 dde 核心服务都冲突掉,从而完成了服务关闭,在最后阶段再将 dbus.service 服务停止,完成最终的清理。

    还有一种退出模式,手动启动了 dde-session-shutdown.service 服务,也会触发完成的退出流程,dde-session-shutdown.service 会利用 dde-session-ctl --logout 将会话注销,从而触发上面的执行流程,完成会话与服务的关闭。

    目前还有一些问题没有解决,没有创建独立的 systemd.slice 和 systemd.scope,这可以帮助 dde 细致的划分和管理自己的子 services,退出 dbus.service 的手段非常黑,而且 dbus 服务的程序由于图形服务也已经结束了,导致成片的 X11 Error。这些都是要解决的。

    下一步准备写几个 service,用来代替 startdde 的组件启动,比如 kwin、dde-desktop、dde-dock 和 xdg-autostart 等。

    就目前的情况来看,任重而道远。

    Saturday, December 25, 2021

    DDE 现在正在做 Wayland 的支持,所以我们需要对目前的桌面环境结构进行调整, 考虑到 GNOME 和 KDE 都已经使用 systemd 来管理 session,我认为 deepin 团队也可以考虑这一步了。

    为什么需要 systemd?

    目前 systemd 作为事实上胜利的 init 进程,它现在负责的功能已经越来越多了,支持但不限于:udevsleep/hibernate/suspenddbusservices 等。现在越来越多的程序也开始使用 systemd 管理后台服务。

    不使用 systemd 来管理 session 会有什么问题吗?

    存在一个进程逃逸问题,不过这个问题与是否采用 systemd 管理 session 没有太大关系。

    我先来简单介绍一下目前 DDE 的工作模型:

    首先,DDE 使用 LightDM 作为显示服务器(Display Manager),DDE 提供了 lightdm-deepin-greeter 作为登录界面,greeter 可以通过调用 LightDM 的 api 进行 linux-pam 的认证。

    当认证通过后,LightDM 会启动一个会话(Session),之后运行的程序都将是以登录的用户身份启动,然后 systemd --user 进程会被启动,同时 dbus-daemon 也会启动,之后 LightDM 会启动 startdde。

    此时就开始执行到 DDE 的初始化阶段,startdde 会启动 dde-session-daemon 和 DDE 的核心组件,之后就并行启动 autostart 中的 desktop 文件。

    startdde 目前的流程本身没有问题,问题出在 systemd 接管了 dbus,并且从 systemd 226 版本开始,/etc/pam.d/system-login 默认配置中的 pam_systemd 模块会在用户首次登录的时候, 自动运行一个 systemd --user 实例。 只要用户还有会话存在,这个进程就不会退出;用户所有会话退出时,进程将会被销毁。当#随系统自动启动 systemd 用户实例启用时, 这个用户实例将在系统启动时加载,并且不会被销毁。

    systemd --user 实例是针对每个用户处理的,而不是针对会话。这样做的原理是用户服务处理的大部分资源,像 socket 或状态文件是针对每个用户的(存活于用户的主目录下)而不是会话。这意味着所有的用户服务是独立于会话之外运行的。最终,我们得出结论:基于会话运行的程序可能会导致用户服务中断。systemd 处理用户会话的方式是非常生硬的(pretty much in flux)。

    那么问题就来了,上面说了,systemd 接管了 dbus 服务,通过 dbus 启动的程序会在 dbus.service 下面,成为它的子进程,而 systemd --user 在有用户其它登录的会话存在时,并不会停止 systemd --user,所以 dbus.service 也不会停止。但是 dde 的会话 已经完成注销了,但是通过 dde 启动的程序却没有被退出,从而导致了程序 逃逸

    解决方案?

    如果只是想解决逃逸问题,那么想一个办法,在 session 停止的时候,主动把 dbus.service 服务停掉其实就可以解决了,但是我们想要利用 systemd 的自动依赖解决,这样就不用重复造一点点小轮子了。

    那么需要怎么修改才能符合桌面环境的要求?

    GNOME 和 KDE 现在已经完成了 systemd session 的工作,我们可以在这两个老大哥身上学习。

    提供了一个新的会话入口点 gnome-session-systemd。 这需要一个参数,即开始会话的单元(通常是 .target 单元)。 它将执行一些清理功能,初始化 systemd 环境,然后启动单元并等待它停止。会话通过修改它们的 Exec 行并删除 RequiredComponents 来选择以这种方式启动。

    XDG autostart 现在 systemd 提供了一个 wrapper,可以自动生成出 service 单元,所以功能可以复用。

    在 systemd 管理的系统上,每个用户都被分配了一个 user-x.slice (systemd.slice(5)),并且用户的会话将在 session-Y.scope (systemd.scope(5)) 中运行。您还可以在主机上看到一些其它用户特定的单元,包括 [email protected],它是用户的 systemd 实例。这是用户的一个单独的 systemd 进程,如果用户不再登录,它将再次关闭。

    随着 systemd 的移动,不仅 DBus 激活的应用程序和服务,您的整个会话现在都使用用户的 systemd 实例启动。 这有一些副作用,起初可能看起来很奇怪。 例如,之前提到的 session-Y.scope 曾经包含 200 多个进程,现在减少到仅 4 个进程。 另一个副作用是更难理解进程属于哪个会话(这与许多服务相关)或者 ps 将不再显示 tty。

    但是根据 GNOME blog 的文章说明,这些副作用已经被处理了。GNOME会话仍然始终绑定到session-Y.scope(例如,使用 loginctl kill-session 继续可靠地工作)。

    查看了 GNOME 提供的 dbus 服务单元文件,最终明白了 GNOME 是怎么做到防止 dbus 程序逃逸的,其实操作非常简单,就是提供了一个 gnome-session-shutdown.target,在这个 target 里关联了 gnome-sessino-restart-dbus.service,里面执行的是 gnome-session-ctl --restart-dbus, 其实就是通过 DBus 调用了 systemd --user 的 dbus 方法,将 dbus.service 给停掉了。(- - |)

    改造方案

    既然了解了前因后果,那么我们可以着手改造 DDE 了。

    还需要补充一个知识,会话启动以后,会有一个进程作为会话的入口,会话所有的程序都是从这个入口开始启动的,这个入口就是 startdde

    我们既然希望利用 systemd 的服务来启动,并且帮助我们自动解决依赖,但是拆分项目目前压力比较大,我们还计划重写一部分后端功能,可以在重写时进行调整。

    启动入口

    首先我们需要创建一系列的 servicetarget 文件。

    dde-session-initialized.target
    dde-session-manager.service
    dde-session-manager.target
    dde-session-pre.target
    dde-session-restart-dbus.service
    dde-session-shutdown.service
    dde-session-shutdown.target
    dde-session.target
    dde-session-x11-services-ready.target
    dde-session-x11-services.target
    dde-session-x11.target
    org.deepin.Session.service
    

    看起来非常的多,不用担心,他们都有各自的作用,你可以认为这是将生命周期在 systemd 实现了。

    由于 systemd 会帮助我们自动完成依赖解析,那么我们只需要保留一个入口服务,其它服务都禁止手动启动即可。

    我选择使用 dde-session-x11.target 作为入口,因为未来 deepin 还要支持 wayland,那么在这里就区分开会比较方便一些,因为桌面环境的后续启动与使用什么图形服务没有太大关系。

    根据文件名称就可以方便的了解到这些文件是在什么阶段被执行的。

    退出时清理

    创建的 dde-session-shutdown.target 用来关联所有退出时需要执行的 services

    清理 dbus.service

    提供了一个 dde-session-restart-dbus.service 用来注销以后关闭 dbus.service,不要问,问就是没办法~。

    dde-session-shutdown.target 中会关联这个服务,当用户的会话注销或者桌面环境服务出现问题时,就可以退出所有的 dbus.service

    当发现会话注销时,dde-session-manager.service 会执行退出,在服务关闭时启动 dde-session-shutdown.target,并且使用 replace-irreversibly 标记为不可撤销。

    dde-session-shutdown.target 中又会清理 dbus.service 下的所有程序,这样就避免了服务可以通过 dbus 逃逸出会话。

    架构模型

    最终效果

    可以看到 startdde 和它的进程树挂在 systemd 下面。

    原本的位置现在只有一个占位的程序。

    引用资料

    https://blogs.gnome.org/benzea/2019/10/01/gnome-3-34-is-now-managed-using-systemd/

    原文链接:https://blog.justforlxz.com/2021/12/25/Starting-sessions-with-systemd/

    DDE 现在正在做 Wayland 的支持,所以我们需要对目前的桌面环境结构进行调整,
    考虑到 GNOME 和 KDE 都已经使用 systemd 来管理 session,我认为 deepin 团队也可以考虑这一步了。

    为什么需要 systemd?

    目前 systemd 作为事实上胜利的 init 进程,它现在负责的功能已经越来越多了,支持但不限于:udevsleep/hibernate/suspenddbusservices 等。现在越来越多的程序也开始使用 systemd 管理后台服务。

    不使用 systemd 来管理 session 会有什么问题吗?

    存在一个进程逃逸问题,不过这个问题与是否采用 systemd 管理 session 没有太大关系。

    我先来简单介绍一下目前 DDE 的工作模型:

    首先,DDE 使用 LightDM 作为显示服务器(Display Manager),DDE 提供了 lightdm-deepin-greeter 作为登录界面,greeter 可以通过调用 LightDM 的 api 进行 linux-pam 的认证。

    当认证通过后,LightDM 会启动一个会话(Session),之后运行的程序都将是以登录的用户身份启动,然后 systemd --user 进程会被启动,同时 dbus-daemon 也会启动,之后 LightDM 会启动 startdde。

    此时就开始执行到 DDE 的初始化阶段,startdde 会启动 dde-session-daemon 和 DDE 的核心组件,之后就并行启动 autostart 中的 desktop 文件。

    startdde 目前的流程本身没有问题,问题出在 systemd 接管了 dbus,并且从 systemd 226 版本开始,/etc/pam.d/system-login 默认配置中的 pam_systemd 模块会在用户首次登录的时候, 自动运行一个 systemd --user 实例。 只要用户还有会话存在,这个进程就不会退出;用户所有会话退出时,进程将会被销毁。当#随系统自动启动 systemd 用户实例启用时, 这个用户实例将在系统启动时加载,并且不会被销毁。

    systemd --user 实例是针对每个用户处理的,而不是针对会话。这样做的原理是用户服务处理的大部分资源,像 socket 或状态文件是针对每个用户的(存活于用户的主目录下)而不是会话。这意味着所有的用户服务是独立于会话之外运行的。最终,我们得出结论:基于会话运行的程序可能会导致用户服务中断。systemd 处理用户会话的方式是非常生硬的(pretty much in flux)。

    那么问题就来了,上面说了,systemd 接管了 dbus 服务,通过 dbus 启动的程序会在 dbus.service 下面,成为它的子进程,而 systemd --user 在有用户其它登录的会话存在时,并不会停止 systemd --user,所以 dbus.service 也不会停止。但是 dde 的会话
    已经完成注销了,但是通过 dde 启动的程序却没有被退出,从而导致了程序 逃逸

    解决方案?

    如果只是想解决逃逸问题,那么想一个办法,在 session 停止的时候,主动把 dbus.service 服务停掉其实就可以解决了,但是我们想要利用 systemd 的自动依赖解决,这样就不用重复造一点点小轮子了。

    那么需要怎么修改才能符合桌面环境的要求?

    GNOME 和 KDE 现在已经完成了 systemd session 的工作,我们可以在这两个老大哥身上学习。

    提供了一个新的会话入口点 gnome-session-systemd。 这需要一个参数,即开始会话的单元(通常是 .target 单元)。 它将执行一些清理功能,初始化 systemd 环境,然后启动单元并等待它停止。会话通过修改它们的 Exec 行并删除 RequiredComponents 来选择以这种方式启动。

    XDG autostart 现在 systemd 提供了一个 wrapper,可以自动生成出 service 单元,所以功能可以复用。

    在 systemd 管理的系统上,每个用户都被分配了一个 user-x.slice (systemd.slice(5)),并且用户的会话将在 session-Y.scope (systemd.scope(5)) 中运行。您还可以在主机上看到一些其它用户特定的单元,包括 [email protected],它是用户的 systemd 实例。这是用户的一个单独的 systemd 进程,如果用户不再登录,它将再次关闭。

    随着 systemd 的移动,不仅 DBus 激活的应用程序和服务,您的整个会话现在都使用用户的 systemd 实例启动。 这有一些副作用,起初可能看起来很奇怪。 例如,之前提到的 session-Y.scope 曾经包含 200 多个进程,现在减少到仅 4 个进程。 另一个副作用是更难理解进程属于哪个会话(这与许多服务相关)或者 ps 将不再显示 tty。

    但是根据 GNOME blog 的文章说明,这些副作用已经被处理了。GNOME会话仍然始终绑定到session-Y.scope(例如,使用 loginctl kill-session 继续可靠地工作)。

    查看了 GNOME 提供的 dbus 服务单元文件,最终明白了 GNOME 是怎么做到防止 dbus 程序逃逸的,其实操作非常简单,就是提供了一个 gnome-session-shutdown.target,在这个 target 里关联了 gnome-sessino-restart-dbus.service,里面执行的是 gnome-session-ctl --restart-dbus
    其实就是通过 DBus 调用了 systemd --user 的 dbus 方法,将 dbus.service 给停掉了。(- - |)

    改造方案

    既然了解了前因后果,那么我们可以着手改造 DDE 了。

    还需要补充一个知识,会话启动以后,会有一个进程作为会话的入口,会话所有的程序都是从这个入口开始启动的,这个入口就是 startdde

    我们既然希望利用 systemd 的服务来启动,并且帮助我们自动解决依赖,但是拆分项目目前压力比较大,我们还计划重写一部分后端功能,可以在重写时进行调整。

    启动入口

    首先我们需要创建一系列的 servicetarget 文件。

    dde-session-initialized.target
    dde-session-manager.service
    dde-session-manager.target
    dde-session-pre.target
    dde-session-restart-dbus.service
    dde-session-shutdown.service
    dde-session-shutdown.target
    dde-session.target
    dde-session-x11-services-ready.target
    dde-session-x11-services.target
    dde-session-x11.target
    org.deepin.Session.service

    看起来非常的多,不用担心,他们都有各自的作用,你可以认为这是将生命周期在 systemd 实现了。

    由于 systemd 会帮助我们自动完成依赖解析,那么我们只需要保留一个入口服务,其它服务都禁止手动启动即可。

    我选择使用 dde-session-x11.target 作为入口,因为未来 deepin 还要支持 wayland,那么在这里就区分开会比较方便一些,因为桌面环境的后续启动与使用什么图形服务没有太大关系。

    根据文件名称就可以方便的了解到这些文件是在什么阶段被执行的。

    退出时清理

    创建的 dde-session-shutdown.target 用来关联所有退出时需要执行的 services

    清理 dbus.service

    提供了一个 dde-session-restart-dbus.service 用来注销以后关闭 dbus.service,不要问,问就是没办法~。

    dde-session-shutdown.target 中会关联这个服务,当用户的会话注销或者桌面环境服务出现问题时,就可以退出所有的 dbus.service

    当发现会话注销时,dde-session-manager.service 会执行退出,在服务关闭时启动 dde-session-shutdown.target,并且使用 replace-irreversibly 标记为不可撤销。

    dde-session-shutdown.target 中又会清理 dbus.service 下的所有程序,这样就避免了服务可以通过 dbus 逃逸出会话。

    架构模型

    systemd model

    最终效果

    可以看到 startdde 和它的进程树挂在 systemd 下面。

    systemd session

    原本的位置现在只有一个占位的程序。

    systemd session

    引用资料

    https://blogs.gnome.org/benzea/2019/10/01/gnome-3-34-is-now-managed-using-systemd/

    Saturday, August 7, 2021

    DOM文档排版的特点

    块级元素只能垂直排版,行内元素只能水平排版。

    块级元素可以设置宽高属性,行内元素没有宽高属性,只能被内部元素撑起来,水平放置不下时会换行继续排版。

    CSS 浮动

    浮动就是让文档脱离文档流,在父元素内部可以移动到指定位置。

    浮动的元素可以设置宽高,像块级元素一样,又向行内元素一样可以水平排版。

    <!doctype html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>元素的浮动</title>
    <style type="text/css">
    /*定义父元素的样式*/
    .father {
    background: #ccc;
    border: 1px dashed #999;
    }

    /*定义 box01、 box02、 box03 三个盒子的样式*/
    .box01, .box02, .box03 {
    height: 50px;
    width: 50px;
    background: #FF9;
    border: 1px solid #F33;
    margin: 15px;
    padding: 0px 10px;
    }

    /*定义段落文本的样式*/
    p {
    background: #FCF;
    border: 1px dashed #F33;
    padding: 0px 10px;
    }
    </style>
    <style>
    .box01, .box02, .box03 {
    float: left;
    }
    </style>
    </head>
    <body>
    <div class="father">
    <div class="box01">box01</div>
    <div class="box02">box02</div>
    <div class="box03">box03</div>
    </div>
    <p>12</p>
    <!--不定义float属性,float属性值都为其默认值 none-->
    </body>
    </html>
    元素的浮动

    运行后,box01、box02、box03 及段落文本从上到下一一罗列。 可见如果不对元素设置浮动,则该元素及其内部的子元素将按照标准文档流的样式显示,即块元素占据页面整行。

    接下来以 box01 为设置对象,对其应用左浮动样式,在 head 的 style 标签下添加新的 style 标签,具体CSS代码如下:

    .box01 {
    float: left;
    }

    可以看到 box01 已经脱离了文档流,接下来为 box02 和 box03 设置左浮动:

    .box02 {
    float: left;
    }

    .box03 {
    float: left;
    }
    元素的浮动

    上述代码运行后,box01、 box02、 box03 三个盒子排列在同一行,同时,周围的段落文本将环绕盒子,出现了图文混排的网页效果。

    CSS 浮动清除

    由于浮动元素不占用原文档流的位置,使用浮动时会影响后面相邻的固定元素。

    CSS 提供了 clear 属性,可以清除掉浮动元素。

    clear 有三个属性值:

    left :不允许左侧有浮动元素(清除左侧浮动的影响)

    right :不允许右侧有浮动元素(清除右侧浮动的影响)

    both :同时清除左右两侧浮动的影响

    浮动清除存在一个问题,浮动清除本质上处理的是左右元素,如果父元素内所有的元素都浮动了,此时因为文档流中没有其他元素可以在内部撑起父元素的高度,父元素此时会坍缩为 0 高度。

    方法一:使用空标记清除浮动

    在浮动元素之后添加空标记,并对该标记应用 clear:both 样式,可清除元素浮动所产生的影响,这个空标记可以为 <div><p><hr /> 等任何标记。

    需要注意的是,上述方法虽然可以清除浮动, 但是在无形中增加了毫无意义的结构元素(空标记),因此在实际工作中不建议使用。

    (空标记)因此在实际工作中不建议使用。

    <!doctype html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>空标记清除浮动</title>
    <style type="text/css">
    /*没有给父元素定义高度*/
    .father {
    background: #ccc;
    border: lpx dashed #999;
    }

    /*定义box01、box02、box03三个盒子左浮动*/
    .box01, .box02, .box03 {
    height: 50px;
    line-height: 50px;
    background: #f9c;
    border:1px dashed 999;
    margin: 15px;
    padding: 0px 10px;
    float: left;
    }

    /*对空标记应用clear:both;*/
    .box04 {
    clear: both;
    }
    </style>
    </head>
    <body>
    <div class="father">
    <div class="box01">box01</div>
    <div class="box02">box02</div>
    <div class="box03">box03</div>
    <div class="box04"></div>
    <!--在浮动元素后添加空标记-->
    </div>
    </body>
    </html>
    空标记清除浮动

    方法二:使用 overflow 属性清除浮动

    对元素应用 overflow: hidden; 也可以清除浮动对该元素的影响,该方法弥补了空标记清除浮动的不足。

    对父元素应用 overflow: hidden; 样式来清除子元素浮动对父元素的影响。

    父元素又被其子元素撑开了,即子元素浮动对父元素的影响已经不存在。

    <!doctype html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>overflow属性清除浮动</title>
    <style type="text/css">

    /*没有给父元素定义高度*/
    .father {
    background: #ccc;
    border: 1px dashed #999;
    /*对父元素应用overflow:hidden;*/
    overflow: hidden;
    }

    /*定义box01、box02、box03三个盒子左浮动*/
    .box01, .box02, .box03 {
    height: 50px;
    line-height: 50px;
    background: #f9c;
    border: 1px dashed #999;
    margin: 15px;
    padding: 0px 10px;
    float: left;
    }
    </style>
    </head>
    <body>
    <div class="father">
    <div class="box01">box01</div>
    <div class="box02">box02</div>
    <div class="box03">box03</div>
    </div>
    </body>
    </html>
    overflow 清除浮动

    方法三: 使用 after 伪对象清除浮动

    使用 after 伪对象也可以清除浮动,但是该方法只适用于 IE8 及以上版本浏览器和其他非 IE 浏览器。

    另外,使用 after 伪对象清除浮动时需要注意:

    (1) 必须为需要清除浮动的元素伪对象设置 height: (); 样式,否则该元素会比其实际高度高出若干像素。

    (2) 必须在伪对象中设置 content 属性,属性值可以为空,如 content:" ";

    <!doctype html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>使用after伪对象清除浮动</title>
    <style type="text/css">

    /*没有给父元素定义高度*/
    .father {
    background: #ccc;
    border: 1px dashed #999;
    }

    /*对父元素应用after伪对象样式*/
    .father:after {
    display: block;
    clear: both;
    content:" ";
    visibility: hidden;
    height: 0;
    }

    /*定义box01、box02、box03三个盒子左浮动*/
    .box01, .box02, .box03 {
    height: 50px;
    line-height: 50px;
    background: #f9c;
    border: 1px dashed #999;
    margin: 15px;
    padding: 0px 10px;
    float: left;
    }

    </style>
    </head>
    <body>
    <div class="father">
    <div class="box01">box01</div>
    <div class="box02">box02</div>
    <div class="box03">box03</div>
    </div>
    </body>
    </html>
    使用after伪对象清除浮动

    Thursday, March 11, 2021

    原文:https://emersion.fr/blog/2019/xdc2019-wrap-up

    > XDC 2019 wrap-up 2019-10-10

    这篇文章是我从加拿大蒙特利尔飞往赫尔辛基的路上写的,我刚参加完 X.Org 的开发者研讨会。从去年西班牙那次会议以来,这是我第二次参加 XDC(译者注:X.Org Developer’s Conference)。

    今年的研讨会非常的给力。我遇到了很多一起工作过的同志,他们来自四面八方的各个组织和项目。会议内容也很牛,我会尝试总结一下这次讨论的一些事情。

    libliftoff

    前一阵子,我搞了一个新项目,名字叫 libliftoff。它是我在这次会议上的亮点,所以接下来我会详细的介绍它。带着问题往下看:“libliftoff 是用来解决什么问题的呢?”

    libliftoff 是什么

    它是一个合成器的中间层,负责从 clients 中获取 buffers,把它们绘制在一个独立的 buffer 上,并把这个 buffer 显示到屏幕上。假设我现在打开了一个“文本编辑器”和一个“终端”,合成器会把它们的窗口 buffer 复制到屏幕的 buffer 上,一般是用 OpenGL 做这个事。

    不过,复制 buffers 的操作很浪费资源,因为它会做很多事情,比如:它总是进行 alpha 混合、在不同格式之间转换、让渲染引擎一直干活(注:GPU 在执行 OpenGL/Vulkan 的命令)等等,这些操作不仅占用时间,而且也会增加耗电量。

    为了改善性能,许多 GPU 都提供了硬件实现的“叠加平面(planes)”支持(下文直接称为“叠加平面”)。“叠加平面”可以作为显示引擎负责合成工作,这种行为称为直接显示,可以避免全让合成器做复制 buffers 的活。

    在 Android 上,硬件“叠加平面”在“Hardware Composer”组件中用的很广。许多 Wayland 合成器也会为光标使用“叠加平面”,但是用在其它场景的情况还是很少见。Weston(译者注:一个 Wayland 合成器) 是仅有的使用“叠加平面”的合成器之一。

    使用“叠加平面”并不是一件容易的事情,它有一些限制,比如:不支持 Alpha 通道、不是任意的 buffer 都可以用。虽然这些限制往往会让人觉得很扯淡,但它是由硬件特性决定的。举个例子:对于某些格式的 buffers,Intel 的硬件只能将其放在“叠加平面”的偶数坐标位置、某些 buffers 是在内存上分配的,“叠加平面”干脆就不支持这一类的 buffer、另外,显示设备一般都有带宽限制,在“叠加平面”上使用过大的 buffer 时会失败、某些 ARM 设备上,多个“叠加平面”之间不支持重叠。

    目前为止,关于硬件的这些限制信息,合成器程序还查询不了,因为硬件之间的差异性很大,所以很难设计一个能查询硬件限制信息的 API,甚至在某些新出 GPU 上还会有更奇怪的限制。唯一的方法是搞一些前置工作,用它做一些尝试性的使用。

    因此,设计和实现一个能有效的使用“叠加平面”的代码是一个相当复杂的事情。我一直在想,共享这些代码,是不是会对合成器使用“叠加平面”有所帮助,于是我就奔着这个目标设计了 libliftoff。

    libliftoff 的研讨会

    图一

    我在 XDC 搞了一个研讨会,讨论关于 libliftoff 的事情,目的是把合成器和驱动专家凑一块,一起商量如何才让合成器有效的用上“叠加平面”。

    libliftoff 的受欢迎程度让我很惊讶,连桌子附近都坐满了人:

    • 在合成器这边,有 wlroots 团队的人(Drew DeVault, Scott Anderson 和我自己),KDE 的 Roman Gilg,Weston 的 Daniel Stone,还有 X server 的 Keith Packard。
    • 在驱动这边,有很多来自于不同厂商的开发者:有 AMD、Arm、Google、Nvidia、Qualcomm 等等。

    大家貌似都很兴奋,为 libliftoff 提了很多宝贵的意见。我在此感谢所有参与研讨会的人!

    短期的计划是,让 libliftoff 为合成器提供能实际使用的功能(实验性的),不过有个事我还需要弄清楚,那就是应该怎么样支持多个 output:因为每个 output 都有自己的时序,所以,如何将“叠加平面”从一个 output 迁移到另一个 output 是一件很难搞的问题。最终,如果能实现这样的功能就完美了:为每一个图层分配一个优先级,并把那些频繁在别的图层之前更新的图层放在同一个“叠加平面”上。

    另外也讨论了一些长期计划。

    首先,我们想搞定一个跟内存相关的问题:clients 往往会在内存上分配 buffer,这些 buffer 没法直接用在“叠加平面”上。通常,合成器都会给一些提示信息:“你现在正在使用 Y_TILED,所以我不能将你用在“叠加平面”上,但是如果你能使用 X_TILED 我就能把你用在“叠加平面”上”,这样 client 就可以决定切换它的 buffer 格式。我大概在一年前给 Wayland 提了一个补丁,这个补丁新增的信息能被 libliftoff 识别出来,在这种情况下,合成器可以把这个信息转发给 clients。

    接下来是关于内核 API 的问题,现在只有一个办法能让我们知道是否可以使用“叠加平面”,那就是用 atomic 相关的 test 接口测试(译者注:atomic 是 DRM 提供的一系列原子操作接口,支持事务),这个做法相当的不清真,因为我们必须得遍历尝试多个组合条件,基本上是暴力求解。除此之外,比较好的做法都要跟具体的硬件相关。

    为了搞定这个问题,我们必须让内核提供更多的信息,但是因为不同硬件的差异性很大,设计一个足够通用的 API 将是一个很蛋疼的事情,另一个解决方案是在 libliftoff 中添加特定厂商的插件,允许每个驱动添加自己的代码,以此更好的匹配自己的硬件。目前来说,貌似这是最好的方案了。

    这是本次讨论的总结(PPT):

    视频:XDC 2019 - Day 3

    Scott Anderson 也写了一些总结,是关于 Wayland 协议的一些想法的详细介绍。

    分配器

    在三年前的 XDC 上,Nvidia 的 James Jones 提出了这个分配器项目,用于解决 GBM/EGLStreams 现有的问题。今年,他做了一个关于 GBM、Nouveau 和 transitions 的新的分享。跟之前的提案相比,这个提案的目的是在现有 API 的基础上建立一种叫 transition 的机制。

    视频:XDC 2019 - Day 2

    transition 可以用来减少渲染引擎的带宽占用。某些 GPU 支持使用压缩的 buffer,与未压缩的 buffer 相比,GPU 可以在这些压缩后的 buffer 上直接执行 OpenGL/Vulkan 的操作,这样做效率很高。所以,我们应该在渲染时使用压缩后的 buffers。

    不过,压缩后的 buffers 不能直接用在“叠加平面”上,需要先将它们解压才能使用,有一个方法是把它解压为一个新的 buffer,但是这样需要复制数据,速度会很慢。

    在某些 GPU 上有个更好的方法,它们支持在“叠加平面”中直接解压 buffer。这指的是:当一个 Wayland client 在压缩后的 buffer 上渲染,如果这个 buffer 可以在“叠加平面”中解压,并且之后可以直接使用它,那我们就将这种“buffer 就地解压的程序”称为 transition。

    之后我们在一个研讨会中继续探讨 transitions,讨论了应该在何时何地进行 transition 操作。想法一是:如果合成器准备把一个 buffer 用到“叠加平面”中,那就在合成器中做这个事,可以用 libliftoff 判断什么时候应该使用 transition。另一个想法是:在 client 把 buffer 提交给合成器之前做这个事,client 需要从合成器那获取一个信息,以便知道应该什么时候执行操作(这就是我之前提到的那个 Wayland 协议的补丁可以做的事情)。

    这是这次研讨会的总结:

    视频:XDC 2019 - Day 3

    可变刷新率(VRR)

    AMD 的 Harry Wentland 搞了一个关于“自适应同步”(DisplayPort 的技术)、“可变刷新率”(HDMI 的技术)和“FreeSync”(AMD 的技术)的研讨会。所有这些技术都是为了适配屏幕的功能,允许它从“固定为 60Hz”的模式变成“可以稍微等待下一帧”的模式(译者注:动态刷新率的模式)。

    视频:XDC 2019 - Day 2

    这东西的主要使用场景是游戏应用。游戏通常是用动态刷新率的方式更新画面(依赖于场景的复杂度),下一帧可以更快也可以更慢。游戏还希望能降低延迟,即避免渲染到在屏幕上显示的时间差过长。

    在屏幕固定刷新率的情况下,如果有一帧的渲染用时稍微长了一点,它错过了显示器的刷新时机,这样就会导致这一帧的显示有些滞后,而 VRR 则允许增加屏幕刷新时的等待时间,以免丢帧。

    另一种场景是视频播放。视频有固定的帧率,但是通常会与屏幕的刷新率不同,视频播放器需要用帧插值的方式,才能适应屏幕的刷新率,而 VRR 则允许降低刷新率以匹配能完美播放视频的时序。

    屏幕内容通常都是处于静止状态,在这种情况下,VRR 还可以降低电量消耗。比如用户在使用文本编辑器时,完全不需要 60FPS,这时候合成器可以降低屏幕的刷新率。

    游戏的使用场景比较简单,合成器可以比 deadline 稍微晚一点点提交帧,硬件可以搞定这种情况。其它场景需要做更多的工作,要想把 VRR 用在视频播放器上,我们需要某种定时提交帧的 API。要想用 VRR 做节能,合成器要能支持帧率改变时的平滑过渡,否则屏幕会有闪烁问题(这是支持 VRR 功能的屏幕的限制)。

    在我看来,我们目前应该把精力放在 Wayland 协议对游戏场景的支持上,因为搞其它两种场景需要做的事情太多了,它们不仅需要更多的新 API(内核和 Wayland 的都需要),还需要做很多的实验工作。

    Chamelium

    我在 Intel 实习期间,参与了一个叫 Chamelium 的项目,它是一个基础的屏幕模拟器,你可以通过网络向它发送命令,它已经被用在了 ChromeOS 的 i915 显卡环境的 CI 服务中。

    我搞了一个关于这个项目以及我在项目内的工作的研讨会:

    视频:XDC 2019 - Day 3

    除了这三个主题,我们还讨论了很多其它的东西,但是不能全塞到这一篇博客中讲,所以就说到这吧。感谢所有参加 XDC 的人,感谢 Mark Filion 组织了这次活动,还要感谢所有赞助商,没有你们的赞助就不会有的这次活动!

    Thursday, March 4, 2021

    Arch 使用的是 pacman 包管理器,包格式是 tar.zst。Arch 提供了一些工具用于创建 tar.zst 包,首先需要安装 base-devel 包和 devtools 包。

    pacman -S base-devel devtools

    Arch 的打包流程是这样的,要先写一个 PKGBUILD 文件,这个文件描述了构建一个包所需的全部信息,如从哪里下载源码,依赖有哪些,构建的版本是多少,如何进行构建等。

    PKGBUILD

    一个基本的 PKGBUILD 格式如下:

    # Maintainer: justforlxz <[email protected]>

    pkgname=dtkcore-git
    pkgver=5.4.0.r1.ge7e7a99
    pkgrel=1
    pkgdesc='DTK core modules'
    arch=('x86_64')
    url="https://github.com/linuxdeepin/dtkcore"
    license=('LGPL3')
    depends=('dconf' 'deepin-desktop-base-git' 'python' 'gsettings-qt' 'lshw')
    makedepends=('git' 'qt5-tools' 'gtest')
    conflicts=('dtkcore')
    provides=('dtkcore')
    groups=('deepin-git')
    source=("$pkgname::git://github.com/linuxdeepin/dtkcore.git")
    sha512sums=('SKIP')

    pkgver() {
    cd $pkgname
    git describe --long --tags | sed 's/\([^-]*-g\)/r\1/;s/-/./g'
    }

    build() {
    cd $pkgname
    qmake-qt5 PREFIX=/usr DTK_VERSION=$pkgver LIB_INSTALL_DIR=/usr/lib
    make
    }

    package() {
    cd $pkgname
    make INSTALL_ROOT="$pkgdir" install
    }

    以 dtkcore 为例,整个配置文件十分清晰明了,一看就知道构建工具是如何一步步制作出一个包的。

    将配置文件保存到目录里,以 PKGBUILD 命名,然后执行 makepkg

    makepkg 是用于执行 PKGBUILD 文件的工具,它根据文件中的描述,进行包的构建。

    如何提交一个包到 aur

    当你希望自己打的包可以被别人使用的时候,一般都会请求上传到官方仓库,但是进入官方仓库的流程异常繁琐,而且对贡献者的要求会比较高。Arch 提供了一个用户仓库,供用户自由分享配置文件,只需要下载配置文件,在本机打包就可以了。

    根据 aur 的 贡献指南,我们可以很轻松的上传自己的配置文件,并借用yay等aur工具自动下载和构建软件包。

    用户可以通过 Arch User Repository 分享 PKGBUILD。AUR 中不包含任何二进制包,仅包含用户上传的 PKGBUILD,供其他用户下载使用。所有软件包都是非官方的,使用风险自担。

    提交aur之前,需要先了解一下 aur 的一些要求,Rules of submission 提供了一些规则,只要我们按照规则来,就可以上传和维护我们自己的包。

    首先需要去 aur官网 注册一个账号,并上传自己的 ssh key 我们通过克隆一个新的仓库来作为上传的方式。

    git clone ssh://[email protected]/<pkgname>.git

    以dtkcore为例,克隆的时候把pkgname改成dtkcore。

    git clone ssh://[email protected]/dtkcore.git

    如果是第一次创建,会提示你克隆的一个空仓库。

    把 PKGBUILD 文件复制到克隆的目录,然后执行 makepkg --printsrcinfo > .SRCINFO 创建一个软件包信息,将 .SRCINFOPKGBUILD 文件都添加到 git 中,然后提交 commit 信息,推送到服务器。

    注意: 如果您忘记在首次提交中包含 .SRCINFO,您可以使用 rebasing with –root 或是 filtering the tree 使得 AUR 接受您的第一次推送

    提示: 为了保持工作目录和提交尽可能的干净,可以创建 gitignore 文件来排除所有文件,然后再按需添加文件。

    参考资料:

    https://wiki.archlinux.org/index.php/PKGBUILD

    Friday, February 26, 2021

    2020

    拖了这么久才更新博客,想必大家都在 2020 年过的挺不好的,年初武汉的疫情打了全国一个措不及防,甚至已经一整年了,全球疫情还是十分紧张。截止到 2021 年 2 月 26 日,全球每日新增仍然有30 多万,而且全球累计死亡已有 250 万人之多。

    疫情

    生命是如此的脆弱。有些时候和朋友们一起吃饭,会说起一月份时大家是如何 逃离 武汉的,在家隔离又多了哪些事情,我们活着是多么的不容易。虽然人类已经可以生产 5 纳米的芯片了,但是就如同我们对这个宇宙的认知一样,几百亿光年外任我们看,可太阳系内有什么我们都不知道,甚至在地球上还有很多我们未知的东西。

    计划失误

    2020 年我的计划几乎没有一件事是完成的,我计划应该一整年掌握 TypeScriptweb 开发,并开发自己的blog系统,但是方案被我推到重来了三次,我就没耐心继续开发了,只完成了基本的 md 文件编译和服务器渲染就暂停了。计划阅读 《1984》《TensorFlow》《TypeScript实战》 ,然而只有 《TypeScript实战》阅读完了,剩余两本书我则是只看了一丢丢,看来只能放到 2021 年读完了。

    2019Review 的文章里,我提到了我遇到了生命中的那个她,而 2020 年,我和她 结婚 啦。

    在家隔离的日子里,有她陪伴着我,虽然老家的人都挺敌视从武汉回来的我,但是大门一关我谁也看不到,眼不见心不烦,在家一块搓麻将挺好的,还一起练 (zao) 习 (ta)了厨艺,朋友圈做饭比赛第一名。

    学习

    虽然我读书拉后了,但是我的英语 坚持 了下来,我使用多邻国进行英语和日语的学习,已经坚持快6个月了,需要继续加油努力,争取早日把学校里拉下的英语 回来。

    2020review

    工作

    2020 年最大的收益可能是创建了 deepin-git 的仓库,提供了 dde 所有组件在 arch 上的git版,并且还成功申请到了 archlinuxcn 的贡献者。

    文章

    2020 年一共水了18篇文章。

    12-23   ccls
    11-17 Qt多线程
    09-17 deepin-wine中文乱码
    09-08 使用rEFInd来安全启动系统
    09-06 deepin git version
    09-03 使用VSCode远程开发DDE
    08-06ArchLinux上开发startdde
    07-27 use github action to check dde-launcher
    07-21 使用perf工具分析程序性能
    06-16 CTest & QTest/GTest
    06-15 CPP项目的一些坑
    06-15 使用inquirer提供交互式git commit
    06-01 vue-router路由复用后页面没有刷新
    05-31 vue3升级遇到的坑
    02-01 JavaScript建造者模式
    01-31 浅谈Javascript构造器模式
    01-01 2019 Review
    01-01 使用伪元素创建一个圆点

    研究了 vscode 的插件,开发了一个 qmake 的插件原型,可以打开 dtkqt pro 的项目,利用 bear 拦截 make 的编译信息,生成 ccls 需要的数据库,这样就可以为项目提供自动补全和语法检查等功能了。

    写了一些关于远程开发的文章,也是利用 vscode 的远程功能,不得不说 vscode 真的可以说是宇宙第一编辑器了,让老牌的 vimemacs 知道了什么叫易用性的力量。

    展望

    2021 年,不能松懈,今年要继续努力学习,努力提升自己。

    在 2021 年里,我将会负责 deepin 的社区,由于前两年公司需要快速扩张,导致忽略了社区,现在我们的首要任务就是恢复社区,并让社区良性的自由发展,社区是我们的根本。

    挖了很多坑,今年争取都填一下。

    其他

    又搬家了,找了一个一室一厅,开启了二人的小日子,过了年以后把狗子也带来了,现在天天早上起来打狗,晚上回来打狗。

    我又给 肝疼3 氪了一个月卡,这次只忘了一天没登录,不然 30 块大洋又要白花了。

    2020review2020review

    祝大家幸福安康,新年快乐!(^▽^)

    Thursday, September 17, 2020

    众所周知,使用wine来运行windows下的一些软件是linux用户的常用操作,deepin为社区贡献了好几款中国用户必备的软件,例如QQ、微信、企业微信,以此来让更多的人无痛的切换到linux来。近年来value也一直在linux上布局,先后推出了steam主机和proton,前者是基于kvm和steam大屏幕模式的系统,而proton则是wine的分支,value提供了几组补丁用来提高性能和与Windows游戏的兼容性。

    Arch上有非常方便的aur仓库,有很多用户都自己提交一些软件到aur上面,就有几个维护者将deepin打包的deepin wine和对应的软件包移植到Arch上面来,已经是很多人在Arch上面运行QQ和微信的首选方案。

    但是使用的过程中我遇到了以下几个问题:

    • 输入框中文乱码
    • 在DDE桌面使用kwin的情况下最小化会卡死
    • KDE桌面环境无法使用deepin wine的程序

    第二个问题比较特殊,因为dde在deepin和uos下运行的是fork版本的kwin,而Arch上运行的则是原版的kwin,一些操作代码并不具备,所以会出现一些奇葩的状况。

    第三个问题则是xsettings的原因,在移植者的仓库有对应的issue讨论 《KDE环境完全无法使用wine-tim》

    第一个问题解决起来也比较简单,是因为缺少了宋体文件,从而无法正确的渲染中文字体。而比较奇葩的是,把方块复制再粘贴,就可以正确的渲染了。

    解决的方法也很简单,把windows的的字体复制过来就可以了。由于版权的问题,没办法直接提供文件,需要各位自己复制了。

    引用资料

    https://github.com/wszqkzqk/deepin-wine-ubuntu/issues/136

    Tuesday, September 8, 2020

    今年的七夕,我老婆给我买了一台surface laptop 2代,8G内存 + 256G存储版本,我也成功的用上了田牌的机器。

    2020/09/17更新: 不知道为啥,反正是开了对内核签名以后,哪怕是BIOS关闭了安全启动,仍然出现mkinitcpio会卡在autodetect上,无奈全部都删掉重来了,没有弄签名,希望各位看到本文章以后解决了这个问题能回复一下,谢谢。

    surface默认是开启了安全启动(Microsoft签名)和bitlocker来保障设备和系统安全,我作为一个linux系统的开发者,当然是需要在surface上装一个linux了,但是前两年zccrs已经踩过坑了,linux不识别surface键盘,同样的触摸、网卡、声卡等设备也工作的不是很正常,本来以为我要开启远程开发的生活了,还特意写了一篇《使用VSCode远程开发DDE》,来给公司里有同样烦恼的人,让他们也感受一下远程开发的魅力。

    苍天不负有心人,我成功的!

    我成功的使用上了ArchLinux,并且工作的十分良好。这多亏了github上的一个组织linux-surface,有这么一些人,他们付出劳动来让手里的surface设备也用上linux,并且要和普通的x86兼容机一样工作,感谢他们的付出,也让我吃上了螃蟹,安装的过程我就不在这里详细说了,其实非常简单。

    首先因为surface只有一个usb口,而键盘并不能工作,所以需要一个usb的扩展器。先按fn+f6进bios关闭掉安全启动,修改引导顺序为usb优先,之后就是正常的安装系统,但是不需要安装仓库里的内核,我们需要安装linux-surface提供的仓库里的内核。

    这些都是非常正常的步骤,linux-surface提供了自己的仓库和内核,我们正常使用即可。

    这里开始就是我研究了半天的内容, 开启安全模式!

    首先我看了一下arch wiki上有关于安全启动的内容,写的挺详细的,就是看不懂。讲了各种的知识点,各种签名的方式,但是真正到我开始用的时候,我是一直失败的,失败的方式我就不说了,直接说我如何成功的。

    首先我放弃了grub,一个原因是grub的安全启动我一直没有尝试成功,另外一个是我只有一个系统,没必要用grub。我换成了rEFInd来作为我的bootloader,首先安装rEFInd的引导。

    yay -S refind shim-signed sbsigntools
    sudo refind-install --shim /usr/share/shim-signed/shimx64.efi --localkeys

    来解释一下上面两条明令。第一个是安装必要的软件包,refind是bootloader本体,shim-signed是aur里面的用于安全启动的包,shim提供了一种并行的安全启动验证功能,我们使用它来启动refind的efi,再通过refind的efi启动内核,达到终极套娃启动。sbsigntools是用于给文件签名的工具,我们安装完refind以后,refind会帮助我们生成一份默认的key,我们需要使用这个key来为内核进行签名。

    在执行第二条明令以后,会有几次询问,都选择Y回车就行。

    然后使用sbsigntools来对内核进行签名。

    sudo sbsign --key /etc/refind.d/keys/refind_local.key --cert /etc/refind.d/keys/refind_local.crt --output /boot/vmlinuz-linux-surface /boot/vmlinuz-linux-surface

    准备工作已经进行一半了,我们只需要写一下refind的配置文件,就可以启动了。

    refind的配置文件有两个地方,一个是boot分区下面的refind_linux.conf,还有一个是在efi分区里的EFI/refind/refind.conf,我们需要修改的是后者。

    默认配置文件都是注释的,其实我们全部删了就可以了,有需要修改的地方去看原始文件或者文档就行了。

    添加一个menuentry,就可以启动系统了。

    also_scan_dirs +,ArchFS/boot
    dont_scan_dirs ESP:/EFI/boot,EFI/boot
    dont_scan_files shim.efi,MokManager.efi,fbx64.efi,mmx64.efi,shimx64.efi
    scan_all_linux_kernels false

    menuentry "Arch Linux" {
    icon /EFI/refind/icons/os_arch.png
    volume 8B131F77-62D7-4B4A-82D4-B60D7ACA2F6C
    loader /ArchFS/boot/vmlinuz-linux-surface
    initrd /ArchFS/boot/intel-ucode.img
    initrd /ArchFS/boot/initramfs-linux-surface.img
    options "root=UUID=9f8f9556-8ec1-4feb-9519-435beac8376f rw rootflags=subvol=ArchFS loglevel=3 quiet add_efi_memmap"
    submenuentry "Boot using fallback initramfs" {
    initrd /ArchFS/boot/initramfs-linux-surface-fallback.img
    }
    submenuentry "Boot to terminal" {
    add_options "systemd.unit=multi-user.target"
    }
    }

    我用的是btrfs文件系统,所以配置文件有点罗嗦。解释一下上面的内容。

    also_scan_dirs是指定扫描某个目录,因为我是btrfs文件系统,必须使用这个才能让refind扫描到内核文件,否则会无法启动。

    dont_scan_dirs是跳过指定的目录,因为refind默认是会扫描所有的efi文件,我们自己提供了emnuentry,所以不需要让它扫描了。

    dont_scan_files是跳过指定的文件,这里是防止其他目录出现对应的efi也被扫描到。

    scan_all_linux_kernels是扫描所有linux内核,这样所有的内核就会出现在启动列表里,我们同样也是不需要的。

    menuentry里面需要修改的地方有,volume是分区的partuuid,我因为这个uuid就测试了好几次,最后才反应过来不是filesystem uuid,要求的是partition uuid. 所有遇到ArchFS的地方都是不需要的,因为btrfs支持字卷,我的系统是在一个叫ArchFS的卷里面的,如果不是btrfs的文件系统,这个是不需要的,同样options里的rootflags选项也是不需要的,这是传递给内核的参数,让内核可以正确的加载根分区。

    这样就算完工了,重启系统,然后进bios里把安全启动改成Microsoft & 3rd party CA,然后重新启动。

    当第一次加载rEFInd的时候,因为我们的证书是才生成的,主板并没有存储对应的签名,rEFInd会启动mmx64.efi来让我们加载证书,证书的位置在/etc/refind.d/keys下,选择refind_local.cer导入,然后选择重启,重新进入系统就可以了。

    导入证书这部分我其实不太确定,因为我除了使用shim方案,我还测试了preloader方案,那个方案会一开始就启动一个MOK的工具进行证书导入,我记不太清shim到底需不需要手动导入了,如果出现了,那就导入一下就行了,没出现的话就能正常的看到引导界面和进入系统了。

    还有一个后续的动作需要处理,就是内核升级以后,我们需要对内核重新签名,否则会被bios拒绝启动。

    编辑/etc/pacman.d/hooks/99-secureboot.hook,并写入以下配置:

    [Trigger]
    Operation = Install
    Operation = Upgrade
    Type = Package
    Target = linux
    Target = linux-surface
    Target = systemd

    [Action]
    Description = Signing Kernel for SecureBoot
    When = PostTransaction
    Exec = /usr/bin/sh -c "/usr/bin/find /boot/ -type f \( -name 'vmlinuz-*' -o -name 'systemd*' \) -exec /usr/bin/sh -c 'if ! /usr/bin/sbverify --list {} 2>/dev/null | /usr/bin/grep -q \"signature certificates\"; then /usr/bin/sbsign --key /etc/refind.d/keys/refind_local.key --cert /etc/refind.d/keys/refind_local.crt --output {} {}; fi' \;"
    Depends = sbsigntools
    Depends = findutils
    Depends = grep

    享受安全启动吧~

    图片图片图片