<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Posts on STARRY-S&#39; Blog</title>
    <link>https://blog.starry-s.moe/posts/</link>
    <description>Recent content in Posts on STARRY-S&#39; Blog</description>
    <image>
      <title>STARRY-S&#39; Blog</title>
      <url>https://blog.starry-s.moe/web-app-manifest-512x512.png</url>
      <link>https://blog.starry-s.moe/web-app-manifest-512x512.png</link>
    </image>
    <generator>Hugo</generator>
    <language>zh</language>
    <copyright>2016 - 2026 STARRY-S | CC BY-NC-SA 4.0 | Hosted on GitHub Pages
</copyright>
    <lastBuildDate>Fri, 02 Jan 2026 23:07:35 +0800</lastBuildDate>
    <atom:link href="https://blog.starry-s.moe/posts/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>【置顶】一些 Arch Linux 的常用组件整理</title>
      <link>https://blog.starry-s.moe/posts/2024/archlinux-utils/</link>
      <pubDate>Tue, 30 Jan 2024 18:44:07 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2024/archlinux-utils/</guid>
      <description>&lt;p&gt;用这么久 Arch 了，但是却很少写 Arch 相关的博客……&lt;/p&gt;
&lt;p&gt;最近常需要在虚拟机上装 Arch，所以把常用工具及配置整理在这儿，省得每次 &lt;code&gt;pacstrap&lt;/code&gt; 时都要想半天咱需要装什么……&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>用这么久 Arch 了，但是却很少写 Arch 相关的博客……</p>
<p>最近常需要在虚拟机上装 Arch，所以把常用工具及配置整理在这儿，省得每次 <code>pacstrap</code> 时都要想半天咱需要装什么……</p>
<meting-js server="netease" type="song" id="2104716079" theme="#233333"></meting-js>
<hr>
<h2 id="装系统">装系统</h2>
<p>Arch Wiki 的 Installation Guide 在使用 <code>pacstrap</code> 装系统时只写了最基础的软件包 <code>base</code>, <code>linux</code> 和 <code>linux-firmware</code>，可以在这一步补充亿些常用的软件。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">pacstrap -K /mnt base linux linux-firmware <span class="se">\
</span></span></span><span class="line"><span class="cl">    base-devel gcc grub amd-ucode intel-ucode <span class="se">\
</span></span></span><span class="line"><span class="cl">    vim neovim git openbsd-netcat <span class="se">\
</span></span></span><span class="line"><span class="cl">    sudo man-db htop wget <span class="se">\
</span></span></span><span class="line"><span class="cl">    fastfetch
</span></span></code></pre></div><p>进 chroot 后编辑 <code>/etc/pacman.conf</code>，添加以下配置，启用 Arch Linux CN。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"># /etc/pacman.conf
</span></span><span class="line"><span class="cl">[archlinuxcn]
</span></span><span class="line"><span class="cl"># Server = https://repo.archlinuxcn.org/$arch
</span></span><span class="line"><span class="cl">Server = https://mirrors.bfsu.edu.cn/archlinuxcn/$arch
</span></span></code></pre></div><p>之后安装 <code>paru</code> (<span class="spoiler" > <s>莫名其妙的会把这玩意联想到尼禄旋转的 PADORU</s> </span>):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo pacman -Syy <span class="o">&amp;&amp;</span> sudo pacman -S archlinuxcn-keyring
</span></span><span class="line"><span class="cl">sudo pacman -S paru
</span></span></code></pre></div><p>如果电脑上安装了其他系统的话，需要额外安装 <code>os-prober</code>，让 GRUB 在生成配置文件时搜索安装了其他系统的磁盘。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo pacman -S os-prober
</span></span></code></pre></div><p>如果是为 QEMU KVM 虚拟机装系统的话，在执行 <code>grub-install</code> 配置 UEFI 启动引导时记得加一个 <code>--removable</code> 参数。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo pacman -S efibootmgr
</span></span><span class="line"><span class="cl">sudo grub-install --target<span class="o">=</span>x86_64-efi --efi-directory<span class="o">=</span>/boot --bootloader-id<span class="o">=</span>GRUB --removable
</span></span><span class="line"><span class="cl">sudo grub-mkconfig -o /boot/grub/grub.cfg
</span></span></code></pre></div><p>如果不装其他网络工具，只使用 <code>systemd-networkd</code> 的话，需要创建一份默认的配置文件使用 DHCP，否则连不上网。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"># /etc/systemd/network/10-default.network
</span></span><span class="line"><span class="cl">[Match]
</span></span><span class="line"><span class="cl">Name=enp*
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">[Network]
</span></span><span class="line"><span class="cl">DHCP=yes
</span></span></code></pre></div><p>如果需要配置静态网络地址：<br>
（这里只配置了静态 IPv4，如需要禁用 IPv6 的 DHCP，请参照下方<a href="/posts/2024/archlinux-utils/#%e6%a1%a5%e6%8e%a5%e7%bd%91%e7%bb%9c">桥接网络</a>）</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"># /etc/systemd/network/10-static.network
</span></span><span class="line"><span class="cl">[Match]
</span></span><span class="line"><span class="cl">Name=eth0
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">[Network]
</span></span><span class="line"><span class="cl">Address=10.128.0.100/16
</span></span><span class="line"><span class="cl">Gateway=10.128.0.1
</span></span><span class="line"><span class="cl">DNS=10.128.0.1
</span></span></code></pre></div><p>并启用 <code>systemd-networkd</code> Systemd Service：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="go">sudo systemctl enable systemd-networkd
</span></span></span></code></pre></div><p>基本上到这里就可以愉快的 <code>reboot</code> 了，一个精简的系统所需要的软件就基本装好了。</p>
<h3 id="桥接网络">桥接网络</h3>
<p>如果需要使用虚拟机的桥接网络，需要在物理网卡的基础上配置一个<a href="https://wiki.archlinux.org/title/Systemd-networkd#Bridge_interface">桥接网卡</a>，然后为这个桥接网卡配置网络。</p>
<p>先创建一个 <code>br0</code> 网卡设备。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"># /etc/systemd/network/25-br0.netdev
</span></span><span class="line"><span class="cl">[NetDev]
</span></span><span class="line"><span class="cl">Name=br0
</span></span><span class="line"><span class="cl">Kind=bridge
</span></span></code></pre></div><p>将 <code>br0</code> 绑定到某个物理网卡设备。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"># /etc/systemd/network/25-br0-en.network
</span></span><span class="line"><span class="cl">[Match]
</span></span><span class="line"><span class="cl">Name=en*
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">[Network]
</span></span><span class="line"><span class="cl">Bridge=br0
</span></span></code></pre></div><p>为 <code>br0</code> 桥接网卡配置静态 IP 地址，这里禁用了 IPv4 和 IPv6 的 DHCP。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"># /etc/systemd/network/25-br0.network
</span></span><span class="line"><span class="cl">[Match]
</span></span><span class="line"><span class="cl">Name=br0
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">[Network]
</span></span><span class="line"><span class="cl">DHCP=no
</span></span><span class="line"><span class="cl">DNS=10.128.0.1
</span></span><span class="line"><span class="cl">IPv6AcceptRA=false
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">[Address]
</span></span><span class="line"><span class="cl">Address=10.128.0.100/16
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># IPv6 static address
</span></span><span class="line"><span class="cl"># [Address]
</span></span><span class="line"><span class="cl"># Address=fd00:cafe:abcd::1001/64
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">[Route]
</span></span><span class="line"><span class="cl">Gateway=10.128.0.1
</span></span><span class="line"><span class="cl">GatewayOnLink=yes
</span></span></code></pre></div><h2 id="常用命令行工具">常用命令行工具</h2>
<p>如果只作为服务器 / 不包含图形的虚拟机使用的话，装这些咱常用软件，这部分因人而异，仅供参考。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo pacman -S go <span class="se">\
</span></span></span><span class="line"><span class="cl">    kubectl helm <span class="se">\
</span></span></span><span class="line"><span class="cl">    docker docker-buildx <span class="se">\
</span></span></span><span class="line"><span class="cl">    podman <span class="se">\
</span></span></span><span class="line"><span class="cl">    privoxy <span class="se">\
</span></span></span><span class="line"><span class="cl">    proxychains <span class="se">\
</span></span></span><span class="line"><span class="cl">    wireguard-tools <span class="se">\
</span></span></span><span class="line"><span class="cl">    axel aria2 <span class="se">\
</span></span></span><span class="line"><span class="cl">    ffmpeg <span class="se">\
</span></span></span><span class="line"><span class="cl">    jq go-yq <span class="se">\
</span></span></span><span class="line"><span class="cl">    jdk8-openjdk <span class="se">\
</span></span></span><span class="line"><span class="cl">    lm_sensors <span class="se">\
</span></span></span><span class="line"><span class="cl">    net-tools traceroute <span class="se">\
</span></span></span><span class="line"><span class="cl">    openssh <span class="se">\
</span></span></span><span class="line"><span class="cl">    nodejs npm <span class="se">\
</span></span></span><span class="line"><span class="cl">    python3 python-pip <span class="se">\
</span></span></span><span class="line"><span class="cl">    btrfs-progs <span class="se">\
</span></span></span><span class="line"><span class="cl">    <span class="nb">bind</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">    ethtool <span class="se">\
</span></span></span><span class="line"><span class="cl">    bc <span class="se">\
</span></span></span><span class="line"><span class="cl">    age <span class="se">\
</span></span></span><span class="line"><span class="cl">    zsh zsh-syntax-highlighting zsh-autosuggestions <span class="se">\
</span></span></span><span class="line"><span class="cl">    starship fzf <span class="se">\
</span></span></span><span class="line"><span class="cl">    lua-language-server <span class="se">\
</span></span></span><span class="line"><span class="cl">    xclip wl-clipboard
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># golangci-lint</span>
</span></span><span class="line"><span class="cl">paru -S golangci-lint-bin <span class="se">\
</span></span></span><span class="line"><span class="cl">    krew-bin
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 装完 Docker 后把普通用户添加到 docker group 中</span>
</span></span><span class="line"><span class="cl">sudo usermod -aG docker <span class="nv">$USER</span>
</span></span></code></pre></div><p>创建 Docker Daemon 的配置文件 <code>/etc/docker/daemon.json</code>，设定国内的 Mirror，这里用的是咱自己搭的反向代理：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;insecure-registries&#34;</span> <span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;127.0.0.1:5000&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;registry-mirrors&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;https://docker.hxstarrys.me/&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>除了 Docker，还建议使用 Podman 运行一些容器，使用方式和 Docker 没什么大区别，以免去 Daemon 依赖并支持 Systemd。</p>
<p>如果需要跑虚拟机，需要装 QEMU 和 <code>libvirt</code> 相关的组件（咱用 <code>virsh</code> 管理虚拟机，不手搓 qemu 指令）：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo pacman -S qemu-full libvirt
</span></span></code></pre></div><h3 id="k3s--rke2-server">K3s / RKE2 Server</h3>
<p>在 Arch Linux 上安装了 K3s 或 RKE2，关机时会卡在 <code>a stop is running for libcontainer containerd...</code> 一分多钟……</p>
<p>参考 <a href="https://github.com/k3s-io/k3s/issues/2400#issuecomment-1312621468">这个 Issue</a>，创建一个 <code>/etc/systemd/system/shutdown-k3s.service</code> Systemd 文件。</p>
<p>(如果用的是 RKE2，把文件的 <code>k3s</code> 替换为 <code>rke2</code>)</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">[Unit]
</span></span><span class="line"><span class="cl">Description=Kill containerd-shims on shutdown
</span></span><span class="line"><span class="cl">DefaultDependencies=false
</span></span><span class="line"><span class="cl">Before=shutdown.target umount.target
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">[Service]
</span></span><span class="line"><span class="cl">ExecStart=/usr/local/bin/k3s-killall.sh
</span></span><span class="line"><span class="cl">Type=oneshot
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">[Install]
</span></span><span class="line"><span class="cl">WantedBy=shutdown.target
</span></span></code></pre></div><p>之后启用 <code>shutdown-k3s.service</code>，在关机时 Kill 掉 K3s。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo systemctl daemon-reload
</span></span><span class="line"><span class="cl">sudo systemctl <span class="nb">enable</span> shutdown-k3s.service
</span></span></code></pre></div><h3 id="wireguard-client">WireGuard Client</h3>
<p>如果 Arch Linux 还配置了 WireGuard 客户端，而这台 Arch Linux Server 被放在了家里，只能通过有公网 IP 的 WireGuard 服务器连接进去，这时尽管设置了 WireGuard 的 <code>persistent keepalive</code>，但在运营商更换了你家的公网 IP 后，还是会碰到无法自动连接回去的情况，这时可以用咱的 <a href="https://github.com/STARRY-S/wireguard-keepalive">这个简单粗暴的脚本</a>，在 WireGuard 断连一段时间后，自动重启接口。</p>
<h2 id="图形界面">图形界面</h2>
<p>显卡驱动：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="c1"># AMD</span>
</span></span><span class="line"><span class="cl">sudo pacman -S xf86-video-amdgpu
</span></span><span class="line"><span class="cl"><span class="c1"># NVIDIA</span>
</span></span><span class="line"><span class="cl"><span class="c1"># (咱并不喜欢 DKMS 因为每次更新内核都得编译一遍 Kernel Module，所以这里使用的和 Linux 内核一同更新的 NVIDIA Open Driver)</span>
</span></span><span class="line"><span class="cl">sudo pacman -S nvidia-open nvidia-utils nvidia-container-toolkit
</span></span></code></pre></div><p>X11/Wayland 这些相关组件会随着桌面环境一起安装，所以只需要装桌面环境即可，<span class="spoiler" >这里就不需要你额外装 X 了</span>。</p>
<h3 id="wayland-on-nvidia">Wayland on NVIDIA</h3>
<p>在 NVIDIA 显卡上运行 Wayland 需要一些额外操作。</p>
<ul>
<li>
<p>增加 <code>nvidia_drm.modeset=1</code> 内核参数（记得重新生成 <code>grub.cfg</code>）</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"># /etc/default/grub
</span></span><span class="line"><span class="cl">GRUB_CMDLINE_LINUX=&#34;nvidia_drm.modeset=1&#34;
</span></span></code></pre></div></li>
<li>
<p>禁用 <code>nouveau</code>。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> <span class="nb">echo</span> <span class="s2">&#34;blacklist nouveau&#34;</span> &gt;&gt; /etc/modprobe.d/blacklist.conf
</span></span></code></pre></div></li>
<li>
<p>KMS Early Load。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"># /etc/mkinitcpio.conf
</span></span><span class="line"><span class="cl">MODULES=(nvidia nvidia_modeset nvidia_uvm nvidia_drm)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># 然后移除 HOOKS 那一行里的 kms 以完全禁用 nouveau
</span></span></code></pre></div><p>记得重新 <code>mkinitcpio -P</code>。</p>
</li>
</ul>
<h3 id="gnome">GNOME</h3>
<p>如果使用 GNOME Desktop（咱默认使用这个桌面），需要安装这些组件</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo pacman -S gnome
</span></span><span class="line"><span class="cl"><span class="c1"># 通常不直接装 gnome-extra，而是从里面选咱需要的</span>
</span></span><span class="line"><span class="cl">sudo pacman -S gnome-tweaks
</span></span><span class="line"><span class="cl"><span class="c1"># GNOME 系统使用的 NetworkManager 需要额外安装并手动启用，否则无法联网</span>
</span></span><span class="line"><span class="cl">sudo pacman -S networkmanager
</span></span><span class="line"><span class="cl">sudo systemctl <span class="nb">enable</span> --now NetworkManager
</span></span></code></pre></div><h3 id="xfce">XFCE</h3>
<p>对于服务器或 NAS 的图形界面，咱用 XFCE + TigerVNC Server。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo pacman -S xfce4 tigervnc
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 配置 VNC Server</span>
</span></span><span class="line"><span class="cl">mkdir ~/.vnc
</span></span><span class="line"><span class="cl">cat &gt; ~/.vnc/config <span class="s">&lt;&lt; EOF
</span></span></span><span class="line"><span class="cl"><span class="s">session=xfce
</span></span></span><span class="line"><span class="cl"><span class="s">geometry=1920x1080
</span></span></span><span class="line"><span class="cl"><span class="s">localhost=no
</span></span></span><span class="line"><span class="cl"><span class="s">alwaysshared
</span></span></span><span class="line"><span class="cl"><span class="s">EOF</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># VNC 登录密码</span>
</span></span><span class="line"><span class="cl">vncpasswd
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;:1=&lt;USERNAME&gt;&#34;</span> &gt;&gt; /etc/tigervnc/vncserver.users <span class="c1"># 为用户配置使用 VNC 端口 5901 </span>
</span></span><span class="line"><span class="cl">sudo systemctl <span class="nb">enable</span> --now vncserver@:1
</span></span></code></pre></div><h2 id="常用的-gui-软件">常用的 GUI 软件</h2>
<p>装好图形界面并顺利跑起来之后，就可以装常用的桌面软件了，下面这些是部分可能用到的软件，这些因人而异，仅供参考。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo pacman -S vlc <span class="se">\
</span></span></span><span class="line"><span class="cl">    virt-manager <span class="se">\
</span></span></span><span class="line"><span class="cl">    ttf-monaco <span class="se">\
</span></span></span><span class="line"><span class="cl">    noto-fonts noto-fonts-cjk noto-fonts-emoji ttf-dejavu <span class="se">\
</span></span></span><span class="line"><span class="cl">    ttf-jetbrains-mono-nerd <span class="se">\
</span></span></span><span class="line"><span class="cl">    ibus ibus-rime <span class="se">\
</span></span></span><span class="line"><span class="cl">    firefox <span class="se">\
</span></span></span><span class="line"><span class="cl">    emacs
</span></span></code></pre></div><p>在 AUR 中安装的软件：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">paru -S google-chrome <span class="se">\
</span></span></span><span class="line"><span class="cl">    visual-studio-code-bin
</span></span></code></pre></div><h3 id="iommu-group">IOMMU Group</h3>
<p>默认情况下 AMD CPU 不需要编辑内核参数就已经启用了 IOMMU Group，英特尔平台需要添加内核参数 <code>intel_iommu=on</code> 以启用 IOMMU Group。</p>
<p>使用 <a href="https://wiki.archlinux.org/title/PCI_passthrough_via_OVMF#Ensuring_that_the_groups_are_valid">Arch Linux Wiki 提供的以下脚本</a>查看 PCI 设备的 IOMMU Group。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl"><span class="nb">shopt</span> -s nullglob
</span></span><span class="line"><span class="cl"><span class="k">for</span> g in <span class="k">$(</span>find /sys/kernel/iommu_groups/* -maxdepth <span class="m">0</span> -type d <span class="p">|</span> sort -V<span class="k">)</span><span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> <span class="s2">&#34;IOMMU Group </span><span class="si">${</span><span class="nv">g</span><span class="p">##*/</span><span class="si">}</span><span class="s2">:&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> d in <span class="nv">$g</span>/devices/*<span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">        <span class="nb">echo</span> -e <span class="s2">&#34;\t</span><span class="k">$(</span>lspci -nns <span class="si">${</span><span class="nv">d</span><span class="p">##*/</span><span class="si">}</span><span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">done</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">done</span><span class="p">;</span>
</span></span></code></pre></div><h3 id="启用-multilib">启用 Multilib</h3>
<p>启用 Multilib 以安装那些 32 位的软件，例如 Steam。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"># /etc/pacman.conf
</span></span><span class="line"><span class="cl">[multilib]
</span></span><span class="line"><span class="cl">Include = /etc/pacman.d/mirrorlist
</span></span></code></pre></div><p>之后安装 Steam。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo pacman -S steam
</span></span></code></pre></div><p>如果需要加速 Steam 游戏，可以安装 <a href="https://aur.archlinux.org/packages/uuplugin-bin">uuplugin-bin</a>，把电脑伪装成 Steam Deck，酱紫路由器有 UU 加速器插件的话就能给 Steam 加速。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">paru -S uuplugin-bin
</span></span></code></pre></div><p>如果要运行 Windows 游戏，还要安装 Proton。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">paru -S proton
</span></span></code></pre></div><h3 id="音乐">音乐</h3>
<p><code>netease-cloud-music</code> 这个包已经很久没更新了，现在很多功能用不了，除了这个还有一些基于 GTK4 写的网易云音乐客户端也能用。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="c1"># paru -S netease-cloud-music # 网易云音乐 (很久未更新，不太好用)</span>
</span></span><span class="line"><span class="cl">sudo pacman -S netease-cloud-music-gtk4     <span class="c1"># GTK4 版本的网易云音乐</span>
</span></span><span class="line"><span class="cl">sudo pacman -S electron-netease-cloud-music <span class="c1"># Electron 网易云音乐</span>
</span></span><span class="line"><span class="cl">paru -S cider2-bin  <span class="c1"># Apple Music （Cider2 软件需要购买）</span>
</span></span></code></pre></div><h3 id="流程图">流程图</h3>
<p>Draw.io 这个工具画流程图很好用，而且支持 Linux，可以直接从 Arch Linux CN 安装。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo pacman -S drawio-desktop-bin
</span></span></code></pre></div><h3 id="iphone">iPhone</h3>
<p>如果需要挂载 iPhone 手机（<span class="spoiler">安分守己</span>）到电脑上，需要安装这些软件。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo pacman -Sy ifuse usbmuxd libplist libimobiledevice
</span></span></code></pre></div><p>之后挂载 iPhone 的数据到某个文件夹下，就可以把手机的照片通过数据线拷贝到电脑上了。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">mkdir -p iPhone
</span></span><span class="line"><span class="cl">ifuse ~/iPhone
</span></span></code></pre></div><hr>
<p>未完待续，如果还想到了别的再补充到这儿。</p>]]></content:encoded>
    </item>
    <item>
      <title>2025 End of the Year Mix</title>
      <link>https://blog.starry-s.moe/posts/2026/2025-end-of-the-year-mix/</link>
      <pubDate>Fri, 02 Jan 2026 23:07:35 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2026/2025-end-of-the-year-mix/</guid>
      <description>&lt;p&gt;忘记了收藏去年 Vicetone 的 2024 End of the Year Mix。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>忘记了收藏去年 Vicetone 的 2024 End of the Year Mix。</p>
<hr>
<meting-js server="netease" type="song" id="1407072572" theme="#233333"></meting-js>
<p>年末总结没什么太多可写的，于是将去年的和今年的合到一起来写。</p>
<p>福瑞相关的事情，从去年咱的兽装翻新开始，咱带着新毛去了许多大展子，也可以说咱是从去年开始正式的接触 Furry 群体。</p>
<p>去年年初去了长白山的白兽渊，五一带着新毛去了广州佛山 HiFurry，七月份成都云幽岛，顺路去了世界线漫展。八月份去了唐山绒兽，国庆上海兽界，
年底补全之后，十二月末带着全装去了上海冬兽。</p>
<p>今年没有去太多兽聚，五一去的厦门鹭兽岛，七月长春星源节，八月重庆云幽和天津绒兽，十二月末上海冬兽。</p>
<p>除了大展子之外，还有一些零散的小聚，主要范围都是东三省附近。虽然兽聚去过许多次但依旧还是解决不了社恐这个问题，和聚聚们吃饭我全程不说话所以经常会被认为我耍大牌装高冷，一群人聚在一起吃饭实在是太恐怖了，不过没办法实在做不到的事情就不勉强自己了，我已经习惯了聚餐我全程社恐这个事情了。
在 Furry 群体认识了许多新朋友，去陌生的城市旅行也终于不再是自己一个人独自瞎逛了，虽然人多我就社恐但是去聚还是肥肠开心的。</p>
<p>今年重新回坑了山地车，下半年一直没更新博客的原因基本就是一直在骑车、练车。折腾山地车的这段时间认识了几只玩车的毛，于是就有大佬带着我重新入坑山地车，帮我规避了许多坑。
今年六月份从富龙山地车公园回来之后，开始练基础动作，几乎每天晚上都出去练定车、钩后轮、双轮跳和兔跳这些基础。斥巨资买了全套的护具头盔速降服，练习动过的这个过程非常之痛苦，可以说是怎么练都没有效果的那种无力感。定车练了俩月了到现在还不会定，钩后轮现在能钩起来但动作不连贯，钩不高，遇到台阶慢上上不去。双轮跳倒是莫名其妙没怎么学就会了（没救了废了），兔跳也是练了两个多月怎么都练不会。后轮骑、后轮滑这类观赏性动作害怕拉翻也没怎么练，还是胆子太小了。</p>
<p>在富龙的泵道摔了几次，回沈阳后先是骑车去南京桥的免费泵道，先找压抬和切弯的感觉，之后扛着自行车坐地铁去铁西的国际泵道公园，买了次卡，在专业场地练了几次泵道，初级道和中级道属于是能骑下来，但压抬和切弯可以说是练的稀碎，啥也不是。</p>
<p>车子的配件还做了一些改动，在富龙被海带骂了一顿之后，回来重新换了曲柄、脚踏、把套、飞轮，解决了牙盘磕车架、变速不流畅，把套不抓手这些问题，嗯升降座管也换了。轮组没有预算换了，加上我现在强度不高我也不玩干坠，所以就先用 DPS 的轮组作为过渡吧。之后由于我搞错了后刹车 B 转 A 的转换座规格，和禧玛诺的不匹配，在练飞包时后刹车卡钳直接断掉了，之后不得不重新买了二手卡钳和换油工具自己折腾着给刹车注油，断掉的卡钳放家里留着当备用配件，活塞和螺丝都还是好的。一开始注油把整个碟片都搞得全是油，矿物质油没买禧玛诺原厂的还又被训了一顿，然后工具买的不合适导致刹把油囊被漏斗戳破好几次，折腾了很久，搞的乱七八糟的。</p>
<p>七月份报名了驾校，原计划是打算八月份再去富龙骑车的，结果周末时间全拿去考驾照用了，之后就是夏天几乎每天都下雨，根本没办法去山里骑车，上山骑车的计划也是一直在向后推迟。驾驶证考的很顺利，所有考试项目全是一遍过，仅周末练车的情况下两个月的时间就拿到了驾驶证，然后在沈阳租了两天车，一上路就直接被导航带上了东一环快速路跑夜路，吓得手心全是汗。二环的匝道口新手总是开过头，最后还车时租车店收车的人指着车上的旧伤说这是我给撞的，幸好一开始租车时咱录了完整的车外观视频，拿着视频和收车的那几个人吵了好一顿之后才把车还回去，从此就打消了租车的念头，再也不想租车了……</p>
<p>八月份扛着自行车坐高铁去了吉林松花湖，在松花湖滑雪场的山地车公园玩了两天，还去朋友家试骑了软尾和土坡。本着来都来了的心态，把松花湖的日落、腾跃道和老速降道都骑了几遍（当然老速降道是黑线，一半路程都是推车下来的），试了软尾下山之后很明显的能感觉出软尾与硬尾的区别，软尾的后避震对于新手有更高的容错率，硬尾是真难控车，更吃技术。第一次下日落时被石头和坡吓得全程坐着骑下来，虽然网上看的视频觉得松花湖好像难度不高，但实际上日落是蓝线，别被网上拍的第一人称速降视角的视频给骗了，广角运动相机拍出的画面比较吃坡，视频里看到的平路实际上已经是很陡的下坡了，视频画面看起来不起眼的小土堆，实际就是一两米高的大包。5x11 视频刷多了就有一种这坠台人均实力有手就行的错觉，但现实我连半米高的台阶都不敢往下跳。</p>
<p>运气比较好在松花湖没摔车，因为清楚自己是什么技术水瓶所以没尝试什么危险行为，不会飞包所以腾跃道的大包我都是低速压抬过去的，速度一快就有一种要被弹出去的感觉，要不是有这根 FOX 保命我估计早就翻车了。在松花湖回来之后，国庆节没去任何兽聚，扛着自行车坐高铁又一次去了富龙。因为是十月份，张家口已经非常冷了，上山要穿冲锋衣，加上富龙马上要闭园了所以自行车道的路况有点差（不不不还是自己太菜了）。这次去富龙终于尝试了上次没敢骑的蓝线，蓝线初段的搓衣板对硬尾来说是能把人颠飞的程度，从山上下来一趟胳膊都震麻了。后来还试着骑了半段的 ALine，腾跃道初段的陡坡和弯墙全是碎石也是很难的，ALine 后半段不敢骑了于是换到 2X 双人竞速路线，顺路看了 ALine 的超大甩尾包（终于是找到这个大包的位置了），这大包对我来说就看看得了，我是不敢往上飞。</p>
<p>十一月份趁着天没太冷又去了一次沈阳的泵道公园，这次在泵道公园的腾跃线终于是找到了飞包的感觉，动作不标准但起码能跳起来一点了，但总是失误，时机掌握不好就会卡，然后上身就会被惯性猛甩。借了本地车友的后轮滑辅助器之后，用他的土坡车试了几次拉翻，然后终于会做兔跳了，只是兔跳拉不高，发力掌握不好，只能勉强做出这个动作，跃过障碍物这种还不行。后来还收了一台二手土坡车，相比硬 AM 的大车架子，土坡很灵活轻便，非常好拉，也更适合城市玩车，收的二手整车有一些零散的小毛病，到手后又给他装了后刹车，换了前叉碗组把套，换了中轴，擦光所有界面脂，才把这个车修得差不多。现在天气太冷了没办法在户外骑车，只能先在地下车库找空地试着驯服一下这台土坡。</p>
<p>然后减肥的话，最近是一直处于保持体重尽量不再变胖的这个状态。去年去健身房纯粹就是康复训练，没有啥目的就单纯的拉伸和缓解颈肩的肌肉酸痛，今年骑车瘦了一些，年底天冷之后，回健身房开始上了点强度，打算把身上最后那圈肚子肥肉给减掉，不敢练太猛容易发炎然后发烧。瘦肚子很缓慢，估计完全瘦下来得明年夏天了，倒是胳膊没怎么注意，莫名其妙就越来越粗了。</p>
<p>下半年还搞了台汽车，可以拉着我的自行车到处跑了。天冷了之后就在晚上练车，无聊了就去机场加油，心情不好就上三环跑圈，要是心情还不好的话就再跑一圈。后来还尝试了一口气开三百多公里回老家，嗯今年新修的京哈高速特别宽很适合起飞，回想年初的时候自己废了很大力气骑自行车回家，顶着逆风和阵雨骑了三天才只骑了不到一半的路程，中间车子还坏了修了好几次，开车跑高速只要三个小时就能回家了。</p>
<p>本来没打算写辣么多内容到年终总结的，但好像一提起自行车就停不下来。新手爱记录，车骑的啥也不是连车混都不如，但就爱拍点史一样的视频给别人显摆看，然后被大佬打压。除了自行车之外好像没什么太多可写的，等新手阶段过了估计也不会再记录这长篇大论的无聊事情了。</p>]]></content:encoded>
    </item>
    <item>
      <title>十年过去了，我组了一台自己的山地车</title>
      <link>https://blog.starry-s.moe/posts/2025/commencal-meta-ht/</link>
      <pubDate>Wed, 11 Jun 2025 00:03:52 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2025/commencal-meta-ht/</guid>
      <description>&lt;p&gt;回想起来，入坑山地车还是刚上高中时候的事情……&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>回想起来，入坑山地车还是刚上高中时候的事情……</p>
<meting-js server="netease" type="song" id="1414289966" theme="#233333"></meting-js>
<h2 id="起因">起因</h2>
<p>刚上高中那时候，当时家里给买了辆永久山地车用来上学通勤，之后不断的上网查资料用零花钱买配件升级，逐渐的入坑了山地车，但当时精力有限，高考结束之后也一直没有时间精力去鼓捣自行车，直到毕业回沈阳后才买了一辆喜德胜入门山地。虽然很入门但起码比高中时骑的永久强太多了（起码是个入门 XC 车），拿来通勤压压马路参加一些业余的骑行活动还勉强凑活。<br>
今年清明假期的时候尝试骑这台喜德胜从沈阳长途骑行 180KM 到锦州然后坐动车回老家，实际上沿途全程逆风中间还有一天下暴雨被浇透，国道路况很不好每天的骑行距离大大缩减，最终只骑了 110KM，中间打了两次货拉拉最后到的盘锦坐动车。尽管第一次骑长途不太顺利但重新唤醒了咱埋藏在心里很久的咱还喜欢山地车这个事情，于是从这次跑长途回来之后，就开始研究自己组山地车这件事。</p>
<p>起初是打算组 XC/DC 类型的车来着，一开始定的需求就是装个行程稍微长一点的前叉即能轻度越野又能压马路，因为沈阳的骑行环境玩公路的一抓一大把，但很少能见到骑山地的（也可能他们都是山里灵活的狗所以我在市区见不到），所以还是保留了山地骑长途的想法，想着能骑车参加一些长途骑行活动之类的。<br>
但后来逐渐的意识到想越野就得增大胎阻并选用更激进的车架几何，这样就得牺牲掉长途骑行追求速度和轻便的需求，况且我一直认为山地车它就应该去山里面骑而不是拿它来压马路（对那些山马党持有不理解但尊重的态度），骑长途的话应该是搞一辆 Gravel 瓜车或者把我手里这个已有的 XDS 换个窄的光胎。后来经过朋友的疯狂安利，纠正了咱的错误念头，把咱的计划改成了硬尾 AM，以便于入门林道和练习技术，做山地车该做的事情，而不是搞一个碳架子的 XC 车拿去当山马骑。</p>
<p>选配件的过程对于一个新手来说也是很复杂，咱也是一边搜攻略一边改配置清单，意识到车架牌子会很大程度的决定一个玩车团队的风格，（卜威的车混比较多，很多小孩哥在骑，Dartmoor 大木耳倒是没什么问题但的车架几何有点很丑，Marin 马林玩的比较少然后有反馈说硬尾的架子强度不太够），最终决定购买 Commencal 看门狗 Meta HT 这个很有名的二手硬尾 AM 车架，从此入坑了狗门。前叉选的是 38mm 管径 Fox 38 Grip1（之所以没买 Grip2 是因为咱自认为 Grip 2 对于我这种巨菜新手来说根本不会调所以还是先从 G1 傻瓜模式上手），套件是禧玛诺 SLX M7120，为了省预算坐管没有选升降的等着后期有机会再换。这里可能有人会对咱的配置有一些争议比如 Fox 36 Grip2 都比 38G1 强之类的，由于咱太菜了刚入门所以 G1 阻尼和 G2 阻尼是骑不出什么区别来的，然后对于咱这种大体重新手来说自认为还是 38mm 管径的 G1 傻瓜模式阻尼更适合我些，这里重点声明一下咱并不是为了装 X 才买的廉价版 Fox 38 G1，还有就是 G1 就算再差它毕竟也是 Fox 的叉子，咱不接受 G1 和 G2 的理论区别讨论，咱只想有这时间老老实实上山练习技术发挥 G1 或 G2 的真正性能而不是争吵 G1 和 G2 到底差多少）</p>
<p><img loading="lazy" src="images/01.jpg" alt="" />

</p>
<p>然后新手组车总会遇到各种奇葩问题，比较常见的就是购物网站上排在首页推荐的车配件往往并不一定就是最好的，如果小白没有大佬的指引的话很容易就踩坑误入歧途买了一堆垃圾配件浪费钱。然后就是在初次装车时大概率会遇到缺工具的情况，比如咱开始装车需要砸前叉底挡时才意识到忘记买砸底挡的工具，之后给整个浑南区的自行车店打电话问要么就是不知道底挡是什么，要么就是说现在的公路车哪有需要砸底挡的了，都是很久以前的车了（大哥哪个山地车不需要砸底挡的啊我装的是山地车别跟我讲公路车了好不好），后来我买的二手架子上带了已经压好的碗组，因为和我买的碗组规格不符所以还得拆，于是又不得不求助于车店老板找工具帮我拆碗组（拎着自行车架子坐地铁，一个老大爷很没礼貌的二话没说上来敲了几下我的车架然后说了句这不是碳的我内心……），不过车店老板人很好，认识看门狗的架子，愉快的聊了几句后肥肠热心的免费帮我把旧碗组拆了下来。</p>
<p><img loading="lazy" src="images/02.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/12.jpg" alt="" />

</p>
<p>虽然是第一次装车，在装前叉和碗组时踩了一些坑，这辆车只耗时两天就装好了（白天上班晚上装车，光是砸这个前叉底挡就用了一天时间），之后由于想上山的念头达到了极致，于是当机立断决定带着刚装好的车去了国内北方最好的山地车公园，去和那些速降大佬们学一下技术，感受一下速降的氛围，于是在当天凌晨 2 点装好车之后，早晨 5 点就起床打货拉拉把车拉到高铁站用袋子把车包起来坐高铁去北京，行动力这一块真的是没话说。因为北京到太子城的高铁车次太少了所以咱去程是从清河站坐高铁到张家口然后接着打货拉拉去了富龙滑雪场。到富龙后先是找当地的车店老板帮我修一下变速和链条，车店老板一边修我的变速一边劈头盖脸的把我的整个车都骂了一顿，指出了咱购买配件的许多问题，比如变速压根就没装对（但我甚至还能骑），飞轮应该买禧玛诺原厂的但我买的国产日晖，然后花鼓的塔基也买成 HG 的了，架子应该买软尾而不是硬尾（咱特地买的硬尾所以这里懒得和车店老板争吵了），前叉不应该买 G1 阻尼，曲柄买错了应该买 M7120 boost 规格的曲柄但我买成了 M7100，然后车的其他零碎的地方也全骂了一遍，有些地方咱能承认车店老板说的很对骂的没毛病毕竟专业的车店老板往往说话比较直，所以咱也就没和他争论什么，倒是觉得这个老板说话蛮有意的，风格很独特。</p>
<p><img loading="lazy" src="images/11.jpg" alt="" />

</p>
<p>由于是第一次来富龙，稳妥起见还请了一天的教练并租了全套的护具，之后由于缆车的原因没办法直接去家庭道（超级绿），只能上来就直接从新绿线下山，因为之前从来没在山上骑过，所以在新绿线的第一个 180 度大弯那里不会压弯直接一个急刹车然后失去平衡扑到了树丛……
之后从亲子道开始练习基本的过弯和压车进攻姿态这些基础，然后跑了几遍亲子道之后就能慢一点的全程绿道了。下午和教练说想学一下泵道，但咱不会过弯导致一到弯道就失控摔车，要么就是入弯的角度不对前轮直接冲出弯道接着连人带车从弯道顶上翻下来砸回地面，把 Apple Watch 的摔倒检测摔出来了一次。之后由于摔的次数有点太多心率有点飘，加上张家口海拔高搞得我轻微高反，于是不在泵道练习了休息一段时间接着上山，后来骑绿道时在一个小弯前轮冲了出去卡进沟里直接被车来了个过肩摔，第二次触发了 Apple Watch 的摔倒检测，有全套的护具保护所以这几次摔车都没有严重的受伤，只有点小擦伤。富龙的绿道虽然叫新手初级道但实际上是有一定难度的，尤其是那几个大弯道全是碎石速度不够的话后轮很难控制，非常容易打滑然后摔车。</p>
<p><img loading="lazy" src="images/13.jpg" alt="" />

</p>
<blockquote>
<p>摔车后的第一件事是起来拿手机拍手表检测到严重摔倒的照片哈哈哈哈哈哈<br>
苹果的检测还蛮准的轻微摔车它不会提醒，只有那两次较重的摔车才有提醒。</p>
</blockquote>
<p>练了两天的绿道后起码能勉强熟悉一些下山过弯的技巧，除了那种 180 度大弯得减速之外小的弯道能勉强流畅的骑过去了。有点遗憾的就是两天强度对咱来说有点大第二天体力不支，没力气尝试中级道（蓝线）了，实际上第二天全靠着雪碧的糖和冰美式硬撑着骑下来的。虽然说蓝线慢点骑也能骑下去但当时已经没力气站着骑车了，绿线我都是全程坐着下来的，更何况我这是硬尾很颠，所以只好等以后把绿线过弯练熟练之后再去富龙时再尝试中级道。</p>
<p>返程是打货拉拉从富龙到太子城站坐高铁回北京，之后在北京带着自行车坐地铁换乘。</p>
<p><img loading="lazy" src="images/14.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/15.jpg" alt="" />

</p>
<p>说起来已经有将近三年没回北京坐北京的地铁了，拎着自行车这个大显眼包坐地铁可比打货拉拉在路面跑有意思多了，就是清河站离朝阳站实在是有点太远了。</p>
<p>要说这次去富龙的收获的话就是和那边的速降大佬学到了很多技术，看着他们在 A-Line 飞大包真的好帅而我只能在绿线慢悠悠的骑，虽然摔了几次车但起码也是克服了摔车的恐惧，也是体会到护具的重要性了，回家后第一时间就是下单全套的护具，不过后来回家一看摔车的影响还是蛮大的，右腿青了一大片，左脚踝也摔内出血红了一块，要说身上最疼的地方其实是屁股，唉当时就应该直接买升降坐管的，坐管不能灵活调节摔车时屁股顶到坐管是真的痛。然后接下来就是老老实实的回家练技术了，真希望下次去富龙不要再在绿道摔车了。<br>
从富龙回来后肉眼可见的身子瘦了一圈，尽管体重没有太明显的变化但近期一直在骑行/跑步/去健身房健身，所以效果还是很明显的身体在一点点的变瘦，但肚子的那一圈肥肉真的太难减下去了。</p>
<p>由于最近实在巨忙，没时间精力整理博客所以隔了 3 个月才更新，Furry 和兽聚相关的事情虽然最近去了几次兽聚和福瑞朋友在一起出去玩，但福瑞相关的事情咱不打算写到博客里面了，所以借着这个组装山地车和去富龙的机会水了一篇博客。</p>]]></content:encoded>
    </item>
    <item>
      <title>折腾一台 Arch Linux 服务器 - 2</title>
      <link>https://blog.starry-s.moe/posts/2025/archlinux-server-2/</link>
      <pubDate>Mon, 10 Mar 2025 01:55:58 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2025/archlinux-server-2/</guid>
      <description>&lt;p&gt;没想到这么短的时间把前阵子新组的服务器拆了换了一大堆配件……&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>没想到这么短的时间把前阵子新组的服务器拆了换了一大堆配件……</p>
<meting-js server="netease" type="song" id="2634469026" theme="#233333"></meting-js>
<hr>
<p>起因是意识到 8 核 16 线程的 CPU 根本就满足不了咱对虚拟机的需求，然后就是 B550M 重炮手这主板带两张 2080Ti 显卡有点吃力，当主板插上两张显卡之后，第二槽的显卡是 PCIE 3.0x8 模式，第四槽的显卡只能运行在 PCIE 3.0x4，且第四槽不是一个单独的 IOMMU Group，而是和一堆其他主板设备共享总线。尽管这两张卡用 NVLink 连了起来性能应该不会有太大损耗，但卡二只能跑 PCIE 3.0x4 这个带宽实在有点不太能接受。</p>
<p>其实一开始是看了几年前的 AMD EPYC 县城撕裂者 CPU，能搜到在卖的某宝店铺蛮少的，而且价格也偏贵，后来想了下就算换成 R9 5950X 这种 32 线程 CPU 也并不够用，于是逛 B 站找到了这套十年前的 Intel E5 V3 神机，虽然是十年前的服务器电脑，但性能数据和现在的 AMD 5600G 差不多，想来想去除了便宜这玩意应该没什么别的优点了，于是只花了 1600 块钱买了一套双路 E5 2696 V3 板 U 套装。然后还发现服务器的 DDR4 ECC 内存某宝上价格也特别便宜，一百多块钱就能买一根 32G 的条子，于是又买了 128G 的内存条，除此之外为了跑更多的虚拟机，咱还又加了根 2TB 的 NVME 硬盘。</p>
<p>于是更新后的服务器配置变为：</p>
<ul>
<li>主板：华南 X99-F8D</li>
<li>CPU：Intel E5 2696 V3 * 2（36 核心 72 线程）</li>
<li>内存：8 * DDR4 32G</li>
<li>GPU：魔改 22G 显存 NVIDIA 2080 Ti * 2 (With NVLink)</li>
<li>硬盘：两根国产 PCIE 3.0 速率 NVME 固态，容量 2 TB * 2</li>
<li>机箱：先马黑洞 7pro</li>
<li>亿些静音风扇</li>
<li>电源：先马黑洞 1000W ATX 3.0 金牌</li>
</ul>
<p><img loading="lazy" src="images/1.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">主板塞在机箱后的样子</p>
</p>
<p>X99 主板开机有一段的自检时间，因为在换硬盘迁移文件的时候把系统搞崩了于是又不得不重装系统以及重装一些之前已部署过的虚拟机。以现在这个配置来看，这台电脑总算有一个服务器该有的配置了。尽管是十年前的老配置但 Linux 系统本身并不怎么吃性能，咱打算拿他开亿些虚拟机组集群折腾，发挥一下它仅剩的多核性能。至于显卡咱也不打算搞什么 vGPU 给虚拟机了，DKMS 驱动实在是太折腾了，而且咱有两张显卡，如果哪个虚拟机要显卡的话可以把某张空闲的卡直通到虚拟机里就好了，没必要拆卡来用了。这个华南的 X99-F8D 主板素质是要比 B550M 强很多的，两张 2080Ti 显卡其中一个能运行在 PCIE 3.0x16 模式，另一个是 PCIE 3.0x8 模式，然后他们都被划分在了单独的 IOMMU Group 里，所以能很轻易的就把他们直通给虚拟机。（这个主板有三根 PCIE 3.0X16 的插槽所以我甚至还能加一根 PCIE 延长线把之前买的 Tesla P4 也插在这个主板上，组三块显卡……）</p>
<p><img loading="lazy" src="images/2.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">安装显卡和 CPU 散热</p>
</p>
<p><img loading="lazy" src="images/3.png" alt="" />

</p>
<p>这台服务器打算作为一个 BareMetal Server 装一个单节点的 RKE2 集群，用来运行一些 GPU 相关的服务，然后虚拟机里打算装几个 Harvester 节点，用来搞一些嵌套虚拟化之类的东西，像套娃一样在虚拟机里运行虚拟机、再在虚拟机里运行 K8s 集群，甚至还能在集群里运行一个虚拟机群，不过嵌套虚拟化的稳定性比单层的虚拟化差了好多实测总出一些各种奇怪的问题，折腾了很久也没搞清楚是什么问题所以就先不管他了，如果后续能折腾好所有咱遇到的奇怪问题有时间的话还能再水一篇博客。</p>
<p>物理机上面咱在 RKE2 集群中部署了 Ollama、OpenWebUI 这些服务，可以跑一些本地的大模型，最多能跑 70b 模型，推理速度够用，这部分用起来都蛮舒服的没遇到什么问题，就是这个涡轮显卡风扇巨吵，推理过程和引擎发动一样嗷嗷响，等以后打算给他改成双风扇散热，解决一下噪音。</p>
<p><img loading="lazy" src="images/4.jpg" alt="" />

</p>
<blockquote>
<p>快过年了，不要再讨论什么 Kubernetes、云原生、容器和虚拟化了。你带你的破笔记本电脑连着集群回到家，打开 <code>kubectl</code>，而朋友们兜里掏出一大把钱吃喝玩乐，你却用破电脑写着 Golang 代码，改你的 YAML Config，处理 CrashLoopBack。亲戚朋友吃饭问你这一年收获了什么，你说你搞了一台服务器，开了十几个虚拟机，组了几个高可用集群，还能嵌套虚拟化，运行虚拟集群。他们懵了，你还在心里默默嘲笑他们不懂 Kubernetes，不懂 KVM，不懂 Rancher，连 Docker 都是云里雾里，你接触的世界他们永远无法触及，你便是唯一拥有者。亲戚们都在说自己的子女一年的收获，儿子买了个房，女儿买了个车，而你的父母却默默无语，说我的孩子用攒一年的钱搞了个破铁盒子，整天嗡嗡响，电表越转越快了……</p>
</blockquote>
<p>最后是咱非常关心的耗电问题，咱把它插在小米的插座上用来监测电量，轻度使用一天大概 5 度电，一个月差不多 80 多块钱电费 (……</p>
<h2 id="后续">后续</h2>
<blockquote>
<p>2025-03-30 补充</p>
</blockquote>
<p>因为涡轮显卡实在是太吵了，然后 128G 内存对于咱这堆虚拟机来说还是不太够用，于是又加了 4 根32G 内存条，共组成了 256G 的内存，然后还买了两张 2080Ti 双风扇版本的显卡散热器，给显卡改装了散热。</p>
<p><img loading="lazy" src="images/5.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/6.jpg" alt="" />

</p>
<p>改装显卡散热时因为咱这个不是公版显卡，所以需要把新买来的公版散热器中框用锯切掉一部分不然显卡尾部的电容会顶着中框。
然后丽台的显卡主板虽然有两个散热风扇接口但实际只有一个能用，所以需要用分线器将两个散热风扇连到一个接口上去。</p>
<p>改装完成后就可以美滋滋的用这个服务器跑模型推理并折腾咱那一大堆虚拟机了。</p>
<p><img loading="lazy" src="images/7.png" alt="" />

</p>]]></content:encoded>
    </item>
    <item>
      <title>折腾一台 Arch Linux 服务器</title>
      <link>https://blog.starry-s.moe/posts/2025/archlinux-server/</link>
      <pubDate>Sun, 19 Jan 2025 01:37:09 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2025/archlinux-server/</guid>
      <description>&lt;p&gt;又买了一堆电子垃圾，折腾了一台家用的服务器。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>又买了一堆电子垃圾，折腾了一台家用的服务器。</p>
<meting-js server="netease" type="song" id="2102766948" theme="#233333"></meting-js>
<hr>
<p>两年前，咱折腾了一台 ITX 主机作为 NAS 使用，以存储这几年相机拍的照片。不过一台迷你 ITX NAS 主机能运行的虚拟机数量已经满足不了咱对于折腾的需求了，于是又捡了些垃圾配件组了个配置差不多的 M-ATX 台式电脑，作为咱的服务器使用。</p>
<p>配置清单：</p>
<ul>
<li>主板：华硕 B550M PLUS 重炮手</li>
<li>CPU：AMD R7 5700X</li>
<li>内存：旧电脑上拆下来的镁光 DDR4 3200 8G * 4</li>
<li>GPU：NVIDIA Tesla P4</li>
<li>硬盘：国产的杂牌 PCIE 3.0 速率的 NVME 固态，容量 2 TB</li>
<li>机箱：先马黑洞 7pro</li>
<li>亿些静音风扇</li>
<li>散热为旧电脑上换下来的利民双塔散热</li>
<li>电源：先马黑洞 1000W ATX 3.0 金牌</li>
</ul>
<p>因为是拿二手的电脑配件拼的电脑，CPU 也是买的是散片，内存用的是旧电脑上拆的 8G 条子，所以这个服务器有点灵车的性质，咱也不知道它能不能稳定运行，不过它并不作为 NAS 使用，不需要备份重要文件，所以稳定性不是非常重要，咱也没有给他配 UPS 备用电源。除此之外，因为是家用的服务器，所以选的无集显的 AMD 5700X CPU，插上了一个无视频输出的服务器 GPU 卡。装机过程中虽然主板插上了这个服务器显卡，但因为显卡没有视频输出接口，CPU 也没有集显，所以主板开机启动时 VGA 自检灯会一直白色常亮报警。不过搜了一下 NGA 论坛上的讨论，华硕的 550/570 系列的这个 AMD 主板的 BIOS 里虽然没有显卡无头启动的选项开关，但默认是已经支持无头启动了，虽然自检的 VGA 灯会一直白色常亮，但系统可以正常启动，只是显示器一直黑屏，没有画面输出（BIOS 也不能显示）。</p>
<p>于是在装系统时，咱把之前装机时买的 GT730D 亮机卡插上去，进 BIOS 关掉亮瞎眼的 ARGB 灯，启用 SVM，配置好内存频率和 UEFI 启动，关掉安全启动，设置风扇为静音模式。之后用 U 盘把系统安装好，写好网络配置文件并启用 SSH 远程连接，之后关机（PCIE 不支持热插拔一定要关机再拔卡）把显卡换成没有视频输出的 Tesla P4。这样一台“无头”，但有 GPU 的服务器就装好了。</p>
<blockquote>
<p>其实咱试过要不要把这个 GT730D 亮机卡和 Tesla P4 一起插在主板上，因为毕竟这个主板有两个 PCIE x16 的槽子，一个是 PCIE 4.0，另一个是 PCIE 3.0。但是 GT730D 只支持 nvidia-470xx 版本的驱动（或 nouveau），Tesla P4 尽管不支持近几年新出的 nvidia-open 开源驱动（这个驱动只支持近几年新出的 Turing 图灵系列的卡而 P4 是 Pascal 架构）， 但是可以用最新的闭源 <code>nvidia</code> 驱动。咱不想装太旧的显卡驱动于是只好在装机配置电脑的过程中无数次关机，开机箱换显卡……</p>
</blockquote>
<p>Tesla P4 显卡是一个半高卡（刀卡），仅需 PCIE 供电，功耗 75W，无主动散热风扇，因此需要改装风扇散热，咱使用的是网上买的 3D 打印风扇。</p>
<p><img loading="lazy" src="images/2.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">Tesla P4 改装散热</p>
</p>
<p>安装好主板和 CPU、显卡之后是酱紫的。</p>
<p><img loading="lazy" src="images/3.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">安装主板 CPU 和显卡</p>
</p>
<p>最终安装上散热风扇和内存的样子。</p>
<p><img loading="lazy" src="images/4.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">安装散热风扇和内存</p>
</p>
<p>一开始咱用的是先马平头哥 M2 Mesh 这个机箱，但这个机箱比较小只能塞 M-ATX 主板，且只能放一张显卡，如果要塞两张卡的话第二张卡的厚度只能是单槽厚度，所以后来换了个更大，有点闷罐但十分静音的先马黑洞 7pro 机箱。</p>
<h2 id="gpu-passthrough">GPU Passthrough</h2>
<p>为了能够在虚拟机里也能使用 GPU 资源，有一种方式是可以通过 GPU Passthrough 的方式将显卡设备直通至 KVM 虚拟机，在折腾 vGPU 之前，先试一下 Single GPU Passthrough。</p>
<h3 id="iommu-group">IOMMU Group</h3>
<p>参照 <a href="https://wiki.archlinux.org/title/PCI_passthrough_via_OVMF">Arch Wiki</a>，主板在开启 SVM 后，默认就启用了 AMD VT-d 虚拟化并支持 IOMMU，所以默认情况下不需要配置 <code>vfio</code>，直接执行<a href="https://wiki.archlinux.org/title/PCI_passthrough_via_OVMF#Ensuring_that_the_groups_are_valid">这个 <code>iommu.sh</code> 脚本</a>就可以查看 PCIE 设备对应的 IOMMU Group。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo dmesg <span class="p">|</span> grep AMD-Vi
</span></span><span class="line"><span class="cl"><span class="go">[    0.796858] pci 0000:00:00.2: AMD-Vi: IOMMU performance counters supported
</span></span></span><span class="line"><span class="cl"><span class="go">[    0.799440] AMD-Vi: Extended features (0x58f77ef22294a5a, 0x0): PPR NX GT IA PC GA_vAPIC
</span></span></span><span class="line"><span class="cl"><span class="go">[    0.799447] AMD-Vi: Interrupt remapping enabled
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo dmesg <span class="p">|</span> grep IOMMU
</span></span><span class="line"><span class="cl"><span class="go">[    0.741773] perf/amd_iommu: Detected AMD IOMMU #0 (2 banks, 4 counters/bank).
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> iommu.sh
</span></span><span class="line"><span class="cl"><span class="go">...
</span></span></span><span class="line"><span class="cl"><span class="go">IOMMU Group 14:
</span></span></span><span class="line"><span class="cl"><span class="go">	07:00.0 3D controller [0302]: NVIDIA Corporation GP104GL [Tesla P4] [10de:1abc] (rev a1)
</span></span></span><span class="line"><span class="cl"><span class="go">...
</span></span></span></code></pre></div><p>咱的 Tesla P4 显卡插在华硕 B550M 重炮手的 PCIE 4.0x16 插槽上，执行 <code>iommu.sh</code> 可以看到 Tesla P4 这个卡只在一个 <code>IOMMU Group 14</code> 这个 Group 里面，因为显卡没有输出接口所以这个 Group 里也没有音频输出的 Audio Device。</p>
<p>注意不能将显卡插在主板的 PCIE 3.0x16 插槽里，如果这么弄的话，这个卡设备会和 CPU 等好多主板设备共用一个 Group，这样就没办法只将显卡设备直通到虚拟机里。</p>
<p>除此之外因为服务器只有一块显卡，所以当显卡被直通到虚拟机后，服务器将不能运行 X Server，且一个显卡只能直通给一个虚拟机使用，不能分成多个 vGPU。在这篇 <a href="https://gitlab.com/risingprismtv/single-gpu-passthrough/-/wikis/2%29-Editing-your-Bootloader">Single GPU Passhrough Wiki</a> 里介绍可以加内核参数 <code>video=efifb:off</code>，避免显卡设备从虚拟机返回到主机时遇到问题。</p>
<h3 id="配置-libvirt">配置 libvirt</h3>
<p>服务器上面只有一块显卡，且这块卡要被直通给虚拟机使用，所以服务器不能跑 X Server 图形界面，也就是说只能手动配置 libvirt 的 Domain XML 启动虚拟机。或者先跑起来一个 VNC Server，使用 <code>virt-manager</code> 创建好虚拟机之后，再停掉 VNC Server 并 <code>rmmod</code> 主机上已加载的显卡驱动内核模块。</p>
<p>libvirt 的安装和配置教程可以直接参照 <a href="https://wiki.archlinux.org/title/Libvirt">Wiki</a>，这里不再赘述。</p>
<p>最后编辑需要直通显卡设备的虚拟机 Domain XML，参照这里<a href="https://gitlab.com/risingprismtv/single-gpu-passthrough/-/wikis/8%29-Attaching-the-GPU-to-your-VM#removing-the-virtual-display">移除掉 Virtual Display</a>，然后添加一段 <code>hostdev</code> 将主机的显卡设备直通到虚拟机。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;hostdev</span> <span class="na">mode=</span><span class="s">&#34;subsystem&#34;</span> <span class="na">type=</span><span class="s">&#34;pci&#34;</span> <span class="na">managed=</span><span class="s">&#34;yes&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;source&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;address</span> <span class="na">domain=</span><span class="s">&#34;0x0000&#34;</span> <span class="na">bus=</span><span class="s">&#34;0x07&#34;</span> <span class="na">slot=</span><span class="s">&#34;0x00&#34;</span> <span class="na">function=</span><span class="s">&#34;0x0&#34;</span><span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/source&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;address</span> <span class="na">type=</span><span class="s">&#34;pci&#34;</span> <span class="na">domain=</span><span class="s">&#34;0x0000&#34;</span> <span class="na">bus=</span><span class="s">&#34;0x07&#34;</span> <span class="na">slot=</span><span class="s">&#34;0x00&#34;</span> <span class="na">function=</span><span class="s">&#34;0x0&#34;</span><span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/hostdev&gt;</span>
</span></span></code></pre></div><p>这里咱的显卡为 <code>07:00.0</code>，对应 XML 中的 <code>domain=&quot;0x0000&quot; bus=&quot;0x07&quot; slot=&quot;0x00&quot; function=&quot;0x0&quot;</code>。</p>
<p>因为显卡被直通给虚拟机，所以主机上不能运行 X Server，如果 libvirt 在启动虚拟机时卡住，可能是因为显卡驱动还在被主机加载，显卡设备依旧被主机使用而无法被直通给虚拟机。
这是可以执行 <code>rmmod</code> 重新加载显卡驱动，让主机让出显卡给虚拟机使用。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># reload-nvidia.sh</span>
</span></span><span class="line"><span class="cl">sudo rmmod nvidia_drm nvidia_uvm nvidia_modeset nvidia
</span></span><span class="line"><span class="cl">sudo modprobe nvidia nvidia_uvm
</span></span></code></pre></div><p>虚拟机启动后，需要通过 SSH 连接到虚拟机，或通过虚拟机的 VNC 图形界面连接到虚拟机，之后在虚拟机里安装好 <code>nvidia</code> 驱动，就可以看到一整块的显卡设备都被直通给这个虚拟机里了。</p>
<p><img loading="lazy" src="images/1.png" alt="" />

</p>
<h2 id="vgpu-分割显卡">vGPU 分割显卡</h2>
<p>Single GPU Passthrough 的局限很明显，只能将显卡直通给一个虚拟机独享，且这期间主机不能使用图形界面。所以可以用 NVIDIA 的 vGPU 将显卡分为多个“虚拟的小卡”，这样每个虚拟机都有显卡资源使用，肥肠适合在虚拟机中模拟 GPU 集群的开发等折腾场景。</p>
<p>NVIDIA vGPU 驱动为付费使用，可以申请 90 天试用，vGPU 的驱动获取方式这里不赘述。</p>
<p>因为 Tesla P4 仅支持 G16 及之前版本的 vGPU 驱动，Arch Linux 的 <code>linux</code> 内核版本很别新，且 GCC 版本为 14，英伟达的 vGPU 驱动并没有已编译好的可直接无痛安装的版本，于是只能用 The Hard Way 来编译 NVIDIA 驱动内核模块了。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="c1"># 卸载已安装的显卡驱动</span>
</span></span><span class="line"><span class="cl">pacman -R nvidia nvidia-utils
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#  解压显卡驱动安装文件</span>
</span></span><span class="line"><span class="cl">./NVIDIA-Linux-x86_64-535.xxx-vgpu-kvm.run -x
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> NVIDIA-Linux-x86_64*/
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 编译 DKMS 内核模块并安装</span>
</span></span><span class="line"><span class="cl">./nvidia-installer --dkms
</span></span></code></pre></div><p>首先需要解决一下 <code>error: implicit declaration of function 'follow_pfn'; did you mean 'folio_pfn'?</code> 报错，新版的内核移除了 <code>follow_pfn</code>，所以要参照<a href="https://forums.developer.nvidia.com/t/gpl-only-symbols-follow-pte-and-rcu-read-unlock-prevent-470-256-02-to-build-with-kernel-6-10/300052">这里</a>手动改下代码。</p>
<ol>
<li>
<p>编辑 <code>kernel/conftest.sh</code>，修改 <code>unsafe_follow_pfn</code> 为 <code>follow_pfn</code>。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">        follow_pfn<span class="o">)</span>
</span></span><span class="line"><span class="cl">            <span class="c1">#</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># Determine if follow_pfn() is present.</span>
</span></span><span class="line"><span class="cl">            <span class="c1">#</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># follow_pfn() was added by commit 69bacee7f9ad</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># (&#34;mm: Add follow_pfn&#34;) in v5.13-rc1.</span>
</span></span><span class="line"><span class="cl">            <span class="c1">#</span>
</span></span><span class="line"><span class="cl">            <span class="nv">CODE</span><span class="o">=</span><span class="s2">&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">            #include &lt;linux/mm.h&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">            void conftest_follow_pfn(void) {
</span></span></span><span class="line"><span class="cl"><span class="s2">                follow_pfn();
</span></span></span><span class="line"><span class="cl"><span class="s2">            }&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            compile_check_conftest <span class="s2">&#34;</span><span class="nv">$CODE</span><span class="s2">&#34;</span> <span class="s2">&#34;NV_FOLLOW_PFN_PRESENT&#34;</span> <span class="s2">&#34;&#34;</span> <span class="s2">&#34;functions&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">;;</span>
</span></span></code></pre></div></li>
<li>
<p>编辑 <code>kernel/nvidia/nvidia.Kbuild</code>，改成 <code>follow_pfn</code>。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">NV_CONFTEST_FUNCTION_COMPILE_TESTS += follow_pfn
</span></span></code></pre></div></li>
<li>
<p>编辑 <code>kernel/nvidia/os-mlock.c</code> 的 <code>nv_follow_pfn</code> 函数，修改为这样子。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl">    <span class="k">static</span> <span class="kr">inline</span> <span class="kt">int</span> <span class="nf">nv_follow_pfn</span><span class="p">(</span><span class="k">struct</span> <span class="n">vm_area_struct</span> <span class="o">*</span><span class="n">vma</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                    <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">address</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                    <span class="kt">unsigned</span> <span class="kt">long</span> <span class="o">*</span><span class="n">pfn</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="cp">#if defined(NV_FOLLOW_PFN_PRESENT)
</span></span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nf">follow_pfn</span><span class="p">(</span><span class="n">vma</span><span class="p">,</span> <span class="n">address</span><span class="p">,</span> <span class="n">pfn</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="cp">#else
</span></span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="cp">#endif
</span></span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span></code></pre></div></li>
</ol>
<p>之后再编译时还会遇到 <code>error: 'no_llseek' undeclared here (not in a function); did you mean 'noop_llseek'?</code> 报错，搜了一下发现在 Kernel 6.12 中 <code>no_llseek</code> <a href="https://github.com/google/gasket-driver/commit/4b2a1464f3b619daaf0f6c664c954a42c4b7ce00.patch">已被移除</a>，
可以直接编辑 <code>kernel/nvidia-vgpu-vfio/nvidia-vgpu-vfio.c</code> <a href="https://lore.kernel.org/linux-next/20220715140259.205ef267@canb.auug.org.au/">移除掉有 <code>no_llseek</code> 的行</a>。</p>
<p>然后在加载编译后的内核驱动模块时会遇到 <code>failing symbol_get of non-GPLONLY symbol nvidia_vgpu_vfio_get_ops.</code> 报错。这里为了能让内核模块被加载到内核里，只能编辑 <code>kernel/nvidia/nv-vgpu-vfio-interface.c</code> 将 <code>EXPORT_SYMBOL(nvidia_vgpu_vfio_get_ops)</code> 改成 <code>EXPORT_SYMBOL_GPL(nvidia_vgpu_vfio_get_ops)</code>，将 <code>EXPORT_SYMBOL(nvidia_vgpu_vfio_set_ops)</code> 改成 <code>EXPORT_SYMBOL_GPL(nvidia_vgpu_vfio_set_ops)</code>。尽管并不建议直接把非 GPL Symbol 改成 GPL，但如果要在最新的内核版本中安装这个驱动，只能这么修改，或者试着降级至某个 <code>linux-lts</code> 版本，不过降级至其他 LTS 版本还需要手动解决 LTS 版本内核编译时遇到的依赖冲突问题……</p>
<p>驱动安装好之后，就可以参照<a href="https://documentation.suse.com/sles/15-SP6/html/SLES-all/article-nvidia-vgpu.html#configure-nvidia-vgpu-passthrough">这个步骤</a>，将显卡的 <code>mdev</code> 设备分给虚拟机使用了。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> <span class="nb">cd</span> /sys/bus/pci/devices/0000:07:00.0/mdev_supported_types
</span></span><span class="line"><span class="cl"><span class="gp">$</span> ls
</span></span><span class="line"><span class="cl"><span class="go">nvidia-157  nvidia-214  nvidia-243  nvidia-288  nvidia-289  nvidia-63  nvidia-64  nvidia-65  nvidia-66  nvidia-67  nvidia-68  nvidia-69  nvidia-70  nvidia-71
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> cat nvidia-64/name
</span></span><span class="line"><span class="cl"><span class="go">GRID P4-2Q
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> cat nvidia-64/description
</span></span><span class="line"><span class="cl"><span class="go">num_heads=4, frl_config=60, framebuffer=2048M, max_resolution=7680x4320, max_instance=4
</span></span></span></code></pre></div><p><code>/sys/bus/pci/devices/0000:07:00.0/mdev_supported_types</code> 目录中存在许多不同种类的 vGPU 目录，Tesla P4 的 vGPU Type 的介绍可参照<a href="https://docs.nvidia.com/vgpu/14.0/grid-vgpu-user-guide/index.html#vgpu-types-tesla-p4">这里</a>。咱使用的是 Q 系列的 vGPU，<code>nvidia-64</code> 目录对应的是 <strong>2Q</strong> 系列的 vGPU，拥有 2G 显存，最多可创建 4 块 vGPU。</p>
<p>生成几个 UUID 用于创建 vGPU 显卡。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo bash -c <span class="s2">&#34;echo </span><span class="k">$(</span>uuidgen<span class="k">)</span><span class="s2"> &gt; nvidia-64/create&#34;</span>
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo bash -c <span class="s2">&#34;echo </span><span class="k">$(</span>uuidgen<span class="k">)</span><span class="s2"> &gt; nvidia-64/create&#34;</span>
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo bash -c <span class="s2">&#34;echo </span><span class="k">$(</span>uuidgen<span class="k">)</span><span class="s2"> &gt; nvidia-64/create&#34;</span>
</span></span><span class="line"><span class="cl"><span class="gp">$</span> mdevctl list
</span></span><span class="line"><span class="cl"><span class="go">06e5abd1-2ecc-c621-30c8-a5134daf4eac 0000:07:00.0 nvidia-64 manual
</span></span></span><span class="line"><span class="cl"><span class="go">4765ca4e-f690-4074-8085-bfb5f6fba68a 0000:07:00.0 nvidia-64 manual
</span></span></span><span class="line"><span class="cl"><span class="go">95b70c98-ac5e-431d-961c-a7a493f45009 0000:07:00.0 nvidia-64 manual
</span></span></span><span class="line"><span class="cl"><span class="go">bfa21f24-d1ea-dcc3-2bf6-3e7acc8281c4 0000:07:00.0 nvidia-64 manual
</span></span></span></code></pre></div><p>编辑 libvirt 的 Domain XML，添加 <code>mdev</code> 设备，将主机中的 vGPU 添加至虚拟机中。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;hostdev</span> <span class="na">mode=</span><span class="s">&#39;subsystem&#39;</span> <span class="na">type=</span><span class="s">&#39;mdev&#39;</span> <span class="na">managed=</span><span class="s">&#39;no&#39;</span> <span class="na">model=</span><span class="s">&#39;vfio-pci&#39;</span> <span class="na">display=</span><span class="s">&#39;off&#39;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;source&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;address</span> <span class="na">uuid=</span><span class="s">&#39;abcabcd9-defd-4611-abcabc-abcabc4cef4eac&#39;</span><span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/source&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;address</span> <span class="na">type=</span><span class="s">&#39;pci&#39;</span> <span class="na">domain=</span><span class="s">&#39;0x0000&#39;</span> <span class="na">bus=</span><span class="s">&#39;0x00&#39;</span> <span class="na">slot=</span><span class="s">&#39;0x07&#39;</span> <span class="na">function=</span><span class="s">&#39;0x0&#39;</span><span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/hostdev&gt;</span>
</span></span></code></pre></div><p><img loading="lazy" src="images/5.png" alt="" />

</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> lspci <span class="p">|</span> grep VGA
</span></span><span class="line"><span class="cl"><span class="go">00:07.0 VGA compatible controller: NVIDIA Corporation GP104GL [Tesla P4] (rev a1)
</span></span></span></code></pre></div><p>在虚拟机中，需要安装对应的 GRID 显卡驱动，同样的也需要调整虚拟机的内核版本或根据安装驱动时的编译报错来修改驱动的 CFLAGS 和代码，这里不再赘述。</p>
<p>不出意外的话，在虚拟机折腾完显卡驱动后，vGPU 设备就能被驱动正常检测到并使用了。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> nvidia-smi
</span></span><span class="line"><span class="cl"><span class="go">Mon Feb  3 10:02:45 2025       
</span></span></span><span class="line"><span class="cl"><span class="go">+-----------------------------------------------------------------------------+
</span></span></span><span class="line"><span class="cl"><span class="go">| NVIDIA-SMI 510.85.02    Driver Version: 510.85.02    CUDA Version: 11.6     |
</span></span></span><span class="line"><span class="cl"><span class="go">|-------------------------------+----------------------+----------------------+
</span></span></span><span class="line"><span class="cl"><span class="go">| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
</span></span></span><span class="line"><span class="cl"><span class="go">| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
</span></span></span><span class="line"><span class="cl"><span class="go">|                               |                      |               MIG M. |
</span></span></span><span class="line"><span class="cl"><span class="go">|===============================+======================+======================|
</span></span></span><span class="line"><span class="cl"><span class="go">|   0  GRID P4-2Q          On   | 00000000:00:07.0 Off |                    0 |
</span></span></span><span class="line"><span class="cl"><span class="go">| N/A   N/A    P8    N/A /  N/A |      0MiB /  2048MiB |      0%      Default |
</span></span></span><span class="line"><span class="cl"><span class="go">|                               |                      |                  N/A |
</span></span></span><span class="line"><span class="cl"><span class="go">+-------------------------------+----------------------+----------------------+
</span></span></span><span class="line"><span class="cl"><span class="go">                                                                               
</span></span></span><span class="line"><span class="cl"><span class="go">+-----------------------------------------------------------------------------+
</span></span></span><span class="line"><span class="cl"><span class="go">| Processes:                                                                  |
</span></span></span><span class="line"><span class="cl"><span class="go">|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
</span></span></span><span class="line"><span class="cl"><span class="go">|        ID   ID                                                   Usage      |
</span></span></span><span class="line"><span class="cl"><span class="go">|=============================================================================|
</span></span></span><span class="line"><span class="cl"><span class="go">|  No running processes found                                                 |
</span></span></span><span class="line"><span class="cl"><span class="go">+-----------------------------------------------------------------------------+
</span></span></span></code></pre></div><p>之后可以编辑 <code>/etc/nvidia/gridd.conf</code> 配置 License 服务器等设置，参照英伟达的官网文档即可，这里不再赘述。</p>
<h2 id="others">Others</h2>
<p>经过了好久的折腾之后，咱的感觉是特斯拉 P4 这张卡有些年头了，因为是 Pascal 架构，不支持图灵，所以也不能支持最新的 nvidia-open 驱动，只能安装旧版驱动。</p>
<p>性能方面因为这就是个半高的计算卡，不需要外接供电，只有 PCIE 插槽供电的 75W 功耗，所以别指望它能跑什么复杂的图形运算。不过一些基本的视频渲染之类的应该还是能跑的。如果想搞现在热火朝天的人工智能的话，还是买新一点的大显存显卡比较好，所以想来想去，这个 P4 半高显卡似乎刚好适合插在我的 NAS 上当一个入门级的运算卡来使用……</p>
<p>然后装好 <code>nvidia-container-toolkit</code> 之后，在虚拟机里运行 Kubernetes 并折腾好 <code>gpu-operator</code> 后，就能顺利的使用 vGPU 显卡跑个什么 CUDA Performance Test。</p>
<p><img loading="lazy" src="images/6.png" alt="" />

</p>]]></content:encoded>
    </item>
    <item>
      <title>海王星 4 MAX 3D 打印机入坑小记</title>
      <link>https://blog.starry-s.moe/posts/2025/neptune-4-max/</link>
      <pubDate>Mon, 06 Jan 2025 21:24:37 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2025/neptune-4-max/</guid>
      <description>&lt;p&gt;我为什么把 2024 年 11 月份该发的博客拖到了 2025 年来写……&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>我为什么把 2024 年 11 月份该发的博客拖到了 2025 年来写……</p>
<meting-js server="netease" type="song" id="2099601427" theme="#233333"></meting-js>
<hr>
<p>最近经常折腾 Furry 相关的东西，因为兽装头骨、风扇外壳等好多身体零部件都是需要用 3D 打印来制作的，Cosplay 相关的一些道具也可以用 3D 打印来制作，所以就想搞一台 3D 打印机，打一些咱自己的身体部件。</p>
<p>需求的话就是这个打印机的尺寸至少得能打印兽装头骨，网上推荐刚入坑的新手买拓竹的 3D 打印机，但是搜了一圈拓竹并没有合适的大尺寸 FDM 打印机，于是纠结对比了一阵子创想三维和爱乐酷之后，在双十一的时候买了爱乐酷海王星 MAX 这个超级大的大尺寸 FDM 3D 打印鸡。</p>
<p>收到打印机后的第一感觉就买大了。这个打印机超级大，原本是打算找个桌子放它的结果发现桌子根本就放不下它，只能单独收拾出一块空间把这个打印机放在地板上。</p>
<p>打印耗材这方面因为咱不想用易脆、不防水且易变黄的 PLA 材质，于是直接跳过了新手教程找客服把赠送的 PLA 耗材全换成了 PETG，之后折腾了一两天时间速成了一下切片软件的食用方法，打印了几个小船和 Z-level 测试平面后，从网上下载一些开源的模型自己切片打印玩。</p>
<p>于是遇到了一些问题和解决问题后产生的问题。</p>
<h2 id="网络连接">网络连接</h2>
<p>虽然海王星 4 MAX 支持 WIFI 无线连接，但是它自带的中控屏幕固件有亿些简陋。咱的 WIFI 密码有特殊字符而它的中控屏幕只能输入字母数字和一些简单的符号，这就导致打印机没办法直接连接咱的 WIFI。</p>
<p>最终的解决办法是网上几十块钱买了个带网口的 WIFI 无线中继器，把中继器的网口和打印机的网口用网线连接，这样就解决了 3D 打印机连接局域网的问题。在浏览器访问打印机局域网 IP 终于能完美使用满血的 Klipper 固件的 Web 控制台，从此不用每次打印模型时都来回插拔 U 盘传模型文件。</p>
<p>在网上查了一下这个 Klipper 固件实际就是一个 ARM64 架构的 Linux 系统，而且可以 SSH 远程登录。于是搜了一阵子之后找到了这个打印机的 SSH 用户名/密码为 <code>mks/makerbase</code>。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">$ ssh mks@192.168.10.101
</span></span><span class="line"><span class="cl">mks@192.168.10.101<span class="s1">&#39;s password: 
</span></span></span><span class="line"><span class="cl"><span class="s1">           _              _ 
</span></span></span><span class="line"><span class="cl"><span class="s1"> _ __ ___ | | _____ _ __ (_)
</span></span></span><span class="line"><span class="cl"><span class="s1">| &#39;</span>_ <span class="sb">`</span> _ <span class="se">\|</span> <span class="p">|</span>/ / __<span class="p">|</span> <span class="err">&#39;</span>_ <span class="se">\|</span> <span class="p">|</span>
</span></span><span class="line"><span class="cl"><span class="p">|</span> <span class="p">|</span> <span class="p">|</span> <span class="p">|</span> <span class="p">|</span> <span class="p">|</span>   &lt;<span class="se">\_</span>_ <span class="se">\ </span><span class="p">|</span>_<span class="o">)</span> <span class="p">|</span> <span class="p">|</span>
</span></span><span class="line"><span class="cl"><span class="p">|</span>_<span class="p">|</span> <span class="p">|</span>_<span class="p">|</span> <span class="p">|</span>_<span class="p">|</span>_<span class="p">|</span><span class="se">\_\_</span>__/ .__/<span class="p">|</span>_<span class="p">|</span>
</span></span><span class="line"><span class="cl">                   <span class="p">|</span>_<span class="p">|</span>      
</span></span><span class="line"><span class="cl">Welcome to Armbian 22.05.0-trunk  with bleeding edge Linux 5.16.20-rockchip64
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">No end-user support: built from trunk
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">System load:   3%           	Up time:       1:28	
</span></span><span class="line"><span class="cl">Memory usage:  19% of 976M   	IP:	       10.1.10.1
</span></span><span class="line"><span class="cl">CPU temp:      55°C           	Usage of /:    77% of 6.6G   	
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">[</span> <span class="m">0</span> security updates available, <span class="m">259</span> updates total: apt upgrade <span class="o">]</span>
</span></span><span class="line"><span class="cl">Last check: 2025-01-06 20:46
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Last login: Mon Dec <span class="m">16</span> 23:19:31 <span class="m">2024</span> from 192.168.1.10
</span></span><span class="line"><span class="cl">mks@mkspi:~$ neofetch 
</span></span><span class="line"><span class="cl">                                 mks@mkspi 
</span></span><span class="line"><span class="cl">                                 --------- 
</span></span><span class="line"><span class="cl">      █ █ █ █ █ █ █ █ █ █ █      OS: Armbian <span class="o">(</span>22.05.0-trunk<span class="o">)</span> aarch64 
</span></span><span class="line"><span class="cl">     ███████████████████████     Host: Makerbase mks-pi 
</span></span><span class="line"><span class="cl">   ▄▄██                   ██▄▄   Kernel: 5.16.20-rockchip64 
</span></span><span class="line"><span class="cl">   ▄▄██    ███████████    ██▄▄   Uptime: <span class="m">1</span> hour, <span class="m">29</span> mins 
</span></span><span class="line"><span class="cl">   ▄▄██   ██         ██   ██▄▄   Packages: <span class="m">1362</span> <span class="o">(</span>dpkg<span class="o">)</span> 
</span></span><span class="line"><span class="cl">   ▄▄██   ██         ██   ██▄▄   Shell: bash 5.0.3 
</span></span><span class="line"><span class="cl">   ▄▄██   ██         ██   ██▄▄   Terminal: /dev/pts/0 
</span></span><span class="line"><span class="cl">   ▄▄██   █████████████   ██▄▄   CPU: <span class="o">(</span>4<span class="o">)</span> @ 1.200GHz 
</span></span><span class="line"><span class="cl">   ▄▄██   ██         ██   ██▄▄   Memory: 199MiB / 976MiB 
</span></span><span class="line"><span class="cl">   ▄▄██   ██         ██   ██▄▄
</span></span><span class="line"><span class="cl">   ▄▄██   ██         ██   ██▄▄                           
</span></span><span class="line"><span class="cl">   ▄▄██                   ██▄▄                           
</span></span><span class="line"><span class="cl">     ███████████████████████
</span></span><span class="line"><span class="cl">      █ █ █ █ █ █ █ █ █ █ █
</span></span></code></pre></div><p>SSH 连接到 Klipper 系统后，不要乱改系统里的文件，弄坏了的话得就重新刷固件了，应该会很麻烦。</p>
<h2 id="打印噪音">打印噪音</h2>
<p>一开始 3D 打印机的 Y 轴在打印过程中会出现奇怪的嗡嗡声，而且会随着 Y 轴平台的移动速度而变换声调。通常情况下解决打印机噪音的方法是降低打印速度，每次打印时蹲在它旁边感觉在听交响乐一样，但咱的这台打印机开狂暴模式并调速 150% 以上后居然能减小 Y 轴平台因缓慢移动而产生的嗡嗡声……</p>
<p>不过仅通过提高打印速度并不是噪音的最好的解决办法，而且提速反而容易导致打印失败（不过咱的这台打印机在打 PETG 时反而是在低速打印时更容易失败……），海王星 MAX 是基于 Klipper 固件，而且它的电机是使用的 TMC Driver (<code>tmc2209</code>)，于是在啃了一阵子 Klipper 官方文档后 (<span class="spoiler">RTFM</span>) 找到了一些优化噪音的配置文件。</p>
<h3 id="spreadcycle-vs-stealthchop">spreadCycle vs stealthChop</h3>
<p><a href="https://www.klipper3d.org/TMC_Drivers.html?h=stealthchop_threshold#setting-spreadcycle-vs-stealthchop-mode">根据 Klipper 官网</a>的解释，默认情况下，Klipper 的 TMC 驱动器使用 <strong>spreadCycle</strong> 模式，该模式能提供更高的精度和更好的打印质量。然而这个模式和 <strong>stealthChop</strong> 模式相比噪音则更大一些。</p>
<p>可以通过设置 <code>stealthchop_threshold: 999999</code> 强制 TMC Driver 永远使用 <strong>stealthChop</strong> 模式。对应修改后的样例 <code>printer.cfg</code> 如下。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="p">[</span><span class="l">tmc2209 stepper_x]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">uart_pin</span><span class="p">:</span><span class="w"> </span><span class="l">PB9</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">run_current</span><span class="p">:</span><span class="w"> </span><span class="m">1.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">hold_current</span><span class="p">:</span><span class="w"> </span><span class="m">0.8</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">interpolate</span><span class="p">:</span><span class="w"> </span><span class="kc">True</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">stealthchop_threshold</span><span class="p">:</span><span class="w"> </span><span class="m">999999</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">driver_SGTHRS</span><span class="p">:</span><span class="w"> </span><span class="m">90</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="l">diag_pin:^PC0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">[</span><span class="l">tmc2209 stepper_y]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">uart_pin</span><span class="p">:</span><span class="w"> </span><span class="l">PD2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">run_current</span><span class="p">:</span><span class="w"> </span><span class="m">1.4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">hold_current</span><span class="p">:</span><span class="w"> </span><span class="m">1.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">interpolate</span><span class="p">:</span><span class="w"> </span><span class="kc">True</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">stealthchop_threshold</span><span class="p">:</span><span class="w"> </span><span class="m">999999</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">driver_SGTHRS</span><span class="p">:</span><span class="w"> </span><span class="m">80</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="l">diag_pin:^PB8</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">[</span><span class="l">tmc2209 stepper_z]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">uart_pin</span><span class="p">:</span><span class="w"> </span><span class="l">PC5</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">run_current</span><span class="p">:</span><span class="w"> </span><span class="m">0.8</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">hold_current</span><span class="p">:</span><span class="w"> </span><span class="m">0.5</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">interpolate</span><span class="p">:</span><span class="w"> </span><span class="kc">True</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">stealthchop_threshold</span><span class="p">:</span><span class="w"> </span><span class="m">999999</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">[</span><span class="l">tmc2209 extruder]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">uart_pin</span><span class="p">:</span><span class="w"> </span><span class="l">PC4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">run_current</span><span class="p">:</span><span class="w"> </span><span class="m">0.7</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">hold_current</span><span class="p">:</span><span class="w"> </span><span class="m">0.5</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">interpolate</span><span class="p">:</span><span class="w"> </span><span class="kc">True</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">stealthchop_threshold</span><span class="p">:</span><span class="w"> </span><span class="m">999999</span><span class="w">
</span></span></span></code></pre></div><p>在设置 XYZ 轴和挤出机 (Extruder) 都使用 <strong>stealthChop Mode</strong> 后，能感到打印噪音的确小了一点点。至于打印质量的变化实测没有太多的改变。改善的效果因人而异，如果觉得噪音大的话可以尝试使用 <strong>stealthChop Mode</strong> 但改善的效果可能并不是特别明显。</p>
<blockquote>
<p><strong>记得备份</strong></p>
</blockquote>
<hr>
<p>除此之外经过一段时间的观察发现咱的这个打印机只有 Y 轴移动时平台会有特别大的嗡嗡声，于是实在忍受不了照着网上的拆解视频把它的 Y 轴平台拆了下来，给它的 Y 轴滚轮调整了一下螺丝松紧度确保每个滚轮都能顺畅的转动而不是被履带硬拖着摩擦，顺便给每个轴承都上了点油（咱用的自行车链条油 XD），经过着一番调教之后，Y 轴平台的声音控制在能接受的范围了。</p>
<p>如果还是觉得 X/Y 轴的噪音有些难以接受，而且噪音是由于滚轴与轨道摩擦产生的，可以尝试在某宝搜一下海王星 4 MAX 改装线轨的套件，换成线轨能避免滚轴摩擦产生的异常噪音。至于改装后效果咱并没有试过，感兴趣的可以去试下。</p>
<h2 id="加装摄像头">加装摄像头</h2>
<p>因为不想 3D 打印过程中总来回跑过去看它有没有出问题，或者不在家的时候担心打印机是否在贴心的给我准备回家的炒面惊喜，于是想装一个摄像头可以实时观察它的状态。</p>
<p>因为打印机是 Linux 固件，所以理论上支持 Linux 的摄像头（WebCamera）都可以用到这台打印机上面。咱有一个联想的 1080P 电脑摄像头，具体啥型号忘了但似乎市面上绝大多数电脑的 USB 免驱摄像头它都能用。海王星 4 MAX 的 Klipper 固件已经贴心的预装好了 Webcam 组件，所以理论上是可以插上 USB 摄像头就能用。</p>
<p>但实际情况是这个打印机的 <code>/dev</code> 目录下面一共有 5 个 Video 设备，<code>webcam.txt</code> 配置文件默认使用的是 <code>/dev/video4</code> 作为 USB 摄像头。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">mks@mkspi:~$ ls /dev/video*
</span></span><span class="line"><span class="cl">/dev/video0  /dev/video1  /dev/video2  /dev/video3  /dev/video4  /dev/video5
</span></span></code></pre></div><p>经过咱一段时间的使用后发现，这个机器有时重启过后，USB 摄像头的设备会莫名其妙变成 <code>/dev/video0</code>，而有时又会变回去 <code>/dev/video4</code>。
所以如果重启了打印机后发现摄像头画面不见了，那就编辑一下 <code>webcam.txt</code> 配置文件把 <code>/dev/video4</code> 改成 1-5 之间的数都试一下，每改完一次配置文件记得重启一下 Webcamd Service。</p>
<p>至于如何固定住这个 Video Device 的序号咱可不敢瞎折腾，咱可不想把系统搞坏了再去重装固件。</p>
<hr>
<p>然后把这个摄像头固定到哪里也是个问题。如果找个三脚架把这个摄像头支在打印机旁边的话又有点太占空间了。在 Makerbase 上找到了一个比较符合咱需求的<a href="https://makerworld.com.cn/zh/models/207267#profileId-139768">摄像头支架模型</a>，打印出来后还需要自备两根长一点的 M4 螺丝和螺母，感兴趣的可以去看看。</p>
<h2 id="料盘干燥盒">料盘干燥盒</h2>
<p>沈阳的冬天空气湿度只有 20%，但 PETG 耗材裸露在空气中的话依旧会受潮影响打印质量。于是在网上找到了比较通用的米桶改装耗材料架的解决方案，买了变色硅胶当作干燥机，自己下载了模型打印的耗材料架，最终用特氟龙管连接米桶和打印机的断料检测器。咱最终折腾好的料架为这样子。</p>
<p><img loading="lazy" src="images/1.jpg" alt="" />

</p>
<h2 id="其他的自定义配置">其他的自定义配置</h2>
<p>除了上面这些调整之外，这个打印机的热床加热比较缓慢。默认情况下打印机是只在开始打印时才开始对热床加热，从室温加热到 80 度需要二十分钟左右的时间。如果想避免掉每次打印时都要等十几分钟的热床加热时间，可以修改 Klipper 固件的配置，在打印机刚开机时就对热床进行加热到一定温度，这样在打印开始时就不需要等很久的热床加热时间了。</p>
<p>设置打印机开机时执行的自定义代码配置文件为：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="p">[</span><span class="l">delayed_gcode system_start]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c"># 系统启动 3 秒后执行以下 GCODE</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">initial_duration</span><span class="p">:</span><span class="w"> </span><span class="m">3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">gcode</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="l">SET_HEATER_TEMPERATURE HEATER=heater_bed TARGET=60</span><span class="w"> </span><span class="c"># 热床加热至 60 度</span><span class="w">
</span></span></span></code></pre></div><p>当然开机之后就开启热床预热会产生额外的电费，所以这些配置可能仅适用于咱自己用。</p>]]></content:encoded>
    </item>
    <item>
      <title>自制兽头内置风扇 &amp; LED 发光眼</title>
      <link>https://blog.starry-s.moe/posts/2024/esp32-fan-controller/</link>
      <pubDate>Tue, 01 Oct 2024 00:34:42 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2024/esp32-fan-controller/</guid>
      <description>&lt;p&gt;众所周知兽装的透气性很差，可爱的外表下内胆实际早就热得满头大汗，长时间出毛不吃风扇的话很容易把内胆闷死。兽头的内部空间狭小很难塞下一个风量大而且体积还小的风扇，网上能找到的兽装内置风扇普遍都是基于 5V 电压的 USB 风扇改造的，只能 DC 调速且风量很小，且容易卡毛，所以想了一下还是决定自己造一个符合咱自己需求的兽装内置风扇。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>众所周知兽装的透气性很差，可爱的外表下内胆实际早就热得满头大汗，长时间出毛不吃风扇的话很容易把内胆闷死。兽头的内部空间狭小很难塞下一个风量大而且体积还小的风扇，网上能找到的兽装内置风扇普遍都是基于 5V 电压的 USB 风扇改造的，只能 DC 调速且风量很小，且容易卡毛，所以想了一下还是决定自己造一个符合咱自己需求的兽装内置风扇。</p>
<meting-js server="netease" type="song" id="2154129045" theme="#233333"></meting-js>
<hr>
<h2 id="物料清单">物料清单</h2>
<p>这里选的材料只按照咱自己的需求来的，不同的兽需求不一样所以下面的物料仅供参考。</p>
<ul>
<li>
<p>风扇：要求体积小，风量大，能调速，所以咱使用的是 12V 供电的服务器风扇。<br></p>
<blockquote>
<p>其实DC 调速也不是不行，但想让 ESP32 实现 DC 调速驱动大功率电器的话，你还是得让他先输出 PWM 信号，然后结合 MOS 管调节电压。</p>
</blockquote>
</li>
<li>
<p>控制电路：为了能远程使用手机控制，对比了 ESP32 和树莓派 Pico 之后，因为树莓派 PicoW 的价格更贵且体积比 ESP32C3 大，Wifi 支持效果不是很好，所以最后买的是网上的成品 ESP32C3 开发板，用于输出 PWM 信号，并作为 Wifi AP + Web Server 来使用手机控制（因为咱对蓝牙协议不太熟悉且适配蓝牙的话一般来说还要再开发一个 APP，相对于一个 Web Server 来说还是太麻烦了）。</p>
</li>
<li>
<p>发光眼：自认为发光眼也很好看，所以除了需要控制 PWM 风扇之外，还需要结合一个 MOS 管调节 LED 灯亮度，所以 ESP32 需要输出两个不同频道的 PWM 信号。</p>
</li>
<li>
<p>电源：因为兽头空间小，赛一块电池到脑袋里并不是一个很好的办法，而且网上卖的小作坊产的成品电池总担心会不会有安全隐患在脑子里着火什么的，而且，兽头已经很重了再赛一块几百克的容量很小的电池，风扇的续航时间也不会很久。所以最后咱的办法是用一根长一点的 PD 协议 Type C 数据线连接外置的充电宝，一个超薄的小充电宝可以塞裤兜里或者别的包里，只要想办法把数据线藏在衣服里就好了，这种给脑子充电的方法实测用起来还蛮不错的，而且 1 万毫安的充电宝续航时间非常久，至少能运行十几个小时，唯一需要注意的是电路板的 Type C 接口容易被扯坏，得想办法加固一下。</p>
</li>
</ul>
<p>所以最后咱在网上买的材料为：ESP32C3 开发板、3*4cm 洞洞板、12V 服务器风扇、成品 MOS 管电路板、12V LED 灯带、12V PD 协议诱骗芯片、12V 转 5V 降压电路板、防静电 TVS 管，还有导线和焊接相关的一堆其他工具。</p>
<p>因为咱模电并不好，不会画电路板设计电路，再加上这个只是咱自己用，所以买的都是网上的成品开发模块，把它们焊到洞洞板上，用一堆导线连起来，缺点是体积比较难控制，很难做得更小了，然后一堆电线在电路板上扭来扭去的也很难看，但能用就行。</p>
<h2 id="控制代码">控制代码</h2>
<p>相对于硬件电路来说，代码这部分是重头戏。咱现学了一下 PlatformIO IDE 并照着 ESP32 的官方文档和样例 Example，用纯 C 语言搓了一个 HTTP Handler 和 PWM 控制相关的代码。
后端的接口可以自定义修改 Wifi 名称、信道、默认网关、PWM 输出引脚和 PWM Duty（占比）等一大堆自定义配置，用户修改的设置参数（比如 Wifi 密码、风速和亮度值什么的）能保存到文件中，重启之后数据不会丢失。这堆东西用纯 C 写相比其他更高级点的语言来说还挺复杂的。</p>
<p>然后咱还搞了一套比较好看的 Web UI 界面，并加了亿点点花里胡哨的细节（虽然是给咱自己用，但功能写得还是蛮全的）。</p>
<p>代码放在了<a href="https://github.com/STARRY-S/esp32-pwm-controller">这里</a>，感兴趣的可以去瞅瞅。</p>
<h2 id="食用效果">食用效果</h2>
<p>其实这个风扇早在几个月前就做好了，期间不断修改调整了几次，现在已经装在了头里且没拍什么照片，所以我直接录了段视频演示整体效果。因为风扇被黏在头里了所以不是很能感受到它的风速。要说具体的效果的话，只靠着这个内置风扇，兽聚夜场蹦达几个小时应该没什么问题，但是得小心数据线别把电路板的 Type C 接口扯断了……</p>
<iframe src="//player.bilibili.com/player.html?isOutside=true&aid=113227350410850&bvid=BV1qhxbevE2T&cid=26082543872&p=1" scrolling="no" border="1" frameborder="true" framespacing="0" allowfullscreen="true"></iframe>]]></content:encoded>
    </item>
    <item>
      <title>Pandavan 安装 UU 加速器插件</title>
      <link>https://blog.starry-s.moe/posts/2024/pandavan-uu-booster/</link>
      <pubDate>Mon, 16 Sep 2024 12:38:31 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2024/pandavan-uu-booster/</guid>
      <description>&lt;h2 id=&#34;起因&#34;&gt;起因&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;省流：这段都是废话，想看安装教程的可直接跳过到下一节&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;前阵子搬了新家，办了新的宽带，和联通安宽带的师傅要了光猫桥接。原本是打算用 NanoPi R5S 拨号上网，但 RK3568 这个 ARM 芯片尽管性能蛮强的，但 PPoE 拨号没有硬件加速，跑不满千兆宽带，而且打游戏网络延时有时还忽高忽低，用起来很不舒服。&lt;/p&gt;
&lt;p&gt;因为光猫桥接之后联通还非常贴心的主动给了 IPv4 公网 IP，所以不打算改回光猫拨号模式，于是翻出了很久之前用的小米路由器 R3G，刷了 Pandavan 毛子固件，这个小米路由器虽然芯片性能没有 NanoPi R5S 强，但它有 PPPoE 硬件加速，拨号上网后测速能直接能跑满千兆网口。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="起因">起因</h2>
<blockquote>
<p>省流：这段都是废话，想看安装教程的可直接跳过到下一节</p>
</blockquote>
<p>前阵子搬了新家，办了新的宽带，和联通安宽带的师傅要了光猫桥接。原本是打算用 NanoPi R5S 拨号上网，但 RK3568 这个 ARM 芯片尽管性能蛮强的，但 PPoE 拨号没有硬件加速，跑不满千兆宽带，而且打游戏网络延时有时还忽高忽低，用起来很不舒服。</p>
<p>因为光猫桥接之后联通还非常贴心的主动给了 IPv4 公网 IP，所以不打算改回光猫拨号模式，于是翻出了很久之前用的小米路由器 R3G，刷了 Pandavan 毛子固件，这个小米路由器虽然芯片性能没有 NanoPi R5S 强，但它有 PPPoE 硬件加速，拨号上网后测速能直接能跑满千兆网口。</p>
<p>于是现在家里是用这个小米路由器 R3G 作为主路由 PPPoE 拨号 + 主网关使用，通过网线连接屋子里的另两个有线中继路由器作为 AP。因为咱已经有一台 X86 架构的 NAS，也没有软路由的需求，所以 NanoPi R5S 现在想不出拿它做点什么于是先闲置起来了。</p>
<meting-js server="netease" type="song" id="724557" theme="#233333"></meting-js>
<h2 id="安装脚本">安装脚本</h2>
<p>网易<a href="https://router.uu.163.com/app/html/online/baike_share.html?baike_id=5f963c9304c215e129ca40e8">官方提供的 UU 加速器插件脚本</a>不支持 Pandavan 系统，但是简单搜了一下网上已有的教程发现 Pandavan 系统的安装方式和 OpenWRT 比较类似，只需要在官方脚本的基础上改一下插件的安装位置以及开机自启动脚本就行。不过网上能找到的教程比较老，于是咱下载了最新的 UU 加速器插件脚本，在此基础上魔改了一下，增添了 Pandavan 系统的支持。</p>
<p>修改后的插件脚本放在了 <a href="https://github.com/STARRY-S/pandavan-uu-plugin">github.com/STARRY-S/pandavan-uu-plugin</a> 这里，照着中文的安装教程在路由器上执行 <code>install.sh</code> 就行。</p>
<p>安装脚本日志默认输出在 <code>/tmp/install.log</code>，如果安装成功，打开手机上的 UU 主机加速 APP 就能识别到路由器。</p>
<p>这个安装脚本仅在咱的小米路由器 R3G 上做了测试，其他陆游器安装了 Pandavan 固件不确定是否能稳定安装，执行安装脚本之前请确保做了必要数据备份防止系统出问题。</p>]]></content:encoded>
    </item>
    <item>
      <title>联想 ThinkBook 16 Gen6 安装 Arch Linux</title>
      <link>https://blog.starry-s.moe/posts/2024/lenovo-thinkbook-16-6g/</link>
      <pubDate>Mon, 05 Aug 2024 01:02:47 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2024/lenovo-thinkbook-16-6g/</guid>
      <description>&lt;p&gt;本来没打算这么早换笔记本，但是之前大学时买的联想 R7000P 它坏了……&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>本来没打算这么早换笔记本，但是之前大学时买的联想 R7000P 它坏了……</p>
<hr>
<meting-js server="netease" type="song" id="590252" theme="#233333"></meting-js>
<h2 id="起因">起因</h2>
<blockquote>
<p>这段基本是废话，可以直接跳过……</p>
</blockquote>
<p>旧笔记本经常蓝屏死机掉驱动重启，送修过一次但依旧没修好，于是把它当有问题的二手电脑卖掉了，买了今年新出的联想 ThinkBook 16 Gen6。因为咱有台式，所以笔记本的需求就是轻薄，Linux 友好，便宜就行。之前总是被笔记本的双显卡驱动折磨得头疼，英伟达显卡驱动 4202 年了依旧是那么的拉跨，相同规格的 AMD 版本比 Intel 便宜好几百，所以最终选的这个能 PD 充电，只有 AMD 集显，8 核 16 线程 32G 内存的电脑，只想拿它写代码，本地跑编译，跑 KVM 虚拟机用（稍微吐嘈一下某 ARM 架构的电脑，KVM 虚拟机各种限制，想建一个 NAT 类型的 Libvirt Network 都不行）。</p>
<p>为避免引战，这里先只说一下咱个人的观点，可能是因为折腾 Linux 系统时间很久了吧所以对咱来说最舒服的系统还是 Linux，咱很赞同 Linus TechTips 这个视频里对 macOS 的吐嘈，因为 Win/Linux 与 macOS 的键盘、快捷键、手势操作、鼠标指针加速度不一致，所以想同时适应两套不同的手势快捷键、鼠标加速度很是让人头疼。不过这些在倒是能在长时间的适应之后通过修改系统设置的快捷键布局、安装第三方的鼠标加速度修复工具、和第三方输入法来缓解。</p>
<iframe src="//player.bilibili.com/player.html?isOutside=true&aid=563073437&bvid=BV1Rv4y127d5&cid=904126423&p=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"></iframe>
<p>咳，还是要再强调一下，咱只是个人觉得水果的电脑不太好用，尤其是需要 Mac + Win + Linux 来回切换着使用的情况下，很折磨。加上咱眼睛对 LED 的低频 PWM 调光敏感，所以显示设备都尽可能的买低蓝光的 DC 调光 LCD 屏了。</p>
<h2 id="安装-arch-linux">安装 Arch Linux</h2>
<p>其实咱是直接把之前的 R7000p 的硬盘拆下来装到 Thinkbook 16 的第二硬盘插槽里了，所以没有重装系统的步骤，卸载掉 NVIDIA 驱动，改了 SWAP 分区大小就可以用，不需要再配置双显卡切换的工具，没遇到什么奇葩的问题。</p>
<h3 id="wayland">Wayland</h3>
<p>因为 16 寸的 2K 屏幕，不调缩放的情况下显示的图标和字会特别小，所以需要启用分数倍缩放。X11 只能整数倍缩放，但笔记本只有一个 AMD GPU，所以可以完美使用 Wayland（没有英伟达显卡真的太爽了哈哈哈哈hhh）。</p>
<p><img loading="lazy" src="images/1.png" alt="" />

</p>
<p>切换到 Wayland 并启用 150% 的分数倍缩放后，终于不需要再拿显微镜看屏幕的图标和文字了，但很快就会发现 Chrome 和 Electron 的应用的文字全是模糊的，强迫症当然不能忍，查了一下发现原来虽然系统切到 Wayland 了，但这些应用依旧在使用 X 没有切换成 Wayland。</p>
<p>所以对于 Chrome，需要创建 <code>~/.config/chrome-flags.conf</code> 加几个 Flag 使用 GTK4。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">--gtk-version=4 --ozone-platform=wayland --enable-wayland-ime
</span></span></code></pre></div><p>那些基于 Electron 的应用，例如 Visual Studio Code，设置 <code>~/.config/code-flags.conf</code>：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">--gtk-version=4 --enable-wayland-ime
</span></span></code></pre></div><p>然后如果你是 GNOME 用户，你会发现把 Chrome/Electron 切换到 Wayland 后没办法使用第三方输入法，因为 GNOME 不支持 <code>text-input-v1</code> 而 Chromium 只支持 <code>text-input-v3</code>，所以对于这个问题，目前的 Workaround 是安装 <code>archlinuxcn/mutter-performance</code> 替换掉 GNOME 的 <code>mutter</code>。</p>
<p>不过这个不是十分完美的解决方法，VSCode 在启动多个窗口，放到不同的 Workspace 里会遇到鼠标右键点不开和输入法的问题，不过勉强能接受，所以不管了（或者我最近在考虑要不要换一个文本编辑器而不是 VSCode）。</p>
<p>然后就是 Chromium 最近好像在支持 <code>text-input-v3</code> 了，所以别骂了别骂了，不要再黑 GNOME 了……</p>
<hr>
<p>所以现在就是，每新安装一个 Electron 应用，都得给他创建个 <code>~/.config/[NAME].conf</code>……</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">--enable-wayland-ime
</span></span></code></pre></div><h2 id="吐嘈">吐嘈</h2>
<p>以后再买笔记本时要多留意一下，尽量别买高分屏（但现在中高端本全是高分屏），还要避免双显卡，一旦买了双显卡的电脑大概率没办法用 Wayland，还要多花好多时间折腾双显卡混合交火，还得规避掉英伟达新版本驱动会 Kernel Panic 的 Bug。</p>
<p>一旦你买了双显卡高分屏笔记本，那么恭喜你只能用 X11，没有分数倍缩放，所以你只需要准备一个放大镜用来看屏幕就能解决问题（确信）。</p>
<p><img loading="lazy" src="images/2.png" alt="" />

</p>]]></content:encoded>
    </item>
    <item>
      <title>再探容器网络</title>
      <link>https://blog.starry-s.moe/posts/2024/container-network-2/</link>
      <pubDate>Thu, 11 Jul 2024 01:12:36 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2024/container-network-2/</guid>
      <description>&lt;p&gt;在写之前的 &lt;a href=&#34;../container-network-1/&#34;&gt;初探容器网络&lt;/a&gt; 时是想过什么时候写后续的，这期间鸽了不到半年，嗯也不算很久，忙完手头的事情继续更一下容器网络系列……&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>在写之前的 <a href="../container-network-1/">初探容器网络</a> 时是想过什么时候写后续的，这期间鸽了不到半年，嗯也不算很久，忙完手头的事情继续更一下容器网络系列……</p>
<meting-js server="netease" type="song" id="1416321955" theme="#233333"></meting-js>
<p>在上篇 <a href="../container-network-1/">初探容器网络</a> 中咱简单的写了 Linux Network Namespace 和容器相关的东西，所以这篇会继续上一篇的内容拓展一下 CNI 网络插件和他的好朋友们……</p>
<h2 id="container-network-interface-cni">Container Network Interface (CNI)</h2>
<p>有关 CNI 的介绍可以在他的<a href="https://www.cni.dev/">官网</a> 找到，CNI 定义了一套 Linux 容器的网络接口规范和相关代码库，还提供了一些简单的样例网络插件代码。Kubernetes 使用 CNI 网络插件为 Pod 创建网络。
需要注意的是 Docker 的容器网络不是由 CNI 插件创建的，而是由 Docker 自己的 <a href="https://docs.docker.com/network/drivers/">Driver</a> 负责创建，这里需要注意别弄混了，其他的容器运行时也不完全是使用 CNI 网络插件，也可能是用的别的插件的标准。</p>
<p>不过 CNI 在 Kubernetes 中广为使用，学起来也不是很难（），所以本篇就先围绕着 CNI 进行简单的介绍，熟悉完 CNI 后其他种类的网络插件也相对的能更容易上手一些。</p>
<p>首先来熟悉一下到底什么是 CNI 插件，有关 CNI 的定义和详细介绍可以在<a href="https://www.cni.dev/docs/spec/">这里</a>找到，但新手可能单凭这个介绍，无法对 CNI 有详细的了解，实际上官网的定义介绍感觉更像是给一个熟悉 CNI 网络插件的查阅的手册而不是帮一个萌新去了解的入门指南。</p>
<p>需要知道的是，CNI 插件是一个可执行文件，一个最简单的 CNI 插件可以是一个有执行权限的脚本，执行 <code>ip link</code>, <code>ip route</code> 等命令，为 Pod 的 Network Namespace 实现添加、删除虚拟接口等操作，就可以算得上是一个 CNI 插件。但强大一点的 CNI 插件都是由更灵活的编程语言写的程序，编译成二进制文件放在系统的某个路径下面。Kubernetes 集群可以指定默认使用的 CNI 网络插件（比如广为人知的 Calico、Flannel、Cilium 等），除此之外还可以使用一些额外插件为 Pod 创建多个虚拟网卡（例如 Multus CNI）。在 Pod 创建时，执行 CNI 插件为 Pod 的 Network Namespace 创建网卡接口。这里网卡接口类型不再局限为单纯的 Veth Pair，而可以是其他复杂类型的接口（比如 Macvlan、IPvlan 甚至你自己可以写个网络驱动）。在部署一个 Kubernetes 集群后，节点上执行 <code>ip link</code> 能看到一大堆名称为 <code>vethXXXX@ifN</code> 的虚拟接口，这些其实是由集群使用的 CNI 插件创建的 Veth Pair，因为这些 NS 的文件被放在了 <code>/run/netns</code> 路径下面，所以可以被 <code>ip link</code> 命令识别到，而 Docker 的 NS 文件不在这个路径里面，所以用 Docker 跑容器时，执行 <code>ip link</code> 不会有许多 Veth 虚拟设备输出在屏幕上。</p>
<h3 id="cni-参数">CNI 参数</h3>
<p>执行 CNI 时，通过一些环境变量向 CNI 传递参数，传递的环境变量为：</p>
<ul>
<li><code>CNI_COMMAND</code>: CNI 插件执行的命令，在 CNI Spec 1.0.0 中，CNI 插件支持 <code>ADD</code>, <code>DEL</code>, <code>CHECK</code>, <code>VERSION</code> 这四个命令。</li>
<li><code>CNI_CONTAINERID</code>: 容器的 Container ID，由 Container Runtime 管理。</li>
<li><code>CNI_NETNS</code>: 容器的 Network Namespace 在节点上的路径，通常是在 <code>/run/netns/[nsname]</code> 路径下面。</li>
<li><code>CNI_IFNAME</code>: 待创建的网卡名称，例如最常见的情况是容器里有一块网卡，名为 <code>eth0</code>。</li>
<li><code>CNI_ARGS</code>: 向 CNI 插件传递的一些其他参数，格式为 <code>KEY=VALUE</code>，由分号分隔，例如 <code>FOO=BAR;ABC=123</code>。</li>
<li><code>CNI_PATH</code>: CNI 可执行文件所在路径列表。</li>
</ul>
<h3 id="cni-返回值">CNI 返回值</h3>
<p>如果 CNI 成功执行并完成了指定的命令，它的返回值为 0，其他非 0 的返回值代表错误，输出一串 JSON，包含错误的详细内容，关于 CNI Errors 的定义可以看<a href="https://www.cni.dev/docs/spec/#error">这里</a>。</p>
<h3 id="cni-命令">CNI 命令</h3>
<p>在 CNI Spec 0.4.0 之前，CNI 插件只定义了 <code>ADD</code>, <code>DEL</code>, <code>VERSION</code> 这三个命令，分别对应 “添加网络”、“删除网络”、“支持版本”。CNI Spec 0.4.0 新引入了 <code>CHECK</code> 命令，用于对已创建网络的容器进行校验。</p>
<h3 id="配置文件">配置文件</h3>
<p>CNI 配置文件为 JSON 格式，以下是一个样例配置文件：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// CNI Spec Version
</span></span></span><span class="line"><span class="cl">  <span class="nt">&#34;cniVersion&#34;</span><span class="p">:</span> <span class="s2">&#34;1.0.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// Network name.
</span></span></span><span class="line"><span class="cl">  <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;dbnet&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;plugins&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="c1">// CNI Plugin Binary name
</span></span></span><span class="line"><span class="cl">      <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;bridge&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="c1">// CNI Plugin specific parameters...
</span></span></span><span class="line"><span class="cl">      <span class="nt">&#34;bridge&#34;</span><span class="p">:</span> <span class="s2">&#34;cni0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="c1">// Dictionary with IPAM (IP Address Management) specific values.
</span></span></span><span class="line"><span class="cl">      <span class="nt">&#34;ipam&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// IPAM Plugin Binary name
</span></span></span><span class="line"><span class="cl">        <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;host-local&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// IPAM specific params...
</span></span></span><span class="line"><span class="cl">        <span class="nt">&#34;subnet&#34;</span><span class="p">:</span> <span class="s2">&#34;10.1.0.0/16&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;gateway&#34;</span><span class="p">:</span> <span class="s2">&#34;10.1.0.1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;routes&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span> <span class="nt">&#34;dst&#34;</span><span class="p">:</span> <span class="s2">&#34;0.0.0.0/0&#34;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;dns&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;nameservers&#34;</span><span class="p">:</span> <span class="p">[</span> <span class="s2">&#34;10.1.0.1&#34;</span> <span class="p">]</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>通常高级的 CNI 插件可以自动的由 Controller 生成他所需的 CNI Config，而简易的 CNI 插件需要手动的编写 Config 文件放在 <code>/etc/cni/net.d/</code> 目录下面（不同类型的集群的 Config 路径可能不一致）。</p>
<p>光凭上面这一大堆 Specification 定义比较难理解这个抽象的 CNI 插件，所以接下来我们拆解一个样例 CNI 插件，并手撮一个简易的 CNI 插件。</p>
<h3 id="样例-cni-插件">样例 CNI 插件</h3>
<p>在 GitHub 的 <code>containernetworking</code> Org 里能找到许多 CNI Plugin 代码，<a href="https://github.com/containernetworking/plugins/tree/main/plugins/main/ptp">这里</a>为样例 <code>p2p</code> CNI Plugin 插件代码，这个插件能为容器和主机之间创建一对 Veth Pair，可以主机和容器之间的点对点访问。在<a href="https://github.com/containernetworking/plugins">这里</a>还能找到一些其他样例插件代码，例如为容器创建 Linux Kernel 的 <code>bridge</code>、<code>macvlan</code>、<code>ipvlan</code> 等类型的网卡接口。</p>
<p>样例插件由 Go 语言编写，所以这里需要你熟悉 Go 编程语言。为了折腾 CNI 插件，你需要有一个调试使用的 Kubernetes 集群，因为 Pod 需要具备跨节点通信的能力，所以集群最好至少有一个 Master (etcd, controlplane, scheduler) 和 2 个 Worker (scheduler) 节点，将 Pod 调度到不同节点上验证节点之间 Pod 连同性，因为折腾 CNI 时很可能同一个节点的 Pod 能互相访问而不能跨节点访问，也可能节点能访问其他节点上的 Pod 但无法访问运行在当前节点的 Pod 等一堆复杂问题。集群的节点最好是可以灵活重启抗造的物理机或 KVM 虚拟机（因公有云的网路环境略微复杂且大多数公有云都不支持 KVM，所以不是很建议在公有云上折腾 CNI 网络插件）。
同时本篇需要你具备一些基本的计算机网络基础，例如可以先看一下 IPv4/IPv6 的网络编址/子网划分、OSI 七层模型的 L2 和 L3 层，例如 2 层交换机 (Switch) 和 3 层交换机 (Router) 的区别，更复杂一点的地方需要你清楚常见的 VLAN (<a href="https://en.wikipedia.org/wiki/IEEE_802.1Q">IEEE 802.1Q</a>、<a href="https://en.wikipedia.org/wiki/IEEE_802.1ad">802.1ad</a>) 以及后续衍生出来的 <a href="https://en.wikipedia.org/wiki/Virtual_Extensible_LAN">VXLAN</a> 等 *VLAN 协议……</p>
<p>往简单来说 CNI 插件基本的功能就是执行 <code>ip link</code>, <code>ip route</code>, <code>ip netns</code> 等一系列命令为 Pod 的 Network Namespace 和主机的 Default Network Namespace 之间创建虚拟网卡实现互相通信。Go 语言同样有 Library 提供了 Linux <code>ip</code> 命令的代码，常用的 Go Library 包含以下的：</p>
<ul>
<li><a href="https://github.com/vishvananda/netlink">https://github.com/vishvananda/netlink</a>: Go 语言实现的 <code>iproute2</code> 命令行工具的 API，可以执行类似 <code>ip link</code>, <code>ip route</code> 等命令</li>
<li><a href="https://github.com/vishvananda/netns">https://github.com/vishvananda/netns</a>: Go 语言实现的处理 Linux Network Namespace API</li>
<li><a href="https://github.com/containernetworking/cni">https://github.com/containernetworking/cni</a>: 包含 CNI 定义 Types 和常用组件</li>
</ul>
<p>打开<a href="https://github.com/containernetworking/plugins/blob/main/plugins/main/ptp/ptp.go">样例 p2p CNI 插件代码</a>，先看他的 <code>main</code> 函数只有简洁的一行 <code>skel.PluginMain</code>，这个方法会处理环境变量传入的 CNI 参数，加载 Config，执行相应的 COMMAND。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">skel</span><span class="p">.</span><span class="nf">PluginMain</span><span class="p">(</span><span class="nx">cmdAdd</span><span class="p">,</span><span class="w"> </span><span class="nx">cmdCheck</span><span class="p">,</span><span class="w"> </span><span class="nx">cmdDel</span><span class="p">,</span><span class="w"> </span><span class="nx">version</span><span class="p">.</span><span class="nx">All</span><span class="p">,</span><span class="w"> </span><span class="nx">bv</span><span class="p">.</span><span class="nf">BuildString</span><span class="p">(</span><span class="s">&#34;ptp&#34;</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><h4 id="add-命令">ADD 命令</h4>
<p>ADD 命令用于为容器创建网卡（或修改已有的网卡），找一下 p2p CNI 插件的 <code>cmdAdd</code> 函数，大致简化一下里面的代码流程为：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">cmdAdd</span><span class="p">(</span><span class="nx">args</span><span class="w"> </span><span class="o">*</span><span class="nx">skel</span><span class="p">.</span><span class="nx">CmdArgs</span><span class="p">)</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Load CNI Config</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">conf</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">NetConf</span><span class="p">{}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">json</span><span class="p">.</span><span class="nf">Unmarshal</span><span class="p">(</span><span class="nx">args</span><span class="p">.</span><span class="nx">StdinData</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">conf</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;failed to load netconf: %v&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// ---------------------------------------------</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Execute IPAM command to get IP address</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">r</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">ipam</span><span class="p">.</span><span class="nf">ExecAdd</span><span class="p">(</span><span class="nx">conf</span><span class="p">.</span><span class="nx">IPAM</span><span class="p">.</span><span class="nx">Type</span><span class="p">,</span><span class="w"> </span><span class="nx">args</span><span class="p">.</span><span class="nx">StdinData</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">result</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">current</span><span class="p">.</span><span class="nf">NewResultFromResult</span><span class="p">(</span><span class="nx">r</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nb">len</span><span class="p">(</span><span class="nx">result</span><span class="p">.</span><span class="nx">IPs</span><span class="p">)</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nx">errors</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;IPAM plugin returned missing IP config&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">ip</span><span class="p">.</span><span class="nf">EnableForward</span><span class="p">(</span><span class="nx">result</span><span class="p">.</span><span class="nx">IPs</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;Could not enable IP forwarding: %v&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// ---------------------------------------------</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Create Veth Pair for Pod Network Namespace</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">netns</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">ns</span><span class="p">.</span><span class="nf">GetNS</span><span class="p">(</span><span class="nx">args</span><span class="p">.</span><span class="nx">Netns</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;failed to open netns %q: %v&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">args</span><span class="p">.</span><span class="nx">Netns</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">defer</span><span class="w"> </span><span class="nx">netns</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">hostInterface</span><span class="p">,</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nf">setupContainerVeth</span><span class="p">(</span><span class="nx">netns</span><span class="p">,</span><span class="w"> </span><span class="nx">args</span><span class="p">.</span><span class="nx">IfName</span><span class="p">,</span><span class="w"> </span><span class="nx">conf</span><span class="p">.</span><span class="nx">MTU</span><span class="p">,</span><span class="w"> </span><span class="nx">result</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// ---------------------------------------------</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Setup Veth Pair for default Network Namespace</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nf">setupHostVeth</span><span class="p">(</span><span class="nx">hostInterface</span><span class="p">.</span><span class="nx">Name</span><span class="p">,</span><span class="w"> </span><span class="nx">result</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Some other IP forward (masquerade) operations...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="nx">types</span><span class="p">.</span><span class="nf">PrintResult</span><span class="p">(</span><span class="nx">result</span><span class="p">,</span><span class="w"> </span><span class="nx">conf</span><span class="p">.</span><span class="nx">CNIVersion</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>简单概括样例 P2P Plugin 的 ADD 命令流程大致为：</p>
<ol>
<li>加载 CNI Config 配置文件</li>
<li>执行 IPAM 获取 Pod IP</li>
<li>创建一对 Veth Pair，其中一个 Iface 接口放在 Pod NS 中，配置 IP、路由等</li>
<li>另一个 Veth Pair 的 Iface 接口放在 default NS 中，配置 IP、路由……</li>
<li>配置 Default NS 的 Masquerade 等额外操作</li>
<li>输出运行结果</li>
</ol>
<h4 id="del-命令">DEL 命令</h4>
<p>DEL 命令用于释放容器和主机的网卡接口资源，在 Pod 删除时被执行，以下是简化的样例 P2P Plugin 的 <code>cmdDel</code> 函数代码：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">cmdDel</span><span class="p">(</span><span class="nx">args</span><span class="w"> </span><span class="o">*</span><span class="nx">skel</span><span class="p">.</span><span class="nx">CmdArgs</span><span class="p">)</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Load CNI Config</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">conf</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">NetConf</span><span class="p">{}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">json</span><span class="p">.</span><span class="nf">Unmarshal</span><span class="p">(</span><span class="nx">args</span><span class="p">.</span><span class="nx">StdinData</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">conf</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;failed to load netconf: %v&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// ---------------------------------------------</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Execute IPAM command to release IP address</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">ipam</span><span class="p">.</span><span class="nf">ExecDel</span><span class="p">(</span><span class="nx">conf</span><span class="p">.</span><span class="nx">IPAM</span><span class="p">.</span><span class="nx">Type</span><span class="p">,</span><span class="w"> </span><span class="nx">args</span><span class="p">.</span><span class="nx">StdinData</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// ---------------------------------------------</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Release link interface &amp; masquerades...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">var</span><span class="w"> </span><span class="nx">ipnets</span><span class="w"> </span><span class="p">[]</span><span class="o">*</span><span class="nx">net</span><span class="p">.</span><span class="nx">IPNet</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">ns</span><span class="p">.</span><span class="nf">WithNetNSPath</span><span class="p">(</span><span class="nx">args</span><span class="p">.</span><span class="nx">Netns</span><span class="p">,</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">_</span><span class="w"> </span><span class="nx">ns</span><span class="p">.</span><span class="nx">NetNS</span><span class="p">)</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">var</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="kt">error</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">ipnets</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">ip</span><span class="p">.</span><span class="nf">DelLinkByNameAddr</span><span class="p">(</span><span class="nx">args</span><span class="p">.</span><span class="nx">IfName</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="nx">ip</span><span class="p">.</span><span class="nx">ErrLinkNotFound</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="o">...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nb">len</span><span class="p">(</span><span class="nx">ipnets</span><span class="p">)</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="nx">conf</span><span class="p">.</span><span class="nx">IPMasq</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">for</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">ipn</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">ipnets</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nx">err</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">ip</span><span class="p">.</span><span class="nf">TeardownIPMasq</span><span class="p">(</span><span class="nx">ipn</span><span class="p">,</span><span class="w"> </span><span class="o">...</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>简单概括 DEL 命令流程大致为：</p>
<ol>
<li>加载 CNI Config 配置文件</li>
<li>执行 IPAM 释放 Pod IP</li>
<li>释放 Veth Pair 和其他配置 (Masquerade&hellip;)</li>
</ol>
<h4 id="check-命令">CHECK 命令</h4>
<p>CHECK 命令用于校验 Pod 网络，在 CNI Spec 0.4.0 中，Config 新增了 <code>prevResult</code> 字段，记录了 CNI 插件上一次执行 ADD 命令的结果。
CHECK 命令将 <code>prevResult</code> 记录的状态信息和设定的期望值进行比对。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">cmdCheck</span><span class="p">(</span><span class="nx">args</span><span class="w"> </span><span class="o">*</span><span class="nx">skel</span><span class="p">.</span><span class="nx">CmdArgs</span><span class="p">)</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Load CNI Config</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">conf</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">NetConf</span><span class="p">{}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">json</span><span class="p">.</span><span class="nf">Unmarshal</span><span class="p">(</span><span class="nx">args</span><span class="p">.</span><span class="nx">StdinData</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">conf</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;failed to load netconf: %v&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// -----------------------------------</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Run IPAM plugin CHECK command and get results</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">err</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">ipam</span><span class="p">.</span><span class="nf">ExecCheck</span><span class="p">(</span><span class="nx">conf</span><span class="p">.</span><span class="nx">IPAM</span><span class="p">.</span><span class="nx">Type</span><span class="p">,</span><span class="w"> </span><span class="nx">args</span><span class="p">.</span><span class="nx">StdinData</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// -----------------------------------</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Parse prevResult</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">conf</span><span class="p">.</span><span class="nx">NetConf</span><span class="p">.</span><span class="nx">RawPrevResult</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;ptp: Required prevResult missing&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">version</span><span class="p">.</span><span class="nf">ParsePrevResult</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">conf</span><span class="p">.</span><span class="nx">NetConf</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Convert whatever the IPAM result was into the current Result type</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">result</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">current</span><span class="p">.</span><span class="nf">NewResultFromResult</span><span class="p">(</span><span class="nx">conf</span><span class="p">.</span><span class="nx">PrevResult</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">var</span><span class="w"> </span><span class="nx">contMap</span><span class="w"> </span><span class="nx">current</span><span class="p">.</span><span class="nx">Interface</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Find interfaces for name whe know, that of host-device inside container</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">intf</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">result</span><span class="p">.</span><span class="nx">Interfaces</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="nx">args</span><span class="p">.</span><span class="nx">IfName</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="nx">intf</span><span class="p">.</span><span class="nx">Name</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">if</span><span class="w"> </span><span class="nx">args</span><span class="p">.</span><span class="nx">Netns</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="nx">intf</span><span class="p">.</span><span class="nx">Sandbox</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nx">contMap</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="o">*</span><span class="nx">intf</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="k">continue</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// -----------------------------------</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Check Network Namespace Name</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">args</span><span class="p">.</span><span class="nx">Netns</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="nx">contMap</span><span class="p">.</span><span class="nx">Sandbox</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;Sandbox in prevResult %s doesn&#39;t match configured netns: %s&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nx">contMap</span><span class="p">.</span><span class="nx">Sandbox</span><span class="p">,</span><span class="w"> </span><span class="nx">args</span><span class="p">.</span><span class="nx">Netns</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Check prevResults for ips, routes and dns against values found in the container</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">netns</span><span class="p">.</span><span class="nf">Do</span><span class="p">(</span><span class="kd">func</span><span class="p">(</span><span class="nx">_</span><span class="w"> </span><span class="nx">ns</span><span class="p">.</span><span class="nx">NetNS</span><span class="p">)</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Check interface</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nf">validateCniContainerInterface</span><span class="p">(</span><span class="nx">contMap</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Check IPs</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">err</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">ip</span><span class="p">.</span><span class="nf">ValidateExpectedInterfaceIPs</span><span class="p">(</span><span class="nx">args</span><span class="p">.</span><span class="nx">IfName</span><span class="p">,</span><span class="w"> </span><span class="nx">result</span><span class="p">.</span><span class="nx">IPs</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Check routes</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">err</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">ip</span><span class="p">.</span><span class="nf">ValidateExpectedRoute</span><span class="p">(</span><span class="nx">result</span><span class="p">.</span><span class="nx">Routes</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Other checks...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">});</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>样例 P2P 插件的 DEL 命令流程大致为：</p>
<ol>
<li>加载 CNI Config 配置文件</li>
<li>执行 IPAM CHECK 命令</li>
<li>加载 prevResult 信息</li>
<li>依次校验 Network Namespace 名称、Pod 网卡、Pod IP、路由表等状态信息</li>
</ol>
<h3 id="运行样例-cni">运行样例 CNI</h3>
<p>上述的样例 P2P CNI 插件仅能为 Pod NS 与主机的 Default NS 之间创建 Veth Pair 并简单的配置 IP 地址和路由表，并不能用于更复杂的场景。如果想在你的调试集群中试用上述样例的 CNI 插件，可以使用 <a href="https://github.com/k8snetworkplumbingwg/multus-cni/">Multus CNI</a>。Multus CNI 可以为 Pod 创建多块网卡，其中 Pod 的默认网卡（通常是 <code>eth0</code>）为 Kubernetes 集群的原生 CNI（例如 Calico、Flannel、Cilium 或其他 CNI），使用 Multus CNI 可以调用上述的样例 P2P CNI 插件为 Pod 创建额外的网卡。</p>
<h4 id="安装-multus-cni">安装 Multus CNI</h4>
<p>目前 Multus CNI 最新版本 (<code>4.0.2</code>) 支持的最高 CNI Spec 版本为 <code>1.0.0</code>，可以运行在 K3s 但有亿点小问题（参考 <a href="https://github.com/k8snetworkplumbingwg/multus-cni/issues/1089#issuecomment-1550442393">Issue</a>），咱写这篇博客用的集群是 K3s <code>v1.28.10+k3s1</code>，一共有两个节点，运行在 KVM 虚拟机中方便折腾。</p>
<p>参照 <a href="https://github.com/k8snetworkplumbingwg/multus-cni/blob/master/docs/quickstart.md#installation">Multus CNI</a> 文档，部署 Multus Daemonset，在每个节点中安装 Multus CNI Binary 文件。</p>
<p>在 K3s 上安装 Multus 的步骤可以看咱之前写的 <a href="../k3s-multus-macvlan/">K3s + Multus CNI 插件使用 Macvlan</a>。</p>
<h3 id="安装样例-cni-binary-文件">安装样例 CNI Binary 文件</h3>
<p>需要将上述的样例 P2P CNI 插件拷贝到 K3s 每个集群节点的 <code>/var/lib/rancher/k3s/data/current/bin</code> 目录下（如果是其他集群，路径为 <code>/opt/cni/bin</code>）。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> mkdir -p cni <span class="o">&amp;&amp;</span> <span class="nb">cd</span> cni
</span></span><span class="line"><span class="cl"><span class="gp">$</span> wget https://github.com/containernetworking/plugins/releases/download/v1.5.1/cni-plugins-linux-amd64-v1.5.1.tgz
</span></span><span class="line"><span class="cl"><span class="gp">$</span> tar -zxvf cni-plugins-linux-amd64-v1.5.1.tgz
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo cp ptp /var/lib/rancher/k3s/data/current/bin/
</span></span></code></pre></div><p>创建一个 <code>NetworkAttachmentDefinition</code> Custom Resource，将 p2p 的 CNI Config 存储在这里，配置 Pod 使用 ptp CNI 插件。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;k8s.cni.cncf.io/v1&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">NetworkAttachmentDefinition</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">ptp-conf</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">config</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;{
</span></span></span><span class="line"><span class="cl"><span class="s1">  &#34;cniVersion&#34;: &#34;1.0.0&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s1">  &#34;type&#34;: &#34;ptp&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s1">  &#34;ipam&#34;: {
</span></span></span><span class="line"><span class="cl"><span class="s1">    &#34;type&#34;: &#34;host-local&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s1">    &#34;subnet&#34;: &#34;192.168.1.0/24&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">  },
</span></span></span><span class="line"><span class="cl"><span class="s1">  &#34;dns&#34;: {
</span></span></span><span class="line"><span class="cl"><span class="s1">    &#34;nameservers&#34;: [ &#34;192.168.1.0&#34;, &#34;8.8.8.8&#34; ]
</span></span></span><span class="line"><span class="cl"><span class="s1">  }
</span></span></span><span class="line"><span class="cl"><span class="s1">}&#39;</span><span class="w">
</span></span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> k get network-attachment-definitions.k8s.cni.cncf.io
</span></span><span class="line"><span class="cl"><span class="go">NAME       AGE
</span></span></span><span class="line"><span class="cl"><span class="go">ptp-conf   9s
</span></span></span></code></pre></div><h3 id="创建样例-workload">创建样例 Workload</h3>
<p>接下来可以创建样例工作负载，设置 <code>k8s.v1.cni.cncf.io/networks</code> Annotation 定义 Pod 的第二网卡由上述的 P2P 插件创建。为便于折腾这里的样例负载为 DaemonSet，并赋予容器 Privileged 权限。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">apps/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">DaemonSet</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">example-ds</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">default</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">selector</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">matchLabels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">example-alpine-ds</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">template</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">example-alpine-ds</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">annotations</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">k8s.v1.cni.cncf.io/networks</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;ptp-conf&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">containers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">example-alpine</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">alpine</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">imagePullPolicy</span><span class="p">:</span><span class="w"> </span><span class="l">IfNotPresent</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;sleep&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">args</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;infinity&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">securityContext</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">privileged</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> vim example-ds.yaml
</span></span><span class="line"><span class="cl"><span class="gp">$</span> k apply -f example-ds.yaml
</span></span><span class="line"><span class="cl"><span class="go">daemonset.apps/example-ds created
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> k get pods -o wide
</span></span><span class="line"><span class="cl"><span class="go">NAME               READY   STATUS    RESTARTS   AGE   IP           NODE    NOMINATED NODE   READINESS GATES
</span></span></span><span class="line"><span class="cl"><span class="go">example-ds-4865k   1/1     Running   0          3s    10.42.1.7    k3s-2   &lt;none&gt;           &lt;none&gt;
</span></span></span><span class="line"><span class="cl"><span class="go">example-ds-9g5kp   1/1     Running   0          3s    10.42.0.12   k3s-1   &lt;none&gt;           &lt;none&gt;
</span></span></span></code></pre></div><p>查看 Pod 中的网卡信息，除了 <code>lo</code> 回环接口和 <code>eth0</code> 接口外，还有一个由 <code>ptp</code> 创建的 <code>net1</code> 接口，IP 地址为 <code>192.168.1.2</code>。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> k <span class="nb">exec</span> -it example-ds-9g5kp -- sh
</span></span><span class="line"><span class="cl"><span class="gp">#</span> ip a
</span></span><span class="line"><span class="cl"><span class="go">1: lo: &lt;LOOPBACK,UP,LOWER_UP&gt; mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
</span></span></span><span class="line"><span class="cl"><span class="go">    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
</span></span></span><span class="line"><span class="cl"><span class="go">    inet 127.0.0.1/8 scope host lo
</span></span></span><span class="line"><span class="cl"><span class="go">       valid_lft forever preferred_lft forever
</span></span></span><span class="line"><span class="cl"><span class="go">    inet6 ::1/128 scope host
</span></span></span><span class="line"><span class="cl"><span class="go">       valid_lft forever preferred_lft forever
</span></span></span><span class="line"><span class="cl"><span class="go">2: eth0@if18: &lt;BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN&gt; mtu 1450 qdisc noqueue state UP
</span></span></span><span class="line"><span class="cl"><span class="go">    link/ether ba:ac:48:99:66:73 brd ff:ff:ff:ff:ff:ff
</span></span></span><span class="line"><span class="cl"><span class="go">    inet 10.42.0.12/24 brd 10.42.0.255 scope global eth0
</span></span></span><span class="line"><span class="cl"><span class="go">       valid_lft forever preferred_lft forever
</span></span></span><span class="line"><span class="cl"><span class="go">    inet6 fe80::b8ac:48ff:fe99:6673/64 scope link
</span></span></span><span class="line"><span class="cl"><span class="go">       valid_lft forever preferred_lft forever
</span></span></span><span class="line"><span class="cl"><span class="go">3: net1@if19: &lt;BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN&gt; mtu 1500 qdisc noqueue state UP
</span></span></span><span class="line"><span class="cl"><span class="go">    link/ether 6a:0b:7d:4b:6f:c5 brd ff:ff:ff:ff:ff:ff
</span></span></span><span class="line"><span class="cl"><span class="go">    inet 192.168.1.2/24 brd 192.168.1.255 scope global net1
</span></span></span><span class="line"><span class="cl"><span class="go">       valid_lft forever preferred_lft forever
</span></span></span><span class="line"><span class="cl"><span class="go">    inet6 fe80::680b:7dff:fe4b:6fc5/64 scope link
</span></span></span><span class="line"><span class="cl"><span class="go">       valid_lft forever preferred_lft forever
</span></span></span><span class="line"><span class="cl"><span class="gp">#</span> ip r
</span></span><span class="line"><span class="cl"><span class="go">default via 10.42.0.1 dev eth0
</span></span></span><span class="line"><span class="cl"><span class="go">10.42.0.0/24 dev eth0 scope link  src 10.42.0.12
</span></span></span><span class="line"><span class="cl"><span class="go">10.42.0.0/16 via 10.42.0.1 dev eth0
</span></span></span><span class="line"><span class="cl"><span class="go">192.168.1.0/24 via 192.168.1.1 dev net1  src 192.168.1.2
</span></span></span><span class="line"><span class="cl"><span class="go">192.168.1.1 dev net1 scope link  src 192.168.1.2
</span></span></span></code></pre></div><p>在节点上执行 <code>ip</code> 命令，查看节点的网卡和 IP 地址信息，可以看到除了节点的 <code>lo</code> 和 <code>eth0</code>，Flannel CNI 的 <code>flannel.1</code>, <code>cni0</code> 和一些其他 Pod 的 Veth Pair，有一个 Veth Pair 的 IP 地址为 <code>192.168.1.1/32</code>，这个是由样例 ptp CNI 创建。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> ip a
</span></span><span class="line"><span class="cl"><span class="go">1: lo: &lt;LOOPBACK,UP,LOWER_UP&gt; mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
</span></span></span><span class="line"><span class="cl"><span class="go">    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
</span></span></span><span class="line"><span class="cl"><span class="go">    inet 127.0.0.1/8 scope host lo
</span></span></span><span class="line"><span class="cl"><span class="go">       valid_lft forever preferred_lft forever
</span></span></span><span class="line"><span class="cl"><span class="go">    inet6 ::1/128 scope host noprefixroute
</span></span></span><span class="line"><span class="cl"><span class="go">       valid_lft forever preferred_lft forever
</span></span></span><span class="line"><span class="cl"><span class="go">2: eth0: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc fq_codel state UP group default qlen 1000
</span></span></span><span class="line"><span class="cl"><span class="go">    link/ether 52:54:00:de:78:16 brd ff:ff:ff:ff:ff:ff
</span></span></span><span class="line"><span class="cl"><span class="go">    altname enp1s0
</span></span></span><span class="line"><span class="cl"><span class="go">    inet 10.128.0.101/12 brd 10.143.255.255 scope global eth0
</span></span></span><span class="line"><span class="cl"><span class="go">       valid_lft forever preferred_lft forever
</span></span></span><span class="line"><span class="cl"><span class="go">    inet6 fe80::5054:ff:fede:7816/64 scope link proto kernel_ll
</span></span></span><span class="line"><span class="cl"><span class="go">       valid_lft forever preferred_lft forever
</span></span></span><span class="line"><span class="cl"><span class="go">3: flannel.1: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1450 qdisc noqueue state UNKNOWN group default
</span></span></span><span class="line"><span class="cl"><span class="go">    link/ether ee:82:b4:a0:d9:4d brd ff:ff:ff:ff:ff:ff
</span></span></span><span class="line"><span class="cl"><span class="go">    inet 10.42.0.0/32 scope global flannel.1
</span></span></span><span class="line"><span class="cl"><span class="go">       valid_lft forever preferred_lft forever
</span></span></span><span class="line"><span class="cl"><span class="go">    inet6 fe80::ec82:b4ff:fea0:d94d/64 scope link proto kernel_ll
</span></span></span><span class="line"><span class="cl"><span class="go">       valid_lft forever preferred_lft forever
</span></span></span><span class="line"><span class="cl"><span class="go">4: cni0: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1450 qdisc noqueue state UP group default qlen 1000
</span></span></span><span class="line"><span class="cl"><span class="go">    link/ether 6e:99:8d:63:e5:4b brd ff:ff:ff:ff:ff:ff
</span></span></span><span class="line"><span class="cl"><span class="go">    inet 10.42.0.1/24 brd 10.42.0.255 scope global cni0
</span></span></span><span class="line"><span class="cl"><span class="go">       valid_lft forever preferred_lft forever
</span></span></span><span class="line"><span class="cl"><span class="go">    inet6 fe80::6c99:8dff:fe63:e54b/64 scope link proto kernel_ll
</span></span></span><span class="line"><span class="cl"><span class="go">       valid_lft forever preferred_lft forever
</span></span></span><span class="line"><span class="cl"><span class="go">18: vethf91807b2@if2: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1450 qdisc noqueue master cni0 state UP group default
</span></span></span><span class="line"><span class="cl"><span class="go">    link/ether 6a:dd:6f:b9:29:8a brd ff:ff:ff:ff:ff:ff link-netns cni-dba4ed62-c7d7-98fa-0efb-ffa6a8e526a3
</span></span></span><span class="line"><span class="cl"><span class="go">    inet6 fe80::68dd:6fff:feb9:298a/64 scope link proto kernel_ll
</span></span></span><span class="line"><span class="cl"><span class="go">       valid_lft forever preferred_lft forever
</span></span></span><span class="line"><span class="cl"><span class="go">19: veth37d38de3@if3: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc noqueue state UP group default
</span></span></span><span class="line"><span class="cl"><span class="go">    link/ether ee:29:cc:72:e0:a5 brd ff:ff:ff:ff:ff:ff link-netns cni-dba4ed62-c7d7-98fa-0efb-ffa6a8e526a3
</span></span></span><span class="line"><span class="cl"><span class="go">    inet 192.168.1.1/32 scope global veth37d38de3
</span></span></span><span class="line"><span class="cl"><span class="go">       valid_lft forever preferred_lft forever
</span></span></span><span class="line"><span class="cl"><span class="go">    inet6 fe80::ec29:ccff:fe72:e0a5/64 scope link proto kernel_ll
</span></span></span><span class="line"><span class="cl"><span class="go">       valid_lft forever preferred_lft forever
</span></span></span></code></pre></div><p>显然这个样例 P2P CNI 只能通过 Veth Pair 访问当前节点的 Pod，无法跨节点访问运行在别的节点的 Pod。</p>
<h3 id="手动执行-cni-check-命令">手动执行 CNI CHECK 命令</h3>
<p>到这里其实你可以魔改一下上面介绍的 P2P 样例 CNI 插件，打一些日志输出到某个文件中，看一下 <code>netlink</code> 执行的结果以及 CNI 执行时传递的参数之类的……</p>
<p>在 Pod 创建时会执行 ADD 命令，删除时会执行 DEL 命令，但如果想调试 CHECK 命令，可以手动为 CNI 传递相应参数执行 CHECK 命令。</p>
<p>首先准备一份包含 <code>prevResult</code> 的 CNI Config，参照下方的 Config 修改 <code>prevResult</code> 字段。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;cniVersion&#34;</span><span class="p">:</span> <span class="s2">&#34;1.0.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;ptp-conf&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;ptp&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;ipam&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;host-local&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;subnet&#34;</span><span class="p">:</span> <span class="s2">&#34;192.168.1.0/24&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;dns&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;nameservers&#34;</span><span class="p">:</span> <span class="p">[</span> <span class="s2">&#34;192.168.1.0&#34;</span><span class="p">,</span> <span class="s2">&#34;8.8.8.8&#34;</span> <span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;prevResult&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;cniVersion&#34;</span><span class="p">:</span> <span class="s2">&#34;1.0.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;ptp&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;interfaces&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;mac&#34;</span><span class="p">:</span> <span class="s2">&#34;6a:0b:7d:4b:6f:c5&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;net1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;sandbox&#34;</span><span class="p">:</span> <span class="s2">&#34;/var/run/netns/cni-dba4ed62-c7d7-98fa-0efb-ffa6a8e526a3&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;ips&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;address&#34;</span><span class="p">:</span> <span class="s2">&#34;192.168.1.2/24&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;interface&#34;</span><span class="p">:</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;ipam&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;host-local&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;subnet&#34;</span><span class="p">:</span> <span class="s2">&#34;192.168.1.0/24&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;dns&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;nameservers&#34;</span><span class="p">:</span> <span class="p">[</span> <span class="s2">&#34;192.168.1.0&#34;</span><span class="p">,</span> <span class="s2">&#34;8.8.8.8&#34;</span> <span class="p">]</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>设置 CNI 环境变量，传递 CHECK 命令需要的参数。如果实在不清楚 NETNS 和 CONTAINERID 的话，可以魔改 ADD 和 DEL 命令的代码，把参数打印到某个日志文件中。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">CNI_PATH</span><span class="o">=</span><span class="s2">&#34;/var/lib/rancher/k3s/data/current/bin&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">PATH</span><span class="o">=</span><span class="nv">$CNI_PATH</span>:<span class="nv">$PATH</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">CNI_CONTAINERID</span><span class="o">=</span><span class="s2">&#34;f19d5f601d6227bdf0cb28b43862632e98ecd23cd44d08e8ba1b2d8f27c9639c&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">CNI_NETNS</span><span class="o">=</span><span class="s2">&#34;/var/run/netns/cni-dba4ed62-c7d7-98fa-0efb-ffa6a8e526a3&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">CNI_IFNAME</span><span class="o">=</span>net1
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">CNI_COMMAND</span><span class="o">=</span>CHECK
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">/var/lib/rancher/k3s/data/current/bin/ptp &lt; p2p.json
</span></span></code></pre></div><p>如果验证错误，会返回一串包含错误信息的 JSON：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;code&#34;</span><span class="p">:</span> <span class="mi">999</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;msg&#34;</span><span class="p">:</span> <span class="s2">&#34;host-local: Failed to find address added by container caf3bc30ca71c847b84741b48a188456277867b404c409628ed33dc7aeb7d1a8&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>如果 CHECK 运行成功，CNI 程序的返回值将为 0，没有文字输出。</p>
<p>同理，你可以手动创建一个 Network Namespace 模拟容器网络，编辑上方相应的参数执行 CNI Binary 为这个 NS 创建/删除 Veth Pair。</p>
<h2 id="others">Others</h2>
<p>前几天生病了所以咕了好几天，感觉一篇博客把 CNI 和它的好朋友们全讲完不太现实，而且篇幅会变得巨长。所以到这里你已经清楚了 CNI 是个什么东西并具备手搓一个简单的 CNI 网络插件的能力了，至于上面提到的 *VLAN 以及那些目前成熟的网络插件们（Calico、Flannel、Cilium 等）感兴趣的话可以自行去折腾了，Kubernetes 集群上使用的成熟的 CNI 插件可不单要实现上面样例的 P2P 插件简单的功能，后面咱如果有时间的话打算再开一篇博客写这些东西了。</p>]]></content:encoded>
    </item>
    <item>
      <title>Leader Election 折腾小记</title>
      <link>https://blog.starry-s.moe/posts/2024/leader-election/</link>
      <pubDate>Wed, 12 Jun 2024 23:58:26 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2024/leader-election/</guid>
      <description>&lt;p&gt;最近好忙，有很多想写博客的东西都没时间写，五一去了佛山的 HiFurry，本来想着整理点照片水一篇博客但没时间也没精力，所以最后想写的东西就都咽肚里就饭吃了。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>最近好忙，有很多想写博客的东西都没时间写，五一去了佛山的 HiFurry，本来想着整理点照片水一篇博客但没时间也没精力，所以最后想写的东西就都咽肚里就饭吃了。</p>
<meting-js server="netease" type="song" id="2055270589" theme="#233333"></meting-js>
<hr>
<p>最近在折腾 Operator，就是用现成的框架写的 Controller。Operator 省去了重复且繁琐的使用 client-go 手搓 ClientSet、Informer、Lister、WorkQueue 等一大堆重复代码的步骤，只要基于已有的框架去写资源对象更新/删除时的业务处理逻辑就行了。</p>
<h2 id="leader-election-是什么">Leader Election 是什么</h2>
<p>当负载在 Kubernetes 运行时，通常会设置多个 Replicas 冗余副本，以实现高可用（HA），例如通常会将某些系统组件的 Replicas 设置为 2，就会创建两个对应的 Pods，通常这俩 Pod 会被调度到不同的节点上，在某个 Pod 挂掉时还能用另一个节点的 Pod。</p>
<p>Leader Election 机制是由“领导人选举机制”抽象而来的，可以理解为在多个“候选者”中选取某一个作为 Leader。这里的候选者指的是负载创建的多个冗余 Pod，Leader Election 机制从中选取某一个 Pod 作为 Leader，其他 Pod 则处于“待命”状态，如果 Leader Pod 出现故障，则会重新选举一个 Leader Pod。</p>
<p>Kubernetes 使用 Lease 资源（译作：租约）作为 Leader Election 的锁。和常用的 Mutex 互斥锁不同，Lease 资源会被 Leader Pod 每隔几秒钟更新一次。如果长达一段时间 Lease 没有被更新，则说明 Leader 挂掉了，其他 Pods 会竞争，尝试更新这个 Lease 锁，而成功更新了 Lease 的 Pod 会成为新的 Leader，其余 Pod 则继续处于待命状态。</p>
<p>大多数情况下，当某个资源发生更新时，我们不希望所有的冗余副本 Pod 都去处理某一个资源的更新，而是让某一个 Pod 去处理就可以了，不然会混乱（比如刷 Conflict 报错: <code>the object has been modified; please apply your changes to the latest version and try again</code>）。这时可以用到 Leader Election 机制，从多个冗余 Pod 中只选其中某一个 Pod 作为 Leader 处理资源更新，其余 Pod 只作为待命或其他用途。</p>
<p>如果你的 Controller 没有 Leader Election 机制，通常只能强行设定其 Replicas 为 1，但如果有小聪明修改了冗余数值为 2，则会出现一些问题，日志会刷大量的 Conflict 报错之类的，所以更严谨的方式是为 Controller 添加 Leader Election，以允许多 Replicas 冗余。</p>
<h2 id="举个栗子">举个栗子</h2>
<p>client-go 的样例代码中有 <a href="https://github.com/kubernetes/client-go/blob/v0.30.1/examples/leader-election/main.go">Leader Election 例子</a>，所以直接拿这个 Example 做简单的介绍了，把这个 Example 代码拷贝下来在本地跑一下。</p>
<p>首先你需要有一个 Kubernetes 集群用来调试，如果你觉得搭一个集群太麻烦，或者手里没有可供调试使用的集群的话，一个超级简单的方式是使用 <a href="https://k3d.io/">K3d</a> 在你的 Docker Runtime 中跑一个迷你版 K3s 集群。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> k3d cluster create example
</span></span><span class="line"><span class="cl"><span class="go">INFO[0000] Prep: Network
</span></span></span><span class="line"><span class="cl"><span class="go">INFO[0000] Created network &#39;k3d-example&#39;
</span></span></span><span class="line"><span class="cl"><span class="go">......
</span></span></span><span class="line"><span class="cl"><span class="go">INFO[0012] Cluster &#39;example&#39; created successfully!
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> kubectl get nodes -o wide
</span></span><span class="line"><span class="cl"><span class="go">NAME                   STATUS   ROLES                  AGE   VERSION        INTERNAL-IP   EXTERNAL-IP   OS-IMAGE           KERNEL-VERSION   CONTAINER-RUNTIME
</span></span></span><span class="line"><span class="cl"><span class="go">k3d-example-server-0   Ready    control-plane,master   98s   v1.28.8+k3s1   172.19.0.2    &lt;none&gt;        K3s v1.28.8+k3s1   6.9.3-arch1-1    containerd://1.7.11-k3s2
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> docker ps
</span></span><span class="line"><span class="cl"><span class="go">CONTAINER ID   IMAGE                            COMMAND                  CREATED          STATUS              PORTS                           NAMES
</span></span></span><span class="line"><span class="cl"><span class="go">a4c1367c04a2   ghcr.io/k3d-io/k3d-proxy:5.6.3   &#34;/bin/sh -c nginx-pr…&#34;   2 minutes ago    Up About a minute   80/tcp, 0.0.0.0:6443-&gt;6443/tcp  k3d-example-serverlb
</span></span></span><span class="line"><span class="cl"><span class="go">7c95a6ea069b   rancher/k3s:v1.28.8-k3s1         &#34;/bin/k3d-entrypoint…&#34;   2 minutes ago    Up 2 minutes                                        k3d-example-server-0
</span></span></span></code></pre></div><p>按照样例的 <a href="https://github.com/kubernetes/client-go/blob/v0.30.1/examples/leader-election/README.md">README</a>，在 3 个终端中运行 Leader Election 样例代码。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> go run main.go -kubeconfig<span class="o">=</span>~/.kube/config -logtostderr<span class="o">=</span><span class="nb">true</span> -lease-lock-name<span class="o">=</span>example -lease-lock-namespace<span class="o">=</span>default -id<span class="o">=</span><span class="m">1</span>
</span></span><span class="line"><span class="cl"><span class="go">I0612 22:59:20.118613   27504 leaderelection.go:250] attempting to acquire leader lease default/example...
</span></span></span><span class="line"><span class="cl"><span class="go">I0612 22:59:20.124630   27504 leaderelection.go:260] successfully acquired lease default/example
</span></span></span><span class="line"><span class="cl"><span class="go">I0612 22:59:20.124696   27504 main.go:87] Controller loop...
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> go run main.go -kubeconfig<span class="o">=</span>~/.kube/config -logtostderr<span class="o">=</span><span class="nb">true</span> -lease-lock-name<span class="o">=</span>example -lease-lock-namespace<span class="o">=</span>default -id<span class="o">=</span><span class="m">2</span>
</span></span><span class="line"><span class="cl"><span class="go">I0612 22:59:32.692373   27815 leaderelection.go:250] attempting to acquire leader lease default/example...
</span></span></span><span class="line"><span class="cl"><span class="go">I0612 22:59:32.695277   27815 main.go:151] new leader elected: 1
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> go run main.go -kubeconfig<span class="o">=</span>~/.kube/config -logtostderr<span class="o">=</span><span class="nb">true</span> -lease-lock-name<span class="o">=</span>example -lease-lock-namespace<span class="o">=</span>default -id<span class="o">=</span><span class="m">3</span>
</span></span><span class="line"><span class="cl"><span class="go">I0612 22:59:36.424251   28089 leaderelection.go:250] attempting to acquire leader lease default/example...
</span></span></span><span class="line"><span class="cl"><span class="go">I0612 22:59:36.427674   28089 main.go:151] new leader elected: 1
</span></span></span></code></pre></div><p>按顺序在 3 个终端中依次运行样例代码，可以看到 ID 为 1 的程序最先运行所以它成了 Leader，其余两个程序则在待命中。</p>
<p>这时对 ID 1 的程序执行 Ctrl-C，发送 <code>SIGINT</code> 中断信号，让它 Context Canceled，ID 1 程序会释放 Lease 锁并结束运行，其余两个程序中的某一个则会重新竞争，其中一个变成 Leader。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> go run main.go -kubeconfig<span class="o">=</span>~/.kube/config -logtostderr<span class="o">=</span><span class="nb">true</span> -lease-lock-name<span class="o">=</span>example -lease-lock-namespace<span class="o">=</span>default -id<span class="o">=</span><span class="m">3</span>
</span></span><span class="line"><span class="cl"><span class="go">I0612 22:59:36.424251   28089 leaderelection.go:250] attempting to acquire leader lease default/example...
</span></span></span><span class="line"><span class="cl"><span class="go">I0612 22:59:36.427674   28089 main.go:151] new leader elected: 1
</span></span></span><span class="line"><span class="cl"><span class="go">I0612 23:02:56.584777   28089 leaderelection.go:260] successfully acquired lease default/example
</span></span></span><span class="line"><span class="cl"><span class="go">I0612 23:02:56.584866   28089 main.go:87] Controller loop...
</span></span></span></code></pre></div><p>查看样例程序代码，<code>leaderelection.RunOrDie</code> 的参数传递的 Config 定义了 Leader Election 机制的 Callback 回调函数以及租约相关的时间 Duration。</p>
<p><code>Callbacks</code> 回调函数分为：</p>
<ul>
<li><code>OnStartedLeading</code>: 当该程序被选举为 Leader 时，执行此回调函数，通常该回调函数启动 Controller 的 Sync 逻辑等一些操作。</li>
<li><code>OnStoppedLeading</code>: 当该程序不再是 Leader 时（可能是收到了 <code>SIGINT</code> 信号，Context Canceled 或程序出故障，很长一段时间没有去更新 Lease 锁），会执行此回调函数，执行一些资源释放等操作，然后直接 <code>os.Exit</code> 结束程序。</li>
<li><code>OnNewLeader</code>: 当其他某个程序被选举为 Leader 时，会执行此函数，一般没什么用，可以不配置。</li>
</ul>
<p>Config 的其他参数：</p>
<ul>
<li><code>Lock</code>: Lease Lock。</li>
<li><code>ReleaseOnCancel</code>: 当 Context Cancel（当前的 Leader 结束运行）时，释放当前的 Lease 锁，使得其他 Pod 可以立即进行新一轮的选举。如果设置为 false 的话，当前 Leader 挂掉后其他 Pod 并不知道当前 Leader 已经挂掉了，只有过很长一段时间，发现 Lease 锁超过了 <code>LeaseDuration</code> 时间还没被更新，才会去强行的执行新一轮的选举。</li>
<li><code>LeaseDuration</code>: 结合上方的 <code>ReleaseOnCancel</code> 的介绍，假设当前 Leader Pod 出故障了（例如被 <code>SIGKILL</code> 立即杀死，Context 来不及 Cancel，或者调试进入了 Breakpoint 断点，程序暂停），Lease 锁没被释放，但当前 Leader 出问题挂掉了，其他待命的 Pod 发现 Lease 锁已经超过 <code>LeaseDuration</code> 没有被更新，则会强行进行新一轮的选举，而原 Leader 如果还活着的话，也会执行 <code>OnStoppedLeading</code> 回调函数结束运行。</li>
<li><code>RenewDeadline</code>: Leader 每隔一段时间会更新一次 Lease 锁。</li>
<li><code>RetryPeriod</code>: 如果 Leader 更新 Lease 锁失败了，会在一段时间后重试。</li>
</ul>
<p>所以有些小朋友在调试软件时，进入断点再恢复运行时会莫名其妙的结束运行，其实就是 Leader Election 机制搞的。所以如果想调试程序，可以临时把 <code>LeaseDuration</code> 设置长一些（例如好几天），这样调试断点恢复后，程序就不会被杀死了。</p>
<h2 id="杂谈">杂谈</h2>
<p>常用的 Operator 框架都支持 Leader Election，所以基本不用手写 <code>RunOrDie</code> 这部分代码，例如 Rancher 使用的 <a href="https://github.com/rancher/wrangler/">Wrangler</a> 框架，当程序成为 Leader 时，直接执行 <a href="https://github.com/rancher/rancher/blob/v2.9.0-rc1/pkg/wrangler/context.go#L175">OnLeader</a> 回调函数启动一系列业务逻辑。而当程序还没被选为 Leader 时，只初始化 Informer Cache 等初始化步骤，不启动 Sync 相关逻辑。</p>
<p>通常 <code>sample-controller</code> 或其他简单的 Controller 在 Worker Start 执行完之后，会加一个 <code>&lt;-ctx.Done()</code> 阻塞（<a href="https://github.com/kubernetes/sample-controller/blob/master/controller.go#L182">代码位置</a>），遇到 Context Cancel 后直接结束运行。但如果加了 Leader Election 机制，当 Context Cancel 时是由 Leader Election 的 <code>OnStoppedLeading</code> 回调函数结束运行并释放 Lease 锁，所以 <code>main</code> 函数可以改为使用 <code>select {}</code> 阻塞，否则程序在 Context Cancel 时 Lease 锁还没来得及释放就由 <code>main</code> 函数结束运行了。</p>
<p>所以读到这里，可以得出结论，就算设置数量特别多的 Replicas，实际上依旧只有一个 Controller Pod 在执行真正的 Sync 逻辑，而其他 Pod 只是在观望，或者只提供一些 Web Server 功能。如果想让多个冗余 Pod 分别 Sync 不同的资源更新，需要设计一个更复杂的锁，而这又会增加一定的 API Server 请求数量……</p>]]></content:encoded>
    </item>
    <item>
      <title>摄影日记 - 2024 春</title>
      <link>https://blog.starry-s.moe/posts/2024/spring-2024/</link>
      <pubDate>Wed, 24 Apr 2024 23:54:04 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2024/spring-2024/</guid>
      <description>&lt;p&gt;差一点就错过了今年春天的花……&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>差一点就错过了今年春天的花……</p>
<meting-js server="netease" type="song" id="32364750" theme="#233333"></meting-js>
<blockquote>
<p>感兴趣的话还可以看看咱的<a href="/gallery/">相册</a>页面。</p>
</blockquote>
<hr>
<p>公寓楼下的花，不确定是什么品种，应该是桃花。</p>
<p><img loading="lazy" src="../../../gallery/images/20240421-114524.jpg" alt="" />

</p>
<p><img loading="lazy" src="../../../gallery/images/20240421-114518.jpg" alt="" />

</p>
<p><img loading="lazy" src="../../../gallery/images/20240421-114517.jpg" alt="" />

</p>
<p><img loading="lazy" src="../../../gallery/images/20240421-114516.jpg" alt="" />

</p>
<p><img loading="lazy" src="../../../gallery/images/20240421-114521.jpg" alt="" />

</p>
<hr>
<p>去长白岛的时候已经过了桃花的花期了，拍到的是刚开放的樱花。</p>
<p><img loading="lazy" src="../../../gallery/images/20240421-114054.jpg" alt="" />

</p>
<p><img loading="lazy" src="../../../gallery/images/20240421-114057.jpg" alt="" />

</p>
<p><img loading="lazy" src="../../../gallery/images/20240421-114058.jpg" alt="" />

</p>
<p><img loading="lazy" src="../../../gallery/images/20240421-114100.jpg" alt="" />

</p>
<p><img loading="lazy" src="../../../gallery/images/20240421-114101.jpg" alt="" />

</p>
<p><img loading="lazy" src="../../../gallery/images/20240421-114105.jpg" alt="" />

</p>
<p><img loading="lazy" src="../../../gallery/images/20240421-114108.jpg" alt="" />

</p>
<hr>
<p><img loading="lazy" src="../../../gallery/images/20240421-114112.jpg" alt="" />

</p>
<p><img loading="lazy" src="../../../gallery/images/20240421-114113.jpg" alt="" />

</p>
<p><img loading="lazy" src="../../../gallery/images/20240421-114123.jpg" alt="" />

</p>]]></content:encoded>
    </item>
    <item>
      <title>Arch Linux 连接小米照片打印机 1S</title>
      <link>https://blog.starry-s.moe/posts/2024/archlinux-xiaomi-photo-printer-1s/</link>
      <pubDate>Sun, 03 Mar 2024 22:49:16 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2024/archlinux-xiaomi-photo-printer-1s/</guid>
      <description>&lt;p&gt;奇怪的反图方式增加了……&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>奇怪的反图方式增加了……</p>
<meting-js server="netease" type="song" id="41652392" theme="#233333"></meting-js>
<hr>
<blockquote>
<p>参烤链接：<a href="https://wiki.archlinux.org/title/CUPS#Permissions">CUPS - ArchWiki</a>。</p>
</blockquote>
<p>安装 <code>CUPS</code> 和 <code>ghostscript</code>，启用 <code>cups.service</code>。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo pacman -S cups
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo systemctl <span class="nb">enable</span> --now cups.service
</span></span></code></pre></div><p>小米的这个照片打印机支持免驱 <a href="https://en.wikipedia.org/wiki/AirPrint">AirPrint</a>，所以直接使用 <code>lpadmin</code> 添加打印机就行，不用安装驱动（也根本找不到对应的 Linux 驱动），只需要先在路由器中查询打印机的 IP 地址。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo lpadmin -p <span class="s2">&#34;Xiaomi-Photo-AirPrint&#34;</span> -E -v <span class="s2">&#34;ipp://192.168.x.x/ipp/print&#34;</span> -m everywhere
</span></span></code></pre></div><p>之后打印照片时就可以选择已添加的打印机设备了。</p>
<p><img loading="lazy" src="images/1.png" alt="" />

</p>
<p>初次打印时需要改一下打印的纸张大小为 4x6 英寸，不然照片尺寸会有问题，然后图片的质量可以改成最高。</p>
<p>照片打印出来会比屏幕上看到的更有内种感觉，打印的照片会有一些偏色，但个人认为这种色调害挺好看的。</p>
<p><img loading="lazy" src="images/2.jpg" alt="" />

</p>]]></content:encoded>
    </item>
    <item>
      <title>初探容器网络</title>
      <link>https://blog.starry-s.moe/posts/2024/container-network-1/</link>
      <pubDate>Mon, 26 Feb 2024 19:21:28 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2024/container-network-1/</guid>
      <description>&lt;p&gt;稍微折腾一下容器网络相关的东西……&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>稍微折腾一下容器网络相关的东西……</p>
<meting-js server="netease" type="song" id="1388992194" theme="#233333"></meting-js>
<h2 id="linux-网络命名空间">Linux 网络命名空间</h2>
<p>在熟悉容器网络之前，首先来看一下 Linux 网络命名空间 (Network Namespace) 这个东西。Linux 提供了多个不同种类的 Namespace，可以用 <code>lsns</code> 命令查看。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> lsns
</span></span><span class="line"><span class="cl"><span class="go">        NS TYPE   NPROCS   PID USER     COMMAND
</span></span></span><span class="line"><span class="cl"><span class="go">4026531834 time        2  2251 starry-s -zsh
</span></span></span><span class="line"><span class="cl"><span class="go">4026531835 cgroup      2  2251 starry-s -zsh
</span></span></span><span class="line"><span class="cl"><span class="go">4026531836 pid         2  2251 starry-s -zsh
</span></span></span><span class="line"><span class="cl"><span class="go">4026531837 user        2  2251 starry-s -zsh
</span></span></span><span class="line"><span class="cl"><span class="go">4026531838 uts         2  2251 starry-s -zsh
</span></span></span><span class="line"><span class="cl"><span class="go">4026531839 ipc         2  2251 starry-s -zsh
</span></span></span><span class="line"><span class="cl"><span class="go">4026531840 net         2  2251 starry-s -zsh
</span></span></span><span class="line"><span class="cl"><span class="go">4026531841 mnt         2  2251 starry-s -zsh
</span></span></span><span class="line"><span class="cl"><span class="go">...
</span></span></span></code></pre></div><p>参考 <a href="https://man7.org/linux/man-pages/man7/network_namespaces.7.html">network_namespace manpage</a>，Network Namespace 是 Linux 实现网络资源隔离的功能，不同的 Network Namespace 拥有不同的网卡、ARP、路由表等数据。可以使用 <code>iproute2</code> 工具的 <code>ip</code> 命令对 Linux Network Namespace 执行一系列的操作。</p>
<blockquote>
<p>本篇介绍的指令不会系统产生损坏，但建议在虚拟机或一个用于测试的系统上执行 Network Namespace 相关操作，以便于执行重启等暴力操作。</p>
</blockquote>
<p>开始之前，先安装 <code>net-tools</code> 网络工具包。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo pacman -S net-tools
</span></span></code></pre></div><p>查看设备中已有的 Network Namespace。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> ip netns list
</span></span><span class="line"><span class="cl"><span class="gp">$</span> ip netns ls
</span></span></code></pre></div><p><code>ip netns</code> 命令会列出 <code>/var/run/netns/</code> 目录下存在的 Network Namespace，如果之前没有使用 <code>ip</code> 命令创建过 netns，以上命令基本不会有输出（除非有别的工具也修改了这个目录）。首先创建两个 Network Namespace。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo ip netns add ns0
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip netns add ns1
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip netns ls
</span></span><span class="line"><span class="cl"><span class="go">ns0
</span></span></span><span class="line"><span class="cl"><span class="go">ns1
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> ls /var/run/netns/
</span></span><span class="line"><span class="cl"><span class="go">ns0 ns1
</span></span></span></code></pre></div><p>每个 Network Namespace 拥有不同的网卡、路由表、ARP 表等信息，可以使用 <code>ip -n [NAMESPACE]</code> 对某个 netns 进行操作，或通过 <code>ip netns exec</code> 在不同的 netns 下执行命令。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo ip -n ns0 link
</span></span><span class="line"><span class="cl"><span class="go">1: lo: &lt;LOOPBACK&gt; mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
</span></span></span><span class="line"><span class="cl"><span class="go">    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip netns <span class="nb">exec</span> ns0 ip link
</span></span><span class="line"><span class="cl"><span class="go">1: lo: &lt;LOOPBACK&gt; mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
</span></span></span><span class="line"><span class="cl"><span class="go">    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip netns <span class="nb">exec</span> ns0 route
</span></span><span class="line"><span class="cl"><span class="go">Kernel IP routing table
</span></span></span><span class="line"><span class="cl"><span class="go">Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip netns <span class="nb">exec</span> ns0 arp
</span></span></code></pre></div><h3 id="veth-pair-连接-network-namespace">veth pair 连接 Network Namespace</h3>
<p>新建的 netns 只有一个 DOWN 状态的回环接口，没有 ARP 和路由表信息，如果想在不同的 netns 之间通信，需要建立 veth pair（Virtual Cabel），把 netns 连接起来。</p>
<p>可以使用 <code>ip link add type veth</code> 创建一对 veth pair，注意 veth pair 是成对出现的，可以在创建 veth pair 时指定这对 veth pair 名称。</p>
<p>首先看一下系统自带的接口信息，默认情况下系统有一个 <code>lo</code> 回环接口和一个 <code>eth0</code> (被重命名为 <code>enp*s*</code> 的接口)，如果运行了 Docker，还会有一个 <code>docker0</code> 接口。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo ip link
</span></span><span class="line"><span class="cl"><span class="go">1: lo: &lt;LOOPBACK,UP,LOWER_UP&gt; mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
</span></span></span><span class="line"><span class="cl"><span class="go">    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
</span></span></span><span class="line"><span class="cl"><span class="go">2: enp1s0: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
</span></span></span><span class="line"><span class="cl"><span class="go">    link/ether ab:cd:ef:89:8f:f5 brd ff:ff:ff:ff:ff:ff
</span></span></span><span class="line"><span class="cl"><span class="go">3: docker0: &lt;NO-CARRIER,BROADCAST,MULTICAST,UP&gt; mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default 
</span></span></span><span class="line"><span class="cl"><span class="go">    link/ether 02:42:b7:a9:6a:55 brd ff:ff:ff:ff:ff:ff
</span></span></span></code></pre></div><p>创建一对 veth pair 名为 <code>veth0</code> 和 <code>veth1</code>。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo ip link add veth0 <span class="nb">type</span> veth peer name veth1
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip link
</span></span><span class="line"><span class="cl"><span class="go">......
</span></span></span><span class="line"><span class="cl"><span class="go">4: veth1@veth0: &lt;BROADCAST,MULTICAST,M-DOWN&gt; mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
</span></span></span><span class="line"><span class="cl"><span class="go">    link/ether 5e:7a:4e:96:b1:df brd ff:ff:ff:ff:ff:ff
</span></span></span><span class="line"><span class="cl"><span class="go">5: veth0@veth1: &lt;BROADCAST,MULTICAST,M-DOWN&gt; mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
</span></span></span><span class="line"><span class="cl"><span class="go">    link/ether 5e:5b:51:12:d0:b6 brd ff:ff:ff:ff:ff:ff
</span></span></span></code></pre></div><p>之后使用 <code>ip link set</code> 将这对 veth pair 分配到不同的 netns 中。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo ip link <span class="nb">set</span> veth0 netns ns0
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip link <span class="nb">set</span> veth1 netns ns1
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip -n ns0 link
</span></span><span class="line"><span class="cl"><span class="go">1: lo: &lt;LOOPBACK&gt; mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
</span></span></span><span class="line"><span class="cl"><span class="go">    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
</span></span></span><span class="line"><span class="cl"><span class="go">5: veth0@if4: &lt;BROADCAST,MULTICAST&gt; mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
</span></span></span><span class="line"><span class="cl"><span class="go">    link/ether 5e:5b:51:12:d0:b6 brd ff:ff:ff:ff:ff:ff link-netns ns1
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip -n ns1 link
</span></span><span class="line"><span class="cl"><span class="go">1: lo: &lt;LOOPBACK&gt; mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
</span></span></span><span class="line"><span class="cl"><span class="go">    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
</span></span></span><span class="line"><span class="cl"><span class="go">4: veth1@if5: &lt;BROADCAST,MULTICAST&gt; mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
</span></span></span><span class="line"><span class="cl"><span class="go">    link/ether 5e:7a:4e:96:b1:df brd ff:ff:ff:ff:ff:ff link-netns ns0
</span></span></span></code></pre></div><p>使用 <code>ip addr add</code> 为 veth pair 接口创建 IP 地址，并使用 <code>ip link set [INTERFACE] up</code> 启动网卡接口。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo ip -n ns0 addr add 10.0.0.100/24 dev veth0
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip -n ns1 addr add 10.0.0.101/24 dev veth1
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip -n ns0 link <span class="nb">set</span> veth0 up
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip -n ns1 link <span class="nb">set</span> veth1 up
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip -n ns0 addr
</span></span><span class="line"><span class="cl"><span class="go">1: lo: &lt;LOOPBACK&gt; mtu 65536 qdisc noop state DOWN group default qlen 1000
</span></span></span><span class="line"><span class="cl"><span class="go">    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
</span></span></span><span class="line"><span class="cl"><span class="go">5: veth0@if4: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc noqueue state UP group default qlen 1000
</span></span></span><span class="line"><span class="cl"><span class="go">    link/ether 5e:5b:51:12:d0:b6 brd ff:ff:ff:ff:ff:ff link-netns ns1
</span></span></span><span class="line"><span class="cl"><span class="go">    inet 10.0.0.100/24 scope global veth0
</span></span></span><span class="line"><span class="cl"><span class="go">       valid_lft forever preferred_lft forever
</span></span></span><span class="line"><span class="cl"><span class="go">    inet6 fe80::5c5b:51ff:fe12:d0b6/64 scope link proto kernel_ll 
</span></span></span><span class="line"><span class="cl"><span class="go">       valid_lft forever preferred_lft forever
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip -n ns1 addr
</span></span><span class="line"><span class="cl"><span class="go">1: lo: &lt;LOOPBACK&gt; mtu 65536 qdisc noop state DOWN group default qlen 1000
</span></span></span><span class="line"><span class="cl"><span class="go">    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
</span></span></span><span class="line"><span class="cl"><span class="go">4: veth1@if5: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc noqueue state UP group default qlen 1000
</span></span></span><span class="line"><span class="cl"><span class="go">    link/ether 5e:7a:4e:96:b1:df brd ff:ff:ff:ff:ff:ff link-netns ns0
</span></span></span><span class="line"><span class="cl"><span class="go">    inet 10.0.0.101/24 scope global veth1
</span></span></span><span class="line"><span class="cl"><span class="go">       valid_lft forever preferred_lft forever
</span></span></span><span class="line"><span class="cl"><span class="go">    inet6 fe80::5c7a:4eff:fe96:b1df/64 scope link proto kernel_ll 
</span></span></span><span class="line"><span class="cl"><span class="go">       valid_lft forever preferred_lft forever
</span></span></span></code></pre></div><p><code>ip addr add</code> 命令在添加 IP 地址时会自动创建路由表信息。现在两个 netns 之间可通过 veth pair 互相通信。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo ip -n ns0 route
</span></span><span class="line"><span class="cl"><span class="go">10.0.0.0/24 dev veth0 proto kernel scope link src 10.0.0.100 
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip -n ns1 route
</span></span><span class="line"><span class="cl"><span class="go">10.0.0.0/24 dev veth1 proto kernel scope link src 10.0.0.101 
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip netns <span class="nb">exec</span> ns0 ping -c <span class="m">1</span> 10.0.0.101
</span></span><span class="line"><span class="cl"><span class="go">PING 10.0.0.101 (10.0.0.101) 56(84) bytes of data.
</span></span></span><span class="line"><span class="cl"><span class="go">64 bytes from 10.0.0.101: icmp_seq=1 ttl=64 time=0.051 ms
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">--- 10.0.0.101 ping statistics ---
</span></span></span><span class="line"><span class="cl"><span class="go">1 packets transmitted, 1 received, 0% packet loss, time 0ms
</span></span></span><span class="line"><span class="cl"><span class="go">rtt min/avg/max/mdev = 0.051/0.051/0.051/0.000 ms
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip netns <span class="nb">exec</span> ns1 ping -c <span class="m">1</span> 10.0.0.100
</span></span><span class="line"><span class="cl"><span class="go">PING 10.0.0.100 (10.0.0.100) 56(84) bytes of data.
</span></span></span><span class="line"><span class="cl"><span class="go">64 bytes from 10.0.0.100: icmp_seq=1 ttl=64 time=0.040 ms
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">--- 10.0.0.100 ping statistics ---
</span></span></span><span class="line"><span class="cl"><span class="go">1 packets transmitted, 1 received, 0% packet loss, time 0ms
</span></span></span><span class="line"><span class="cl"><span class="go">rtt min/avg/max/mdev = 0.040/0.040/0.040/0.000 ms
</span></span></span></code></pre></div><p>到这里，<code>ns0</code> 和 <code>ns1</code> 两个 Network Namespace 之间的拓扑图如下。</p>
<p><img loading="lazy" src="images/veth.webp" alt="" />

</p>
<h3 id="使用-bridge-连接多个-network-namespace">使用 bridge 连接多个 Network Namespace</h3>
<p>veth pair 只能用于两个 netns 之间的通信，如果需要多个 netns 访问到同一个网络中，需要配置桥接网络。</p>
<p>重启系统（清理掉之前创建的 netns 和 veth pair），之后重新建立 <code>ns0</code>, <code>ns1</code> 和 <code>ns2</code> 三个 Network Namespace。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo ip netns add ns0
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip netns add ns1
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip netns add ns2
</span></span></code></pre></div><p>使用 <code>ip link add</code> 创建一个桥接接口，并建立三对 veth pair，用于连接 <code>br0</code> 和上述三个 netns。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo ip link add br0 <span class="nb">type</span> bridge
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip link add veth0-br <span class="nb">type</span> veth peer name veth0
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip link add veth1-br <span class="nb">type</span> veth peer name veth1
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip link add veth2-br <span class="nb">type</span> veth peer name veth2
</span></span><span class="line"><span class="cl"><span class="gp">$</span> ip link
</span></span><span class="line"><span class="cl"><span class="go">...
</span></span></span><span class="line"><span class="cl"><span class="go">4: br0: &lt;BROADCAST,MULTICAST&gt; mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
</span></span></span><span class="line"><span class="cl"><span class="go">    link/ether be:60:00:25:c5:37 brd ff:ff:ff:ff:ff:ff
</span></span></span><span class="line"><span class="cl"><span class="go">5: veth0@veth0-br: &lt;BROADCAST,MULTICAST,M-DOWN&gt; mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
</span></span></span><span class="line"><span class="cl"><span class="go">    link/ether 5e:5b:51:12:d0:b6 brd ff:ff:ff:ff:ff:ff
</span></span></span><span class="line"><span class="cl"><span class="go">6: veth0-br@veth0: &lt;BROADCAST,MULTICAST,M-DOWN&gt; mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
</span></span></span><span class="line"><span class="cl"><span class="go">    link/ether 72:01:3d:42:16:8c brd ff:ff:ff:ff:ff:ff
</span></span></span><span class="line"><span class="cl"><span class="go">7: veth1@veth1-br: &lt;BROADCAST,MULTICAST,M-DOWN&gt; mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
</span></span></span><span class="line"><span class="cl"><span class="go">    link/ether 5e:7a:4e:96:b1:df brd ff:ff:ff:ff:ff:ff
</span></span></span><span class="line"><span class="cl"><span class="go">8: veth1-br@veth1: &lt;BROADCAST,MULTICAST,M-DOWN&gt; mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
</span></span></span><span class="line"><span class="cl"><span class="go">    link/ether 1e:13:96:f1:b6:9d brd ff:ff:ff:ff:ff:ff
</span></span></span><span class="line"><span class="cl"><span class="go">9: veth2@veth2-br: &lt;BROADCAST,MULTICAST,M-DOWN&gt; mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
</span></span></span><span class="line"><span class="cl"><span class="go">    link/ether 62:13:73:b6:5d:f9 brd ff:ff:ff:ff:ff:ff
</span></span></span><span class="line"><span class="cl"><span class="go">10: veth2-br@veth2: &lt;BROADCAST,MULTICAST,M-DOWN&gt; mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
</span></span></span><span class="line"><span class="cl"><span class="go">    link/ether f2:e6:df:92:de:71 brd ff:ff:ff:ff:ff:ff
</span></span></span></code></pre></div><p>把 <code>veth0</code>, <code>veth1</code>, <code>veth2</code> 分别放到 <code>ns0</code>, <code>ns1</code> 和 <code>ns2</code> 三个 Network Namespace 中，并将他们重命名为 <code>eth0</code>。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo ip link <span class="nb">set</span> dev veth0 netns ns0
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip link <span class="nb">set</span> dev veth1 netns ns1
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip link <span class="nb">set</span> dev veth2 netns ns2
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip -n ns0 link <span class="nb">set</span> dev veth0 name eth0
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip -n ns1 link <span class="nb">set</span> dev veth1 name eth0
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip -n ns2 link <span class="nb">set</span> dev veth2 name eth0
</span></span></code></pre></div><p>并把 <code>veth0-br</code>, <code>veth1-br</code>, <code>veth2-br</code> 分别连接到 <code>br0</code> 桥接网卡中。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo ip link <span class="nb">set</span> dev veth0-br master br0
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip link <span class="nb">set</span> dev veth1-br master br0
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip link <span class="nb">set</span> dev veth2-br master br0
</span></span></code></pre></div><p>启用所有的网卡接口（为了能 ping 通每个 netns 的 <code>127.0.0.1</code>，将每个 ns 的 <code>lo</code> 回环接口也启动）。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo ip link <span class="nb">set</span> dev br0 up
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip link <span class="nb">set</span> veth0-br up
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip link <span class="nb">set</span> veth1-br up
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip link <span class="nb">set</span> veth2-br up
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip -n ns0 link <span class="nb">set</span> eth0 up
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip -n ns1 link <span class="nb">set</span> eth0 up
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip -n ns2 link <span class="nb">set</span> eth0 up
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip -n ns0 link <span class="nb">set</span> lo up
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip -n ns1 link <span class="nb">set</span> lo up
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip -n ns2 link <span class="nb">set</span> lo up
</span></span></code></pre></div><p>为 <code>br0</code> 和 netns 中的 veth 接口 （<code>eth0</code>）添加 IP 地址。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo ip addr add 10.1.1.1/24 dev br0
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip -n ns0 addr add 10.1.1.10/24 dev eth0
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip -n ns1 addr add 10.1.1.11/24 dev eth0
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip -n ns2 addr add 10.1.1.12/24 dev eth0
</span></span></code></pre></div><p>查看一下 <code>br0</code> 和 netns 中的 <code>eth0</code> 接口的 IP 地址。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> ip a
</span></span><span class="line"><span class="cl"><span class="go">...
</span></span></span><span class="line"><span class="cl"><span class="go">4: br0: &lt;BROADCAST,MULTICAST&gt; mtu 1500 qdisc noop state DOWN group default qlen 1000
</span></span></span><span class="line"><span class="cl"><span class="go">    link/ether be:60:00:25:c5:37 brd ff:ff:ff:ff:ff:ff
</span></span></span><span class="line"><span class="cl"><span class="go">    inet 10.0.0.1/24 scope global br0
</span></span></span><span class="line"><span class="cl"><span class="go">       valid_lft forever preferred_lft forever
</span></span></span><span class="line"><span class="cl"><span class="go">6: veth0-br@if5: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc noqueue master br0 state UP group default qlen 1000
</span></span></span><span class="line"><span class="cl"><span class="go">    link/ether 72:01:3d:42:16:8c brd ff:ff:ff:ff:ff:ff link-netns ns0
</span></span></span><span class="line"><span class="cl"><span class="go">    inet6 fe80::7001:3dff:fe42:168c/64 scope link proto kernel_ll 
</span></span></span><span class="line"><span class="cl"><span class="go">       valid_lft forever preferred_lft forever
</span></span></span><span class="line"><span class="cl"><span class="go">8: veth1-br@if7: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc noqueue master br0 state UP group default qlen 1000
</span></span></span><span class="line"><span class="cl"><span class="go">    link/ether 1e:13:96:f1:b6:9d brd ff:ff:ff:ff:ff:ff link-netns ns1
</span></span></span><span class="line"><span class="cl"><span class="go">    inet6 fe80::1c13:96ff:fef1:b69d/64 scope link proto kernel_ll 
</span></span></span><span class="line"><span class="cl"><span class="go">       valid_lft forever preferred_lft forever
</span></span></span><span class="line"><span class="cl"><span class="go">10: veth2-br@if9: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc noqueue master br0 state UP group default qlen 1000
</span></span></span><span class="line"><span class="cl"><span class="go">    link/ether f2:e6:df:92:de:71 brd ff:ff:ff:ff:ff:ff link-netns ns2
</span></span></span><span class="line"><span class="cl"><span class="go">    inet6 fe80::f0e6:dfff:fe92:de71/64 scope link proto kernel_ll 
</span></span></span><span class="line"><span class="cl"><span class="go">       valid_lft forever preferred_lft forever
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip -n ns0 a
</span></span><span class="line"><span class="cl"><span class="go">5: eth0@if6: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc noqueue state UP group default qlen 1000
</span></span></span><span class="line"><span class="cl"><span class="go">    link/ether 5e:5b:51:12:d0:b6 brd ff:ff:ff:ff:ff:ff link-netnsid 0
</span></span></span><span class="line"><span class="cl"><span class="go">    inet 10.1.1.10/24 scope global eth0
</span></span></span><span class="line"><span class="cl"><span class="go">       valid_lft forever preferred_lft forever
</span></span></span><span class="line"><span class="cl"><span class="go">    inet6 fe80::5c5b:51ff:fe12:d0b6/64 scope link proto kernel_ll 
</span></span></span><span class="line"><span class="cl"><span class="go">       valid_lft forever preferred_lft forever
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip -n ns1 a
</span></span><span class="line"><span class="cl"><span class="go">7: eth0@if8: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc noqueue state UP group default qlen 1000
</span></span></span><span class="line"><span class="cl"><span class="go">    link/ether 5e:7a:4e:96:b1:df brd ff:ff:ff:ff:ff:ff link-netnsid 0
</span></span></span><span class="line"><span class="cl"><span class="go">    inet 10.1.1.11/24 scope global eth0
</span></span></span><span class="line"><span class="cl"><span class="go">       valid_lft forever preferred_lft forever
</span></span></span><span class="line"><span class="cl"><span class="go">    inet6 fe80::5c7a:4eff:fe96:b1df/64 scope link proto kernel_ll 
</span></span></span><span class="line"><span class="cl"><span class="go">       valid_lft forever preferred_lft forever
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip -n ns2 a
</span></span><span class="line"><span class="cl"><span class="go">9: eth0@if10: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc noqueue state UP group default qlen 1000
</span></span></span><span class="line"><span class="cl"><span class="go">    link/ether 62:13:73:b6:5d:f9 brd ff:ff:ff:ff:ff:ff link-netnsid 0
</span></span></span><span class="line"><span class="cl"><span class="go">    inet 10.1.1.12/24 scope global eth0
</span></span></span><span class="line"><span class="cl"><span class="go">       valid_lft forever preferred_lft forever
</span></span></span><span class="line"><span class="cl"><span class="go">    inet6 fe80::6013:73ff:feb6:5df9/64 scope link proto kernel_ll 
</span></span></span><span class="line"><span class="cl"><span class="go">       valid_lft forever preferred_lft forever
</span></span></span></code></pre></div><p>此时在主机上可以 ping 通三个 netns 的 IP 地址。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> ping -c <span class="m">1</span> 10.1.1.10
</span></span><span class="line"><span class="cl"><span class="go">PING 10.1.1.10 (10.1.1.10) 56(84) bytes of data.
</span></span></span><span class="line"><span class="cl"><span class="go">64 bytes from 10.1.1.10: icmp_seq=1 ttl=64 time=0.130 ms
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> ping -c <span class="m">1</span> 10.1.1.11
</span></span><span class="line"><span class="cl"><span class="go">PING 10.1.1.11 (10.1.1.11) 56(84) bytes of data.
</span></span></span><span class="line"><span class="cl"><span class="go">64 bytes from 10.1.1.11: icmp_seq=1 ttl=64 time=0.117 ms
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> ping -c <span class="m">1</span> 10.1.1.12
</span></span><span class="line"><span class="cl"><span class="go">PING 10.1.1.12 (10.1.1.12) 56(84) bytes of data.
</span></span></span><span class="line"><span class="cl"><span class="go">64 bytes from 10.1.1.12: icmp_seq=1 ttl=64 time=0.119 ms
</span></span></span></code></pre></div><p>三个 netns 也可以访问主机的 IP 地址 <code>10.1.1.1</code>。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo ip netns <span class="nb">exec</span> ns0 ping -c <span class="m">1</span> 10.1.1.1
</span></span><span class="line"><span class="cl"><span class="go">PING 10.1.1.1 (10.1.1.1) 56(84) bytes of data.
</span></span></span><span class="line"><span class="cl"><span class="go">64 bytes from 10.1.1.1: icmp_seq=1 ttl=64 time=0.076 ms
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip netns <span class="nb">exec</span> ns1 ping -c <span class="m">1</span> 10.1.1.1
</span></span><span class="line"><span class="cl"><span class="go">PING 10.1.1.1 (10.1.1.1) 56(84) bytes of data.
</span></span></span><span class="line"><span class="cl"><span class="go">64 bytes from 10.1.1.1: icmp_seq=1 ttl=64 time=0.071 ms
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip netns <span class="nb">exec</span> ns2 ping -c <span class="m">1</span> 10.1.1.1
</span></span><span class="line"><span class="cl"><span class="go">PING 10.1.1.1 (10.1.1.1) 56(84) bytes of data.
</span></span></span><span class="line"><span class="cl"><span class="go">64 bytes from 10.1.1.1: icmp_seq=1 ttl=64 time=0.072 ms
</span></span></span></code></pre></div><p>默认情况下 Linux 会把 bridge 的二层转发（交换机）功能禁用掉，因此不同的 netns 之间仍无法互相访问。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo ip netns <span class="nb">exec</span> ns0 ping -c <span class="m">1</span> -W <span class="m">5</span> 10.1.1.11
</span></span><span class="line"><span class="cl"><span class="go">PING 10.1.1.11 (10.1.1.11) 56(84) bytes of data.
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">--- 10.1.1.11 ping statistics ---
</span></span></span><span class="line"><span class="cl"><span class="go">1 packets transmitted, 0 received, 100% packet loss, time 0ms
</span></span></span></code></pre></div><p>使用 IP 桌子，激活桥接接口的转发功能。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo iptables -A FORWARD -i br0 -j ACCEPT
</span></span></code></pre></div><p>此时不同的 netns 之间可以互相 ping 通了。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo ip netns <span class="nb">exec</span> ns0 ping 10.1.1.12
</span></span><span class="line"><span class="cl"><span class="go">PING 10.1.1.12 (10.1.1.12) 56(84) bytes of data.
</span></span></span><span class="line"><span class="cl"><span class="go">64 bytes from 10.1.1.12: icmp_seq=1 ttl=64 time=0.148 ms
</span></span></span></code></pre></div><p>到这里有关 Linux Network Namespace 的配置就可以完美的告一段落了，咱创建了三个 netns，它们之间可以通过 <code>10.1.1.0/24</code> 这一个网段通过 bridge 桥接网卡和 veth pair 实现互相二层（交换机）访问，此时的网络拓扑图变成了下面这样子。</p>
<p><img loading="lazy" src="images/veth-bridge.webp" alt="" />

</p>
<hr>
<p>如果想要更进一步，要实现 netns 内访问其他网段的 IP 地址，还需要再做一些配置，让主机实现网关功能，并配置 NAT，让主机实现 3 层地址转发（路由器）。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo ip netns <span class="nb">exec</span> ns0 ping -c <span class="m">1</span> 8.8.8.8
</span></span><span class="line"><span class="cl"><span class="go">ping: connect: Network is unreachable
</span></span></span></code></pre></div><p>首先需要将主机的网卡（咱这里为 <code>enp1s0</code>，不同系统可能不一样）也添加到将桥接网卡 <code>br0</code> 中，这里要注意把主机的网卡 <code>enp1s0</code> 添加到桥接网卡 <code>br0</code> 后，要把 <code>enp1s0</code> 网卡上的 IP 地址（咱这里为 <code>192.168.122.101/24</code>）改到桥接网卡 <code>br0</code> 上，不然过一段时间后会网络中断。</p>
<blockquote>
<p>这里<strong>不要</strong> ssh 远程操作。</p>
</blockquote>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo ip link <span class="nb">set</span> enp1s0 master br0
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip addr del 192.168.122.101/24 dev enp1s0
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip addr add 192.168.122.101/24 dev br0
</span></span></code></pre></div><p>手动为 netns 设定默认网关 <code>10.1.1.1/24</code>。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo ip -n ns0 route add default via 10.1.1.1
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip -n ns1 route add default via 10.1.1.1
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip -n ns2 route add default via 10.1.1.1
</span></span></code></pre></div><p>接下来使用 IP 桌子配置 IP 地址转发，这里的指令和之前配置 Linux 主机做路由器是一样的。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo iptables --table nat -A POSTROUTING -s 10.1.1.0/24 -j MASQUERADE
</span></span></code></pre></div><p>查看一下 netns 中的路由表，这时的默认流量会走 <code>10.1.1.1</code> 网关。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo ip -n ns0 route
</span></span><span class="line"><span class="cl"><span class="go">default via 10.1.1.1 dev eth0 
</span></span></span><span class="line"><span class="cl"><span class="go">10.1.1.0/24 dev eth0 proto kernel scope link src 10.1.1.10
</span></span></span></code></pre></div><p>到这里如果不出意外的话，三个 netns 已经具备访问公网的能力了。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo ip netns <span class="nb">exec</span> ns0 ping -c <span class="m">1</span> 8.8.8.8
</span></span><span class="line"><span class="cl"><span class="go">PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
</span></span></span><span class="line"><span class="cl"><span class="go">64 bytes from 8.8.8.8: icmp_seq=1 ttl=112 time=66.0 ms
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">--- 8.8.8.8 ping statistics ---
</span></span></span><span class="line"><span class="cl"><span class="go">1 packets transmitted, 1 received, 0% packet loss, time 0ms
</span></span></span><span class="line"><span class="cl"><span class="go">rtt min/avg/max/mdev = 65.964/65.964/65.964/0.000 ms
</span></span></span></code></pre></div><h2 id="容器网络">容器网络</h2>
<p>其实上面咱演示的使用 bridge 桥接网卡 + veth pair 配置多个 Network Namespace 互相访问的这个网络模型基本上就和 Docker 默认的 <code>bridge</code> 网络模型没啥区别了。</p>
<p>只是 Docker 使用 <a href="https://github.com/opencontainers/runc/tree/main/libcontainer">runc/libcontainer</a> 没有把容器对应的 Network Namespace 文件放到 <code>/var/run/netns/</code> 目录，使用 <code>ip netns</code> 命令发现不到它。不过带胶布，可以把 Docker 容器中应用的 Network Namespace 对应文件软链接到 <code>/var/run/netns/</code> 目录中，再使用 <code>ip</code> 命令执行一些操作。</p>
<p>首先跑一个 <code>nginx</code> 容器，使用 <code>docker inspect</code> 获取进程的 PID。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> docker run -dit --name nginx -p 80:80 nginx
</span></span><span class="line"><span class="cl"><span class="gp">$</span> docker inspect --format <span class="s1">&#39;{{.State.Pid}}&#39;</span> nginx
</span></span><span class="line"><span class="cl"><span class="go">993
</span></span></span></code></pre></div><p>创建软链接，将进程的 netns 文件链接到 <code>/var/run/netns</code> 目录。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo mkdir -p /var/run/netns
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ln -s /proc/993/ns/net /var/run/netns/ns-993
</span></span><span class="line"><span class="cl"><span class="gp">$</span> ip netns
</span></span><span class="line"><span class="cl"><span class="go">ns-993
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip netns <span class="nb">exec</span> ns-993 ip addr
</span></span><span class="line"><span class="cl"><span class="go">1: lo: &lt;LOOPBACK,UP,LOWER_UP&gt; mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
</span></span></span><span class="line"><span class="cl"><span class="go">    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
</span></span></span><span class="line"><span class="cl"><span class="go">    inet 127.0.0.1/8 scope host lo
</span></span></span><span class="line"><span class="cl"><span class="go">       valid_lft forever preferred_lft forever
</span></span></span><span class="line"><span class="cl"><span class="go">4: eth0@if5: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc noqueue state UP group default 
</span></span></span><span class="line"><span class="cl"><span class="go">    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
</span></span></span><span class="line"><span class="cl"><span class="go">    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
</span></span></span><span class="line"><span class="cl"><span class="go">       valid_lft forever preferred_lft forever
</span></span></span></code></pre></div><p>之后可以像上面那样，再建立一对 veth pair，为容器创建 “第二个网卡” <code>eth1</code>，实现主机和容器之间的访问。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo ip link add eth0-ns-993 <span class="nb">type</span> veth peer name veth-ns-993
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip link <span class="nb">set</span> eth0-ns-993 netns ns-993
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip -n ns-993 link <span class="nb">set</span> dev eth0-ns-993 name eth1
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip -n ns-993 addr
</span></span><span class="line"><span class="cl"><span class="go">1: lo: &lt;LOOPBACK,UP,LOWER_UP&gt; mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
</span></span></span><span class="line"><span class="cl"><span class="go">    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
</span></span></span><span class="line"><span class="cl"><span class="go">    inet 127.0.0.1/8 scope host lo
</span></span></span><span class="line"><span class="cl"><span class="go">       valid_lft forever preferred_lft forever
</span></span></span><span class="line"><span class="cl"><span class="go">4: eth0@if5: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc noqueue state UP group default 
</span></span></span><span class="line"><span class="cl"><span class="go">    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
</span></span></span><span class="line"><span class="cl"><span class="go">    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
</span></span></span><span class="line"><span class="cl"><span class="go">       valid_lft forever preferred_lft forever
</span></span></span><span class="line"><span class="cl"><span class="go">9: eth1@if8: &lt;BROADCAST,MULTICAST&gt; mtu 1500 qdisc noop state DOWN group default qlen 1000
</span></span></span><span class="line"><span class="cl"><span class="go">    link/ether 36:57:fa:63:3b:f0 brd ff:ff:ff:ff:ff:ff link-netnsid 0
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip addr add 10.1.0.1/24 dev veth-ns-993
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip -n ns-993 addr add 10.1.0.2/24 dev eth1
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip link <span class="nb">set</span> dev veth-ns-993 up
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo ip -n ns-993 link <span class="nb">set</span> dev eth1 up
</span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> ping 10.1.0.2
</span></span><span class="line"><span class="cl"><span class="go">PING 10.1.0.2 (10.1.0.2) 56(84) bytes of data.
</span></span></span><span class="line"><span class="cl"><span class="go">64 bytes from 10.1.0.2: icmp_seq=1 ttl=64 time=0.040 ms
</span></span></span><span class="line"><span class="cl"><span class="go">...
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> curl 10.1.0.2
</span></span><span class="line"><span class="cl"><span class="go">&lt;!DOCTYPE html&gt;
</span></span></span><span class="line"><span class="cl"><span class="go">&lt;html&gt;
</span></span></span><span class="line"><span class="cl"><span class="go">&lt;head&gt;
</span></span></span><span class="line"><span class="cl"><span class="go">&lt;title&gt;Welcome to nginx!&lt;/title&gt;
</span></span></span><span class="line"><span class="cl"><span class="go">...
</span></span></span></code></pre></div><p>看吧，就是这么的简单（确信）。</p>
<p>所以在运行了 Docker 的主机上执行 <code>ip</code> 命令有时能看到一大堆 <code>veth</code> 开头的网卡设备名，到这里我们就能明白这些实际上是 veth pair，一端连接到了 <code>docker0</code> 桥接网卡上，另一端则连接在 Docker 容器的 Network Namespace 中。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> ip l
</span></span><span class="line"><span class="cl"><span class="go">3: docker0: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc noqueue state UP mode DEFAULT group default 
</span></span></span><span class="line"><span class="cl"><span class="go">    link/ether 02:42:48:1b:aa:e0 brd ff:ff:ff:ff:ff:ff
</span></span></span><span class="line"><span class="cl"><span class="go">5: veth077b91e@if4: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default 
</span></span></span><span class="line"><span class="cl"><span class="go">    link/ether 6e:29:0d:fb:d2:43 brd ff:ff:ff:ff:ff:ff link-netnsid 0
</span></span></span></code></pre></div><h2 id="kubernetes-pod">Kubernetes Pod</h2>
<p>众所周知，Kubernetes 的一个 Pod 中可以包含多个容器，这些容器共用一个网络命名空间，不同容器运行的程序可以直接通过 <code>127.0.0.1</code> 回环地址互相访问。这里需要补充一个萌新容易混淆的概念就是，Linux 的 Namespace 和 Kubernetes 的 Namespace 不是一个东西，前者是 Linux 内核 Level 的特性，后者是 Kubernetes API Server Level 的功能，虽然都叫 Namespace 但他俩不是一个东西。</p>
<p>那么 Kubernetes 的 Pod 是如何实现多个容器共用一个 Network Namespace 的呢？之前用过 Kubernetes 的小朋友可能会注意到他们的 Container Runtime 中总能看到名叫 <code>pause</code> 的容器，这又是干什么的呢？</p>
<p>Docker 的网络模型除了默认的 <code>bridge</code> 之外，还有 <code>host</code>, <code>none</code> 和 <code>container</code> 这几种。其中 <code>host</code> 是指和主机共用同一个网络命名空间，<code>none</code> 是容器的 Network Namespace 不配置任何额外的网络。而 <code>container</code> 网络模型则是用来指定一个已有的容器，和他共用同一个 Network Namespace。</p>
<p>Kubernetes 的 Pod 需要让多个容器共用同一个 Network Namespace，所以需要先找一个容器创建 Network Namespace，再让其他容器加入到这个预先创建好的 Network Namespace 中。让 Pod 中任何一个容器作为创建 Network Namespace 的容器都不合适，所以就出来了一个 <code>pause</code> 容器，这个容器体积很小，运行之后其进程永远处于休眠（pause）状态，且 pause 容器的进程 PID 为 1，因为除了创建网络命名空间外，<code>pause</code> 容器还创建了 Linux 进程命名空间，用于回收僵尸进程。</p>
<p>Pause 容器的源码可以在 <a href="https://github.com/kubernetes/kubernetes/blob/master/build/pause/linux/pause.c">这里</a> 找到，可以看到它主要确保自己的 PID 为 1，处理一些 Linux Signal 之外，其余时间一直都在 <code>pause</code>。</p>
<p>可以用 Docker 的 <code>container</code> 网络模型模拟一个 Kubernetes 的 Pod，因为想不出什么太合适的栗子，所以这个 “Pod” 里运行了一个 nginx server 和一个 registry server。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> docker run -d --name pause -p 8080:80 -p 5000:5000 --ipc<span class="o">=</span>shareable rancher/mirrored-pause:3.6
</span></span><span class="line"><span class="cl"><span class="gp">$</span> docker run -d --name nginx --net<span class="o">=</span>container:pause --ipc<span class="o">=</span>container:pause --pid<span class="o">=</span>container:pause nginx
</span></span><span class="line"><span class="cl"><span class="gp">$</span> docker run -d --name registry --net<span class="o">=</span>container:pause --ipc<span class="o">=</span>container:pause --pid<span class="o">=</span>container:pause registry
</span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> docker ps
</span></span><span class="line"><span class="cl"><span class="go">CONTAINER ID   IMAGE                        COMMAND                  CREATED         STATUS         PORTS                                                                              NAMES
</span></span></span><span class="line"><span class="cl"><span class="go">eaace8974956   registry                     &#34;/entrypoint.sh /etc…&#34;   2 minutes ago   Up 2 minutes                                                                                      registry
</span></span></span><span class="line"><span class="cl"><span class="go">247ed1ca07e3   nginx                        &#34;/docker-entrypoint.…&#34;   2 minutes ago   Up 2 minutes                                                                                      nginx
</span></span></span><span class="line"><span class="cl"><span class="go">6cdf835a09f0   rancher/mirrored-pause:3.6   &#34;/pause&#34;                 2 minutes ago   Up 2 minutes   0.0.0.0:5000-&gt;5000/tcp, :::5000-&gt;5000/tcp, 0.0.0.0:8080-&gt;80/tcp, :::8080-&gt;80/tcp   pause
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> curl 127.0.0.1:8080
</span></span><span class="line"><span class="cl"><span class="go">&lt;!DOCTYPE html&gt;
</span></span></span><span class="line"><span class="cl"><span class="go">&lt;html&gt;
</span></span></span><span class="line"><span class="cl"><span class="go">&lt;head&gt;
</span></span></span><span class="line"><span class="cl"><span class="go">&lt;title&gt;Welcome to nginx!&lt;/title&gt;
</span></span></span><span class="line"><span class="cl"><span class="go">...
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> docker login 127.0.0.1:5000
</span></span><span class="line"><span class="cl"><span class="go">Username: admin
</span></span></span><span class="line"><span class="cl"><span class="go">Password: 
</span></span></span><span class="line"><span class="cl"><span class="go">Login Succeeded
</span></span></span></code></pre></div>]]></content:encoded>
    </item>
    <item>
      <title>K3s &#43; Multus CNI 插件使用 Macvlan</title>
      <link>https://blog.starry-s.moe/posts/2024/k3s-multus-macvlan/</link>
      <pubDate>Tue, 30 Jan 2024 18:52:00 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2024/k3s-multus-macvlan/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://k3s.io/&#34;&gt;K3s&lt;/a&gt; 是一个轻量的 Kubernetes 集群，&lt;a href=&#34;https://github.com/k8snetworkplumbingwg/multus-cni&#34;&gt;Multus&lt;/a&gt; 是一个用于给 Pod 创建多个网络接口的 CNI (Container Network Interface) 插件，其创建的接口支持 &lt;code&gt;macvlan&lt;/code&gt;。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><a href="https://k3s.io/">K3s</a> 是一个轻量的 Kubernetes 集群，<a href="https://github.com/k8snetworkplumbingwg/multus-cni">Multus</a> 是一个用于给 Pod 创建多个网络接口的 CNI (Container Network Interface) 插件，其创建的接口支持 <code>macvlan</code>。</p>
<meting-js server="netease" type="song" id="4017232" theme="#233333"></meting-js>
<hr>
<h2 id="啥是-macvlan">啥是 Macvlan</h2>
<p>字面意思，根据 MAC 地址划分的虚拟子网 (Vlan) 就是 macvlan，网上能搜到很多有关 Macvlan 的介绍，这里不再过多描述。</p>
<p>与之相对应的还有一个叫 ipvlan，是通过 IP 地址划分的虚拟子网。</p>
<p>Macvlan 和 ipvlan 都是 Linux 系统的特性，其他系统不支持这个功能。</p>
<h2 id="prerequisites">Prerequisites</h2>
<p>可以用 <code>modinfo macvlan</code> 检查系统是否有安装 <code>macvlan</code> 模块，根据 <a href="https://docs.docker.com/network/network-tutorial-macvlan/#prerequisites">Docker 文档</a> 中描述的建议是使用 Linux 3.9 或 4.0 及更新的内核版本。</p>
<p>可以用以下指令检查系统是否支持 Macvlan（这里使用桥接模式）：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo ip link add macvlan0 link enp1s0 <span class="nb">type</span> macvlan mode bridge  <span class="c1"># 这里替换 enp1s0 为网卡接口名称</span>
</span></span><span class="line"><span class="cl">sudo ip address add 192.168.122.205/24 broadcast 192.168.122.255 dev macvlan0 <span class="c1"># 注意 IP 地址冲突</span>
</span></span></code></pre></div><p>之后可尝试使用其他处于同一个网络（CIDR）的设备 ping 这个 <code>192.168.122.205</code> IP 地址，能 Ping 通就说明你的防火墙没有屏蔽不同设备之间的二层数据转发。</p>
<h2 id="安装-k3s">安装 K3s</h2>
<p>根据 <a href="https://github.com/k8snetworkplumbingwg/multus-cni/blob/master/docs/quickstart.md">Multus 的 QuickStart 手册</a>，准备一个新版本的 Kubernetes 集群（这里用的是 <code>v1.27.8+k3s2</code>），K3s 默认的 CNI 插件使用的是 Flannel。</p>
<p>在国内的话需要先创建 <code>/etc/rancher/k3s/registries.yaml</code> 配置 Registry Mirror：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">mirrors</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">docker.io</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">endpoint</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="s2">&#34;https://docker.nju.edu.cn&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ghcr.io</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">endpoint</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="s2">&#34;https://ghcr.nju.edu.cn&#34;</span><span class="w">
</span></span></span></code></pre></div><p>之后使用国内源一键安装 K3s：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">curl -sfL https://rancher-mirror.oss-cn-beijing.aliyuncs.com/k3s/k3s-install.sh <span class="p">|</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">	<span class="nv">INSTALL_K3S_VERSION</span><span class="o">=</span>v1.27.8+k3s2 <span class="se">\
</span></span></span><span class="line"><span class="cl">	<span class="nv">INSTALL_K3S_MIRROR</span><span class="o">=</span>cn <span class="se">\
</span></span></span><span class="line"><span class="cl">	sh -s - server <span class="se">\
</span></span></span><span class="line"><span class="cl">	--cluster-init <span class="se">\
</span></span></span><span class="line"><span class="cl">	--system-default-registry <span class="s2">&#34;docker.nju.edu.cn&#34;</span>
</span></span></code></pre></div><h2 id="安装-multus-cni">安装 Multus CNI</h2>
<p>接下来安装 Multus CNI 插件，下载 <code>multus-daemonset.yml</code> 配置，需要编辑 <code>kube-multus-ds</code> DaemonSet hostPath 的路径到 K3s 对应的路径上去。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">wget <span class="s1">&#39;https://raw.githubusercontent.com/k8snetworkplumbingwg/multus-cni/master/deployments/multus-daemonset.yml&#39;</span>
</span></span></code></pre></div><p>编辑 <code>kube-multus-ds</code> DaemonSet 的 <code>hostPath</code> 的配置为 K3s 的路径。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">cni</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">hostPath</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">/var/lib/rancher/k3s/agent/etc/cni/net.d</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">cnibin</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">hostPath</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">/var/lib/rancher/k3s/data/current/bin</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nn">...</span><span class="w">
</span></span></span></code></pre></div><p>还要编辑 <code>kube-multus-ds</code> DaemonSet 的 Container 配置，增添一条 command arg：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">containers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">kube-multus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">ghcr.io/k8snetworkplumbingwg/multus-cni:snapshot</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;/thin_entrypoint&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">args</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="s2">&#34;--multus-conf-file=auto&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="s2">&#34;--multus-autoconfig-dir=/host/etc/cni/net.d&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="s2">&#34;--cni-conf-dir=/host/etc/cni/net.d&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c"># ADD THIS LINE:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="s2">&#34;--multus-kubeconfig-file-host=/var/lib/rancher/k3s/agent/etc/cni/net.d/multus.d/multus.kubeconfig&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nn">...</span><span class="w">
</span></span></span></code></pre></div><p>之后 <code>kubectl apply</code> 上面的 Multus Daemonset 配置，等待 <code>kube-multus-ds</code> DaemonSet 跑起来后，可以看到 <code>/var/lib/rancher/k3s/data/current/bin</code> 目录下有新增 <code>multus</code> 可执行文件。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo ls /var/lib/rancher/k3s/data/current/bin <span class="p">|</span> grep multus
</span></span><span class="line"><span class="cl"><span class="go">multus
</span></span></span></code></pre></div><h2 id="自定义-multus-cni-配置文件">自定义 Multus CNI 配置文件</h2>
<p>新建一个名为 <code>macvlan-conf</code> 的 <code>NetworkAttachmentDefinition</code> Custom Resource，自定义 multus 配置文件：</p>
<p>这里需要注意 <code>config</code> 中的 <code>master</code> 网卡接口要设置为物理机上对应的网卡接口名。</p>
<p>咱把 K3s Server 安装在了 QEMU 虚拟机中，虚拟机使用的是 libvirt 创建的默认网卡，CIDR 编址为 <code>192.168.122.0/24</code>，网关 <code>192.168.122.1</code>。
为了能在其他虚拟机 / 物理机上也能访问到虚拟机中使用了 macvlan 的 pod，multus macvlan 配置文件也使用 libvirt 网卡的 CIDR。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;k8s.cni.cncf.io/v1&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">NetworkAttachmentDefinition</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">macvlan-conf</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">config</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;{
</span></span></span><span class="line"><span class="cl"><span class="s1">      &#34;cniVersion&#34;: &#34;0.3.1&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s1">      &#34;type&#34;: &#34;macvlan&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s1">      &#34;master&#34;: &#34;enp1s0&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s1">      &#34;mode&#34;: &#34;bridge&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s1">      &#34;ipam&#34;: {
</span></span></span><span class="line"><span class="cl"><span class="s1">        &#34;type&#34;: &#34;host-local&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s1">        &#34;subnet&#34;: &#34;192.168.122.0/24&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s1">        &#34;rangeStart&#34;: &#34;192.168.122.200&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s1">        &#34;rangeEnd&#34;: &#34;192.168.122.210&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s1">        &#34;routes&#34;: [
</span></span></span><span class="line"><span class="cl"><span class="s1">          { &#34;dst&#34;: &#34;0.0.0.0/0&#34; }
</span></span></span><span class="line"><span class="cl"><span class="s1">        ],
</span></span></span><span class="line"><span class="cl"><span class="s1">        &#34;gateway&#34;: &#34;192.168.122.1&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">      }
</span></span></span><span class="line"><span class="cl"><span class="s1">    }&#39;</span><span class="w">
</span></span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> kubectl apply -f macvlan-conf.yaml
</span></span><span class="line"><span class="cl"><span class="gp">$</span> kubectl get net-attach-def
</span></span><span class="line"><span class="cl"><span class="go">NAME           AGE
</span></span></span><span class="line"><span class="cl"><span class="go">macvlan-conf   59s
</span></span></span></code></pre></div><h2 id="创建-macvlan-pod">创建 Macvlan Pod</h2>
<p>K3s 将安装包体积做了精简移除了 <code>macvlan</code> CNI 插件，所以创建 Pod 之前需要手动下载 <code>macvlan</code> CNI 插件放到 K3s 的 data bin 目录。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> mkdir -p cni-plugin <span class="o">&amp;&amp;</span> <span class="nb">cd</span> cni-plugin
</span></span><span class="line"><span class="cl"><span class="gp">$</span> wget https://github.com/containernetworking/plugins/releases/download/v1.4.0/cni-plugins-linux-amd64-v1.4.0.tgz
</span></span><span class="line"><span class="cl"><span class="gp">$</span> tar -zxvf cni-plugins-linux-amd64-v1.4.0.tgz
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo cp ./macvlan /var/lib/rancher/k3s/data/current/bin/
</span></span></code></pre></div><p>之后创建 Pod，使用 Annotation 指定网络的配置文件，并让 Pod 被 Multus CNI 识别。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Pod</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">nginx-macvlan</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">annotations</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">k8s.v1.cni.cncf.io/networks</span><span class="p">:</span><span class="w"> </span><span class="l">macvlan-conf</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">containers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">nginx</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">nginx</span><span class="w">
</span></span></span></code></pre></div><p>如果一切顺利的话，<code>kubectl describe pod nginx-macvlan</code> 能看到以下的 Events：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">Events:
</span></span><span class="line"><span class="cl">  Type    Reason          Age   From               Message
</span></span><span class="line"><span class="cl">  ----    ------          ----  ----               -------
</span></span><span class="line"><span class="cl">  Normal  Scheduled       2s    default-scheduler  Successfully assigned default/nginx-macvlan to archlinux-k3s-1
</span></span><span class="line"><span class="cl">  Normal  AddedInterface  2s    multus             Add eth0 [10.42.0.26/24] from cbr0
</span></span><span class="line"><span class="cl">  Normal  AddedInterface  2s    multus             Add net1 [192.168.122.200/24] from default/macvlan-conf
</span></span></code></pre></div><p>因为 K3s 服务器跑在了 QEMU KVM 虚拟机里面，libvirt 默认网卡 CIDR 是 <code>192.168.122.0/24</code>。所以咱在物理机上访问虚拟机内的 Macvlan Pod IP <code>192.168.122.200</code>，是能正常访问的。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> curl 192.168.122.200
</span></span><span class="line"><span class="cl"><span class="go">&lt;!DOCTYPE html&gt;
</span></span></span><span class="line"><span class="cl"><span class="go">&lt;html&gt;
</span></span></span><span class="line"><span class="cl"><span class="go">&lt;head&gt;
</span></span></span><span class="line"><span class="cl"><span class="go">&lt;title&gt;Welcome to nginx!&lt;/title&gt;
</span></span></span><span class="line"><span class="cl"><span class="go">...
</span></span></span></code></pre></div><p>然后因为 Macvlan 的子接口 (sub interface) 无法与父接口 (parent interface) 直接访问，所以在节点的主机上访问运行在这个节点内的 macvlan pod 是访问不通的，也就是说无法通过节点主机的接口访问到 macvlan pod 的子接口，除非使用 ipvlan，可以参考以下这几篇讨论：</p>
<ul>
<li><a href="https://stackoverflow.com/questions/69316893/single-node-microk8s-multus-master-interface-cannot-be-reached">Single node Microk8s multus master interface cannot be reached</a></li>
<li><a href="https://forums.docker.com/t/host-and-containers-cannot-communicate-macvlan/112968">Host and Containers cannot communicate - MACVLAN</a></li>
</ul>]]></content:encoded>
    </item>
    <item>
      <title>Hello 2024</title>
      <link>https://blog.starry-s.moe/posts/2024/hello-2024/</link>
      <pubDate>Thu, 18 Jan 2024 23:12:28 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2024/hello-2024/</guid>
      <description>&lt;p&gt;这里没有年终总结。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>这里没有年终总结。</p>
<meting-js server="netease" type="song" id="2101842506" theme="#233333"></meting-js>
<br/>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sed <span class="s1">&#39;s/2023/2024/g&#39;</span>
</span></span></code></pre></div><p>没什么头绪，不知道该从什么地方说起。</p>
<h2 id="旅行">旅行</h2>
<p>2023 年咱去了很多很多的地方，去了成都、重庆、上海、长春、本溪、哈尔滨、长白山（是 2024 年初去的但依旧放在今年的总结里吧）。</p>
<p>现在回想一下，在成都和重庆去了他们那边当地的漫展和一些专门为游客准备的景点（世界线展子好大）。今年一共去了三次上海，第一次是去的 Bilibili World 和 Bilibili Macro Link。第二次是去的 KubeCon。第三次是去看梶浦由记的 Live，顺便去了一趟野生动物园。（实际上差点就去了四次，但是上海梦兽和 KubeCon 时间撞车了，门票没买到所以没去成）。在吉林参加了两次兽聚，分别是长春的疯狂一夜小聚和长白山附近的白兽渊。长白山景区因为下雪封闭了所以没去成（嗯，都是某位雪神的锅，哼），滑雪时间来不及了也没滑上。年底去了尔滨冰雪大世界，体验了一把在室外零下二十多度的地方排队四个小时的滋味……</p>
<h2 id="生活">生活</h2>
<p>工作和生活方面与 22 年咱回到辽宁后没什么变化，咱依旧是居住在沈阳这座城市，今年是咱大学毕业后，在沈阳这个城市定居的第一年。不过幸好咱大学也是在沈阳念的，所以没有遇到过多的水土不服和气候、环境不适应的情况，也就是年初的时候经历过许多次的生病发烧、各种感冒（几乎不到一个月一次的那种），应该是因为租的房子的原因吧，空间狭窄、不通风、很闷，沈阳的冬天又那么的干冷，真的很容易生病。不过从校园生活转变为在校园外的感觉很不一样，咱之前可没和这么多的东北人打过交道，虽然第一反应觉得东北话挺逗挺有意思的，但时间久了实际上东北话有一种语气蛮横，充满脏话，很直白的那种，给人很不舒服的感觉。尤其是碰到过很多黑心出租车，欺负外地人，嗯，出租车司机只是咱举的一个栗子，就是总能在很多细节的地方找出曾经的老工业和军阀的气息。而沈阳排斥外地人的方式也很有特点，大多数人的思维方式也蛮与众不同的，总之就是碰到那种能和你唠一道的出租车司机，跟你骂骂咧咧抱怨这抱怨那的，你就迎合他说啊对对对，啊是是是，哄他心情开心就得了，咱一个人生活在这座城市，打车可不敢招惹这样的司机，也犯不上和这种人闹别扭是吧。</p>
<p>咱没有什么地域黑的意思，至少咱还是喜欢沈阳这座城市的，在别的城市也能碰到不同的人和事，强者从不抱怨环境，但咱不是强者，不配抱怨环境，也决定不了自己所在的环境，不要问咱为什么要回沈阳，也不要羡慕那些自己没有而别人有的东西。咱只是觉得这座城市似乎出于什么原因，很多地方都和外界隔绝了一样，有种大范围的信息差不对等的感觉。经常能听到有人说 “东北谁爱回谁回，我反正不回” 这样的言论，但至少现在是有许多人在努力改善这种情况（当然也有许多人在努力防止这种情况被改善就是了）。有机会的话多去沈阳的市博物馆、陵寝和故宫那附近逛逛，了解点历史还是有帮助的，起码能帮助外地人熟悉这座城市，而不是单纯的通过表面现象去做结论。</p>
<p>咳，说多了，至于为什么今年去了那么多城市一个主要的原因就是想尽可能的改善咱的社恐，咱很纳闷为什么在别的城市就能过的好好的偏偏回到东北就变成了这个样子。其实咱在之前上学的时候，就一直想的是以后坚决不留在东北，以后一定要去老师和亲戚朋友口中所说的南方大城市。当时咱就是很难适应东北的种种事情，咱和沈阳本地人几乎没有过什么交流，咱只记得那时候，路边的小商铺一直在用大声的音箱放着土味音乐和吆喝声，总能看到 “精神小伙” 和某手短视频、土味段子的这种低俗没营养的视频（我不看但身边总有同学在外放这些）。至于同学举行的聚会、社团活动什么的，咱在大一转专业之前试着参加过几次但因为很不合群，闹得十分不愉快而最终不再参加这样的活动了。工作之后，在意识到咱回到东北了，突然意识到咱根本就没办法和本地人正常交流这件事，从而变成了十分严重的社恐，就像一个丢了 Context 的 Go Routine 一样，咱就这么成为了一个大号的社会不适应者，等着被这个社会淘汰掉。其实咱有想过要不要去看心理医生之类的但最终还是不想去医院，没有什么理由就是觉得咱有自己的方式能解决这些问题，如果治不好的话就算了。逼着这种病态的社恐人去参加些什么社交活动，只会加重这种情况。</p>
<p>出于爱好，咱经常去沈阳本地的漫展，也就是在年初的漫展上看到了一群大福瑞从而被拖下水入坑了福瑞控。讲真在其他城市的漫展很少能看到这么多的大福瑞，咱也是从这时候开始尝试和他们加好友聊天，还试着参加一些线下的社团活动（但是效果并不好，所以咱后来又把这些社团退掉了，到现在咱还是很谨慎几乎不加入社团什么的）。咱在入坑福瑞之后买了一台入门残幅相机，尝试给他们拍照返图，顺便认识了一些新的朋友。因为福瑞这个圈子，额，不太好和圈外的人解释，所以很长一段时间以来咱从来没和圈外的人提到过咱也是福瑞这个事。也就到了年底咱在别的装师那里捡了一个掉落头之后，才开始大量的向咱的社交帐号上发福瑞相关的东西，顺便去了长白山的兽聚，和一群大佬们合影、交换物料、试着参加一些活动什么的，不过咱依旧是和身边任何人保持着很大的距离感……</p>
<p>咱没有刻意隐瞒咱是福瑞这些相关的东西，也并没有想背着别人搞一些坏事什么的。其实就算被身边的人发现了也没什么大不了的，只取决于圈外的人对福瑞的看法了，所以避免不必要的争吵，大多数时候咱还是不想让他们知道而已。所以请避免当面问我不礼貌的问题，不要开黄腔，不要讨论性别，不要讨论性取向，可以开玩笑但不要过分。崽子是崽子，内胆是内胆，不要把崽子的事情上升到内胆，如果不喜欢看可以不看，不要把崽子的形象和内胆进行绑定，不要盗图盗设定，不要拍打兽装头部，这些都是基本常识。</p>
<p>但是，咱真的很想认识更多的朋友，虽然咱反感社交，但参加完这些活动后真的能开心好长好长一段时间。咱真的不再希望自己一个人孤零零的去漫展了，一个人去兽聚没人拼酒店票真的很没意思，很多旅游景点明摆着就是给多人准备的，一个人根本就玩不了（但是不要问我为什么没有邀请你一起去漫展/兽聚这些活动，只是单纯的认为不合适，我没有准备好，这样子会很尴尬的）。</p>
<p>还有就是，网络上的社交软件能展示的东西太少了，这个窗口只能暴露出极少数的文字和图片等信息，但人们往往把网络上展示出来的部分信息当作成了这个人的全部，这很容易就会引起很大很大的误会，这也是咱为什么逐渐的不想在网络上发更多的消息了，尤其是咱的微信加了各种各样的好友，于是就不想再发只适合某类好友看的朋友圈了。聊天软件上发的几行文字是不带语速、情感、环境信息的，如果可以的话咱更希望是通过面对面的聊天去熟悉一个人，而不是通过他在社交平台上发的一些文字、聊天软件上发的表现不出来完整情感的几句话。至于那些曾经由种种原因已经产生的误会，咱也不打算去和别人解释了，有时候就保持着这些误会也挺好的，到能解开的时候自然就解开了，解不开就算了。</p>
<p>但是咱还是很怀念那些曾经因为种种原因导致的已经不联系了的朋友们，唉。</p>
<h2 id="工作">工作</h2>
<p>工作一年多了，依旧在学很多新的知识。比较值得一提的是咱今年去了上海的 KubeCon，除了在公司的展台那里帮忙收拾东西，空余时间还可以去楼上的各个会场听大佬们的演讲。说实话除了上海应该不会再有别的城市能举行这种大规模国际化的、带有开源社区的大型活动了。貌似有很多公司是奔着商业主题相关的演讲去的，但咱个人层面则是更倾向于去看一些开源技术的演讲，咱属于是刚接触这个领域没多久，属于是什么都想听，但时间紧只能从日程表中选出某几个演讲去听的那种。尽管咱听不懂（听不懂英语 + 听不懂技术内容）但多少能收获到那些大佬们的一些思路、思维方式和写开源项目的风格、感受一下技术氛围之类的，至少咱后面的程序开发思路可以借鉴一部分，这些往往是商业的演讲容易遗漏掉的东西。听这些大佬们的演讲有一种在大学上课的感觉，只是听一遍的话肯定是记不住老师讲的什么内容的，而咱根本就没打算要把所有演讲的内容全记脑子里，咱就是感觉这种氛围是真的和大学的课堂很像很像。因为咱这个年龄就和个小孩子似的，去这种 Conference 要不是戴着公司的胸牌估计就直接被保安拦外面了……当时保安还怀疑我的胸牌是不是复印假的每次都是反复检查好久才放我进去。印象比较深的就是在发物料时，有好多在上海的大学生过来领，然后他们的胸牌上的公司名称写的要么是某某大学什么的，要么就是胡乱起的名字，就挺有意思的…… 嗯咱今年一共领了两种不同的英特尔袋子，一个是在 B 站的 BW 上领的（听说这个袋子在当时虹桥那边暴雨发水时保住了好多 Coser 的衣服），另一个是在 KubeCon 上领的，英特尔他真的好喜欢发袋子。去完 KubeCon 后一直想找时间去学 Rust 和 Linux Kernel 的 eBPF 相关知识来着，但这个貌似要推到 2024 年再去学了。公司对新人真的太好了，能有这样的机会去上海的 KubeCon 真的开心死了。</p>
<p>从 KubeCon 回来后咱把咱自己曾经写的一个开源程序给重构了一遍，基本上就是咱的上一篇博客里讲的那些内容。本来想单独开一篇博客介绍这个程序的但最近真的真的太忙了没时间了所以就没写（实际博客内容都写一半了但既然写年终总结了就压缩一下内容放在这里吧）。因为这个程序主要还是应用在云原生相关的场景的，所以咱也仿照着 Rancher 官网、K3s 官网这些去重写了一下咱这个程序的文档网站（官网）。咱还把咱的程序按照咱的思维去重构了一遍，比如咱个人的观点认为，要编写一个清真的开源软件，除非万不得已，不要调用任何其他第三方的二进制文件（当然这里说的二进制文件是指一个可执行文件，不是动态库，还有除非你的程序就是设计为一个 Wrapper，否则为了长远考虑，不要为了实现某个功能而调用某个第三方的可执行文件），而是通过已有的 API 和 Library 通过代码去实现这些功能，再大一点的项目还会手搓轮子，不引入第三方库。然后咱还从用户侧的角度考虑了一下把那些用户可能遇到的使用体验不友好的地方全都重构了一遍（其实之前的用户体验也蛮好的，只是有那么几个可优化的地方……）。咱今年可是花了很大的精力去重新设计这个程序的代码，毕竟写一个 Demo 程序和真正的把它拿去给用户去用是两码事。起初这个程序是咱刚初学 Go 语言时，一边学 Go 的基本语法一边去糊这个程序的功能，现在一想当时咱写的程序真的很乱，设计的很难看（但起码能用），当时咱对于怎么处理 Go 的并发、怎么用 Context、接口到底是怎么用的这些一无所知，而且书本只能教给你接口、Context、Signal 是什么，但他们不会告诉你怎么在大型项目中优雅的使用它。重构完了这个项目之后尽管程序设计上依旧存在缺陷，但起码他比以前好了很多了，咱这回用了 Go 的 Channel 和 Context 更好的处理了异常和错误信息，还相对正确的使用了 Go 的 Interface 接口去设计。尽管重构后的程序功能和之前貌似没什么区别，对用户来讲改动不是很大（也就少了一些依赖，压缩包格式变了，日志的内容变了），但从代码这边基本上是完全重写了（<span class="spoiler" >修复了一些 bug 并引入了一些新的 bug</span>）。咱可不希望只为了实现功能而写出丑陋的代码，因为这是开源的软件，如果有别的社区的人想做贡献时，没人愿意在丑陋的代码上耗费时间。</p>
<p>所以说了这么多，咱把咱写的程序网址贴在这里，感兴趣的话就去看看：<a href="https://hangar.cnrancher.com">https://hangar.cnrancher.com</a></p>
<h2 id="游戏">游戏</h2>
<p>今年并没打什么游戏，玩了一阵子的守望先锋，但这游戏越来越没意思了逐渐不怎么玩了，Minecraft 也没怎么玩，就偶尔空闲时间进去逛逛。同学有时候还会喊我去打一些游戏，但大多数时间咱都推掉了。</p>
<p>想玩的游戏还有很多，一直想找机会二刷一遍月姬和魔法使之夜，魔夜重置版的支线咱也还没打完，还蛮期待过一阵子即将新出的月姬官方汉化和最终幻想 7 重置版的续作的。</p>
<h2 id="其他">其他</h2>
<p>2023 年实际上对咱来说是一个很糟糕的一年，我很烦那些不切实际的羡慕，我很反感那些对自己现状不满而总是羡慕别人的人。很多时候咱把咱好的一面展现出来而把不好的事情埋在心里，不想让别人担心，就算是生病或者心情不好到了极点咱也会发点和这些不相关的朋友圈和消息之类的，我不想向别人讲自己的烦恼和困扰，但这会给人一种 “他过得比我好，我好羡慕他” 的感觉。很多时候是咱靠着仅存的一点兴趣和爱好一个人在硬撑着，但是那种有些东西自己没有而别人有就去说风凉话的真的很恶心。很多事都不是凭空偶然发生的，特别的能力会招惹来特别的能力。嗯，不知道自己还能撑多久，也不知道自己以后会去哪里，总之就是挺喜欢橙子老师说的，“我们并不是根据背负的罪来选择道路，而是先选择道路再背负起自己的罪孽”，“所谓的 ‘逃’ 有两种，漫无目的的逃以及带有目的的逃。一般将前者称为 ‘漂浮’，后者称为 ‘飞行’”。不知道咱自己还能飞多久，也不知道自己是在故意挥动翅膀，装出自己好像在飞行的样子，实际已经坠落了呢。</p>]]></content:encoded>
    </item>
    <item>
      <title>如何选择 zip 和 tar 文件格式</title>
      <link>https://blog.starry-s.moe/posts/2023/zip-tar/</link>
      <pubDate>Sun, 12 Nov 2023 01:35:41 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2023/zip-tar/</guid>
      <description>&lt;p&gt;最近遇到了一个归档文件格式选择的问题，于是顺手记录下来水一篇博客。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>最近遇到了一个归档文件格式选择的问题，于是顺手记录下来水一篇博客。</p>
<meting-js server="netease" type="song" id="27594398" theme="#233333"></meting-js>
<hr>
<h2 id="tar-文件格式">tar 文件格式</h2>
<p>tar 格式早期是为了将数据记录在磁带上的（现在貌似也可以？），这种归档格式很简单，要将一个文件写入 tar 包的时候，首先写入记录文件信息的 header，在 header 之后记录文件的数据（tar 格式不支持压缩所以是直接把文件数据拷贝在了 header 后面）。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">+--------+
</span></span><span class="line"><span class="cl">| header |
</span></span><span class="line"><span class="cl">+--------+
</span></span><span class="line"><span class="cl">|  data  |
</span></span><span class="line"><span class="cl">+--------+
</span></span><span class="line"><span class="cl">| header |
</span></span><span class="line"><span class="cl">+--------+
</span></span><span class="line"><span class="cl">|  data  |
</span></span><span class="line"><span class="cl">+--------+
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">+--------+
</span></span><span class="line"><span class="cl">| header |
</span></span><span class="line"><span class="cl">+--------+
</span></span><span class="line"><span class="cl">|  data  |
</span></span><span class="line"><span class="cl">+--------+
</span></span><span class="line"><span class="cl">|  end   |
</span></span><span class="line"><span class="cl">+--------+
</span></span></code></pre></div><p>tar 有多种不同格式的 header。这里可以看 Linux 系统上常用的 <code>tar</code> 工具（GNU tar）代码，
GNU tar 中实现的 header 结构定义文档参考这个 <a href="https://www.gnu.org/software/tar/manual/html_node/Standard.html">Basic Tar Format</a>。</p>
<p>GNU tar 的源码可以通过下面的方式克隆下载下来。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> git clone https://git.savannah.gnu.org/git/tar.git
</span></span></code></pre></div><p>在 <code>src/tar.h</code> 源码中可以找到 header 结构定义，其中 <code>posix_header</code> 的定义为：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">posix_header</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>				<span class="cm">/* byte offset */</span>
</span></span><span class="line"><span class="cl">  <span class="kt">char</span> <span class="n">name</span><span class="p">[</span><span class="mi">100</span><span class="p">];</span>		<span class="cm">/*   0 */</span>
</span></span><span class="line"><span class="cl">  <span class="kt">char</span> <span class="n">mode</span><span class="p">[</span><span class="mi">8</span><span class="p">];</span>			<span class="cm">/* 100 */</span>
</span></span><span class="line"><span class="cl">  <span class="kt">char</span> <span class="n">uid</span><span class="p">[</span><span class="mi">8</span><span class="p">];</span>			<span class="cm">/* 108 */</span>
</span></span><span class="line"><span class="cl">  <span class="kt">char</span> <span class="n">gid</span><span class="p">[</span><span class="mi">8</span><span class="p">];</span>			<span class="cm">/* 116 */</span>
</span></span><span class="line"><span class="cl">  <span class="kt">char</span> <span class="n">size</span><span class="p">[</span><span class="mi">12</span><span class="p">];</span>		<span class="cm">/* 124 */</span>
</span></span><span class="line"><span class="cl">  <span class="kt">char</span> <span class="n">mtime</span><span class="p">[</span><span class="mi">12</span><span class="p">];</span>		<span class="cm">/* 136 */</span>
</span></span><span class="line"><span class="cl">  <span class="kt">char</span> <span class="n">chksum</span><span class="p">[</span><span class="mi">8</span><span class="p">];</span>		<span class="cm">/* 148 */</span>
</span></span><span class="line"><span class="cl">  <span class="kt">char</span> <span class="n">typeflag</span><span class="p">;</span>		<span class="cm">/* 156 */</span>
</span></span><span class="line"><span class="cl">  <span class="kt">char</span> <span class="n">linkname</span><span class="p">[</span><span class="mi">100</span><span class="p">];</span>		<span class="cm">/* 157 */</span>
</span></span><span class="line"><span class="cl">  <span class="kt">char</span> <span class="n">magic</span><span class="p">[</span><span class="mi">6</span><span class="p">];</span>		<span class="cm">/* 257 */</span>
</span></span><span class="line"><span class="cl">  <span class="kt">char</span> <span class="n">version</span><span class="p">[</span><span class="mi">2</span><span class="p">];</span>		<span class="cm">/* 263 */</span>
</span></span><span class="line"><span class="cl">  <span class="kt">char</span> <span class="n">uname</span><span class="p">[</span><span class="mi">32</span><span class="p">];</span>		<span class="cm">/* 265 */</span>
</span></span><span class="line"><span class="cl">  <span class="kt">char</span> <span class="n">gname</span><span class="p">[</span><span class="mi">32</span><span class="p">];</span>		<span class="cm">/* 297 */</span>
</span></span><span class="line"><span class="cl">  <span class="kt">char</span> <span class="n">devmajor</span><span class="p">[</span><span class="mi">8</span><span class="p">];</span>		<span class="cm">/* 329 */</span>
</span></span><span class="line"><span class="cl">  <span class="kt">char</span> <span class="n">devminor</span><span class="p">[</span><span class="mi">8</span><span class="p">];</span>		<span class="cm">/* 337 */</span>
</span></span><span class="line"><span class="cl">  <span class="kt">char</span> <span class="n">prefix</span><span class="p">[</span><span class="mi">155</span><span class="p">];</span>		<span class="cm">/* 345 */</span>
</span></span><span class="line"><span class="cl">				<span class="cm">/* 500 */</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>上面 header 结构可以看出默认情况下文件名 <code>name</code> 长度不能超过 99 (最后一位要存储 <code>\0</code>)，但似乎后面 tar 协议支持了长文件名的情况，至于如何支持的各位感兴趣的可以自行去搜一下。</p>
<p>除了 <code>posix_header</code> 之外，还有 <code>star_header</code>、<code>gnu_header</code> 等 header 结构，header 结构体占据的空间小于 512 字节，而 tar 的每个 block 都是 512 字节，所以一个 header block 占据 512 字节，末尾空余的字节填写 <code>\0</code>，文件也以 512 字节为单位写在 header block 后面，多出来的空间填写 <code>\0</code>：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cm">/* tar files are made in basic blocks of this size.  */</span>
</span></span><span class="line"><span class="cl"><span class="cp">#define BLOCKSIZE 512
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">union</span> <span class="n">block</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kt">char</span> <span class="n">buffer</span><span class="p">[</span><span class="n">BLOCKSIZE</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="k">struct</span> <span class="n">posix_header</span> <span class="n">header</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">struct</span> <span class="n">star_header</span> <span class="n">star_header</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">struct</span> <span class="n">oldgnu_header</span> <span class="n">oldgnu_header</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">struct</span> <span class="n">sparse_header</span> <span class="n">sparse_header</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">struct</span> <span class="n">star_in_header</span> <span class="n">star_in_header</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">struct</span> <span class="n">star_ext_header</span> <span class="n">star_ext_header</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>tar 文件的 end 是由至少两个 block size （1024字节）的空白（<code>\0</code>）组成，但是 GNU tar 创建出来的 tar 包的 end 长度可能大于两个 block size，因为似乎它创建的 tar 包的文件体积以 10K 为单位进行了对齐，可以用下面的方式验证一下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> <span class="nb">echo</span> <span class="s2">&#34;hello world&#34;</span> &gt; 1.txt
</span></span><span class="line"><span class="cl"><span class="gp">$</span> tar -cv 1.txt -f test.tar
</span></span><span class="line"><span class="cl"><span class="go">1.txt
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> ls -al test.tar
</span></span><span class="line"><span class="cl"><span class="go">-rw-r--r-- 1 starry-s starry-s 10K Nov 12 11:42 test.tar
</span></span></span></code></pre></div><p>用 <code>hexdump</code> 可以看一下创建的 tar 包中包含的数据：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> hexdump -C ./test.tar
</span></span><span class="line"><span class="cl"><span class="go">0000000   1   .   t   x   t  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
</span></span></span><span class="line"><span class="cl"><span class="go">0000010  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
</span></span></span><span class="line"><span class="cl"><span class="go">*
</span></span></span><span class="line"><span class="cl"><span class="go">0000060  \0  \0  \0  \0   0   0   0   0   6   4   4  \0   0   0   0   1
</span></span></span><span class="line"><span class="cl"><span class="go">0000070   7   5   0  \0   0   0   0   1   7   5   0  \0   0   0   0   0
</span></span></span><span class="line"><span class="cl"><span class="go">0000080   0   0   0   0   0   1   4  \0   1   4   5   2   4   0   4   5
</span></span></span><span class="line"><span class="cl"><span class="go">0000090   3   5   4  \0   0   1   2   1   1   2  \0       0  \0  \0  \0
</span></span></span><span class="line"><span class="cl"><span class="go">00000a0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
</span></span></span><span class="line"><span class="cl"><span class="go">*
</span></span></span><span class="line"><span class="cl"><span class="go">0000100  \0   u   s   t   a   r          \0   s   t   a   r   r   y   -
</span></span></span><span class="line"><span class="cl"><span class="go">0000110   s  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
</span></span></span><span class="line"><span class="cl"><span class="go">0000120  \0  \0  \0  \0  \0  \0  \0  \0  \0   s   t   a   r   r   y   -
</span></span></span><span class="line"><span class="cl"><span class="go">0000130   s  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
</span></span></span><span class="line"><span class="cl"><span class="go">0000140  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
</span></span></span><span class="line"><span class="cl"><span class="go">*
</span></span></span><span class="line"><span class="cl"><span class="go">0000200   h   e   l   l   o       w   o   r   l   d  \n  \0  \0  \0  \0
</span></span></span><span class="line"><span class="cl"><span class="go">0000210  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
</span></span></span><span class="line"><span class="cl"><span class="go">*
</span></span></span><span class="line"><span class="cl"><span class="go">0002800
</span></span></span></code></pre></div><p>其中前 <code>0x200</code> 长度 (512 bytes) 存储的是 header，<code>0x200</code> ~ <code>0x2800</code> (10240 bytes) 区域存储了文件的数据 (<code>hello world\n</code>)，文件数据后面均为空白 <code>\0</code>，整体的 tar 包文件大小对齐到了 10K。</p>
<h3 id="栗子">栗子</h3>
<p>因此这里可以用上面的 <code>posix_header</code> 结构体简单的写一个创建 tar 归档的程序。</p>
<p>因为 header 中还包含了简易的计算 header 校验和的步骤，所以代码比较长，可以在 <a href="https://github.com/STARRY-S/tar-example-c">这里</a> 找到。</p>
<h3 id="压缩">压缩</h3>
<p>在创建 tar 格式的文件时是不支持压缩的，文件的数据直接写在了 header 后面（除非你想魔改创建 tar 格式的步骤，但没这个必要）。如果需要压缩的话是把整个 tar 归档用 gzip/bzip2/zstd 等其他压缩格式进行压缩，文件后缀为 <code>tar.gz/tar.bz2/tar.zstd</code> 等。因为是先将文件写入 tar 归档，再将 tar 归档进行压缩，所以压缩的效果会比把文件单独压缩再合并成一个 tar 包效果要好一些。</p>
<h3 id="特点">特点</h3>
<p>从上面的 tar 归档文件格式可以看出，tar 包中的文件是一个一个顺序排列起来的，因此 tar 包中是允许两个相同名称的文件存在的。</p>
<p>如果想向 tar 包末尾附加新的文件的话也很简单，只需要找到末尾的 end block，将其覆盖重写新的文件的 header，之后再写入新文件的数据即可，因此向未压缩的 tar 包附加新的文件（甚至是覆盖掉末尾的一些文件）都是可行的。但是如果想向已压缩的 tar 包（例如 <code>tar.gz</code>）附加文件就不太可行了，除非先把 <code>tar.gz</code> 解压为 <code>tar</code> 格式，附加新的文件后再重新压缩成 <code>tar.gz</code>，但这样如果 tar 文件体积很大的话会造成额外的磁盘空间浪费和性能、时间的浪费。</p>
<p>还有一点是 tar 中存储的文件是顺序排列起来的，但他没有一个 index 索引记录了每个文件的 header 所处的 offset。所以如果想知道一个 tar 包里面存了哪些文件的话，要从头到尾的遍历一遍 tar 包，因此如果这个 tar 包文件体积很大且包含很多零散的小文件的话，每次都要遍历读取 tar 包中的所有 header，会很麻烦。</p>
<p>因此 tar 包不适合随机读取，未压缩的 tar 包还好，只要在首次打开文件时遍历一下把每个文件的 header 和 offset 记录下来就行，但如果是压缩过的例如 <code>tar.gz</code> 格式的压缩包，几乎就没办法随机读取（除非你得再去折腾 <code>gzip</code> 数据流，但几乎没人去这么做），如果想随机解压 <code>tar.gz</code> 中的某个文件，要从头开始先解压 <code>gzip</code> 数据流，从解压的数据流中遍历每个 tar header，在查找到待解压的文件 header 后再将其解压存储下来，麻烦得很！</p>
<p>所以 <code>tar</code> 以及 <code>tar.gz</code> 等压缩的 <code>tar</code> 归档格式通常适合用在不需要随机读取，不需要向归档末尾附加文件的场景。</p>
<h2 id="zip-文件格式">zip 文件格式</h2>
<p>zip 压缩包中文件的布局也是依次顺序排列的，文件的 data 可以是未压缩的文件原始数据 (<code>Store</code>)，或者是使用 Deflate 算法压缩后的文件数据。
在 zip 文件末尾还有一块区域，记录了每个文件 header 的索引信息，叫做 <code>directory</code> (<code>central directory record</code>)，<code>directory</code> 后面还有一小块区域用来记录 <code>directory</code> 的长度和数量等信息 (<code>end of central directory record</code>)。</p>
<p>zip 压缩包中文件的数据布局简单描述一下是这个样子：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">+---------------+
</span></span><span class="line"><span class="cl">| header        |
</span></span><span class="line"><span class="cl">+---------------+
</span></span><span class="line"><span class="cl">| data          |
</span></span><span class="line"><span class="cl">+---------------+
</span></span><span class="line"><span class="cl">| header        |
</span></span><span class="line"><span class="cl">+---------------+
</span></span><span class="line"><span class="cl">| data          |
</span></span><span class="line"><span class="cl">+---------------+
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">+---------------+
</span></span><span class="line"><span class="cl">| header        |
</span></span><span class="line"><span class="cl">+---------------+
</span></span><span class="line"><span class="cl">| data          |
</span></span><span class="line"><span class="cl">+---------------+
</span></span><span class="line"><span class="cl">| directory     |
</span></span><span class="line"><span class="cl">+---------------+
</span></span><span class="line"><span class="cl">| directory     |
</span></span><span class="line"><span class="cl">+---------------+
</span></span><span class="line"><span class="cl">....
</span></span><span class="line"><span class="cl">+---------------+
</span></span><span class="line"><span class="cl">| directory     |
</span></span><span class="line"><span class="cl">+---------------+
</span></span><span class="line"><span class="cl">| directory end |
</span></span><span class="line"><span class="cl">+---------------+
</span></span></code></pre></div><p>详细的 <code>zip</code> 文件格式定义在 <a href="https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT">这里</a>，因为 <code>zip</code> 包中，每个文件的 header 长度是不固定，而且还分为早期的 <code>zip</code> 和后续新增的 <code>zip64</code> 两种格式，手搓代码还蛮复杂的，图省事咱就不写样例代码了。</p>
<h3 id="特点-1">特点</h3>
<p>所以 <code>zip</code> 格式支持随机读取，如果想知道 <code>zip</code> 中存储了多少文件的话，只需要读取文件末尾的 <code>directory</code>。和 <code>tar</code> 一样 <code>zip</code> 也允许存在多个重名的文件。
而 <code>zip</code> 中存储的文件如果压缩的话，是每个文件单独压缩再写入到 <code>zip</code> 归档的，所以压缩的效果会较 <code>tar</code> 把所有文件都打包到一起再压缩要差些。</p>
<p>如果想向已有的 <code>zip</code> 压缩包中增加新的文件，需要将新文件 header 和数据从 <code>directory</code> 处覆盖掉，最后重新在文件末尾写入新的 <code>directory record</code>。</p>
<p>所以 zip 看起来比 tar 格式要更灵活一些，支持随机读取，同时还支持在不解压整个压缩包的情况下，增加新的文件。</p>
<p>zip 支持 Deflate 压缩算法或 Store 不压缩仅存储文件原始数据这两种方式。Deflate 压缩算法与 <code>gzip</code> 使用的 Deflate 压缩算法是一个东西。区别就是 zip 是把文件单独用 Deflate 算法压缩，存储起来，而 <code>tar.gz</code> 是将所有文件先打包到一起，再用 Deflate 算法压缩。</p>
<h2 id="背景">背景</h2>
<p>上面说了这么多，zip 和 tar 的区别读着应该都已经清楚了。下面咱讲一下为什么要调查这个问题，写这篇博客，不感兴趣的话可以浏览器右上角关掉这个页面节省时间。</p>
<p>起初是咱写了一个将容器镜像的 Blobs 文件导入/导出成一个压缩包的工具（类似 <code>docker save/load</code>，但是要支持多架构和多平台一起导出）（关于这个工具等咱逐渐完善后有时间的话打算单独再写一篇博客），一开始用的是 <code>tar.gz</code> 格式压缩。导出的逻辑是先把容器镜像的 Blobs (Layers, Manifest 和 Config) 文件先全部下载到本地，之后把这这些巨大的文件打包成一个 <code>tar.gz</code> 压缩包。逻辑上没什么问题，但是容器镜像普遍体积不小，尤其是要导出上百个镜像时，最后创建的 tar 包体积要几十个 GB。所以先把 Blobs 文件下载到本地占用了一次磁盘空间，再把本地未打包的 Blobs 文件打包成一个 tar 包又占用了一次空间。最后搞得磁盘被占据了双倍的空间。</p>
<p>这还不算什么，如果在导出容器镜像时有时会遇到网络问题或其他因素导致某些镜像 Blobs 导出失败，这样导出生成的压缩包是一个不完整的 <code>tar.gz</code> 包，而咱想向已有的 <code>tar.gz</code> 包附加新的镜像 Blobs 文件的话就得把原有的压缩包解压，写入新的文件，再重新打包，体验极其不友好，咱自己用还行，但要是想把工具拿给别人用的话，光是给别人讲背后的逻辑就得磨叽半天，而且本来一个命令就能解决的问题却非要拆成先解压、再追加额外的 Blobs 文件、最后重新压缩这好几个步骤，而且很多时候因为镜像体积太大了解压和压缩很耗时，还会浪费巨多的磁盘空间，很多时候用户根本不知道要给磁盘预留这么大的空间而导致解压到一半失败了。</p>
<p><img loading="lazy" src="images/dev-user.gif" alt="" />

</p>
<p>所以为了解决这个问题，咱想办法在导出镜像时，采用实时写入的方式，在镜像的 Blobs 文件下载到本地后直接写入到压缩包文件中，而不是先把所有镜像的 Blobs 文件下载下来，再把下载的缓存文件夹打一个压缩包。这样导出镜像时消耗双倍磁盘空间的问题倒是解决了，而且还可以用多线程提个速。
但正如上面说的那样，<code>tar.gz</code> 格式的压缩包在创建完成后就没办法增加新的文件了，这期间咱想过要不换成不压缩的 <code>tar</code> 格式而不是 <code>tar.gz</code> 格式，因为大多数镜像的 Layer 文件本身是已经有 <code>gzip</code> 压缩的了，没必要二次压缩，但是镜像的 Config 和 Manifest 通常是未压缩的文本文件，会有一点额外体积开销。
这种方法看似可行，但因为 <code>tar</code> 他缺少文件索引，所以如果我想按照一份镜像列表按顺序依次将 <code>tar</code> 中存储的 Blobs 文件导入到镜像仓库中，就得遍历构建一遍 <code>tar</code> 包中的所有文件 header，程序中自行存一份索引，还是有点麻烦。</p>
<p>所以最后在 Google 上搜有没有带索引、可以随机读取还支持附加文件的压缩归档文件格式时，重新熟悉了一下 <code>zip</code> 的结构和特点。</p>
<p>因为咱的程序是用 Go 写的，Go 官方标准库提供了 <code>archive/tar</code> 和 <code>archive/zip</code>，用来创建/读取 tar 和 zip 归档。但是 Go 标准库不支持向 tar 包和 zip 包中附加额外的文件，tar 附加额外文件的方式蛮简单的所以不需要在修改标准库的基础上就能实现追加文件（只需要移除文件末尾的 end blocks）。但是 zip 想追加文件的话，就得先读取文件末尾存储的 <code>directory</code> 索引记录存储起来，附加完文件后再重新在文件末尾写入新的 <code>directory</code> 索引。</p>
<p>几年前有人向 Go 提过这个 <a href="https://github.com/golang/go/issues/15626">Issue</a>，希望标准库能实现 zip append 文件的功能。
因为 Go 的 <code>zip.Reader</code> 是使用了 Go 的 <code>io.ReaderAt</code> 接口实现的，<code>zip.Writer</code> 是用 <code>io.Writer</code> 实现的。</p>
<p>Go 标准库中提供的 <code>io.ReaderAt</code> 和 <code>io.WriterAt</code> 接口可以看作是参考了 POSIX 协议的 C 接口 <a href="https://man7.org/linux/man-pages/man2/pread.2.html">pread/pwrite</a>（Go 的 Interface 和这个系统调用的 Interface 不是一个东西），<code>pread</code> 可以读取文件中指定 offset 和长度的数据，并不改变文件自身的 seek offset。因为读取 zip 文件时要先读取文件末尾的 <code>directory</code>，所以用 <code>io.ReaderAt</code> 接口实现很合理。而创建 zip 文件时，要按顺序写入文件 header 和 data，最后在文件末尾写入 directory 信息，所以用 <code>io.Writer</code> 也很合理。</p>
<p>但是如果想向 zip 附加文件的话，就得先用一个类似 <code>io.ReaderAt</code> 接口读取文件末尾已有的 <code>directiory</code> 记录，之后用类似 <code>io.WriterAt</code> 接口向文件末尾的位置写数据。而偏偏 Go 标准库没有 <code>io.ReadWriterAt</code> 这样的接口（就是把 <code>io.ReaderAt</code> 和 <code>io.WriterAt</code> 结合一起），所以最终这个 Issue 因为需要涉及到 Go 其他 <code>io</code> 接口的改动而无法实现关闭掉了。这里额外补充一下，Go 的 zip 标准库是用来对数据流进行操作的，而并非单纯的 zip 文件，所以只要实现了 <code>io.ReaderAt</code> 接口的“对象”都可以被 zip 库“解压”，所有实现了 <code>io.Writer</code> 的“对象”都可以写入 zip 数据。</p>
<p>所以最后没办法，为了能够让咱写的工具支持在不解压 zip 文件的前提下增添新的文件的功能，只能自行造轮子，在 Go <code>archive/zip</code> 标准库的基础上增加了一个 <code>zip.Updater</code>。因为 Go 他确实没有 <code>io.ReadWriterAt</code> 这样的接口，但是 Go 他有 <code>io.ReadWriteSeeker</code> 这个接口，所以在不涉及到多线程竞争访问（或者加锁）的情况下，可以用这个接口实现 <code>zip.Updater</code>，向 zip 包附加额外文件的功能。</p>
<p>在搞这些东西的时候刚好赶上公司的 HackWeek，本来咱已经创建了一个 HackWeek Project，就是上面咱说的容器镜像导入/导出工具的开发这些事情。所以咱在这个基础上又创建了一个新的 HackWeek Project，就是在 Go <code>archive/zip</code> 标准库基础上新增 <code>zip.Updater</code> 相关功能，链接我扔在 <a href="https://hackweek.opensuse.org/23/projects/go-zip-updator-appending-new-files-to-zip-archive-without-decompressing-the-whole-file">这里</a>，感兴趣的话可以去瞅瞅。</p>
<p>最终咱实现了 <code>Updater</code> 的代码仓库在 <a href="https://github.com/STARRY-S/zip">这里</a>，感兴趣的可以去看看，点个 star 什么的。因为基于 <code>io.ReadWriteSeeker</code> 实现的 <code>zip.Updater</code> 并不是最优解，最正确的方式是 Go 什么时候出一个类似 <code>io.ReadWriterAt</code> 接口，在不用改变 <code>Seek</code> 的前提下就能读取/写入指定 offset 的数据，加上自认为咱的程序设计水平还赶不上 Go 维护者，所以咱想了一下就还是先不提 PR 给 Go 源码仓库了。</p>]]></content:encoded>
    </item>
    <item>
      <title>修复缩小磁盘空间后受损的 GPT 分区表</title>
      <link>https://blog.starry-s.moe/posts/2023/fix-shrunken-disk-broken-partition-table/</link>
      <pubDate>Wed, 18 Oct 2023 21:42:02 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2023/fix-shrunken-disk-broken-partition-table/</guid>
      <description>&lt;p&gt;磁盘的扩容和分区的扩/缩容场景很常见，比如分区用着用着快满了，而磁盘有空闲的未分区空间，而这块区域恰好在这块分区的后面，这时可以对分区扩容。
在使用虚拟机（例如 QEMU）时，假设起初虚拟机的磁盘只创建了 20G，但用久了会存在不够用的情况，这时可以对磁盘扩容，之后再调整分区的大小。&lt;/p&gt;
&lt;p&gt;但是最近遇到了一个需要对磁盘缩容的场景，例如把一个 64G 的 U 盘用 &lt;code&gt;dd&lt;/code&gt; 将整个磁盘的数据写入到一个 8G 的 U盘中（当然这个 64G U盘实际使用的分区大小不能大于前 8G）。
或者要把一个原本 50G 的 qcow2 虚拟磁盘缩小成 10G，用来制作别的镜像什么的。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>磁盘的扩容和分区的扩/缩容场景很常见，比如分区用着用着快满了，而磁盘有空闲的未分区空间，而这块区域恰好在这块分区的后面，这时可以对分区扩容。
在使用虚拟机（例如 QEMU）时，假设起初虚拟机的磁盘只创建了 20G，但用久了会存在不够用的情况，这时可以对磁盘扩容，之后再调整分区的大小。</p>
<p>但是最近遇到了一个需要对磁盘缩容的场景，例如把一个 64G 的 U 盘用 <code>dd</code> 将整个磁盘的数据写入到一个 8G 的 U盘中（当然这个 64G U盘实际使用的分区大小不能大于前 8G）。
或者要把一个原本 50G 的 qcow2 虚拟磁盘缩小成 10G，用来制作别的镜像什么的。</p>
<meting-js server="netease" type="song" id="1348679974" theme="#233333"></meting-js>
<p>按照正常的思路，缩小磁盘空间之前要先缩小分区（还要缩小文件系统），确保分区都位于磁盘的前面，这样磁盘在截断后文件系统不会受损，大致步骤可以分为：</p>
<ol>
<li>
<p>缩小文件系统：例如使用 <code>btrfs filesystem resize</code> 缩小 BTRFS 文件系统至待缩小的分区的大小，确保文件系统的大小不超过分区大小。</p>
</li>
<li>
<p>缩小分区：可以使用 <code>fdisk</code> 先删掉待缩小的分区，之后再重新创建新的分区，重建分区时设定新的分区的大小，且不要删除已有的 <code>btrfs</code> 或其他文件系统签名。这样在缩小分区的同时，不需要重新格式化，因此分区中的文件没有丢失。</p>
<p>（除了 <code>fdisk</code>，还可以用 <code>sfdisk</code>，<code>gdisk</code> 或 <code>parted</code> 等工具调整分区）</p>
</li>
<li>
<p>确保分区都位于磁盘的起始位置后，执行磁盘缩小的操作，将磁盘末端未使用的数据截断。</p>
</li>
</ol>
<p>GPT / MBR 分区表咱凭感觉来猜的话，是存储在磁盘的起始位置的，所以如果磁盘缩小时，将磁盘末端一些数据截断正常情况下应该是不会影响到存储在起始位置的分区表的。</p>
<p>但是，在一些情况下，会出现缩小完磁盘空间后分区表受损的情况。</p>
<h2 id="举个栗子">举个栗子</h2>
<ol>
<li>
<p>首先使用 <code>qemu-img create</code> 创建一块 10G 的 QEMU 虚拟机磁盘。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> qemu-img create -f qcow2 disk1.qcow2 10G
</span></span><span class="line"><span class="cl"><span class="go">Formatting &#39;disk1.qcow2&#39;, fmt=qcow2 cluster_size=65536 extended_l2=off compression_type=zlib size=10737418240 lazy_refcounts=off refcount_bits=16
</span></span></span></code></pre></div></li>
<li>
<p>使用 <code>qemu-nbd</code> 工具将 qcow2 磁盘镜像与 Linux 内核通过 <a href="https://www.kernel.org/doc/Documentation/blockdev/nbd.txt">nbd</a> 连接，这样可以在不启动 QEMU 虚拟机的情况下直接对 qcow2 磁盘分区进行操作。</p>
<p>加载 <code>nbd</code> 内核模块，其中 <code>max_part</code> 参数是磁盘允许的最大分区数，默认为 0 所以这里需要把数值改大一点。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo modprobe nbd <span class="nv">max_part</span><span class="o">=</span><span class="m">8</span>
</span></span></code></pre></div><p>将创建的 <code>disk1.qcow2</code> 镜像与 <code>/dev/nbd0</code> 连接。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo qemu-nbd -c /dev/nbd0 ./disk1.qcow2
</span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> lsblk /dev/nbd0
</span></span><span class="line"><span class="cl"><span class="go">NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
</span></span></span><span class="line"><span class="cl"><span class="go">nbd0  43:0    0  10G  0 disk
</span></span></span></code></pre></div><p>使用 <code>fdisk</code> 初始化 GPT 分区表，并随便新建几个分区。</p>
<blockquote>
<p>咱都是 Arch Linux 用户了，fdisk 就不用我再详细说了吧。</p>
</blockquote>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo fdisk /dev/nbd0
</span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Welcome to fdisk (util-linux 2.39.2).
</span></span></span><span class="line"><span class="cl"><span class="go">Changes will remain in memory only, until you decide to write them.
</span></span></span><span class="line"><span class="cl"><span class="go">Be careful before using the write command.
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Device does not contain a recognized partition table.
</span></span></span><span class="line"><span class="cl"><span class="go">Created a new DOS (MBR) disklabel with disk identifier 0xf5c43a4b.
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Command (m for help): g
</span></span></span><span class="line"><span class="cl"><span class="go">Created a new GPT disklabel (GUID: 2EB767AB-0958-461B-B56D-697B3305AC83).
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Command (m for help): n
</span></span></span><span class="line"><span class="cl"><span class="go">Partition number (1-128, default 1):
</span></span></span><span class="line"><span class="cl"><span class="go">First sector (2048-20971486, default 2048):
</span></span></span><span class="line"><span class="cl"><span class="go">Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-20971486, default 20969471): +512M
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Created a new partition 1 of type &#39;Linux filesystem&#39; and of size 512 MiB.
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Command (m for help): t
</span></span></span><span class="line"><span class="cl"><span class="go">Selected partition 1
</span></span></span><span class="line"><span class="cl"><span class="go">Partition type or alias (type L to list all): 1
</span></span></span><span class="line"><span class="cl"><span class="go">Changed type of partition &#39;Linux filesystem&#39; to &#39;EFI System&#39;.
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Command (m for help): n
</span></span></span><span class="line"><span class="cl"><span class="go">Partition number (2-128, default 2):
</span></span></span><span class="line"><span class="cl"><span class="go">First sector (1050624-20971486, default 1050624):
</span></span></span><span class="line"><span class="cl"><span class="go">Last sector, +/-sectors or +/-size{K,M,G,T,P} (1050624-20971486, default 20969471):
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Created a new partition 2 of type &#39;Linux filesystem&#39; and of size 9.5 GiB.
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Command (m for help): w
</span></span></span><span class="line"><span class="cl"><span class="go">The partition table has been altered.
</span></span></span><span class="line"><span class="cl"><span class="go">Calling ioctl() to re-read partition table.
</span></span></span><span class="line"><span class="cl"><span class="go">Syncing disks.
</span></span></span></code></pre></div><p>本栗中，磁盘新建了两个分区，<code>/dev/nbd0p1</code> 是 512M 大小的 EFI 分区，剩余空间 <code>/dev/nbd0p2</code> 是 root 分区。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo fdisk -l /dev/nbd0
</span></span><span class="line"><span class="cl"><span class="go">Disk /dev/nbd0: 10 GiB, 10737418240 bytes, 20971520 sectors
</span></span></span><span class="line"><span class="cl"><span class="go">Units: sectors of 1 * 512 = 512 bytes
</span></span></span><span class="line"><span class="cl"><span class="go">Sector size (logical/physical): 512 bytes / 512 bytes
</span></span></span><span class="line"><span class="cl"><span class="go">I/O size (minimum/optimal): 512 bytes / 512 bytes
</span></span></span><span class="line"><span class="cl"><span class="go">Disklabel type: gpt
</span></span></span><span class="line"><span class="cl"><span class="go">Disk identifier: 2EB767AB-0958-461B-B56D-697B3305AC83
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Device        Start      End  Sectors  Size Type
</span></span></span><span class="line"><span class="cl"><span class="go">/dev/nbd0p1    2048  1050623  1048576  512M EFI System
</span></span></span><span class="line"><span class="cl"><span class="go">/dev/nbd0p2 1050624 20969471 19918848  9.5G Linux filesystem
</span></span></span></code></pre></div><p>之后简单的格式化一下两个分区，挂载并往里面写一些文件进去。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo mkfs.vfat -F <span class="m">32</span> /dev/nbd0p1
</span></span><span class="line"><span class="cl"><span class="go">mkfs.fat 4.2 (2021-01-31)
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo mkfs.btrfs /dev/nbd0p2
</span></span><span class="line"><span class="cl"><span class="go">btrfs-progs v6.5.2
</span></span></span><span class="line"><span class="cl"><span class="go">See https://btrfs.readthedocs.io for more information.
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Performing full device TRIM /dev/nbd0p2 (9.50GiB) ...
</span></span></span><span class="line"><span class="cl"><span class="go">NOTE: several default settings have changed in version 5.15, please make sure
</span></span></span><span class="line"><span class="cl"><span class="go">    this does not affect your deployments:
</span></span></span><span class="line"><span class="cl"><span class="go">    - DUP for metadata (-m dup)
</span></span></span><span class="line"><span class="cl"><span class="go">    - enabled no-holes (-O no-holes)
</span></span></span><span class="line"><span class="cl"><span class="go">    - enabled free-space-tree (-R free-space-tree)
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Label:              (null)
</span></span></span><span class="line"><span class="cl"><span class="go">UUID:               a5fb30e5-eb5f-4a0b-8d2a-106e04e1488b
</span></span></span><span class="line"><span class="cl"><span class="go">Node size:          16384
</span></span></span><span class="line"><span class="cl"><span class="go">Sector size:        4096
</span></span></span><span class="line"><span class="cl"><span class="go">Filesystem size:    9.50GiB
</span></span></span><span class="line"><span class="cl"><span class="go">Block group profiles:
</span></span></span><span class="line"><span class="cl"><span class="go">Data:             single            8.00MiB
</span></span></span><span class="line"><span class="cl"><span class="go">Metadata:         DUP             256.00MiB
</span></span></span><span class="line"><span class="cl"><span class="go">System:           DUP               8.00MiB
</span></span></span><span class="line"><span class="cl"><span class="go">SSD detected:       yes
</span></span></span><span class="line"><span class="cl"><span class="go">Zoned device:       no
</span></span></span><span class="line"><span class="cl"><span class="go">Incompat features:  extref, skinny-metadata, no-holes, free-space-tree
</span></span></span><span class="line"><span class="cl"><span class="go">Runtime features:   free-space-tree
</span></span></span><span class="line"><span class="cl"><span class="go">Checksum:           crc32c
</span></span></span><span class="line"><span class="cl"><span class="go">Number of devices:  1
</span></span></span><span class="line"><span class="cl"><span class="go">Devices:
</span></span></span><span class="line"><span class="cl"><span class="go">   ID        SIZE  PATH
</span></span></span><span class="line"><span class="cl"><span class="go">    1     9.50GiB  /dev/nbd0p2
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> mkdir -p mnt
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo mount /dev/nbd0p2 mnt
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo mkdir ./mnt/<span class="o">{</span>boot,home<span class="o">}</span>
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo mount /dev/nbd0p1 mnt/boot
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo touch ./mnt/example.txt
</span></span></code></pre></div></li>
<li>
<p>使用 <code>btrfs filesystem resize</code> 缩小 root 分区中的 BTRFS 文件系统大小至 7G，之后使用 <code>fdisk</code> 缩小 root 分区大小至 7G。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo btrfs filesystem resize 7G mnt
</span></span><span class="line"><span class="cl"><span class="go">Resize device id 1 (/dev/nbd0p2) from 9.50GiB to 7.00GiB
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo sync
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo umount -R ./mnt
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo fdisk /dev/nbd0
</span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Welcome to fdisk (util-linux 2.39.2).
</span></span></span><span class="line"><span class="cl"><span class="go">Changes will remain in memory only, until you decide to write them.
</span></span></span><span class="line"><span class="cl"><span class="go">Be careful before using the write command.
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Command (m for help): d
</span></span></span><span class="line"><span class="cl"><span class="go">Partition number (1,2, default 2): 2
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Partition 2 has been deleted.
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Command (m for help): n
</span></span></span><span class="line"><span class="cl"><span class="go">Partition number (2-128, default 2):
</span></span></span><span class="line"><span class="cl"><span class="go">First sector (1050624-20971486, default 1050624):
</span></span></span><span class="line"><span class="cl"><span class="go">Last sector, +/-sectors or +/-size{K,M,G,T,P} (1050624-20971486, default 20969471): +7G
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Created a new partition 2 of type &#39;Linux filesystem&#39; and of size 7 GiB.
</span></span></span><span class="line"><span class="cl"><span class="go">Partition #2 contains a btrfs signature.
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Do you want to remove the signature? [Y]es/[N]o: N
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Command (m for help): w
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">The partition table has been altered.
</span></span></span><span class="line"><span class="cl"><span class="go">Calling ioctl() to re-read partition table.
</span></span></span><span class="line"><span class="cl"><span class="go">Syncing disks.
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo fdisk -l /dev/nbd0
</span></span><span class="line"><span class="cl"><span class="go">Disk /dev/nbd0: 10 GiB, 10737418240 bytes, 20971520 sectors
</span></span></span><span class="line"><span class="cl"><span class="go">Units: sectors of 1 * 512 = 512 bytes
</span></span></span><span class="line"><span class="cl"><span class="go">Sector size (logical/physical): 512 bytes / 512 bytes
</span></span></span><span class="line"><span class="cl"><span class="go">I/O size (minimum/optimal): 512 bytes / 512 bytes
</span></span></span><span class="line"><span class="cl"><span class="go">Disklabel type: gpt
</span></span></span><span class="line"><span class="cl"><span class="go">Disk identifier: 2EB767AB-0958-461B-B56D-697B3305AC83
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Device        Start      End  Sectors  Size Type
</span></span></span><span class="line"><span class="cl"><span class="go">/dev/nbd0p1    2048  1050623  1048576  512M EFI System
</span></span></span><span class="line"><span class="cl"><span class="go">/dev/nbd0p2 1050624 15730687 14680064    7G Linux filesystem
</span></span></span></code></pre></div><p>调整完分区大小后，因为这里没有移除 BTRFS 签名，所以分区的文件没有被删除，执行 <code>lsblk -no NAME,UUID /dev/nbd0</code> 可以看到 <code>/dev/nbd0p2</code> 的 UUID 也没有变化，和上面执行 <code>mkfs.btrfs</code> 时输出的一致。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> lsblk -no NAME,UUID /dev/nbd0
</span></span><span class="line"><span class="cl"><span class="go">nbd0
</span></span></span><span class="line"><span class="cl"><span class="go">├─nbd0p1 C6B7-EF70
</span></span></span><span class="line"><span class="cl"><span class="go">└─nbd0p2 a5fb30e5-eb5f-4a0b-8d2a-106e04e1488b
</span></span></span></code></pre></div></li>
<li>
<p>断开 NBD 连接，缩小 qcow2 磁盘大小到 8G。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo qemu-nbd -d /dev/nbd0
</span></span><span class="line"><span class="cl"><span class="go">/dev/nbd0 disconnected
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> qemu-img resize ./disk1.qcow2 --shrink 8G
</span></span><span class="line"><span class="cl"><span class="go">Image resized.
</span></span></span></code></pre></div></li>
<li>
<p>重新将 qcow2 磁盘连接到 <code>/dev/nbd0</code>，会发现上面创建的磁盘中的几块分区不见了！</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo qemu-nbd -c /dev/nbd0 ./disk1.qcow2
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo fdisk -l /dev/nbd0
</span></span><span class="line"><span class="cl"><span class="go">GPT PMBR size mismatch (20971519 != 16777215) will be corrected by write.
</span></span></span><span class="line"><span class="cl"><span class="go">Disk /dev/nbd0: 8 GiB, 8589934592 bytes, 16777216 sectors
</span></span></span><span class="line"><span class="cl"><span class="go">Units: sectors of 1 * 512 = 512 bytes
</span></span></span><span class="line"><span class="cl"><span class="go">Sector size (logical/physical): 512 bytes / 512 bytes
</span></span></span><span class="line"><span class="cl"><span class="go">I/O size (minimum/optimal): 512 bytes / 512 bytes
</span></span></span><span class="line"><span class="cl"><span class="go">Disklabel type: dos
</span></span></span><span class="line"><span class="cl"><span class="go">Disk identifier: 0x00000000
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Device      Boot Start      End  Sectors Size Id Type
</span></span></span><span class="line"><span class="cl"><span class="go">/dev/nbd0p1          1 16777215 16777215   8G ee GPT
</span></span></span></code></pre></div><p><code>fdisk</code> 输出中包含一条错误提示：<code>GPT PMBR size mismatch (20971519 != 16777215) will be corrected by write.</code>，大致意思是 GPT 分区表中记录的区块数量 (sectors) 和磁盘实际的区块数不一致。</p>
</li>
</ol>
<h2 id="修复受损的分区表">修复受损的分区表</h2>
<p>所以修复上面栗子中受损的 GPT 分区表的办法是，重新建一个 GPT 分区表，并按照之前的分区位置，重建分区。</p>
<p>这里重建分区时要注意，需要输入精确的区块位置，而不是类似 <code>+50M</code> 这样模糊的值。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo fdisk /dev/nbd0
</span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Welcome to fdisk (util-linux 2.39.2).
</span></span></span><span class="line"><span class="cl"><span class="go">Changes will remain in memory only, until you decide to write them.
</span></span></span><span class="line"><span class="cl"><span class="go">Be careful before using the write command.
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">GPT PMBR size mismatch (20971519 != 16777215) will be corrected by write.
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Command (m for help): g
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Created a new GPT disklabel (GUID: 3C587DB1-5978-45D2-AB05-9135D273D06D).
</span></span></span><span class="line"><span class="cl"><span class="go">The device contains &#39;PMBR&#39; signature and it will be removed by a write command. See fdisk(8) man page and --wipe option for more details.
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Command (m for help): n
</span></span></span><span class="line"><span class="cl"><span class="go">Partition number (1-128, default 1):
</span></span></span><span class="line"><span class="cl"><span class="go">First sector (2048-16777182, default 2048):
</span></span></span><span class="line"><span class="cl"><span class="go">Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-16777182, default 16775167): 1050623
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Created a new partition 1 of type &#39;Linux filesystem&#39; and of size 512 MiB.
</span></span></span><span class="line"><span class="cl"><span class="go">Partition #1 contains a vfat signature.
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Do you want to remove the signature? [Y]es/[N]o: N
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Command (m for help): n
</span></span></span><span class="line"><span class="cl"><span class="go">Partition number (2-128, default 2):
</span></span></span><span class="line"><span class="cl"><span class="go">First sector (1050624-16777182, default 1050624):
</span></span></span><span class="line"><span class="cl"><span class="go">Last sector, +/-sectors or +/-size{K,M,G,T,P} (1050624-16777182, default 16775167): 15730687
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Created a new partition 2 of type &#39;Linux filesystem&#39; and of size 7 GiB.
</span></span></span><span class="line"><span class="cl"><span class="go">Partition #2 contains a btrfs signature.
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Do you want to remove the signature? [Y]es/[N]o: N
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Command (m for help): t
</span></span></span><span class="line"><span class="cl"><span class="go">Partition number (1,2, default 2): 1
</span></span></span><span class="line"><span class="cl"><span class="go">Partition type or alias (type L to list all): 1
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Changed type of partition &#39;Linux filesystem&#39; to &#39;EFI System&#39;.
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Command (m for help): w
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">The partition table has been altered.
</span></span></span><span class="line"><span class="cl"><span class="go">Calling ioctl() to re-read partition table.
</span></span></span><span class="line"><span class="cl"><span class="go">Syncing disks.
</span></span></span></code></pre></div><p>重建分区表后，不出意外的话，重新挂载分区是能访问分区中的文件的，分区的 UUID 也没有发生改动。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> lsblk -no NAME,UUID /dev/nbd0
</span></span><span class="line"><span class="cl"><span class="go">nbd0
</span></span></span><span class="line"><span class="cl"><span class="go">├─nbd0p1 C6B7-EF70
</span></span></span><span class="line"><span class="cl"><span class="go">└─nbd0p2 a5fb30e5-eb5f-4a0b-8d2a-106e04e1488b
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo -l fdisk /dev/nbd0
</span></span><span class="line"><span class="cl"><span class="go">Disk /dev/nbd0: 8 GiB, 8589934592 bytes, 16777216 sectors
</span></span></span><span class="line"><span class="cl"><span class="go">Units: sectors of 1 * 512 = 512 bytes
</span></span></span><span class="line"><span class="cl"><span class="go">Sector size (logical/physical): 512 bytes / 512 bytes
</span></span></span><span class="line"><span class="cl"><span class="go">I/O size (minimum/optimal): 512 bytes / 512 bytes
</span></span></span><span class="line"><span class="cl"><span class="go">Disklabel type: gpt
</span></span></span><span class="line"><span class="cl"><span class="go">Disk identifier: 3C587DB1-5978-45D2-AB05-9135D273D06D
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Device        Start      End  Sectors  Size Type
</span></span></span><span class="line"><span class="cl"><span class="go">/dev/nbd0p1    2048  1050623  1048576  512M EFI System
</span></span></span><span class="line"><span class="cl"><span class="go">/dev/nbd0p2 1050624 15730687 14680064    7G Linux filesystem
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo mount /dev/nbd0p2 mnt
</span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo mount /dev/nbd0p1 mnt/boot
</span></span><span class="line"><span class="cl"><span class="gp">$</span> ls -alh mnt
</span></span><span class="line"><span class="cl"><span class="go">total 20K
</span></span></span><span class="line"><span class="cl"><span class="go">drwxr-xr-x 2 root     root     4.0K Jan  1  1970 boot
</span></span></span><span class="line"><span class="cl"><span class="go">-rw-r--r-- 1 root     root        0 Oct 18 22:36 example.txt
</span></span></span><span class="line"><span class="cl"><span class="go">drwxr-xr-x 1 root     root        0 Oct 18 22:34 home
</span></span></span></code></pre></div><h2 id="sfdisk-备份分区表">sfdisk 备份分区表</h2>
<p>如果觉得重建分区表时，分区的位置记不住的话（废话正常人谁能背下来这一串数字），<code>sfdisk</code> 的 <code>--dump</code> 参数可以备份分区表。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo sfdisk --dump /dev/nbd0
</span></span><span class="line"><span class="cl"><span class="go">label: gpt
</span></span></span><span class="line"><span class="cl"><span class="go">label-id: 3C587DB1-5978-45D2-AB05-9135D273D06D
</span></span></span><span class="line"><span class="cl"><span class="go">device: /dev/nbd0
</span></span></span><span class="line"><span class="cl"><span class="go">unit: sectors
</span></span></span><span class="line"><span class="cl"><span class="go">first-lba: 2048
</span></span></span><span class="line"><span class="cl"><span class="go">last-lba: 16777182
</span></span></span><span class="line"><span class="cl"><span class="go">sector-size: 512
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">/dev/nbd0p1 : start=        2048, size=     1048576, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B, uuid=496140B3-C491-470B-98D7-BB95F55266A7
</span></span></span><span class="line"><span class="cl"><span class="go">/dev/nbd0p2 : start=     1050624, size=    14680064, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=F918E93D-FB1D-4652-9657-CE24A29ADEA5
</span></span></span></code></pre></div><p>在执行磁盘缩小操作之前，可以先使用 <code>sfdisk</code> 导出分区表，缩小磁盘后再恢复。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="go">Backup partition table
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo sfdisk --dump /dev/nbd0 &gt; nbd0.txt
</span></span><span class="line"><span class="cl"><span class="go">Remove the `last-lba` line
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> grep -v last-lba nbd0.txt &gt; partition-backup.txt
</span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">After shrinking the disk size...
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Restore the backup partition table
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> sudo sfdisk /dev/nbd0 &lt; partition-backup.txt
</span></span></code></pre></div>]]></content:encoded>
    </item>
    <item>
      <title>Kobo Libra 2 安装 KOReader</title>
      <link>https://blog.starry-s.moe/posts/2023/kobo-libra-2/</link>
      <pubDate>Tue, 05 Sep 2023 22:45:15 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2023/kobo-libra-2/</guid>
      <description>&lt;p&gt;今年的六月初的时候赶着 618 活动入手了 Kobo Libra 2 电纸书，距离上次博客更新刚好过去一个月，想于是想着把 Kobo Libra 2 安装 KOReader 踩坑的记录写在这里刚好可以水一篇博客。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>今年的六月初的时候赶着 618 活动入手了 Kobo Libra 2 电纸书，距离上次博客更新刚好过去一个月，想于是想着把 Kobo Libra 2 安装 KOReader 踩坑的记录写在这里刚好可以水一篇博客。</p>
<meting-js server="netease" type="song" id="1336969300" theme="#233333"></meting-js>
<blockquote>
<p>这首歌很魔性……</p>
</blockquote>
<p><img loading="lazy" src="images/001.jpg" alt="KOReader" />
<p style="margin-bottom: -0.8em;" class="image-title">KOReader</p>
</p>
<p>Kobo 安装 KOReader 的教程可以参照 <a href="https://github.com/koreader/koreader/wiki/Installation-on-Kobo-devices">KOReader Wiki</a>。</p>
<h2 id="important-notes">Important Notes</h2>
<p>首先把 Kobo 连接到电脑，用文本编辑器打开 <code>.kobo/Kobo/Kobo eReader.conf</code>，确保存在以下配置文件，禁止阅读器加载隐藏文件夹中的内容：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">[FeatureSettings]
</span></span><span class="line"><span class="cl">ExcludeSyncFolders=(\\.(?!kobo|adobe).+|([^.][^/]*/)+\\..+)
</span></span></code></pre></div><h2 id="manual-installation-methods">Manual Installation Methods</h2>
<p>按照手动安装 KOReader 的步骤，有两种安装方式可选择：</p>
<ol>
<li>
<p>第一种是在 <a href="https://github.com/NiLuJe/kfmon">KFMon</a> 的基础上安装 KOReader，需要先安装 KFMon，安装 KFMon 的教程以及文件的下载连接在<a href="http://www.mobileread.com/forums/showthread.php?t=274231">这里</a>。</p>
</li>
<li>
<p>除此之外另一种安装方法是基于 <a href="https://www.mobileread.com/forums/showthread.php?t=329525">NickelMenu</a> 的方式安装 KOReader。</p>
</li>
</ol>
<p>这里咱使用的是第二种基于 <a href="https://www.mobileread.com/forums/showthread.php?t=329525">NickelMenu</a> 的方式安装 KOReader。</p>
<ol>
<li>
<p>首先下载 NickelMenu 的 <a href="https://github.com/pgaskin/NickelMenu/releases/latest/download/KoboRoot.tgz">KoboRoot.tgz</a> 安装包，像升级系统那样把压缩包拖到 <code>.kobo</code> 文件夹内，弹出 USB 后 Kobo 会自动重新更新。安装完成后 Kobo 右下角菜单栏会多一个 <code>NickelMenu</code> 菜单。</p>
</li>
<li>
<p>在 <a href="https://build.koreader.rocks/download/">此处</a> 下载 KOReader 安装包，因为 Libra 2 等使用相似主板的电子书存在死机重启的不稳定 <a href="https://github.com/koreader/koreader/issues/9806">Bug</a> 建议先下载包含修复此问题的 <code>nightly build</code>（<a href="https://github.com/koreader/koreader/pull/10771#issuecomment-1662928674">参考 Issue</a>），文件名以 <code>koreader-kobo</code> 开头的为 Kobo 使用的安装包。</p>
</li>
<li>
<p>再次将 Kobo 连接到电脑，将 KOReader 安装包解压到 <code>.adds</code> 目录下。</p>
</li>
<li>
<p>新建 <code>.adds/nm/koreader</code> 文件，写入以下内容：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">menu_item:main:KOReader:cmd_spawn:quiet:exec /mnt/onboard/.adds/koreader/koreader.sh
</span></span></code></pre></div></li>
<li>
<p>弹出 USB，等待设备同步完数据后，就可以在 Kobo 的右下角菜单栏中启动 KOReader 了。</p>
</li>
</ol>
<h2 id="known-issues">Known Issues</h2>
<p>使用过程中只遇到一个 KOReader 的严重影响体验的问题，就是上面说的阅读过程中会经常死机重启，可以在 <a href="https://github.com/koreader/koreader/issues/9806">这篇 Issue</a> 跟踪进度，目前可以尝试使用 <a href="https://github.com/koreader/koreader/pull/10771">这个 Patch</a>，安装 <code>nightly build</code> 的安装包尝试解决。</p>
<h2 id="others">Others</h2>
<p>贴几张看电纸书的效果图供参考，Kobo 的显示效果比咱之前用过的 KPW4 好太多了。图书的话基本是从网络上找盗版资源了，KOReader 能够对 PDF 重排所以对 PDF 的支持效果也比 Kindle 好很多。</p>
<p><img loading="lazy" src="images/002.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/003.jpg" alt="" />

</p>]]></content:encoded>
    </item>
    <item>
      <title>七月末的短暂旅行小记</title>
      <link>https://blog.starry-s.moe/posts/2023/2023-07-20/</link>
      <pubDate>Fri, 04 Aug 2023 20:47:50 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2023/2023-07-20/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;找不到答案的时候就去看看这个世界。&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <content:encoded><![CDATA[<blockquote>
<p>找不到答案的时候就去看看这个世界。</p>
</blockquote>
<meting-js server="netease" type="song" id="1341556382" theme="#233333"></meting-js>
<h2 id="一些流水帐">一些流水帐</h2>
<p>容我先写一点流水帐…… 咱的习惯是因为这种旅行都十分的珍贵所以咱想把旅行的全部经过都记录到博客里，尽管纯文字给别人的观感不是很好但咱还是想把这些都写出来。</p>
<p>一开始是没打算去 BW 的，计划是只去一趟 BML 就行，所以一开始咱没抢 BW 门票。过几天后 B 站公开了 BML 的消息，然后得知了今年的 BW 和 BML 是赶在一起举办的。于是咱抱着试一试的心态去抢 BML 的门票，第一次大会员优先抢票咱没抢到，于是当天晚上试着从 380 到 1500 的票档依次反复尝试点击抢票后，最终抢到了 1200 的内场票（内心在滴血），本来以为抢不到的，结果出乎意料的被迫忍痛斥巨资去看 BML 内场。然后就是 BML 门票抢到了，因为今年的 BW 和 BML 是在同一时间段举行，得知 BW 还有最后一次抢票机会，不去白不去后，最终抢到了一张 23 号的 BW 门票。然后蹲了好几天的 12306 候补沈阳到上海的卧铺票，结果最终还是没能抢到去上海的卧铺。由于机票和高铁票价格差不多，但高铁要坐十多个小时，所以蹲了几天卖机票的平台后最终以比较便宜的价格买到了沈阳往返上海的半夜机票。</p>
<p>然后是 20 号晚上从沈阳桃仙机场坐飞机去上海，中间经停山东烟台蓬莱机场，被飞机空调冻了一路后在上海普通国际机场 T2 航站楼出发层的沙发睡了一晚（19 年的十一假期也是在这过夜的所以位置咱都记下来了）。21 号早晨七点被吵醒后买了一杯星巴克冰美然后一边回忆四年前去上海时的记忆一边坐地铁 2 号线（当时坐的是磁悬浮）。由于没抢到 21 和 22 号的 BW 门票所以上了 2 号线后咱也不知道该坐到哪里，最终是从 2 号线的始发站坐到了终点站，虽然没买到 BW 门票但是去了 BW 四叶草场馆外面走了两圈，最终实在进不去加上被热得受不了了，不得不从黄牛手里搞了一张邀请函才进去（别学，不要买黄牛手里的邀请函，原因后面会讲）。当天晚上快 5 点的时候上海西边下了大暴雨，由于觉得 5 点闭展的时候再去排队挤地铁一定会排很长时间，于是咱在不到 5 点的时候就早早的去地铁站门口排队了。当时尽管外面雨蛮大的但是场馆内还是很干，地铁口排队的地方用围挡围了十几个弯，然后排队的地方没有空调，在那里排了好长的队后差点没热昏过去。然后由于当天大暴雨出地铁站后打不到车，所以出地铁站后被迫淋着雨从地铁站骑共享单车到旅店，所以 21 号那天从浦东机场到国展中心四叶草再到后来回旅店一共走了 2.8W 步，晚上在手机看到了 BW 场馆部分露天过道被淹的消息，内心狂喜幸好我跑得早。</p>
<p>22 号因为前一天腿差点走断了，于是上午是在旅店休息，下午去了一些上海其他比较知名的二次元打卡点。首先去了南京东路地铁口旁边的什么大药丸商场的 Square Enix 咖啡厅，没买咖啡，单纯的去拿相机给咖啡店摆的周边拍照。看了 FF7 和尼尔的一堆手办和纪念品，之后坐地铁去上海的外文书店。当天外文书店顶楼日文部分的人蛮多的，基本是被二次元攻占了，在书店绕了好几圈后找到了 Fate 画册（还是中文版），于是毫不犹豫的把它买了下来，塞到背包里。由于是画册，这本书巨大而且特别厚+沉。背着巨沉的背包后接着出发去梅赛德斯奔驰文化中心，BML 结束后随着人流和 19 年时一样，徒步走到中华艺术宫的下一个地铁站坐地铁回去。于是 22 号走了 1.4W 步，赶在地铁末班车停运前回到了旅店。</p>
<p>23 号稍微睡了个懒觉后坐地铁去 BW，出地铁后绕了几个大弯排队后，在 BW 的检票炸鸡处被拦了下来，之后找现场的工作人员，在一顿十分尴尬的调查，被关了半个小时后终于把我放了进去（所以说不要买黄牛手里的邀请函！我再也不敢了55555……）进 BW 场馆时已经是十一点多了，由于在检票口被拦了半个小时，心情有点低落，所以一开始逛展时心情并不是很好。从 8 号厅挤到 4 号厅后找到了 21 号咱没找到的 Aniplex 展台，然后隐约的感觉有很多手办和展品 21 号那天没有摆出来。之后就是平复心情拿着相机挤在人堆里拍手办，然后去每个展台领无料（领了一堆袋子，尤其是英特尔的那个超大号麻袋）。晚上继续从 2 号线始发站坐到终点站去普通国际机场，中间在南京东路下车又去了一趟那个 Square Enix 咖啡厅，买了个 FF7 的音乐盒当纪念品。半夜飞回沈阳后，被沈阳的出租车司机唠了一路（还因为我住浑南吓唬我嫌我离得近想把我凌晨一点扔半路高架桥上）（实际是开玩笑但刚从南方回来还是不太适应这么爱唠嗑的司机）。23 号走了 1.7W 步。</p>
<p>之后的几天回老家去了趟亲戚的婚礼，然后还没来得及休息，就 27 号和朋友一起去了吉林长春。27 号下午去了长春的这有山商场和桂林路市场，晚上去了南湖公园的长春解放纪念碑喂蚊子，因为连续好几天睡眠不足，当天晚上是回到酒店洗漱后倒头就睡了。第二天和朋友一起去了长春的兽聚，路上坐了长春的轨道交通（本来以为是地铁，结果发现长春的轨道交通非常日系，但是属于轻轨的那种，车型和沈阳浑南的有鬼电车好像是一样的，但不同的是长春的轨道交通的地面段是被栅栏围起来的，有独立的路权，不用等红绿灯，也不会遇到轻轨被汽车撞出轨等侵线情况）（长春的轨道交通没有屏蔽门，因为按级别划分来说是属于轻轨……）。在兽聚当一个野生摄影师给许多兽兽拍了巨多的照片，录了一些视频，然后一天过后非常非常的累，晚上 11 点眼睛就睁不开了，在兽聚的床位倒头就睡，再睁眼时已经是早晨 7 点了。</p>
<p>29 号回沈阳，下午回公寓后用电脑把咱这几天拍的一大堆照片导到电脑里并做了备份并给这几天拍到的小动物加好友返图。晚上是瘫在床上一动不动了……</p>
<blockquote>
<p>没人疼就去漫展，逛完浑身疼。</p>
</blockquote>
<p>30 号一早起床出发去沈阳 SSCA 漫展，与今年前几次 SSCA 漫展不同，这次 SSCA 在 K11 的展厅举行（终于不是之前冬冷夏热的铁西区四维大仓库了），当天漫展人巨多（给 K11 一点小小的二刺猿震撼），咱依旧是在漫展当野生摄影师给别人拍照。之后晚上去的沈阳站坐高铁回沈阳南站。经过了一个多星期的特种兵旅行，孩子被彻底累瘫了，所以是休息了好几天才有力气去写博客，因为旅行的内容量实在太大了，一时半会写不完，所以这次的博客就先以流水帐的方式写了。</p>
<h2 id="一些照片">一些照片</h2>
<meting-js server="netease" type="song" id="2059134447" theme="#233333"></meting-js>
<h3 id="bilibili-world-2023">Bilibili World 2023</h3>
<p>首先是一些在 BW 拍到的 Coser 和手办（以及第一天的暴雨）。</p>
<p><img loading="lazy" src="images/IMG_3952.jpg" alt="" />

</p>
<p>上面是 D1 下午拍到的暴雨，其实 D1 还拍了一些其他的照片但懒得放在这里了，下面是 D3 拍的手办和 Coser。</p>
<p><img loading="lazy" src="images/IMG_4189.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/IMG_4192.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/IMG_4257.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/IMG_4280.jpg" alt="" />

</p>
<p>（然后咱就因为 Aniplex 展台这边站着的这只 bocchi 酱去看了《孤独摇滚！》……）</p>
<p><img loading="lazy" src="images/IMG_4266.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/IMG_4211.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/IMG_4297.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/IMG_4468.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/IMG_4474.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/IMG_4289.jpg" alt="" />

</p>
<h3 id="bilibili-macro-link-2023">Bilibili Macro Link 2023</h3>
<p>然后是在 BML 拍的一点照片，因为是在内场靠后排的位置，镜头一半都被前面的人挡住了，所以观感还不如看台效果好（下次坚决不买内场票了），大部分时间都在应援所以只拍了一点点照片。</p>
<p><img loading="lazy" src="images/IMG_4050.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/IMG_4040.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/IMG_4034.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/IMG_4041.jpg" alt="" />

</p>
<h3 id="square-enix-coffee">Square Enix Coffee</h3>
<p>在 SE 咖啡厅拍的周边什么的（官方的手办的脸属实是有点崩……幸好没买……）。</p>
<p><img loading="lazy" src="images/IMG_3926.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/IMG_3935.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/IMG_3939.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/IMG_4044.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/IMG_4046.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/IMG_4047.jpg" alt="" />

</p>
<p>（虽然看不懂，但是好活……</p>
<h3 id="长春疯狂一夜">长春·疯狂一夜</h3>
<p>在长春的小聚上拍到的一些大福瑞们，这大热天的在户外穿貂真挺不容易的，当天有见到（并 rua 到）了很多大佬，十分开心。</p>
<p><img loading="lazy" src="images/IMG_4639.JPG" alt="" />

</p>
<p><img loading="lazy" src="images/IMG_4613.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/IMG_4636.JPG" alt="" />

</p>
<p><img loading="lazy" src="images/IMG_4637.JPG" alt="" />

</p>
<p><img loading="lazy" src="images/IMG_4638.JPG" alt="" />

</p>
<p><img loading="lazy" src="images/IMG_4853.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/IMG_4887.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/IMG_4976.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/o0aAAxuCOIA4hApCerj2HbA5xgDBeCnDkEY9A8~tplv-dy-aweme-images_q75.webp" alt="" />

</p>
<h3 id="沈阳-ssca-9th-day3">沈阳 SSCA 9th Day3</h3>
<p><img loading="lazy" src="images/IMG_4455.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/IMG_5562.jpg" alt="" />

</p>
<h3 id="其他">其他</h3>
<p>因为在外文书店只拍了一张照片（书店有什么好拍的……），所以放在这里了。</p>
<p><img loading="lazy" src="images/IMG_4051.jpg" alt="&ldquo;外文书店&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">外文书店</p>
</p>
<p>买的 Fate 画册还送了几张明信片，回家后拆开一看感觉光是这几张明信片就已经值画册的售价了……</p>
<p><img loading="lazy" src="images/IMG_4214.jpg" alt="" />

</p>]]></content:encoded>
    </item>
    <item>
      <title>玩了三年的 Minecraft 生存存档</title>
      <link>https://blog.starry-s.moe/posts/2023/minecraft/</link>
      <pubDate>Sat, 08 Jul 2023 22:56:44 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2023/minecraft/</guid>
      <description>&lt;p&gt;这个 Minecraft 存档是咱在 2020 年年初的时候建立的，起初是 1.15.2 单机生存，当时在 B 站学着 TIS 以及其他生电服的思路安装了一些生存辅助插件，后来升级到了 1.16.5，并在去年年底迁移到了我的 NAS 上改成了私人服务器。尽管玩了三年多的时间了但我从来没详细的在博客上记录这个存档，只有在前几年的年终总结中提到过“我在玩 Mincraft，今年建了什么建筑……”，眼瞅着三周年已经过去了，本来想录个纪念视频发 B 站上的，但因为太懒、不想学视频剪辑，这个计划一直咕咕了，不过最近想到可以把存档截图放博客里作为三周年纪念。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>这个 Minecraft 存档是咱在 2020 年年初的时候建立的，起初是 1.15.2 单机生存，当时在 B 站学着 TIS 以及其他生电服的思路安装了一些生存辅助插件，后来升级到了 1.16.5，并在去年年底迁移到了我的 NAS 上改成了私人服务器。尽管玩了三年多的时间了但我从来没详细的在博客上记录这个存档，只有在前几年的年终总结中提到过“我在玩 Mincraft，今年建了什么建筑……”，眼瞅着三周年已经过去了，本来想录个纪念视频发 B 站上的，但因为太懒、不想学视频剪辑，这个计划一直咕咕了，不过最近想到可以把存档截图放博客里作为三周年纪念。</p>
<meting-js server="netease" type="song" id="812557" theme="#233333"></meting-js>
<h2 id="出生点空置域">出生点空置域</h2>
<p><img loading="lazy" src="images/0010.jpg" alt="出生点空置域" />
<p style="margin-bottom: -0.8em;" class="image-title">出生点空置域</p>
</p>
<p>出生点已经被咱炸成空置域了，当时是在 2020 年花了几天的时间用三向轰炸机清理的空置域，y11 高度以下的岩浆并不碍事所以没有清理，现在的出生点空置域有一个收集末地刷沙机的简单收集装置（以后打算改成自动分类+潜影盒收集装置），橙色的建筑是地铁出生点站，图片右侧的是一个简易的刷鱼农场，主要靠这个农场获取墨鱼的黑色染料。</p>
<h2 id="原点空置域">原点空置域</h2>
<p><img loading="lazy" src="images/0020.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">原点</p>
</p>
<p>原点空置域是咱的第二个空置域，之所以叫原点是因为空置域的中心座标为 (x:0, z:0)，原点空置域里只有一个全物品（垃圾桶）分类装置，这个原点空置域我记得是 2021 年用三向炸的，全物品也是在 21 年上半年建的，现在全物品已经全部完工并正常使用了，不过这个全物品目前只收集了一半左右的可堆叠物品，因为有许多物品在单人生存中很少用到。</p>
<p>全物品垃圾桶右边蓝色圆形的建筑是地铁原点站，顶上是一个仅装饰作用的“停机坪”。</p>
<p><img loading="lazy" src="images/0030.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/0040.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/0050.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/0060.jpg" alt="" />

</p>
<p>全物品分类是这个存档里工程量最大的机器，当时建全物品时特地新建了一个村民交易所，买了巨多的石英和红石、萤石等建筑材料，清理水道时也花了巨长的时间。</p>
<h2 id="生存基地">生存基地</h2>
<p><img loading="lazy" src="images/0070.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">生存时第一个建造的房子</p>
</p>
<p><img loading="lazy" src="images/0080.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">由村庄改造的庭院</p>
</p>
<p><img loading="lazy" src="images/0090.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">生存基地俯瞰</p>
</p>
<p><img loading="lazy" src="images/0100.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">刷铁机、工厂、地铁站</p>
</p>
<p>当初建立存档后，是生存在出生点旁边的一个村庄里的，后来搬到了附近的河边，现在村庄已经没有村民了，被我改建成了一个中式的庭院。</p>
<p>这个生存基地有一个简易的工厂，工厂一层是一个小型牧场和小型熔炉组和 1.15 版本能用的经验熔炉（升级到 1.16 后特性被修复了就不能用了），工厂二层有一个附魔台，三层是简易的村民交易所。工厂旁边有一个简易的刷铁机，尽管后来又盖更大的刷铁机了但这个没拆，尽管效率低点但一直能用。</p>
<p>工厂侧面还有一个白桦树树场，效率蛮高的单人生存足够用了，后期的木材全是从这里获取的。生存基地的地下有一个能换乘三条线路的超大地铁站。</p>
<p><img loading="lazy" src="images/0110.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">1 号线站厅</p>
</p>
<p><img loading="lazy" src="images/0120.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">1 号线站台</p>
</p>
<p><img loading="lazy" src="images/0130.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">空置域线站台</p>
</p>
<p><img loading="lazy" src="images/0140.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">2 号线站台</p>
</p>
<h2 id="工业基地">工业基地</h2>
<p>工业基地有一个 320 超大熔炉组，然后是一个效率蛮高的刷石机和一个简易的复制铁轨的机器，地下有一个地铁工业基地站。</p>
<p><img loading="lazy" src="images/0150.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">320 熔炉组</p>
</p>
<p><img loading="lazy" src="images/0160.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">刷石机 &amp; 铁轨复制机</p>
</p>
<h2 id="机场在建">机场（在建）</h2>
<p><img loading="lazy" src="images/0170.jpg" alt="" />

</p>
<p>不得不说生存修机场真的太麻烦了，时间全用来整地形了，铺混凝土倒不是很费事。现在这个机场只有一条跑道，打算建航站楼但是工程量很大，需要很长的时间整理地形，所以一直在咕咕姑中。</p>
<p><img loading="lazy" src="images/0180.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">地铁站 &amp; 刷铁机 &amp; 刷怪塔</p>
</p>
<p>机场旁边的海上有一个大型的 8 核心刷铁机、一个简易的混凝土固化机和一个刷怪塔以及一个地铁站。后期建地铁等建筑的所有混凝土都是在这个混凝土固化机固化的，然后尽管这个刷铁机效率不是特别高但是单机生存勉强够用了，不够用的话就多挂机刷一会（<code>/tick rate 200</code> 警告）……</p>
<h2 id="摩天楼">摩天楼</h2>
<p><img loading="lazy" src="images/0190.jpg" alt="" />

</p>
<p>其实这个地方我起的地名叫“三号村庄”，因为是第三个发现的村庄……</p>
<p>本来计划建两个摩天楼的，但是建完一个之后，另一个楼就一直处于咕咕的状态。摩天楼旁边是一个大型的双岛式站台地铁站。</p>
<h2 id="跨海大桥">跨海大桥</h2>
<p><img loading="lazy" src="images/0200.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/0210.jpg" alt="" />

</p>
<p>这个存档里一共有俩跨海大桥，第一个桥是 2020 年暑期时建的，第二个桥是 22 年下半年建的。</p>
<h2 id="甘蔗农场">甘蔗农场</h2>
<p>甘蔗农场是今年上半年修建的，是一个简易的全自动甘蔗机，收集的甘蔗主要用于生成烟花火箭的原材料：纸。</p>
<p><img loading="lazy" src="images/0310.jpg" alt="" />

</p>
<h2 id="村民交易所">村民交易所</h2>
<p>在修建全物品分类以及地铁站装饰时，需要大量的石英和萤石，因此修建了这个带打折的村民交易所，因为没有建袭击农场，所以目前与村民交易的绿宝石都是从村民交易所卖铁换取的，而铁是靠村民制作的刷铁机刷出来的。<span class="spoiler" >深刻体现了什么叫“取之于民，用之于民”的资本家思想（逃</span></p>
<p><img loading="lazy" src="images/0260.jpg" alt="" />

</p>
<p>村民交易所旁边还有一个咱自己设计的全自动农场，但是因为咱不缺食物（可以从村民那里买金胡萝卜吃），所以这个自动农场建好后一直在闲置。</p>
<p><img loading="lazy" src="images/0270.jpg" alt="" />

</p>
<h2 id="海底神殿守卫者农场">海底神殿（守卫者农场）</h2>
<p>生存中第一个发现的海底神殿，已经被改成了不需要清空水的袭击者农场（排水实在太麻烦了），目前所有建筑的海晶灯的原材料都是从这个农场获取的，海底神殿旁边是三号线地铁海底神殿站。</p>
<p><img loading="lazy" src="images/0340.jpg" alt="" />

</p>
<h2 id="地狱猪人农场">地狱猪人农场</h2>
<p>因为光影在地狱的效果不好所以咱把光影关掉了……</p>
<p>这个猪人农场用来挂机刷经验以及获取金子，农场还包含了猪灵交易所，可以把刷取的金锭与猪灵换取石英、黑曜石、火焰弹等物品。</p>
<p><img loading="lazy" src="images/0350.jpg" alt="" />

</p>
<h2 id="地铁">地铁</h2>
<p>这个存档里目前有三十多个已建成的地铁站，还有十多个在建的地铁站，这里贴几张咱自认为设计得比较有特点的地铁站。</p>
<p>首先是这个双层侧式站台的地铁站，上层是已经建成的 2 号线，下层是正在建设的通往机场的 6 号线：</p>
<p><img loading="lazy" src="images/0220.jpg" alt="" />

</p>
<p>这个是 3 号线的末地传送门站，这个站直接修在了末地要塞里，上层是地铁站台，下层是由末地要塞改造成的末地传送门入口，同时保留了要塞的书房作为该地铁站的小型图书馆：</p>
<p><img loading="lazy" src="images/0230.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">末地传送门 - 站台</p>
</p>
<p><img loading="lazy" src="images/0240.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">末地传送门 &amp; 图书馆</p>
</p>
<p>这个是村民交易所的地下的地铁站，用的云杉木作为柱子，还挺好看的：</p>
<p><img loading="lazy" src="images/0250.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">村民交易所 - 站台</p>
</p>
<p>空置域西站：</p>
<p><img loading="lazy" src="images/0280.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">3 号线（左）、1 号线（右）</p>
</p>
<p>一号线的保留了正线的侧式站台地铁站，方便甩站用（本次裂车本站不停靠）：</p>
<p><img loading="lazy" src="images/0290.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">南湖公园站</p>
</p>
<p>一号线的地上段，弄了红石灯做了点简易的装饰：</p>
<p><img loading="lazy" src="images/0300.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">冰工厂站</p>
</p>
<p>全物品分类旁边的原点站也很有特色，车站是圆柱体结构，两条线路十字交叉：</p>
<p><img loading="lazy" src="images/0320.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">原点站</p>
</p>
<p>这个是二号线的车辆段，目前存档里只有二号线有车辆段（因为二号线太长了），别的线路还没有修车辆段的计划。</p>
<p><img loading="lazy" src="images/0330.jpg" alt="" />

</p>
<hr>
<p>以上基本是咱玩了三年的单人生存存档的全部内容了，还有些在建和小型的建筑个人认为没必要贴到这里，因为最近逐渐没有玩下去的兴趣了所以不知道以后还能不能继续坚持玩下去，所以想了一下这三年时间建的建筑蛮多的因此写到这里水一篇博客。</p>]]></content:encoded>
    </item>
    <item>
      <title>当你刚开始尝试去写 Kubernetes Controller……</title>
      <link>https://blog.starry-s.moe/posts/2023/kube-controller/</link>
      <pubDate>Sat, 10 Jun 2023 02:33:58 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2023/kube-controller/</guid>
      <description>&lt;p&gt;Controller 对初学着来说有那么亿点点抽象，虽然网络上能找到很多有关 Kubernetes Controller 的讲解，但是 Kubernetes 的学习过程往往是一个离散的而不是连续的过程。如果想弄懂 Controller 还是有蛮高门槛的，不要想着看完 Kubernetes 的文档，速成了 Kubernetes 的基本知识就去尝试写 Controller，这种操作就好比刚过完新手教程就去打高难副本，尽管能仿着 &lt;code&gt;sample-controller&lt;/code&gt; 写一个能“跑”的 Controller，但仅仅只能做到能“跑”的程度……&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;与标题有些不同，这篇博客主要讲的是萌新如何上手编写 Controller，如果你是 Kubernetes 初学者，希望这篇博客能帮助你建立编写 Controller 的学习曲线。&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <content:encoded><![CDATA[<p>Controller 对初学着来说有那么亿点点抽象，虽然网络上能找到很多有关 Kubernetes Controller 的讲解，但是 Kubernetes 的学习过程往往是一个离散的而不是连续的过程。如果想弄懂 Controller 还是有蛮高门槛的，不要想着看完 Kubernetes 的文档，速成了 Kubernetes 的基本知识就去尝试写 Controller，这种操作就好比刚过完新手教程就去打高难副本，尽管能仿着 <code>sample-controller</code> 写一个能“跑”的 Controller，但仅仅只能做到能“跑”的程度……</p>
<blockquote>
<p>与标题有些不同，这篇博客主要讲的是萌新如何上手编写 Controller，如果你是 Kubernetes 初学者，希望这篇博客能帮助你建立编写 Controller 的学习曲线。</p>
</blockquote>
<h2 id="前期准备">前期准备</h2>
<p>对刚接触 Kubernetes 的萌新来讲，这个体系还是蛮复杂和抽象的，只靠读文档看教程自学可不是那么容易。光是怎么安装一个 Kubernetes 集群，在不同的教程里就有无数种方法了。传统的安装 Kubernetes 的方法过于硬核，现在几乎没人选择这种方式部署集群了。咱常用的比较简单的方式有 <a href="https://k3s.io">k3s</a>，光靠一个脚本就能在虚拟机上一键部署一个轻量级的集群，很适合萌新（前提是你没有必须用包管理器安装任何软件的强迫症），但是如果你想在国内的网络环境靠这个脚本安装 <code>k3s</code> 的话，需要一些参数配置国内源，这里不再赘述。除此之外还可以<a href="https://ranchermanager.docs.rancher.com/zh/pages-for-subheaders/rancher-on-a-single-node-with-docker">用 Docker 方式部署一个单节点 Rancher</a>，Rancher 的 Web 界面可以更好的帮助萌新去管理 Kubernetes 资源（当然你还可以选择敲 <code>kubectl</code> 指令的方式），还有很多教程会推荐你使用 <code>minikube</code>，当然你可以选择任何一种方式去部署你自己的集群，只要你觉得这种方法适合你，而且部署的集群版本不要太低即可。</p>
<p>如果想编写 Controller，你得有一定的 Kubernetes 基础（废话），并且熟悉 Go 语言（废话 x 2）。在看完 Kubernetes 文档，熟悉了 k8s 的资源和如何使用 <code>kubectl</code> 操作他们后，先别急着上手写 Controller。首先你得熟悉 <a href="https://github.com/kubernetes/client-go">client-go</a>，<code>client-go</code> 的代码能在 GitHub (<a href="https://github.com/kubernetes/client-go">https://github.com/kubernetes/client-go</a>) 中下载到，但记住它的 Go Module 为 <code>k8s.io/client-go</code>，不在 <code>github.com</code>。</p>
<p>首先了解一些常见的 Kuberntes API 类型，知道 Kubernetes 的资源对象是怎么在 <code>client-go</code> 中用 Go 语言表示的，并如何调用 API 去管理他们（而不是仅凭 <code>kubectl</code> 命令行客户端去管理他们），
这里不单单有 <code>client-go</code> 这一个 Git 仓库，还有 <code>k8s.io/api</code>, <code>k8s.io/apimachinery</code> 等仓库，后面写 Controller 时会经常用到这些 API。认识一下 <code>TypeMeta</code> 和 <code>ObjectMeta</code> （代码位置在<a href="https://github.com/kubernetes/apimachinery/blob/master/pkg/apis/meta/v1/types.go">这里</a>），每个资源对象的 Go 结构中都包含这些数据（除此之外每个资源还有 <code>Spec</code>, <code>Status</code> 等），写代码时会经常用到 <code>json/yaml</code> 的 <code>Marshal/Unmarshal</code> 操作，熟悉到这个程度就可以了。</p>
<p>然后是 Kubernetes 的自定义资源（Custom Resource, CR）这个概念，k8s 内置了一些 Resource 资源对象，例如 <code>pod</code>, <code>deployment</code>, <code>service</code>, <code>secret</code> 等，你可以用 <code>kubectl</code> 去 <code>get/describe/create/delete...</code> 这些资源，但如果你想往 k8s 中添加一些你自己的自定义资源，比如你想定义一个资源叫做 <code>database</code>，你用 <code>kubectl create database ...</code> 就能创建一个你自己想要的数据库，像 <code>create pod</code>, <code>create secret</code> 那样，然后还能对你的自定义资源对象进行 <code>describe/delete/update...</code> 等操作，就需要用到自定义资源（开发者更习惯叫他的简写 CR，以及自定义资源定义的简写 CRD）。Controller 就是用来管理这些 CRs 的。在开发 Controller 时我们需要定义 CR 中包含哪些数据，然后使用代码生成器生成资源的 <code>DeepCopy</code> 等方法，减少不必要的重复代码编写。</p>
<blockquote>
<p>可以不用把每个细节都尝试弄懂，把基本概念过一遍就行，学习 Kubernetes 的过程是一个离散的过程而不是连续的过程，当碰到哪个地方不明白卡住的时候直接跳过去看后面的内容就行啦~</p>
</blockquote>
<h2 id="什么是-controller">什么是 Controller</h2>
<p>在上面介绍 CR 的定义时有解释 Controller 是用来管理 CR 的，比如我们执行 <code>kubectl create database ...</code> （实际是执行 <code>kubectl apply -f</code> 部署了一个 <code>Kind</code> 为 <code>database</code> 的 YAML，不能直接 <code>create database</code>，但这么说比较方便理解~）创建了一个 <code>database</code> 类型的资源，因为这个资源是我们自定义的，所以 Kubernetes 只是在 etcd 数据库中记录了：“我们创建了一个 <code>database</code> 资源，他的数据内容是什么什么……”，并没有进行创建数据库的操作！而 Controller 就是用来管理 Database 资源的生命周期的，比如我们 <code>create database</code> 之后，Controller 会发现我们新建了一个 Database 资源，然后会去创建一个 Database Deployment。当我们 <code>delete database</code> 时，Controller 会注意到我们删除了 Database，之后执行资源释放一系列操作。</p>
<p>往简单了讲，Controller 干的事情就是对比资源当前实际的状态和我们设定的资源状态是否一致。比如这个资源定义的 <code>replicas</code> 为 2，但实际只有一个 Pod 在运行，Controller 就会再去创建一个 Pod 使其实际的 <code>replicas</code> 为 2。</p>
<p>当然 Controller 实现起来比这复杂多了，可不是一个简单的 <code>for</code> 循环不断从 Kube API 中查询资源然后做对比这么简单，这用到了 Cache 缓存机制和 Informer 消息提醒机制，减少 Kube API 请求次数，读取内存中的状态缓存什么的，听不懂没关系，以后会懂的……</p>
<h2 id="sample-controller">sample-controller</h2>
<p><code>github.com/kubernetes/sample-controller</code> 项目是一个样例 Controller，所有的初学者都是靠这个项目学习 Controller 的，相当于是高难副本中最简单的了，可以把这个样例 Controller 改造为自己的 Controller，用来学习。</p>
<p>本篇教程以编写 <code>database-controller</code> 为例，按照 <code>sample-controller</code> 的 Controller 框架编写一个数据库的 Controller，重点在于怎么上手写 Controller，不在数据库。</p>
<p>将 <code>sample-controller</code> 代码克隆到本地 <code>$GOPATH</code> 目录下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> midir -p <span class="nv">$GOPATH</span>/src/github.com/&lt;USERNAME&gt;/ <span class="o">&amp;&amp;</span> <span class="nb">cd</span> <span class="nv">$GOPATH</span>/src/github.com/&lt;USERNAME&gt;/
</span></span><span class="line"><span class="cl"><span class="gp">$</span> git clone git@github.com:kubernetes/sample-controller.git <span class="o">&amp;&amp;</span> <span class="nb">cd</span> sample-controller
</span></span></code></pre></div><h3 id="初始化-controller">初始化 Controller</h3>
<p>按照 <code>sample-controller</code> 的 Controller 框架，将其修改为我们想要实现的 Controller。</p>
<ul>
<li>修改项目名称为 <code>database-controller</code>，修改 <code>git remote</code>。</li>
<li>编辑 <code>go.mod</code> 修改 Module 名称，把代码的 <code>k8s.io/sample-controller</code> 改为 <code>github.com/&lt;USERNAME&gt;/database-controller</code>。</li>
<li>编辑 <code>hack/boilerplate.go.txt</code> 中的版权信息。</li>
<li>修改 <code>README</code>，<code>OWNERS</code>，<code>SECURITY_CONTACTS</code> 等信息。</li>
<li>编辑执行<strong>代码生成器</strong>的脚本 <a href="https://github.com/kubernetes/sample-controller/blob/master/hack/update-codegen.sh">hack/update-codegen.sh</a>
<ul>
<li>编辑脚本中的代码生成器所在位置，脚本中原本写的是使用了 <code>go mod vendor</code> 将 Go 依赖都放到了项目的 <code>vendor</code> 目录下时生成器的位置，按实际情况进行修改（比如改成 <code>$GOPATH</code> 目录下）。</li>
<li>编辑 <code>code-generator</code> 的参数，把 <code>k8s.io/sample-controller</code> 改成 <code>github.com/&lt;USERNAME&gt;/database-controller</code>, 并编辑 <code>--output-base</code> 的目录位置。</li>
<li>执行代码生成器脚本，确保能正确生成代码。</li>
</ul>
</li>
</ul>
<p>之后修改 <code>pkg/apis/samplecontroller</code> 目录为 <code>pkg/apis/databasecontroller</code>，同时把 <code>samplecontroller</code> 包修改为 <code>databasecontroller</code>。</p>
<ul>
<li>把代码中所有使用了 <code>samplecontroller</code> 包的地方都改为 <code>databasecontroller</code>（被代码生成器生成的代码可以不用改，后面会重新生成代码）。</li>
<li>修改 <code>pkg/apis/databasecontroller/register.go</code> 的 <code>GroupName</code> 为 <code>database.&lt;YOUR_DOMAIN&gt;</code>，例如 <code>database.example.io</code>。</li>
<li>修改代码生成器的注释，把 <code>pkg/apis/databasecontroller/v1alpha1/doc.go</code> 的 <code>groupName</code> 修改为 <code>database.example.io</code>。</li>
<li>重新执行代码生成器 <code>./hack/update-codegen.sh</code>。</li>
</ul>
<p>先简单熟悉一下修改后的项目的代码结构：</p>
<ul>
<li>
<p><code>main.go</code> 中先构建了 Kubernetes 和 <code>database-controller</code> 的 <code>Client</code>，之后基于 <code>Client</code> 构建了 <code>SharedInformer</code>，最后创建并启动 Controller。</p>
<p>简单来讲，<code>Informer</code> 在资源发生改动时，调用相应事件的处理函数，它可以对“增加”，“更新”，“删除”三种事件进行“监控”处理（一点也不简单，太抽象了）。然后 Informer 还充当了缓存的作用，查询资源状态时只需要查询 Informer 的缓存即可，不需要反复调用 Kube API，减少性能损耗。</p>
</li>
<li>
<p><code>controller.go</code> 包含这些内容：</p>
<ul>
<li>构建 Controller 的 <code>NewController</code>、启动 Controller 的 <code>Run</code>，还有 Informer 在不同事件（Event）进行处理的函数……</li>
<li>创建 Deployment 的函数，<code>sample-controller</code> 中的 CRD Kind 为 <code>foo</code>，这个 <code>foo</code> 创建的 Deployment 是一个 <code>nginx</code> Pod，有点抽象，后面要把 <code>foo</code> 改成咱们要实现的 <code>database</code>，原理实际都没变。</li>
</ul>
<p>Controller 结构体中包含了：</p>
<ul>
<li><code>kubernetes</code> 和代码生成器生成的 <code>database</code> 的 <code>clientSet</code>。</li>
<li>Informer 的 Lister，用来从缓存中获取资源。</li>
<li><code>workqueue</code>：Rate Limit 消息队列。
Controller 在运行时实际是一直尝试从 <code>workqueue</code> 中获取资源并处理。Informer 在接收到状态更新后，会把更新的状态入队列，然后另一个 Routine 中会获取到队列中的消息，拿去处理。
（蛮复杂的，这里还是去直接看代码比较好）</li>
</ul>
</li>
</ul>
<h3 id="修改-controller">修改 Controller</h3>
<p>接下来按照上面讲的那样，修改 <code>pkg/apis/databasecontroller/v1alpha1/types.go</code> 中的 <code>Spec</code> 和 <code>Status</code> 字段，<code>Spec</code> 中的字段是你想定义的 Database 的状态，然后 Controller 负责按照你定义的 <code>Spec</code> 去创建 Deployments 并更新 <code>Status</code>。</p>
<p>首先需要把 <code>Foo</code> 改名成 <code>Database</code>，然后编辑 <code>Spec</code> 中的字段，例如数据库所使用的镜像名称及 Tag，<code>Replicas</code> 冗余数以及其他你觉得创建 Deployment 所需的自定义配置。在修改完 <code>Spec</code> 和 <code>Status</code> 后需要重新执行代码生成器。</p>
<p>之后在项目根目录下编辑 <code>controller.go</code>，修改控制器创建 Deployment 的逻辑，把 <code>Foo</code> 对象修改为 <code>Database</code>，然后按照你定义的 <code>Spec</code>，编辑 <code>artifacs/example</code> 目录下的 <code>crd.yaml</code> 和 <code>example-database.yaml</code> 文件，这部分咱就不把详细的步骤写到这里了，你可以根据你的想法尝试编写你的 Controller，在这里遇到问题最好还是自行尝试动手解决。</p>
<h2 id="其他">其他</h2>
<p>后面还有好多关于 Controller 相关的知识点我也还没搞懂，就不写到博客里误导别人了。除了 <code>sample-controller</code> 这种框架的 Controller 之外，还有很多人使用其他的框架编写 Controller，因为很多时候我们更关注于实现业务逻辑，因此可以套用一些 Operator 模板，常用的有 <a href="https://sdk.operatorframework.io/">Operator SDK</a>，可以通过这个工具生成一份 Controller 模板，然后按照你想实现的功能去修改代码即可，还有很多其他 Operator 可供选择，比如 Rancher 的开发者们使用 <a href="https://github.com/rancher/wrangler">Wrangler</a> 编写 Controller，基于 <code>Wrangler</code> 编写的 Rancher 使用的 Operator 有 <a href="https://github.com/rancher/eks-operator">eks-operator</a> 等一堆 Operator，感兴趣的话可以去看看。<code>Wrangler</code> 的 README 中写的这一段蛮有意思的：</p>
<blockquote>
<p>Most people writing controllers are a bit lost as they find that there is nothing in Kubernetes that is like <code>type Controller interface</code> where you can just do <code>NewController</code>. Instead a controller is really just a pattern of how you use the generated clientsets, informers, and listers combined with some custom event handlers and a workqueue.</p>
</blockquote>
<p>之后如果想把你编写的 Controller (Operator) 应用到生产环境，打包给更多的人使用，可以把编译好的 Operator 二进制文件放到容器镜像中，之后使用 <a href="https://helm.sh">Helm</a> 创建一个 &ldquo;应用程序 (Chart)&quot;，通过编写 <a href="https://helm.sh/docs/chart_best_practices/templates/">模板</a>，在安装 Helm Chart 时编辑 <code>values.yaml</code> 中定义的字段来自定义 CRD 的参数。Helm 的模板本质上是 Go Template 模板渲染引擎，所以用起来都是很简单的（确信）。</p>]]></content:encoded>
    </item>
    <item>
      <title>使用 Helm Chart 方式部署 Harbor</title>
      <link>https://blog.starry-s.moe/posts/2023/harbor-helm-chart/</link>
      <pubDate>Sun, 28 May 2023 16:49:45 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2023/harbor-helm-chart/</guid>
      <description>&lt;p&gt;打算尝试在咱的 NAS 上搭一个 Harbor Registry Server 玩。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>打算尝试在咱的 NAS 上搭一个 Harbor Registry Server 玩。</p>
<p>首先介绍一下 NAS 上的环境，咱的 Kubernetes 集群运行在几个 QEMU 虚拟机里，虚拟机里运行的是 ArchLinux，因为就是咱折腾着玩的所以使用的 k3s 搭建的轻量级的 kubernetes 集群，然后其中一个集群安装了 Rancher 作为 Local 集群。</p>
<h2 id="环境准备">环境准备</h2>
<ol>
<li>
<p>新建一个 Namespace，将 Harbor 的资源与其他资源隔离：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> kubectl create namespace harbor
</span></span></code></pre></div></li>
<li>
<p>为了启用 HTTPS，提前创建一个 TLS 类型的 <a href="https://kubernetes.io/docs/concepts/configuration/secret/">Secret</a>，存放证书:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> cat &gt; cert.pem &lt;&lt; EOF
</span></span><span class="line"><span class="cl"><span class="go">-----BEGIN CERTIFICATE-----
</span></span></span><span class="line"><span class="cl"><span class="go">...
</span></span></span><span class="line"><span class="cl"><span class="go">-----END CERTIFICATE-----
</span></span></span><span class="line"><span class="cl"><span class="go">EOF
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> cat &gt; cert.key &lt;&lt; EOF
</span></span><span class="line"><span class="cl"><span class="go">-----BEGIN PRIVATE KEY-----
</span></span></span><span class="line"><span class="cl"><span class="go">...
</span></span></span><span class="line"><span class="cl"><span class="go">-----END PRIVATE KEY-----
</span></span></span><span class="line"><span class="cl"><span class="go">EOF
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> kubectl -n harbor create secret tls harbor-tls <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="go">    --cert=cert.pem \
</span></span></span><span class="line"><span class="cl"><span class="go">    --key=cert.key
</span></span></span></code></pre></div></li>
<li>
<p>提前创建 PVC (<a href="https://kubernetes.io/docs/concepts/storage/persistent-volumes/">PersistentVolumeClaim</a>)，咱这里先在 NAS 上新建了一个 NFS 服务器，之后创建了 NFS 类型的 PV (PersistentVolumes)，再基于这个 PV 创建的 PVC。</p>
<p>ArchLinux 上搭建 NFS 服务器：<a href="https://wiki.archlinux.org/title/NFS">https://wiki.archlinux.org/title/NFS</a></p>
<blockquote>
<p>在配置 <code>exports</code> 时，需要配置上 <code>no_root_squash</code> 和 <code>no_subtree_check</code>，使挂载的目录及子目录具有写权限。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="cl"><span class="c1"># /etc/exports - exports(5) - directories exported to NFS clients</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Use `exportfs -arv` to reload.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">/</span><span class="k">var</span><span class="o">/</span><span class="n">nfs</span><span class="o">/</span><span class="n">harbor</span>		<span class="mf">10.0</span><span class="o">.</span><span class="mf">0.0</span><span class="o">/</span><span class="mi">8</span><span class="p">(</span><span class="n">rw</span><span class="p">,</span><span class="n">sync</span><span class="p">,</span><span class="n">no_root_squash</span><span class="p">,</span><span class="n">no_subtree_check</span><span class="p">)</span>
</span></span></code></pre></div></blockquote>
</li>
</ol>
<h2 id="获取-helm-chart">获取 Helm Chart</h2>
<p>Harbor 的 Helm Chart 可以在 <a href="https://github.com/goharbor/harbor-helm">GitHub</a> 获取，这里使用将 Chart 源码克隆到本地的方式安装，方便编辑 <code>values.yaml</code>。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> git clone https://github.com/goharbor/harbor-helm.git <span class="o">&amp;&amp;</span> <span class="nb">cd</span> harbor-helm
</span></span><span class="line"><span class="cl"><span class="gp">$</span> git checkout v1.12.1
</span></span></code></pre></div><blockquote>
<p>写这篇博客时 Chart 的最新版本是 <code>v1.12.1</code> (Harbor OSS v2.8.1)。</p>
</blockquote>
<h3 id="编辑-valuesyaml">编辑 <code>values.yaml</code></h3>
<p>Harbor 的配置都定义在了 <code>values.yaml</code> 文件中，根据需要进行修改。</p>
<p>这里列举些常用的可以修改的选项：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">expose</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># expose type, 可以设置为 ingress, clusterIP, nodePort, nodeBalancer，区分大小写</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># 默认为 ingress（如果不想使用 80/443 标准端口，可以设置为 nodePort，端口为高位 3000X）</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">ingress</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">tls</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># 是否启用 TLS (HTTPS)，建议启用</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># TLS Certificate 的来源，可以为 auto, secret 或 none</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># 如果为 secret，需要在安装 Chart 之前先创建 TLS Secret</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># 1) auto: generate the tls certificate automatically</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># 2) secret: read the tls certificate from the specified secret.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># The tls certificate can be generated manually or by cert manager</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># 3) none: configure no tls certificate for the ingress. If the default</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># tls certificate is configured in the ingress controller, choose this option</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">certSource</span><span class="p">:</span><span class="w"> </span><span class="l">secret</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">secret</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># The name of secret which contains keys named:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># &#34;tls.crt&#34; - the certificate</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># &#34;tls.key&#34; - the private key</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">secretName</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;harbor-tls&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># Only needed when the &#34;expose.type&#34; is &#34;ingress&#34;.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">notarySecretName</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;harbor-tls&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">ingress</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">hosts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># Ingress Host，如果需要允许任意域名/IP 都能访问，将其设置为空字符串（不建议）</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># 这里填写的域名务必能解析到当前集群</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">core</span><span class="p">:</span><span class="w"> </span><span class="l">harbor.example.com</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">notary</span><span class="p">:</span><span class="w"> </span><span class="l">notary.example.com</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c"># Harbor external URL</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c"># 与 Ingress Host 相对应，如果启用了 TLS，那就是 https://&lt;domain&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c"># 如果没启用 TLS，那就是 http://&lt;domain&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c"># 如果 expose type 为 nodePort，则填写 http(s)://&lt;IP_ADDRESS&gt;:3000X (端口号不能丢)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">externalURL</span><span class="p">:</span><span class="w"> </span><span class="l">https://harbor.example.com</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c"># 持久卷配置，默认为 true，如果是测试环境可以设置为 enabled: false (重新安装 Chart 时仓库里所有的数据都会丢失，不建议！)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c"># 如果需要启用持久卷，可以在安装 Chart 之前提前创建好 PVC，并配置 subPath</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">persistence</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">resourcePolicy</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;keep&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">persistentVolumeClaim</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">registry</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># 填写已经创建好的 PVC</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">existingClaim</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;harbor-pvc&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">storageClass</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># 如果共用一个 PVC，需要设置子目录</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">subPath</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;registry&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">accessMode</span><span class="p">:</span><span class="w"> </span><span class="l">ReadWriteOnce</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">size</span><span class="p">:</span><span class="w"> </span><span class="l">5Gi</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">annotations</span><span class="p">:</span><span class="w"> </span>{}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">jobservice</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">jobLog</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">existingClaim</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;harbor-pvc&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">storageClass</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">subPath</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;jobservice&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">accessMode</span><span class="p">:</span><span class="w"> </span><span class="l">ReadWriteOnce</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">size</span><span class="p">:</span><span class="w"> </span><span class="l">1Gi</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">annotations</span><span class="p">:</span><span class="w"> </span>{}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">database</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">existingClaim</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;harbor-pvc&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">storageClass</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">subPath</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;database&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">accessMode</span><span class="p">:</span><span class="w"> </span><span class="l">ReadWriteOnce</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">size</span><span class="p">:</span><span class="w"> </span><span class="l">1Gi</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">annotations</span><span class="p">:</span><span class="w"> </span>{}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">redis</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">existingClaim</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;harbor-pvc&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">storageClass</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">subPath</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;redis&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">accessMode</span><span class="p">:</span><span class="w"> </span><span class="l">ReadWriteOnce</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">size</span><span class="p">:</span><span class="w"> </span><span class="l">1Gi</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">annotations</span><span class="p">:</span><span class="w"> </span>{}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">trivy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">existingClaim</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;harbor-pvc&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">storageClass</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">subPath</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;trivy&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">accessMode</span><span class="p">:</span><span class="w"> </span><span class="l">ReadWriteOnce</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">size</span><span class="p">:</span><span class="w"> </span><span class="l">5Gi</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">annotations</span><span class="p">:</span><span class="w"> </span>{}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c"># Admin 初始密码</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">harborAdminPassword</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Harbor12345&#34;</span><span class="w">
</span></span></span></code></pre></div><h3 id="安装-helm-chart">安装 Helm Chart</h3>
<p>确保 Values 编辑无误后，就可以安装 Chart 了：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> helm --namespace harbor install harbor .
</span></span></code></pre></div><p>如果安装后发现 Values 中有些配置需要修改，可以在修改完配置后以升级的方式使配置生效：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> helm --namespace harbor upgrade harbor .
</span></span></code></pre></div><p>查看 Chart 的 Pods 运行状态：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> kubectl --namespace harbor get pods
</span></span><span class="line"><span class="cl"><span class="go">NAME                                    READY   STATUS    RESTARTS      AGE
</span></span></span><span class="line"><span class="cl"><span class="go">harbor-core-7b75785b64-9vzkx            1/1     Running   0             65m
</span></span></span><span class="line"><span class="cl"><span class="go">harbor-database-0                       1/1     Running   0             77m
</span></span></span><span class="line"><span class="cl"><span class="go">harbor-jobservice-6f4d59bd95-25q44      1/1     Running   2 (65m ago)   65m
</span></span></span><span class="line"><span class="cl"><span class="go">harbor-notary-server-584698b475-lnt99   1/1     Running   1 (60m ago)   65m
</span></span></span><span class="line"><span class="cl"><span class="go">harbor-notary-signer-77685b6f94-pfngc   1/1     Running   0             65m
</span></span></span><span class="line"><span class="cl"><span class="go">harbor-portal-6fb6465fd6-hm4cg          1/1     Running   0             77m
</span></span></span><span class="line"><span class="cl"><span class="go">harbor-redis-0                          1/1     Running   0             77m
</span></span></span><span class="line"><span class="cl"><span class="go">harbor-registry-5bbccf79fb-7hcm9        2/2     Running   0             65m
</span></span></span><span class="line"><span class="cl"><span class="go">harbor-trivy-0                          1/1     Running   0             77m
</span></span></span></code></pre></div><h2 id="其他">其他</h2>
<p>安装完成后，就可以完美使用 Harbor Registry 了。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> docker login harbor.example.com
</span></span><span class="line"><span class="cl"><span class="go">Username: admin
</span></span></span><span class="line"><span class="cl"><span class="go">Password:
</span></span></span><span class="line"><span class="cl"><span class="go">WARNING! Your password will be stored unencrypted in /home/user/.docker/config.json.
</span></span></span><span class="line"><span class="cl"><span class="go">Configure a credential helper to remove this warning. See
</span></span></span><span class="line"><span class="cl"><span class="go">https://docs.docker.com/engine/reference/commandline/login/#credentials-store
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Login Succeeded
</span></span></span></code></pre></div><p>从 DockerHub 中 Mirror 一些镜像到 Harbor 中：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> skopeo copy --all docker://archlinux:latest docker://harbor.example.com/library/archlinux:latest
</span></span><span class="line"><span class="cl"><span class="go">Getting image list signatures
</span></span></span><span class="line"><span class="cl"><span class="go">Copying 1 of 1 images in list
</span></span></span><span class="line"><span class="cl"><span class="go">Copying image sha256:076c0233d1996165721320957be9a037a760574d6334281354b07b3b3c9440b1 (1/1)
</span></span></span><span class="line"><span class="cl"><span class="go">Getting image source signatures
</span></span></span><span class="line"><span class="cl"><span class="go">Copying blob f0e04a7b4686 done
</span></span></span><span class="line"><span class="cl"><span class="go">Copying blob 352736306209 done
</span></span></span><span class="line"><span class="cl"><span class="go">Copying config cc4866169d done
</span></span></span><span class="line"><span class="cl"><span class="go">Writing manifest to image destination
</span></span></span><span class="line"><span class="cl"><span class="go">Storing signatures
</span></span></span><span class="line"><span class="cl"><span class="go">Writing manifest list to image destination
</span></span></span><span class="line"><span class="cl"><span class="go">Storing list signatures
</span></span></span></code></pre></div>]]></content:encoded>
    </item>
    <item>
      <title>容器镜像 Manifest 相关内容整理</title>
      <link>https://blog.starry-s.moe/posts/2023/container-manifest/</link>
      <pubDate>Sun, 21 May 2023 01:50:27 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2023/container-manifest/</guid>
      <description>&lt;p&gt;最近总在弄些容器镜像相关的东西，于是分享一些咱自己总结的有关容器镜像 Manifest 格式、常用工具以及代码相关的芝士。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>最近总在弄些容器镜像相关的东西，于是分享一些咱自己总结的有关容器镜像 Manifest 格式、常用工具以及代码相关的芝士。</p>
<meting-js server="netease" type="song" id="590414" theme="#233333"></meting-js>
<h2 id="skopeo">skopeo</h2>
<p><a href="https://github.com/containers/skopeo">skopeo</a> 是一个肥肠好用的容器镜像的辅助工具，常用到的功能有镜像拷贝 (<code>skopeo copy</code>)、镜像 Manifest 查询 (<code>skopeo inspect</code>)等……</p>
<p><code>skopeo</code> 仅支持 Linux 和 macOS 系统。</p>
<h3 id="安装-skopeo">安装 skopeo</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="c1"># ArchLinux</span>
</span></span><span class="line"><span class="cl">sudo pacman -S skopeo
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># macOS</span>
</span></span><span class="line"><span class="cl">brew install skopeo
</span></span></code></pre></div><p>除此之外还可以使用 <code>skopeo</code> 的容器镜像：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> docker run docker://quay.io/skopeo/stable:latest copy --help
</span></span></code></pre></div><blockquote>
<p><code>skopeo</code> 由 Go 编写，但它启用了 <code>cgo</code>，编译的二进制文件需要动态链接第三方依赖，所以不同的系统编译的 skopeo 二进制文件并不一定互相通用，如果你的发行版的官方源没有提供 <code>skopeo</code> 软件包的话，只能手动安装 Go 和 <code>skopeo</code> 的一些依赖，然后 <a href="https://github.com/containers/skopeo/blob/main/install.md#building-from-source">自行编译 skopeo 二进制文件</a>。</p>
</blockquote>
<h3 id="skopeo-copy">skopeo copy</h3>
<p><code>copy</code> 可以灵活的拷贝容器镜像，它可以将容器镜像从 Registry Server 之间拷贝，还可以将镜像从 Registry Server 拷贝到本地的文件夹中，或者像 <code>docker pull</code> 那样拷贝到 Docker Daemon 中。</p>
<p>在执行 <code>skopeo copy</code> 时还可以用 <code>--format</code> 参数指定拷贝过去的容器镜像的格式，用参数 <code>--dest-compress-format</code> 可以指定压缩格式。</p>
<p>将容器镜像从第三方 DockerHub Registry Server 拷贝到自建的 Private Registry Server：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> skopeo copy docker://docker.io/library/nginx:latest docker://private.registry.io/library/nginx:latest --all
</span></span></code></pre></div><p>将镜像从 DockerHub Registry Server 拷贝到本地文件夹中：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> mkdir -p nginx
</span></span><span class="line"><span class="cl"><span class="gp">$</span> skopeo copy docker://docker.io/library/nginx:latest dir:./nginx
</span></span></code></pre></div><p>将镜像从本地文件夹中拷贝到 Docker Daemon 中：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> skopeo copy dir:./nginx docker-daemon:nginx:latest
</span></span><span class="line"><span class="cl"><span class="gp">$</span> docker images
</span></span><span class="line"><span class="cl"><span class="go">REPOSITORY  TAG       IMAGE ID       CREATED        SIZE
</span></span></span><span class="line"><span class="cl"><span class="go">nginx       latest    448a08f1d2f9   13 days ago    142MB
</span></span></span></code></pre></div><h3 id="skopeo-inspect">skopeo inspect</h3>
<p><code>skopeo inspect</code> 查看容器镜像的信息，例如镜像的 Manifest、Config。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> skopeo inspect docker://docker.io/library/nginx:latest
</span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> skopeo inspect docker://docker.io/library/nginx:latest --raw
</span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> skopeo inspect docker://docker.io/library/nginx:latest --raw --config
</span></span></code></pre></div><p><code>skopeo inspect</code> 不加任何参数时，查询的是容器镜像相关的信息，输出的内容包括镜像 Digest、该镜像其他的所有 Tag 等一系列信息。</p>
<p>在添加 <code>--raw</code> 参数时，输出的是该镜像的 Manifest 原始信息，因为是 RAW，所以输出的 Json 可能格式不是很友好，通常与 <code>jq</code> 一起使用。</p>
<p>添加 <code>--raw</code> 和 <code>--config</code> 参数后，输出的是该镜像的 Config 的原始信息，Config 中包括容器运行时的一些配置项等信息。</p>
<h2 id="manifest">Manifest</h2>
<p>Docker 文档 <a href="https://docs.docker.com/registry/spec/manifest-v2-1/">Registry image manifests</a> 中介绍了几种常见的 Docker 镜像的 Manifest 格式。</p>
<hr>
<p>可以通过 <a href="https://github.com/containers/skopeo">skopeo</a> 工具，从 Docker Hub 上挑一个容器镜像 (例如 <code>nginx:latest</code>)，查看这个镜像的 Manifest。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="c1">// skopeo inspect docker://nginx:latest --raw | jq
</span></span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;manifests&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;digest&#34;</span><span class="p">:</span> <span class="s2">&#34;sha256:3f01b0094e21f7d55b9eb7179d01c49fdf9c3e1e3419d315b81a9e0bae1b6a90&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;mediaType&#34;</span><span class="p">:</span> <span class="s2">&#34;application/vnd.docker.distribution.manifest.v2+json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;platform&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;architecture&#34;</span><span class="p">:</span> <span class="s2">&#34;amd64&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;os&#34;</span><span class="p">:</span> <span class="s2">&#34;linux&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;size&#34;</span><span class="p">:</span> <span class="mi">1570</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;digest&#34;</span><span class="p">:</span> <span class="s2">&#34;sha256:bc4cb92540db42f21dd806c4451f33b623a9b6441c882e8554325f3a3702da76&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;mediaType&#34;</span><span class="p">:</span> <span class="s2">&#34;application/vnd.docker.distribution.manifest.v2+json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;platform&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;architecture&#34;</span><span class="p">:</span> <span class="s2">&#34;arm&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;os&#34;</span><span class="p">:</span> <span class="s2">&#34;linux&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;variant&#34;</span><span class="p">:</span> <span class="s2">&#34;v5&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;size&#34;</span><span class="p">:</span> <span class="mi">1570</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="err">......</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;mediaType&#34;</span><span class="p">:</span> <span class="s2">&#34;application/vnd.docker.distribution.manifest.list.v2+json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;schemaVersion&#34;</span><span class="p">:</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>这里输出的 json object 的 <code>schemaVersion</code> 为 2，<code>mediaType</code> 为 <code>application/vnd.docker.distribution.manifest.list.v2+json</code>。</p>
<h3 id="schemaversion--mediatype">schemaVersion &amp; mediaType</h3>
<p>容器镜像的 Manifest 有很多种不同的格式，先列举一下常见的 Docker 镜像的 Manifest 格式：</p>
<ol>
<li>
<p><code>schemaVersion: 1</code>, <code>mediaType: &quot;application/vnd.docker.distribution.manifest.v1+json&quot;</code></p>
<p>旧版本的 Docker 使用这种 Manifest 格式，现已被弃用，有些旧的容器镜像依旧是这种格式的 Manifest。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="c1">// skopeo inspect docker://mysql:5.5.40 --raw | jq
</span></span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;library/mysql&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;tag&#34;</span><span class="p">:</span> <span class="s2">&#34;5.5.40&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;architecture&#34;</span><span class="p">:</span> <span class="s2">&#34;amd64&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;fsLayers&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;blobSum&#34;</span><span class="p">:</span> <span class="s2">&#34;sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="err">......</span>
</span></span><span class="line"><span class="cl">    <span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;history&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="err">......</span>
</span></span><span class="line"><span class="cl">    <span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;schemaVersion&#34;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;signatures&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="err">......</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>(输出太长了所以我把不关键的内容省略掉了……)</p>
<p>这里用 <code>docker.io/library/mysql:5.5.40</code> 这个镜像举例，实际这个镜像的 Manifest 格式为 <code>schemaVersion: 1</code>，<code>mediaType: &quot;application/vnd.docker.distribution.manifest.v1+prettyjws&quot;</code>，因为包含了签名信息。</p>
</li>
<li>
<p><code>schemaVersion: 2</code>, <code>mediaType: &quot;application/vnd.docker.distribution.manifest.v2+json&quot;</code></p>
<p>这个是现在常见的 Docker 镜像的 Manifest 格式。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="c1">// skopeo inspect docker://hxstarrys/nginx:1.22-amd64 --raw | jq
</span></span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="nt">&#34;schemaVersion&#34;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="nt">&#34;mediaType&#34;</span><span class="p">:</span> <span class="s2">&#34;application/vnd.docker.distribution.manifest.v2+json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="nt">&#34;config&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;mediaType&#34;</span><span class="p">:</span> <span class="s2">&#34;application/vnd.docker.container.image.v1+json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;size&#34;</span><span class="p">:</span> <span class="mi">7898</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;digest&#34;</span><span class="p">:</span> <span class="s2">&#34;sha256:0f8498f13f3adef3f3c8b52cdf069ecc880b081159be6349163d144e8aa5fb29&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="nt">&#34;layers&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;mediaType&#34;</span><span class="p">:</span> <span class="s2">&#34;application/vnd.docker.image.rootfs.diff.tar.gzip&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;size&#34;</span><span class="p">:</span> <span class="mi">31411405</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;digest&#34;</span><span class="p">:</span> <span class="s2">&#34;sha256:f1f26f5702560b7e591bef5c4d840f76a232bf13fd5aefc4e22077a1ae4440c7&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;mediaType&#34;</span><span class="p">:</span> <span class="s2">&#34;application/vnd.docker.image.rootfs.diff.tar.gzip&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;size&#34;</span><span class="p">:</span> <span class="mi">25573496</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;digest&#34;</span><span class="p">:</span> <span class="s2">&#34;sha256:fd03b214f77493ccb73705ac5417f16c7625a7ea7ea997e939c9241a3296763b&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="err">......</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>这个格式的 Manifest 包含了镜像的 Config 的信息以及 Layer 的格式和 Digest 信息。</p>
</li>
<li>
<p><code>schemaVersion: 2</code>, <code>mediaType: &quot;application/vnd.docker.distribution.manifest.list.v2+json&quot;</code></p>
<p>这个格式的 Manifest List 包含一个 <code>manifests</code> 列表：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="c1">// skopeo inspect docker://docker.io/library/nginx:1.22 --raw | jq
</span></span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="nt">&#34;mediaType&#34;</span><span class="p">:</span> <span class="s2">&#34;application/vnd.docker.distribution.manifest.list.v2+json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="nt">&#34;schemaVersion&#34;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="nt">&#34;manifests&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;mediaType&#34;</span><span class="p">:</span> <span class="s2">&#34;application/vnd.docker.distribution.manifest.v2+json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;digest&#34;</span><span class="p">:</span> <span class="s2">&#34;sha256:9081064712674ffcff7b7bdf874c75bcb8e5fb933b65527026090dacda36ea8b&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;size&#34;</span><span class="p">:</span> <span class="mi">1570</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;platform&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;architecture&#34;</span><span class="p">:</span> <span class="s2">&#34;amd64&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;os&#34;</span><span class="p">:</span> <span class="s2">&#34;linux&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;mediaType&#34;</span><span class="p">:</span> <span class="s2">&#34;application/vnd.docker.distribution.manifest.v2+json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;digest&#34;</span><span class="p">:</span> <span class="s2">&#34;sha256:cf4ffe24f08a167176c84f2779c9fc35c2f7ce417b411978e384cbe63525b420&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;size&#34;</span><span class="p">:</span> <span class="mi">1570</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;platform&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;architecture&#34;</span><span class="p">:</span> <span class="s2">&#34;arm64&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;os&#34;</span><span class="p">:</span> <span class="s2">&#34;linux&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>例如在 x86_64 (amd64) 架构的 Linux 主机上拉取 TAG <code>docker.io/library/nginx:1.22</code> 时，会根据此 Manifest List，拉取 Digest 为 <code>sha256:9081064712674ffcff7b7bdf874c75bcb8e5fb933b65527026090dacda36ea8b</code> 的镜像。在 aarch64 (arm64v8) 架构的 Linux 主机上拉取此 TAG 时，会根据 Manifest List，拉取 Digest 为 <code>sha256:cf4ffe24f08a167176c84f2779c9fc35c2f7ce417b411978e384cbe63525b420</code> 的镜像，在其他 OS 的主机上无法拉取这个 TAG 对应的镜像 (例如在 arm32v7 的 Linux 主机上拉取会失败)。</p>
<p><code>manifests</code> 列表中，每个 <code>digest</code> 字段存储的是这个镜像的 Manifest 内容的 sha256 校验和。</p>
<p>可以用 <code>skopeo inspect</code> 查看一下这个 digest 的镜像的 Manifest 内容，其格式为
<code>schemaVersion: 2</code>, <code>mediaType: &quot;application/vnd.docker.distribution.manifest.v2+json&quot;</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="c1">// skopeo inspect docker://nginx@sha256:9081064712674ffcff7b7bdf874c75bcb8e5fb933b65527026090dacda36ea8b --raw | jq
</span></span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;schemaVersion&#34;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;mediaType&#34;</span><span class="p">:</span> <span class="s2">&#34;application/vnd.docker.distribution.manifest.v2+json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;config&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;mediaType&#34;</span><span class="p">:</span> <span class="s2">&#34;application/vnd.docker.container.image.v1+json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;size&#34;</span><span class="p">:</span> <span class="mi">7898</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;digest&#34;</span><span class="p">:</span> <span class="s2">&#34;sha256:0f8498f13f3adef3f3c8b52cdf069ecc880b081159be6349163d144e8aa5fb29&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;layers&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;mediaType&#34;</span><span class="p">:</span> <span class="s2">&#34;application/vnd.docker.image.rootfs.diff.tar.gzip&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;size&#34;</span><span class="p">:</span> <span class="mi">31411405</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;digest&#34;</span><span class="p">:</span> <span class="s2">&#34;sha256:f1f26f5702560b7e591bef5c4d840f76a232bf13fd5aefc4e22077a1ae4440c7&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;mediaType&#34;</span><span class="p">:</span> <span class="s2">&#34;application/vnd.docker.image.rootfs.diff.tar.gzip&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;size&#34;</span><span class="p">:</span> <span class="mi">25573496</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;digest&#34;</span><span class="p">:</span> <span class="s2">&#34;sha256:fd03b214f77493ccb73705ac5417f16c7625a7ea7ea997e939c9241a3296763b&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;mediaType&#34;</span><span class="p">:</span> <span class="s2">&#34;application/vnd.docker.image.rootfs.diff.tar.gzip&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;size&#34;</span><span class="p">:</span> <span class="mi">626</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;digest&#34;</span><span class="p">:</span> <span class="s2">&#34;sha256:ef2fc869b944b87eaf25f4c92953dc69736d5d05aa09f66f54b0eea598e13c9c&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;mediaType&#34;</span><span class="p">:</span> <span class="s2">&#34;application/vnd.docker.image.rootfs.diff.tar.gzip&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;size&#34;</span><span class="p">:</span> <span class="mi">958</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;digest&#34;</span><span class="p">:</span> <span class="s2">&#34;sha256:ac713a9ef2cca7a82e27f0277e4e3d25c64d1cf31e4acd798562d5532742f5ef&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;mediaType&#34;</span><span class="p">:</span> <span class="s2">&#34;application/vnd.docker.image.rootfs.diff.tar.gzip&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;size&#34;</span><span class="p">:</span> <span class="mi">773</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;digest&#34;</span><span class="p">:</span> <span class="s2">&#34;sha256:fd071922d543e072b21cb41a513634657049d632fe48cfed240be2369f998403&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;mediaType&#34;</span><span class="p">:</span> <span class="s2">&#34;application/vnd.docker.image.rootfs.diff.tar.gzip&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;size&#34;</span><span class="p">:</span> <span class="mi">1405</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;digest&#34;</span><span class="p">:</span> <span class="s2">&#34;sha256:2a9f38700bb5a0462e326fe3541b45f24a677ac3cd386c4922d48da5fbb6f0a8&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>镜像的 Digest 实际上是这个镜像的 Manifest 内容的 sha256sum 校验和：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> skopeo inspect --raw docker://nginx@sha256:9081064712674ffcff7b7bdf874c75bcb8e5fb933b65527026090dacda36ea8b
</span></span><span class="line"><span class="cl"><span class="go">{
</span></span></span><span class="line"><span class="cl"><span class="go">    &#34;schemaVersion&#34;: 2,
</span></span></span><span class="line"><span class="cl"><span class="go">    &#34;mediaType&#34;: &#34;application/vnd.docker.distribution.manifest.v2+json&#34;,
</span></span></span><span class="line"><span class="cl"><span class="go">    &#34;config&#34;: {
</span></span></span><span class="line"><span class="cl"><span class="go">        &#34;mediaType&#34;: &#34;application/vnd.docker.container.image.v1+json&#34;,
</span></span></span><span class="line"><span class="cl"><span class="go">        &#34;size&#34;: 7898,
</span></span></span><span class="line"><span class="cl"><span class="go">        &#34;digest&#34;: &#34;sha256:0f8498f13f3adef3f3c8b52cdf069ecc880b081159be6349163d144e8aa5fb29&#34;
</span></span></span><span class="line"><span class="cl"><span class="go">    },
</span></span></span><span class="line"><span class="cl"><span class="go">    ......
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="gp">$</span> skopeo inspect --raw docker://nginx@sha256:9081064712674ffcff7b7bdf874c75bcb8e5fb933b65527026090dacda36ea8b <span class="p">|</span> sha256sum
</span></span><span class="line"><span class="cl"><span class="go">9081064712674ffcff7b7bdf874c75bcb8e5fb933b65527026090dacda36ea8b  -
</span></span></span></code></pre></div><p>同理，Config 的 Digest 为镜像的 Config 内容的 sha256sum 校验和：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> skopeo inspect --raw --config docker://nginx@sha256:9081064712674ffcff7b7bdf874c75bcb8e5fb933b65527026090dacda36ea8b <span class="p">|</span> sha256sum
</span></span><span class="line"><span class="cl"><span class="go">0f8498f13f3adef3f3c8b52cdf069ecc880b081159be6349163d144e8aa5fb29  -
</span></span></span></code></pre></div></li>
</ol>
<hr>
<p>除了上面的几种 Docker 镜像的 Manifest 格式外，还有 <a href="https://github.com/opencontainers/image-spec/blob/main/manifest.md">OCI 容器镜像</a> 这种格式的 Manifest:</p>
<ol>
<li><code>schemaVersion: 2</code>, <code>mediaType: &quot;application/vnd.oci.image.manifest.v1+json&quot;</code>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="c1">// skopeo inspect docker://quay.io/skopeo/stable@sha256:9da6763a4d35592a6279e851738472d9cdaa8ff5a5da3c50b560f065d22c2bff --raw | jq
</span></span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;schemaVersion&#34;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;mediaType&#34;</span><span class="p">:</span> <span class="s2">&#34;application/vnd.oci.image.manifest.v1+json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;config&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;mediaType&#34;</span><span class="p">:</span> <span class="s2">&#34;application/vnd.oci.image.config.v1+json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;digest&#34;</span><span class="p">:</span> <span class="s2">&#34;sha256:6acf3c9f5dd48704618fa7ec2b95968a45c9e7809926a1f90f383bea4e9b3ede&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;size&#34;</span><span class="p">:</span> <span class="mi">3032</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;layers&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;mediaType&#34;</span><span class="p">:</span> <span class="s2">&#34;application/vnd.oci.image.layer.v1.tar+gzip&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;digest&#34;</span><span class="p">:</span> <span class="s2">&#34;sha256:529411ad578ab92819185dd8ef493eaa1eecc4f62b2ed2199db99ae23e6bf4cd&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;size&#34;</span><span class="p">:</span> <span class="mi">73881106</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;mediaType&#34;</span><span class="p">:</span> <span class="s2">&#34;application/vnd.oci.image.layer.v1.tar+gzip&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;digest&#34;</span><span class="p">:</span> <span class="s2">&#34;sha256:eeaa0b0d534352a9398996bcff9dc1184a78d310c22800aa6de07a6e2b1f8864&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;size&#34;</span><span class="p">:</span> <span class="mi">54520878</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;mediaType&#34;</span><span class="p">:</span> <span class="s2">&#34;application/vnd.oci.image.layer.v1.tar+gzip&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;digest&#34;</span><span class="p">:</span> <span class="s2">&#34;sha256:5ebf46cd2e6b356313b1dce504191fefce45df90dd8b5df7fe6b8cdd0fd06667&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;size&#34;</span><span class="p">:</span> <span class="mi">1849</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;mediaType&#34;</span><span class="p">:</span> <span class="s2">&#34;application/vnd.oci.image.layer.v1.tar+gzip&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;digest&#34;</span><span class="p">:</span> <span class="s2">&#34;sha256:d4779f97b4911cd73b8bbe8b96c6759b6f5c210928020e0c351294e7136aeb94&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;size&#34;</span><span class="p">:</span> <span class="mi">4061</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;mediaType&#34;</span><span class="p">:</span> <span class="s2">&#34;application/vnd.oci.image.layer.v1.tar+gzip&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;digest&#34;</span><span class="p">:</span> <span class="s2">&#34;sha256:f2e09c14b28b7453b48d13aace7cef657580e3b1cfdc0be8cfb9e685862a068f&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;size&#34;</span><span class="p">:</span> <span class="mi">228</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;annotations&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;org.opencontainers.image.base.digest&#34;</span><span class="p">:</span> <span class="s2">&#34;sha256:7acf70fa27721ef08357823d79324a19d7e9b0d34873c93f33a1b654d784e3c4&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;org.opencontainers.image.base.name&#34;</span><span class="p">:</span> <span class="s2">&#34;registry.fedoraproject.org/fedora:latest&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div></li>
<li><code>schemaVersion: 2</code>, <code>mediaType: &quot;application/vnd.oci.image.index.v1+json&quot;</code>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="c1">// skopeo inspect docker://quay.io/skopeo/stable:latest --raw | jq
</span></span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;schemaVersion&#34;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;mediaType&#34;</span><span class="p">:</span> <span class="s2">&#34;application/vnd.oci.image.index.v1+json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;manifests&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;mediaType&#34;</span><span class="p">:</span> <span class="s2">&#34;application/vnd.oci.image.manifest.v1+json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;digest&#34;</span><span class="p">:</span> <span class="s2">&#34;sha256:3f678eca3035c64243c70598efeb4f60ef06a07b156444e21feed9488d47944b&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;size&#34;</span><span class="p">:</span> <span class="mi">1239</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;platform&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;architecture&#34;</span><span class="p">:</span> <span class="s2">&#34;arm64&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;os&#34;</span><span class="p">:</span> <span class="s2">&#34;linux&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;mediaType&#34;</span><span class="p">:</span> <span class="s2">&#34;application/vnd.oci.image.manifest.v1+json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;digest&#34;</span><span class="p">:</span> <span class="s2">&#34;sha256:72464a265722c05436b5f46b9247929a882e73462f33ac1c000f4a34094fc90c&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;size&#34;</span><span class="p">:</span> <span class="mi">1239</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;platform&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;architecture&#34;</span><span class="p">:</span> <span class="s2">&#34;amd64&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;os&#34;</span><span class="p">:</span> <span class="s2">&#34;linux&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div></li>
</ol>
<h2 id="library">Library</h2>
<p><a href="https://github.com/containers">containers</a> 和 <a href="https://github.com/opencontainers">opencontainers</a> Org 提供了许多容器镜像相关的 Go Library，例如：</p>
<ul>
<li><a href="https://github.com/containers/image">containers/image</a></li>
<li><a href="https://github.com/containers/common">containers/common</a></li>
<li><a href="https://github.com/opencontainers/image-spec">opencontainers/image-spec</a></li>
</ul>
<p>Docker Manifest 格式的定义位于代码：<a href="https://github.com/containers/image/blob/main/manifest/manifest.go">containers/image/v5/manifest</a></p>
<p>OCI 容器镜像的 Manifest 格式定义位于代码：<a href="https://github.com/opencontainers/image-spec/blob/main/specs-go/v1/mediatype.go">opencontainers/image-spec/specs-go/v1</a></p>
<p><code>skopeo inspect</code> 的代码位于 <a href="https://github.com/containers/skopeo/blob/main/cmd/skopeo/inspect.go">containers/skopeo/cmd/skopeo/inspect.go</a>，<code>skopeo</code> 用了 <a href="https://github.com/spf13/cobra">cobra</a> 框架来处理用户的命令行参数（这里悄悄安利一下 <code>cobra</code> 框架真的很好用，尤其是当你的程序有许多的子命令，每个子命令需要处理的参数还都不一样的情况），执行查询镜像 Manifest 的代码都在 <code>run</code> 函数里面。</p>
<hr>
<p>下面是咱写的一个栗子，使用上述的 Library 模拟一下 <code>skopeo inspect</code> 查看容器镜像 Manifest 的功能，其实查看容器镜像 Manifest 的代码实现还是蛮简单的：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">example</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;context&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;encoding/json&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;testing&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;github.com/containers/image/v5/transports/alltransports&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;github.com/containers/image/v5/types&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">Test_Inspect</span><span class="p">(</span><span class="nx">t</span><span class="w"> </span><span class="o">*</span><span class="nx">testing</span><span class="p">.</span><span class="nx">T</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// reference name format: docker://&lt;image&gt;:&lt;tag&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">refName</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="s">&#34;docker://docker.io/library/nginx:latest&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">ref</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">alltransports</span><span class="p">.</span><span class="nf">ParseImageName</span><span class="p">(</span><span class="nx">refName</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">t</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;ParseImageName: %v&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">sysCtx</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">types</span><span class="p">.</span><span class="nx">SystemContext</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">DockerAuthConfig</span><span class="p">:</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">types</span><span class="p">.</span><span class="nx">DockerAuthConfig</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">Username</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;&#34;</span><span class="p">,</span><span class="w"> </span><span class="c1">// docker username (optional)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">Password</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;&#34;</span><span class="p">,</span><span class="w"> </span><span class="c1">// docker password (optional)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="c1">// set to true if server is HTTP or using insecure certificate</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">OCIInsecureSkipTLSVerify</span><span class="p">:</span><span class="w">    </span><span class="kc">false</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">DockerInsecureSkipTLSVerify</span><span class="p">:</span><span class="w"> </span><span class="nx">types</span><span class="p">.</span><span class="nf">NewOptionalBool</span><span class="p">(</span><span class="kc">false</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">source</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">ref</span><span class="p">.</span><span class="nf">NewImageSource</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nf">TODO</span><span class="p">(),</span><span class="w"> </span><span class="nx">sysCtx</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">t</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;NewImageSource: %v&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">data</span><span class="p">,</span><span class="w"> </span><span class="nx">mime</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">source</span><span class="p">.</span><span class="nf">GetManifest</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nf">TODO</span><span class="p">(),</span><span class="w"> </span><span class="kc">nil</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">t</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;GetManifest: %v&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">&#34;Manifest mediaType: %v\n&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">mime</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">&#34;Manifest RAW data: \n%v\n&#34;</span><span class="p">,</span><span class="w"> </span><span class="nb">string</span><span class="p">(</span><span class="nx">data</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// reformat output</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="kd">var</span><span class="w"> </span><span class="nx">obj</span><span class="w"> </span><span class="kt">any</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">json</span><span class="p">.</span><span class="nf">Unmarshal</span><span class="p">(</span><span class="nx">data</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">obj</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">t</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;Unmarshal: %v&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">data</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">json</span><span class="p">.</span><span class="nf">MarshalIndent</span><span class="p">(</span><span class="nx">obj</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;  &#34;</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">t</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;MarshalIndent: %v&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">&#34;===================================\n&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">&#34;Manifest data: \n%v\n&#34;</span><span class="p">,</span><span class="w"> </span><span class="nb">string</span><span class="p">(</span><span class="nx">data</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><hr>
<p>接下来，是构建 Manifest List 索引的一个简单栗子，假设你分别向 Registry Server 上传了 <code>&lt;namespace&gt;/example:v1.0.0-amd64</code> 和 <code>&lt;namespace&gt;/example:v1.0.0-arm64</code> 两个不同架构的容器镜像，你希望用户在 AMD64 架构的主机上拉取 <code>&lt;namespace&gt;/example:v1.0.0</code> 的 TAG 时，自动拉取 <code>&lt;namespace&gt;/example:v1.0.0-amd64</code> 这个镜像，而在 ARM64 架构的主机上拉取时，自动拉取 <code>&lt;namespace&gt;/example:v1.0.0-arm64</code> 这个镜像。</p>
<blockquote>
<p>这里说的 Manifest List 实际是 <code>schemaVersion 2</code>, <code>mediaType: &quot;application/vnd.docker.distribution.manifest.list.v2+json&quot;</code></p>
<p>基本上你可以使用任何的 Registry Server，但 Harbor V1 除外，因为 Harbor V1 不支持 Manifest List。</p>
</blockquote>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">example</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;context&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;crypto/sha256&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;encoding/json&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;testing&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;github.com/containers/image/v5/manifest&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;github.com/containers/image/v5/transports/alltransports&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;github.com/containers/image/v5/types&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;github.com/opencontainers/go-digest&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">sha256sum</span><span class="p">(</span><span class="nx">data</span><span class="w"> </span><span class="p">[]</span><span class="kt">byte</span><span class="p">)</span><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">sum</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">sha256</span><span class="p">.</span><span class="nf">Sum256</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Sprintf</span><span class="p">(</span><span class="s">&#34;%x&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">sum</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">getManifest</span><span class="p">(</span><span class="nx">refName</span><span class="w"> </span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="nx">ctx</span><span class="w"> </span><span class="o">*</span><span class="nx">types</span><span class="p">.</span><span class="nx">SystemContext</span><span class="p">)</span><span class="w"> </span><span class="p">([]</span><span class="kt">byte</span><span class="p">,</span><span class="w"> </span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// reference name format: docker://&lt;image&gt;:&lt;tag&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">ref</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">alltransports</span><span class="p">.</span><span class="nf">ParseImageName</span><span class="p">(</span><span class="nx">refName</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;ParseImageName: %w&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">source</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">ref</span><span class="p">.</span><span class="nf">NewImageSource</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nf">TODO</span><span class="p">(),</span><span class="w"> </span><span class="nx">ctx</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;NewImageSource: %v&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">source</span><span class="p">.</span><span class="nf">GetManifest</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nf">TODO</span><span class="p">(),</span><span class="w"> </span><span class="kc">nil</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">Test_BuildManifest</span><span class="p">(</span><span class="nx">t</span><span class="w"> </span><span class="o">*</span><span class="nx">testing</span><span class="p">.</span><span class="nx">T</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// reference name format: docker://&lt;image&gt;:&lt;tag&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">refName</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="s">&#34;docker://&lt;REGISTRY_URL:PORT&gt;/&lt;NAMESPACE&gt;/example:v1.0.0&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">ref</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">alltransports</span><span class="p">.</span><span class="nf">ParseImageName</span><span class="p">(</span><span class="nx">refName</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">t</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;ParseImageName: %v&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">sysCtx</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">types</span><span class="p">.</span><span class="nx">SystemContext</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">DockerAuthConfig</span><span class="p">:</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">types</span><span class="p">.</span><span class="nx">DockerAuthConfig</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">Username</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;&#34;</span><span class="p">,</span><span class="w"> </span><span class="c1">// registry username (required)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">Password</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;&#34;</span><span class="p">,</span><span class="w"> </span><span class="c1">// registry password (required)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="c1">// set to true if server is HTTP or using insecure certificate</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">OCIInsecureSkipTLSVerify</span><span class="p">:</span><span class="w">    </span><span class="kc">false</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">DockerInsecureSkipTLSVerify</span><span class="p">:</span><span class="w"> </span><span class="nx">types</span><span class="p">.</span><span class="nf">NewOptionalBool</span><span class="p">(</span><span class="kc">false</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">manifestList</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">manifest</span><span class="p">.</span><span class="nx">Schema2List</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">SchemaVersion</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">MediaType</span><span class="p">:</span><span class="w">     </span><span class="nx">manifest</span><span class="p">.</span><span class="nx">DockerV2ListMediaType</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">Manifests</span><span class="p">:</span><span class="w">     </span><span class="p">[]</span><span class="nx">manifest</span><span class="p">.</span><span class="nx">Schema2ManifestDescriptor</span><span class="p">{},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// add amd64 data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">data</span><span class="p">,</span><span class="w"> </span><span class="nx">mime</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nf">getManifest</span><span class="p">(</span><span class="s">&#34;docker://&lt;REGISTRY_URL:PORT&gt;/&lt;NAMESPACE&gt;/example:v1.0.0-amd64&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">sysCtx</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">t</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;getManifest: %v&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">manifestList</span><span class="p">.</span><span class="nx">Manifests</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nb">append</span><span class="p">(</span><span class="nx">manifestList</span><span class="p">.</span><span class="nx">Manifests</span><span class="p">,</span><span class="w"> </span><span class="nx">manifest</span><span class="p">.</span><span class="nx">Schema2ManifestDescriptor</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">Schema2Descriptor</span><span class="p">:</span><span class="w"> </span><span class="nx">manifest</span><span class="p">.</span><span class="nx">Schema2Descriptor</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">MediaType</span><span class="p">:</span><span class="w"> </span><span class="nx">mime</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">Size</span><span class="p">:</span><span class="w">      </span><span class="nb">int64</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="nx">data</span><span class="p">)),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">Digest</span><span class="p">:</span><span class="w">    </span><span class="nx">digest</span><span class="p">.</span><span class="nf">Digest</span><span class="p">(</span><span class="nf">sha256sum</span><span class="p">(</span><span class="nx">data</span><span class="p">)),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">Platform</span><span class="p">:</span><span class="w"> </span><span class="nx">manifest</span><span class="p">.</span><span class="nx">Schema2PlatformSpec</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">Architecture</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;amd64&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">OS</span><span class="p">:</span><span class="w">           </span><span class="s">&#34;linux&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">OSVersion</span><span class="p">:</span><span class="w">    </span><span class="s">&#34;&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">Variant</span><span class="p">:</span><span class="w">      </span><span class="s">&#34;&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// add arm64 data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">data</span><span class="p">,</span><span class="w"> </span><span class="nx">mime</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nf">getManifest</span><span class="p">(</span><span class="s">&#34;docker://&lt;REGISTRY_URL:PORT&gt;/&lt;NAMESPACE&gt;/example:v1.0.0-arm64&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">sysCtx</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">t</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;getManifest: %v&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">manifestList</span><span class="p">.</span><span class="nx">Manifests</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nb">append</span><span class="p">(</span><span class="nx">manifestList</span><span class="p">.</span><span class="nx">Manifests</span><span class="p">,</span><span class="w"> </span><span class="nx">manifest</span><span class="p">.</span><span class="nx">Schema2ManifestDescriptor</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">Schema2Descriptor</span><span class="p">:</span><span class="w"> </span><span class="nx">manifest</span><span class="p">.</span><span class="nx">Schema2Descriptor</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">MediaType</span><span class="p">:</span><span class="w"> </span><span class="nx">mime</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">Size</span><span class="p">:</span><span class="w">      </span><span class="nb">int64</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="nx">data</span><span class="p">)),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">Digest</span><span class="p">:</span><span class="w">    </span><span class="nx">digest</span><span class="p">.</span><span class="nf">Digest</span><span class="p">(</span><span class="nf">sha256sum</span><span class="p">(</span><span class="nx">data</span><span class="p">)),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">Platform</span><span class="p">:</span><span class="w"> </span><span class="nx">manifest</span><span class="p">.</span><span class="nx">Schema2PlatformSpec</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">Architecture</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;arm64&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">OS</span><span class="p">:</span><span class="w">           </span><span class="s">&#34;linux&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">OSVersion</span><span class="p">:</span><span class="w">    </span><span class="s">&#34;&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">Variant</span><span class="p">:</span><span class="w">      </span><span class="s">&#34;v8&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">dest</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">ref</span><span class="p">.</span><span class="nf">NewImageDestination</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nf">TODO</span><span class="p">(),</span><span class="w"> </span><span class="nx">sysCtx</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">t</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;NewImageSource: %v&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">data</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">json</span><span class="p">.</span><span class="nf">MarshalIndent</span><span class="p">(</span><span class="nx">manifestList</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;  &#34;</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">t</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;MarshalIndent: %v&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">dest</span><span class="p">.</span><span class="nf">PutManifest</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nf">TODO</span><span class="p">(),</span><span class="w"> </span><span class="nx">data</span><span class="p">,</span><span class="w"> </span><span class="kc">nil</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">t</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;PutManifest: %v&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>构建 Manifest 的栗子中，用到了一部分 Manifest 的代码，用来获取 amd64 架构的镜像和 arm64 架构镜像的 Manifest 文本长度，并计算 Digest。</p>]]></content:encoded>
    </item>
    <item>
      <title>咱今年的五一旅行游记</title>
      <link>https://blog.starry-s.moe/posts/2023/2023-05-01/</link>
      <pubDate>Mon, 08 May 2023 00:37:04 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2023/2023-05-01/</guid>
      <description>&lt;p&gt;回忆前几天的旅行就仿佛在回忆一场睡了很长的梦一样……&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>回忆前几天的旅行就仿佛在回忆一场睡了很长的梦一样……</p>
<p>起初是没打算去那么远的地方旅游的，本来打算五一假期在沈阳那些我没去过的地方逛一逛就行，但是后来想了一下可以在平时周末的时候再去沈阳周边我没去过的地方，毕竟离得近，而这个难得的小长假不如去更远一点的地方玩一玩，于是想起来三年前就有去成都的打算了，但是由于种种原因一直拖到了现在，刚好我有几个大学同学在成都，于是最后看了一下机票价格降到了比较合理的水平就决定去四川成都玩了，之后因为成都离重庆很近所以决定先去成都溜达一趟，再去重庆逛一圈。</p>
<p>所以这次的旅行记录就参照四年前咱一个人去上海的旅行那样，把每天的事情简单的回忆一下并记录到这里吧。</p>
<meting-js server="netease" type="song" id="1367709776" theme="#233333"></meting-js>
<h2 id="4月27日">4月27日</h2>
<p>咱是请了几天年假打算错峰去旅行的，所以旅行是从27号开始，在出发之前沈阳迎来了一次大降温于是发烧感冒了好几天，在看完了沈阳的春天的最后一场雪之后（没错，快到5月份的时候沈阳下了一场雪），拖着生病还没完全康复身子出发去机场坐飞机前往成都……</p>
<p>当时尽管身体因为还在生病没力气，但因为记错了飞机起飞的时间所以那天一大早就一直在忙着收拾行李跑去机场，根本管不上什么身体难受不难受的了……</p>
<p>沈阳到成都的飞机飞了4个多小时，北方的天气也不是很好所以没在飞机上拍什么照片，到成都之后已经是晚上了所以那天一整天都没怎么拍风景的照片。</p>
<p>不过晚上去了同学家拍了一阵子他那一柜子的手办……</p>
<p><img loading="lazy" src="images/day1/photo_1.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/day1/photo_2.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/day1/photo_3.jpg" alt="" />

</p>
<h2 id="4月28日">4月28日</h2>
<p>因为出发前并没做攻略，咱就简单的查了一下成都的几个网红打卡点，于是28号那天坐地铁去了成都的野生大能猫基地。趁着人不是很多，可以比较安静舒服的在大森林里面一边溜达一边给大能猫拍照。</p>
<p>那天成都一整天都在下小雨，然后我感冒没完全好，身体基本上是拿感冒药硬扛的，所以出了很多汗，加上下雨十分潮湿，我的双手得抱着相机所以没打伞，所以在能猫基地逛了一圈后上半身全湿了。</p>
<p><img loading="lazy" src="images/day2/IMG_1209.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/day2/IMG_1247.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/day2/IMG_1239.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/day2/IMG_1255.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/day2/IMG_1360.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/day2/IMG_1267.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/day2/IMG_1379.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/day2/IMG_1383.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/day2/IMG_1389.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/day2/IMG_1414.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/day2/IMG_1457.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/day2/IMG_1421_1.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/day2/IMG_1427_1.jpg" alt="" />

</p>
<p>然后是在第一天坐飞机的时候无意间发现我的手机摄像头拍照时照片放大后有几个细微的紫色的斑点，于是傍晚时去了太古里的苹果店约了服务人员帮我检测一下，最终得到的答复是镜头被激光扫到了导致的，尽管我买了AC+但是换镜头需要掏六百多的维修费用，因为相机的紫色斑点不是很严重所以掏六百多块钱维修的话不是很划算，于是回去和同学愉快的恰了一顿烤肉就撤了。</p>
<h2 id="4月29日">4月29日</h2>
<p>五一假期的第一天，和同学去了成都的西部国际博览城，逛了一天的漫展，当时腿都要走断了……</p>
<p><img loading="lazy" src="images/day3/IMG_1509.jpg" alt="" />

</p>
<p>所以这个红石公园有没有红石？MC 玩家狂喜（bushi</p>
<hr>
<p><img loading="lazy" src="images/day3/IMG_1541.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/day3/IMG_2826.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/day3/IMG_2836.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/day3/IMG_2842.JPG" alt="" />

</p>
<p>最后这张照片，能拍到蒂法和克劳德的组合，感觉这次来成都漫展简直值了。当时在漫展找了一下午的克劳德都没找到他，最后碰到了蒂法后刚想给她拍照时突然遇到了准备回去的克劳德，于是赶紧喊过来和蒂法一起合照，真的当时他们摆出姿势后一下子就回想起了去年夏天玩 FF7 前几章时的感觉了，真的太激动了……</p>
<hr>
<p>晚上和同学恰了成都的火锅，因为担心会不会狠辣所以要了微辣的锅底，但后来吃起来觉得微辣不够辣，不是十分过瘾……</p>
<h2 id="4月30日">4月30日</h2>
<p>五一假期的第二天依旧是去西博城的漫展，拍了好多coser的照片……</p>
<p><img loading="lazy" src="images/day4/IMG_3002.jpg" alt="" />

</p>
<p>上午办完酒店退租后在写字楼的楼底下拍到了这只独自蹲在角落的小熊。</p>
<p><img loading="lazy" src="images/day4/IMG_3107.jpg" alt="" />

</p>
<p>之后是拖着行李箱去的漫展，在漫展那里找了存包的地方，顺便给举存包牌牌的小姐姐拍了张照。</p>
<p><img loading="lazy" src="images/day4/IMG_2907.jpg" alt="" />

</p>
<p>铃芽户缔</p>
<hr>
<p>之后是拍到的一些很漂亮的coser……</p>
<p><img loading="lazy" src="images/day4/IMG_2933.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/day4/IMG_2939.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/day4/IMG_2970.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/day4/IMG_2988.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/day4/IMG_3023.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/day4/IMG_3110.jpg" alt="" />

</p>
<p>因为这几天一直在到处溜达走路，所以逛漫展基本上都是走几步就得找个地方坐下休息一会……</p>
<hr>
<p>晚上坐高铁从成都东站前往重庆，因为到重庆之后是晚上所以有一点点迷路，拿手机导航绕了好几个圈……</p>
<p>到重庆时因为赶上五一的高峰期，旅店的价格很贵，尽管我是提前定的，但还是两百多块钱定了一个很普通（甚至有点破）的房间，还没有独立卫浴……</p>
<blockquote>
<p>西南地区尽管还没进入夏天但已经开始湿热起来，不能洗澡是真的要命……</p>
</blockquote>
<h2 id="5月1日">5月1日</h2>
<p><img loading="lazy" src="images/day5/IMG_3165.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/day5/IMG_3163.jpg" alt="" />

</p>
<p>因为听说重庆的轨道交通蛮有名的，所以想去看一下比较知名的2号线李子坝站，就是那个把地铁车站修到了楼里的车站。</p>
<p><img loading="lazy" src="images/day5/IMG_3164.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/day5/IMG_3162.jpg" alt="" />

</p>
<p>但实际去了之后，因为人太多了光是从车站下车后下楼就挤了十多分钟，所以体验并不是很好，在马路上抬头看车站实际上也就觉得是地铁（单轨）从楼里穿了过去而已，没啥稀奇的，但毕竟来都来了……</p>
<p>因为人特别多所以我不打算从李子坝站挤回去坐地铁了，而是徒步从李子坝站走到牛角沱站去坐3号线，尽可能的把人流错开。</p>
<p>因为听说重庆的洪崖洞那边人特别多，加上下午实在是太热了，我去旅游的时候光想着会不会冷了所以带的都是厚衣服……一件短袖都没有带。于是在网上找了一下重庆的漫展，前往了重庆的国家会展中心……</p>
<p><del>果然二刺猿还是应该去二刺猿该去的地方……</del></p>
<p><img loading="lazy" src="images/day5/IMG_3159.jpg" alt="" />

</p>
<p>傍晚漫展结束的时候，因为实在是太热了浑身出汗都湿透了，已经捂出痱子了，就坐地铁去了附近的瓦达瓜叉找了一家优衣库买了一件短袖换上了。</p>
<p>在万达的麦当劳吃完晚饭后，坐地铁去了重庆的朝天门。</p>
<p><img loading="lazy" src="images/day5/IMG_3114.jpg" alt="" />

</p>
<p>到朝天门穿过商场之后，一直沿着江边的路往洪崖洞方向步行，一开始人还不是很多，但快到千厮门嘉陵江大桥后人就变得特别的多，于是在附近拍了一些照片后决定不去洪崖洞里面，想办法徒步离开这里。</p>
<p><img loading="lazy" src="images/day5/IMG_3156.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/day5/IMG_3113.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/day5/IMG_3112.jpg" alt="" />

</p>
<p>因为听说重庆把跨江大桥封了专门供游客通行，所以我也趁着人不是很多的时候从桥下的江边的路绕（爬山）到了大桥上边。</p>
<p>快登桥的时候人巨多，非常的挤，但所有人都很有秩序，一点点的往前移动。</p>
<p>所以走了几个小时之后终于登上了千厮门嘉陵江大桥，拍了一些照片，但实在是太累了没有掏相机拍照，手机的夜景效果比较勉强。</p>
<p><img loading="lazy" src="images/day5/001.jpg" alt="" />

</p>
<p>因为江对面的第一个地铁站人很多，进站要排队，所以咱徒步走到了下一个地铁站，坐9号线回去的。</p>
<p><img loading="lazy" src="images/day5/IMG_3105.jpg" alt="" />

</p>
<blockquote>
<p>六十九号线哈哈哈哈哈……</p>
</blockquote>
<p>这一天走了相当远的距离，步数达到了2W步，但实际咱的运动量可不只这两万步，从19年到现在很久没走过这么多步了……</p>
<h2 id="5月2日">5月2日</h2>
<p>因为返程的飞机是晚上的，所以白天还可以逛一天，上午在酒店收拾完东西，因为实在是走不动路了，也不想白天顶着大太阳去旅游景点，所以下午又去了重庆的漫展，在漫展基本上都是找地方坐着。</p>
<p>顺便拍了一些照片。</p>
<p><img loading="lazy" src="images/day6/IMG_3142.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/day6/IMG_3143.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/day6/IMG_3146.jpg" alt="" />

</p>
<p>晚上坐飞机前往郑州，在郑州新郑机场过夜……</p>
<h2 id="5月3日">5月3日</h2>
<p>抵达郑州时已经是凌晨1点多了，在郑州机场的候机大厅里只睡了几个小时，因为没有可以躺的地方，所以后来实在困得受不了了就抱着行李箱开睡……</p>
<p>郑州飞沈阳的飞机上咱基本上一直在睡觉，只有空乘在发水和早餐时是醒着的。</p>
<p>在重庆机场候机的时候突然意识到5月3号下午还可以再从沈阳玩半天（确信），于是又买了一张沈阳漫展的票，去找之前认识的人在漫展上给他们拍照……</p>
<p><img loading="lazy" src="images/day7/IMG_3327.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/day7/IMG_3381.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/day7/IMG_3438.jpg" alt="" />

</p>
<p>回去的时候已经困得睁不开眼睛了，叫了出租车睡了一路……</p>
<hr>
<meting-js server="netease" type="song" id="22730073" theme="#233333"></meting-js>
<p>回想一下咱五一假期逛了三个城市，一共五天的漫展，还去了一些其他的旅游景点……</p>
<p>算是报复性旅游吧，其实一开始没打算这么高强度旅行的，但玩着玩着就成了特种兵式旅游了……</p>]]></content:encoded>
    </item>
    <item>
      <title>咱的摄影日记 - 2023 春</title>
      <link>https://blog.starry-s.moe/posts/2023/spring-2023/</link>
      <pubDate>Sat, 22 Apr 2023 01:07:53 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2023/spring-2023/</guid>
      <description>&lt;p&gt;从去年的春天到现在已经过去一年了……&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>从去年的春天到现在已经过去一年了……</p>
<p><img loading="lazy" src="images/001.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/002.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/003.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/004.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/005.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/006.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/007.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/008.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/009.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/010.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/010-1.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/011.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/012.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/013.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/014.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/014-1.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/015.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/016.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/017.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/018.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/019.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/020.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/021.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/022.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/023.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/024.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/025.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/026.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/027.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/028.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/029.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/030.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/031.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/034.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/035.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/036.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/032.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/033.jpg" alt="" />

</p>]]></content:encoded>
    </item>
    <item>
      <title>“关于我逛了一趟漫展然后换了新的相机和镜头这件事”</title>
      <link>https://blog.starry-s.moe/posts/2023/canon-eos-r7/</link>
      <pubDate>Fri, 24 Feb 2023 22:50:26 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2023/canon-eos-r7/</guid>
      <description>&lt;p&gt;年初逛了一趟漫展之后突然想换新的相机了，因为对手里的 EOS 800D 成像不是很满意，暗光的高感表现很差，ISO 800 的时候 RAW 格式稍微拉一下阴影就会出现许多噪点，所以打算换个更专业点的相机，开拓些新的领域。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>年初逛了一趟漫展之后突然想换新的相机了，因为对手里的 EOS 800D 成像不是很满意，暗光的高感表现很差，ISO 800 的时候 RAW 格式稍微拉一下阴影就会出现许多噪点，所以打算换个更专业点的相机，开拓些新的领域。</p>
<hr>
<p>预算方面我把相机和镜头控制在了 1W 出头，尽管这个价位能买到前几年的全画幅微单，但是综合考虑了一阵子后还是入手了佳能去年新出的 APS-C 画幅的 R7，用转接环可以搭配我已有的 EF/EF-S 镜头，能稍微节省一点镜头的开销，主要是我还挺喜欢手里的适马 17-50 这个镜头的。</p>
<p>因为 RF 卡口的镜头价格太贵了，所以入手相机之后搜了一些评价不错的 EF-S 镜头，于是就买了广角镜头适马 18-35，所以最终还是超预算了。</p>
<p>相机到了之后试着到处溜达拍了一些照片，用着记忆里很久以前用诺基亚自学摄影时的那点知识拍了些照片，挑几张自认为觉得还行的照片放在这里。</p>
<p><img loading="lazy" src="images/1.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/2.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/3.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/4.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/6.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/7.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/5.jpg" alt="" />

</p>
<p>第一张是在中街用适马 17-50 拍的，后面那几张是在买了适马 18-35 之后去辽宁省博物馆拍的，最后一张是笨蛋公主的 Q 版手办，也是用适马 18-35 拍的，这几张我就稍微调了颜色，没有其他后期，很久没练习拍照了，这几张图就看看就行。</p>
<p><del>拿巨沉的人像广角镜头去拍实物特写，唉我真的是不知道咋想的……</del></p>]]></content:encoded>
    </item>
    <item>
      <title>入手一台家用级 APC UPS</title>
      <link>https://blog.starry-s.moe/posts/2023/apc-ups/</link>
      <pubDate>Sun, 08 Jan 2023 11:05:02 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2023/apc-ups/</guid>
      <description>&lt;p&gt;为了让咱的 NAS 长时间稳定运行，斥巨资买了一台 APC BK650M2-CH，在 Arch Wiki 上看 APC 的 UPS 对 Linux 的支持比较友好，于是挑了个最便宜的带停电自动关机的 APC UPS，防止咱啥时候忘了交电费导致停电数据受损。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>为了让咱的 NAS 长时间稳定运行，斥巨资买了一台 APC BK650M2-CH，在 Arch Wiki 上看 APC 的 UPS 对 Linux 的支持比较友好，于是挑了个最便宜的带停电自动关机的 APC UPS，防止咱啥时候忘了交电费导致停电数据受损。</p>
<blockquote>
<p>参烤链接: <a href="https://wiki.archlinux.org/title/APC_UPS">APC UPS - ArchWiki</a></p>
</blockquote>
<hr>
<p>UPS 到手后花了半个多小时读使用说明书，然后第一件事是把 UPS 的断电报警蜂鸣器关掉，省得我不在公寓的时候停电了 UPS 叫个没完吵到邻居。</p>
<p>之后在装 UPS 之前先把我电脑支架背面一团电线重新整理了一遍，现在是台式机、显示器、NAS、光猫、路由器、无线 AP 都放在一起了，他们的电源线、网线、数据线、显示器线都团在了一起，整理起来炒鸡麻烦。</p>
<p>先把漏油器、光猫和 NAS 的电源都插 UPS 上，然后接好 UPS 的 USB 数据线到 NAS 上。</p>
<h2 id="安装-apcupsd">安装 apcupsd</h2>
<p>安装 <code>apcupsd</code>，然后 <code>systemctl enable --now apcupsd.service</code>。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo apcaccess status
</span></span><span class="line"><span class="cl"><span class="go">APC      : 001,036,0869
</span></span></span><span class="line"><span class="cl"><span class="go">DATE     : 2023-01-08 11:09:23 +0800
</span></span></span><span class="line"><span class="cl"><span class="go">HOSTNAME : ApertureNAS
</span></span></span><span class="line"><span class="cl"><span class="go">VERSION  : 3.14.14 (31 May 2016) unknown
</span></span></span><span class="line"><span class="cl"><span class="go">UPSNAME  : ApertureNAS
</span></span></span><span class="line"><span class="cl"><span class="go">CABLE    : USB Cable
</span></span></span><span class="line"><span class="cl"><span class="go">DRIVER   : USB UPS Driver
</span></span></span><span class="line"><span class="cl"><span class="go">UPSMODE  : Stand Alone
</span></span></span><span class="line"><span class="cl"><span class="go">STARTTIME: 2023-01-02 23:57:44 +0800
</span></span></span><span class="line"><span class="cl"><span class="go">MODEL    : Back-UPS BK650M2-CH
</span></span></span><span class="line"><span class="cl"><span class="go">STATUS   : ONLINE
</span></span></span><span class="line"><span class="cl"><span class="go">......
</span></span></span></code></pre></div><p>先把 NAS 里所有的应用都停掉，之后编辑 <code>/etc/apcupsd/apcupsd.conf</code>，把 <code>TIMEOUT</code> 改为 <code>1</code>，然后给 UPS 断电，这时 NAS 会自动关机。</p>
<blockquote>
<p>UPS 重新连接电源后，NAS 可能会自动开机，我的 NAS 是这样，但不确定所有 NAS 都这样。</p>
</blockquote>
<h2 id="配置自动休眠">配置自动休眠</h2>
<p>按照 Wiki 配置停电后自动休眠 (Hibernate / 休眠到硬盘)。</p>
<blockquote>
<p>在此之前，需要创建 swap 分区 (或 swap file)，然后配置休眠需要的内核参数并重构 <code>initramfs</code>。</p>
</blockquote>
<p>以 root 用户创建 <code>/usr/local/bin/hibernate</code>：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl"><span class="c1"># Hibernate the system - designed to be called via symlink from /etc/apcupsd</span>
</span></span><span class="line"><span class="cl"><span class="c1"># directory in case of apcupsd initiating a shutdown/reboot.  Can also be used</span>
</span></span><span class="line"><span class="cl"><span class="c1"># interactively or from any script to cause a hibernate.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 可以在这里加一些在休眠之前执行的操作，例如让 bot 发个邮件提醒停电了之类的</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Wall message</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 广播消息</span>
</span></span><span class="line"><span class="cl">wall -n System will be hibernate soon
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">sleep <span class="m">1</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Do the hibernate</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 执行休眠</span>
</span></span><span class="line"><span class="cl">/usr/bin/systemctl hibernate
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># At this point system should be hibernated - when it comes back, we resume this script here</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 现在，系统应当已经休眠了，当系统恢复运行的时候，脚本会继续从这里执行</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 可以在这里加一些在系统恢复之后的操作，例如让 bot 发个邮件提醒电力恢复了啥的</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># On resume, tell controlling script (/etc/apcupsd/apccontrol) NOT to continue with default action (i.e. shutdown).</span>
</span></span><span class="line"><span class="cl"><span class="nb">exit</span> <span class="m">99</span>
</span></span></code></pre></div><p>别忘了赋予可执行权限。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">#</span> chmod +x /usr/local/bin/hibernate
</span></span></code></pre></div><p>创建软链接把脚本链接到 <code>/etc/apcupsd</code> 目录下面。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">#</span> ln -s /usr/local/bin/hibernate /etc/apcupsd/doshutdown
</span></span></code></pre></div><p>此时给 UPS 断电后，NAS 会自动休眠，等一两分钟 NAS 会完成休眠，但 UPS 仍处于运行状态没有关机，长时间停电的话，UPS 的电量会耗尽。</p>
<p>UPS 接回电源后，NAS 会从休眠中恢复。</p>
<h3 id="配置休眠后关闭-ups-电源">配置休眠后关闭 UPS 电源</h3>
<p>创建 <code>/usr/lib/systemd/system-sleep/ups-kill</code>：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">case</span> <span class="nv">$2</span> in
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># In the event the computer is hibernating.</span>
</span></span><span class="line"><span class="cl">  hibernate<span class="o">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="nv">$1</span> in
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">       <span class="c1"># Going into a hibernate state.</span>
</span></span><span class="line"><span class="cl">       pre<span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">         <span class="c1"># See if this is a powerfail situation.</span>
</span></span><span class="line"><span class="cl">         <span class="k">if</span> <span class="o">[</span> -f /etc/apcupsd/powerfail <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">           <span class="nb">echo</span>
</span></span><span class="line"><span class="cl">           <span class="nb">echo</span> <span class="s2">&#34;ACPUPSD will now power off the UPS&#34;</span>
</span></span><span class="line"><span class="cl">           <span class="nb">echo</span>
</span></span><span class="line"><span class="cl">           /etc/apcupsd/apccontrol killpower
</span></span><span class="line"><span class="cl">           <span class="nb">echo</span>
</span></span><span class="line"><span class="cl">           <span class="nb">echo</span> <span class="s2">&#34;Please ensure that the UPS has powered off before rebooting&#34;</span>
</span></span><span class="line"><span class="cl">           <span class="nb">echo</span> <span class="s2">&#34;Otherwise, the UPS may cut the power during the reboot!!!&#34;</span>
</span></span><span class="line"><span class="cl">           <span class="nb">echo</span>
</span></span><span class="line"><span class="cl">         <span class="k">fi</span>
</span></span><span class="line"><span class="cl">       <span class="p">;;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">       <span class="c1"># Coming out of a hibernate state.</span>
</span></span><span class="line"><span class="cl">       post<span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">         <span class="c1"># If there are remnants from a powerfail situation, remove them.</span>
</span></span><span class="line"><span class="cl">         <span class="k">if</span> <span class="o">[</span> -f /etc/apcupsd/powerfail <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">           rm /etc/apcupsd/powerfail
</span></span><span class="line"><span class="cl">         <span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">         <span class="c1"># This may also exist, need to remove it.</span>
</span></span><span class="line"><span class="cl">         <span class="k">if</span> <span class="o">[</span> -f /etc/nologin <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">           rm /etc/nologin
</span></span><span class="line"><span class="cl">         <span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	 <span class="c1"># Restart the daemon; otherwise it may be unresponsive in a</span>
</span></span><span class="line"><span class="cl">         <span class="c1"># second powerfailure situation.</span>
</span></span><span class="line"><span class="cl">	 systemctl restart apcupsd
</span></span><span class="line"><span class="cl">       <span class="p">;;</span>
</span></span><span class="line"><span class="cl">    <span class="k">esac</span>
</span></span><span class="line"><span class="cl">  <span class="p">;;</span>
</span></span><span class="line"><span class="cl"><span class="k">esac</span>
</span></span></code></pre></div><p>赋予可执行权限：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">#</span> chmod +x /usr/lib/systemd/system-sleep/ups-kill
</span></span></code></pre></div><p>接下来给 UPS 断电，NAS 会自动休眠，等 NAS 休眠后再过几分钟 UPS 也会关机。</p>
<p>UPS 关机后，给 UPS 接上电源，这时 UPS 会自动开机，然后 NAS 也会从休眠中恢复。</p>
<blockquote>
<p>不要在 UPS 还没关机的时候给 UPS 重新接回电源，会导致 UPS 关机后 NAS 刚从休眠中恢复就被强制断电。</p>
</blockquote>
<h2 id="其他">其他</h2>
<p>折腾 UPS 的时候顺手给 NAS 换了个散热器升级了一下内存。AMD 原装散热器有亿点点吵所以换成了咱之前买的 ITX 散热器。</p>
<figure>
    <img loading="lazy" src="images/1.jpg"/> 
</figure>

<p>用咱写的 telebot 查看一下空载时的 CPU 温度才不到 30 度。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">CPU: +28.2°C
</span></span><span class="line"><span class="cl">Uptime: 0.15 Hour
</span></span><span class="line"><span class="cl">TotalRAM: 46.45G
</span></span><span class="line"><span class="cl">FreeRAM: 42.66G
</span></span><span class="line"><span class="cl">AvailableRAM: 43.91G
</span></span><span class="line"><span class="cl">TotalSwap: 46.00G
</span></span><span class="line"><span class="cl">FreeSwap: 46.00G
</span></span></code></pre></div><p>运行一个虚拟机一个 MineCraft 服务器，CPU 温度不到 40，所以很安静。</p>
<p>但是 UPS 它有噪音，晚上睡觉的时候能听见 UPS 它嗡嗡响，比 AMD 散热器的风扇动静还大，这个实在是无解，算了就先这样吧。</p>]]></content:encoded>
    </item>
    <item>
      <title>Hello 2023</title>
      <link>https://blog.starry-s.moe/posts/2023/hello-2023/</link>
      <pubDate>Mon, 02 Jan 2023 01:20:31 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2023/hello-2023/</guid>
      <description>&lt;p&gt;2023 新年快乐。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>2023 新年快乐。</p>
<meting-js server="netease" type="song" id="592364" theme="#233333"></meting-js>
<hr>
<p><img loading="lazy" src="images/2.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/1.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/3.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/4.jpg" alt="" />

</p>]]></content:encoded>
    </item>
    <item>
      <title>NAS 装机记录</title>
      <link>https://blog.starry-s.moe/posts/2022/build-nas/</link>
      <pubDate>Sat, 12 Nov 2022 13:25:03 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2022/build-nas/</guid>
      <description>&lt;p&gt;差不多去年的这个时候尝试过用树莓派插移动硬盘的方式试探性的组装了一个 NAS，但实际上用了不到两天这个方案就被废弃掉了……&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>差不多去年的这个时候尝试过用树莓派插移动硬盘的方式试探性的组装了一个 NAS，但实际上用了不到两天这个方案就被废弃掉了……</p>
<meting-js server="netease" type="song" id="22682819" theme="#233333"></meting-js>
<h2 id="起因">起因</h2>
<p>不用树莓派的原因是，它是 ARM 架构的微型“电脑”，Arch Linux 官方只支持 x86_64 架构的系统，Arch Linux ARM 准确来说实际上是个第三方的系统。
然后树莓派的性能很差，只有一个板载网卡和 2.4G 无线网卡（我手里的是树莓派 3B），所以这玩意实际上更适合做嵌入式什么的，或者给初学者折腾入门 Linux 来用（但是看了眼现在树莓派的售价，我想应该不会有初学者买树莓派入门 Linux 了）。</p>
<p>后来买了 NanoPi R4S 软路由，它内置了一个 RK3399 CPU，虽然也是 ARM 架构但是性能对于软路由来说很够用了，当时也是给它折腾了 Arch Linux ARM 系统还依次尝试了 <code>systemd-networkd</code> 和 <code>netctl</code> 给它的两个网口配置路由，但折腾归折腾，这种方式并不稳定，在日常使用过程中经常遇到重启路由器后上不去网的情况，还要手动 SSH 到路由器中再重启一次网络服务和防火墙才恢复。虽然手动改网络组件的配置文件的方式配置个路由器更底层些，这个过程也能更好的体会到路由器的一些原理，但是这种方案并不适合长期日常使用，我可不希望动不动家里路由器莫名其妙就断网了还要手动 SSH 到系统里查一堆日志后才能重连网络。</p>
<p>之后我把手里的 R4S 卖掉了，因为 NanoPi 新发布了性价比更高且功耗更低的，拥有俩 2.5G LAN 网口，还有一个 M2 插槽和内置了 8G 闪存的 R5S。我给它安装了更适合路由器使用的基于 OpenWRT 构建的 FriendlyWRT 系统，这个系统内置了 Docker 和一些常用的应用（网络共享、Aria2、硬盘自动休眠之类的），在把光猫改桥接后，用它来做我的主路由器。然后把手里一块空闲的 2T 移动硬盘连接到路由器上，设置了 OpenWRT 的网络共享 (Samba) 服务后，实验性的当作我的 NAS 来使用。</p>
<p>之所以是“实验性”的“NAS”，是因为我不确定 USB 连接移动硬盘的方式是否稳定，因为移动硬盘对供电有一定要求，我并不确定路由器的 USB 接口能否稳定的为硬盘供电，就算连接一块硬盘供电够用的话，我不确定连接两块以上的硬盘组磁盘阵列还能不能带得动，尽管这个问题能通过一根 USB 供电线来解决，但是我那半个巴掌大小的路由器上面既要插三根巨粗无比的 7 类网线，又要插硬盘和 USB 供电线，还要再占用一个插座插一个手机充电器给移动硬盘供电，这也太混乱了点，毕竟机械硬盘在读写过程中很怕震动，我在插拔网线或者插座上其他的电器时都避免不了的会对那块移动硬盘产生震动。</p>
<p>然后路由器毕竟是路由器，你即要它负责整个家庭几十个网络设备的路由功能，又要跑 Samba 服务器，还要往里面装一些“上网插件”的话，对 CPU 的性能还是有一定要求的。因为我的路由器和电脑都有 2.5G 网口，所以我实际测试过当通过 Samba 拷贝文件的速度接近于 200MB/S 时，路由器的 CPU 4个核心就会全跑到 100%，然后拷贝就卡住了，时间久了文件就拷贝失败了（然后我不得不又设置了 QoS 把 2.5G 的网口限速成千兆网口）。</p>
<p>所以最好的办法还是把 NAS 和路由器分开，路由器就用来做路由器该做的事情，NAS 就做 NAS 该做的事情。</p>
<blockquote>
<p>以上就是我组装 NAS 的整个心路历程，如果觉得上面这一大堆太磨叽的话，直接看下面就好了。</p>
</blockquote>
<hr>
<h2 id="配置清单">配置清单</h2>
<ul>
<li>机箱：乔思伯 N1</li>
<li>主板：映泰 B550T-SILVER ITX</li>
<li>CPU：AMD R7 5700G</li>
<li>内存：英睿达 8G 2666</li>
<li>固态：闪迪 500G NVME</li>
<li>硬盘：东芝 MG08ACA16TE * 1</li>
<li>电源：Tt SFX钢影 450W</li>
<li>其他：乐扩 4 口 2.5G PCIE 网卡</li>
</ul>
<p>系统盘是之前折腾软路由时剩下的一块 500G 的 NVME 固态，除此之外双十一的时候还买了一块东芝的 16T 企业盘。</p>
<hr>
<p>本来想把除硬盘外的整体预算控制在 2K 以内的，但实际上光主板 + CPU 就两千多了……</p>
<p>在深水宝上有更便宜的 5600G + B450 ITX 套装，但是这种来路不明的主板和散片 CPU 尽管便宜了几百块钱但是我也不知道它的 CPU 有没有“锻炼”过，主板有没有换过啥零件，反正我是不敢买。所以挑了好久，决定提高了预算，在狗东买的全新的板 U 套装，选的这个带板载 2.5G 网卡的主板，毕竟我可不想贪小便宜吃大亏。</p>
<p>散热器目前用的是 AMD 盒装 CPU 带的散热，听说这个散热器在拆的时候极有可能会把 CPU 连根拔起，但是我有一个闲置的利民的 itx 散热器放在老家了没拿过来，所以现在只好先用原装的过度一下。</p>
<p>本来是没打算买机械硬盘的，想着先用移动硬盘连在 NAS 上先用一阵子的，不过双十一硬盘便宜了好多，信用卡分三期还能再减 50，所以就先买了一块，估计够我用很长时间的了。</p>
<h2 id="装机">装机</h2>
<p><img loading="lazy" src="images/001.jpg" alt="" />

</p>
<p>映泰的这块板子是不带无线网卡的，但送了一个 WIFI5 的无线网卡，需要手动安装上去，当时废了九牛二虎之力才接上了这两根 SMA 线……。</p>
<p><img loading="lazy" src="images/002.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/003.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/004.jpg" alt="" />

</p>
<p>然后走线的过程其实还挺顺利的，插上主板 24PIN、CPU 8PIN 和 SATA 供电以及风扇、机箱前面板跳线和 USB、音频线之外就完事了，找一些空隙把这些线绑起来就好了，说实话走线的过程可比之前给先马趣造装机容易多了，毕竟少了两根显卡供电线和一堆风扇的电源线还有 RGB 灯的线……</p>
<h2 id="装系统">装系统</h2>
<p>NAS 的系统我采用的是 Arch Linux。首先，选一个 NAS 的系统肯定要优先考虑更适合做服务器的 Linux，其次 FreeNAS 这个系统是基于 FreeBSD/Unix 的，我就是单纯的不想用 BSD 所以就把它排除在方案外了（但是 NAS 里装一个虚拟机跑 FreeNAS 也不是不可以），至于网上总能听到的黑群晖我对这种盗版 + 闭源的系统很反感，所以想都不要想了。提到 Linux 的服务器发行版肯定有人更偏向于 Debian 以及 Debian 衍生的服务器系统以及红帽系列的被经常用在服务器的那些企业常用 Linux 系统，但是我只想用我熟悉的 Arch Linux。Arch Linux 的 Wiki 中有介绍过，Arch Linux 的思维是这个系统并不针对某类应用场景，而是让 Arch 的用户自己配置自己的系统来应用在哪些场景，所以理论上是可以把 Arch Linux 配置成一个适合应用在服务器上的系统，实际上也有 <code>vps2arch</code> 这个“黑魔法”脚本可以一键把 VPS 上已安装的其他 Linux 系统转成 Arch Linux。如果在这里你非要和我较真哪个 Linux 发行版好，哪个 Linux 发行版不好的话，我觉得这并不属于一个技术范围该讨论的问题而是一个哲学问题。</p>
<p>安装教程在 Wiki 上就能找到，这里不再赘述，安装系统时需要装一些网络相关的软件，我配置网络使用的是 <code>netctl</code>，因为觉得 <code>systemd-networkd</code> 不怎么好用，我对 NetworkManager 不怎么熟悉所以就没装这个。然后配置无线连接时还需要用到 <code>wpa_supplicant</code>。</p>
<h3 id="配置网络">配置网络</h3>
<p>我的 NAS 上面一共有 5 个网口，其中一个网口为板载的 2.5G 网口，另外四个网口为 2.5G 的 PCIE 网口，我当初买这个 PCIE 网卡的时候想的是给它配置个桥接当交换机来用，这样只买一块网卡肯定比买个 4 口交换机便宜，因此装系统后配置网络这部分是重头戏，Arch Linux Wiki 上对配置桥接这部分只是简单介绍了几句就完事了，所以这部分我足足花了两个晚上才全部搞定。</p>
<p>首先创建 <code>netctl</code> 的配置文件 <code>/etc/netctl/bridge-br0</code> (文件名可以随意修改)，新创建一个虚拟的桥接接口 <code>br0</code>，这个虚拟的桥接网口绑定了上述的5个网口。我打算将板载的网口 (<code>enp9s0</code>) 连接路由器，然后那 4 个 PCIE 网卡的接口 (<code>enp3s0</code> - <code>enp6s0</code>) 用来连接其他网络设备，所以要将 <code>br0</code> 的 MAC 地址设定为 <code>enp9s0</code> 的 MAC 地址。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> cat /etc/netctl/bridge-br0
</span></span><span class="line"><span class="cl"><span class="go">Description=&#34;Example Bridge connection&#34;
</span></span></span><span class="line"><span class="cl"><span class="go">Interface=br0       # 接口的名称
</span></span></span><span class="line"><span class="cl"><span class="go">Connection=bridge   # 桥接模式
</span></span></span><span class="line"><span class="cl"><span class="go">BindsToInterfaces=(enp9s0 enp6s0 enp5s0 enp4s0 enp3s0)  # 将 br0 绑到 5 个物理网口上
</span></span></span><span class="line"><span class="cl"><span class="go">MACAddress=enp9s0   # 设定 br0 的 MAC 地址与 enp9s0 接口的 MAC 地址一致
</span></span></span><span class="line"><span class="cl"><span class="go">IP=dhcp             # 以 DHCP 的方式为 br0 获取 IP 地址
</span></span></span></code></pre></div><p>除此之外还要配置 <code>enp9s0</code> 接口的配置文件 <code>/etc/netctl/noip-enp9s0</code>，<strong>不要让这个接口自动获取 IP 地址</strong>。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> cat /etc/netctl/noip-enp9s0
</span></span><span class="line"><span class="cl"><span class="go">Description=&#39;Example configuration&#39;
</span></span></span><span class="line"><span class="cl"><span class="go">Interface=enp9s0
</span></span></span><span class="line"><span class="cl"><span class="go">Connection=ethernet
</span></span></span><span class="line"><span class="cl"><span class="go">IP=no
</span></span></span></code></pre></div><p>之后执行以下命令使以上两个配置文件生效。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">#</span> netctl <span class="nb">enable</span> bridge-br0
</span></span><span class="line"><span class="cl"><span class="gp">#</span> netctl start bridge-br0
</span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="gp">#</span> netctl <span class="nb">enable</span> noip-enp9s0
</span></span><span class="line"><span class="cl"><span class="gp">#</span> netctl start noip-enp9s0
</span></span></code></pre></div><p>顺利的话，执行 <code>ip addr</code> 可以看到新增加了一个 <code>br0</code> 网口 （不顺利的话就重启一下，再检查一下除了 <code>netctl</code> 之外是不是有别的配置网络的应用产生了干扰），然后原有的 5 个网口都绑定到了 <code>br0</code> 接口上了（接口的那一行出现了 <code>br0</code>）。</p>
<p>然后 <code>enp9s0</code> 接口正常来讲是不应该从路由器上获取到 IP 地址的了，取而代之的是 <code>br0</code> 接口从路由器的 DHCP 服务器中获取了一个 IP 地址，然后 <code>br0</code> 接口的 MAC 地址和 <code>enp9s0</code> 接口的 MAC 地址都一致才对。</p>
<p>以下是一个简单的栗子，在不考虑 IPv6 的情况 <code>ip a</code> 的输出是类似酱紫的：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> ip addr
</span></span><span class="line"><span class="cl"><span class="go">1: enp3s0: &lt;NO-CARRIER,BROADCAST,MULTICAST,PROMISC,UP&gt; mtu 1500 qdisc fq_codel master br0 state DOWN group default qlen 1000
</span></span></span><span class="line"><span class="cl"><span class="go">    link/ether ab:cd:ef:xx:xx:xx brd ff:ff:ff:ff:ff:ff
</span></span></span><span class="line"><span class="cl"><span class="go">2: enp4s0: &lt;NO-CARRIER,BROADCAST,MULTICAST,PROMISC,UP&gt; mtu 1500 qdisc fq_codel master br0 state DOWN group default qlen 1000
</span></span></span><span class="line"><span class="cl"><span class="go">    link/ether ab:cd:ef:xx:xx:xx brd ff:ff:ff:ff:ff:ff
</span></span></span><span class="line"><span class="cl"><span class="go">    这里 enp3s0 - enp6s0 这四个接口的情况基本一致所以在此省略
</span></span></span><span class="line"><span class="cl"><span class="go">    ......
</span></span></span><span class="line"><span class="cl"><span class="go">5: enp9s0: &lt;BROADCAST,MULTICAST,PROMISC,UP,LOWER_UP&gt; mtu 1500 qdisc fq_codel master br0 state UP group default qlen 1000
</span></span></span><span class="line"><span class="cl"><span class="go">    link/ether f4:bb:22:xx:xx:xx brd ff:ff:ff:ff:ff:ff
</span></span></span><span class="line"><span class="cl"><span class="go">    ......
</span></span></span><span class="line"><span class="cl"><span class="go">6: br0: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc noqueue state UP group default qlen 1000
</span></span></span><span class="line"><span class="cl"><span class="go">    link/ether f4:bb:22:xx:xx:xx brd ff:ff:ff:ff:ff:ff
</span></span></span><span class="line"><span class="cl"><span class="go">    inet 10.10.10.233/24 brd 10.10.10.255 scope global dynamic noprefixroute br0
</span></span></span><span class="line"><span class="cl"><span class="go">       valid_lft 1606601sec preferred_lft 1375801sec
</span></span></span><span class="line"><span class="cl"><span class="go">    ......
</span></span></span></code></pre></div><p>至此，NAS 是可以与路由器下的同一局域网内的其他设备互相访问的，然后检查下 <code>/etc/resolv.conf</code> 如果 DNS 配置正确了的话，也是可以访问到公网的，执行 <code>curl baidu.com</code> 是应该有返回的内容的。</p>
<p>但是我们目前只配置了 NAS 自身的板载网口 (<code>enp9s0</code>) 与 <code>br0</code> 虚拟网口的桥接这部分，现在其他设备通过网线插到 PCIE 网卡的那4个接口是上不去网的。</p>
<p>在计网课程中，老师曾反复强调路由器和交换机的区别，路由器是 OSI 七层模型中的网络层的设备，而交换机是第二层的数据链路层的设备，但光是这么讲的话，死记硬背是能记住这两个设备之间的区别，但这种知识实在过于抽象，很难真正的理解，况且这个“网络模型”是按照已有的网络设备给它拆分成不同的层的，而并不是先制定出了分层的标准然后让设备严格按照这个模型去制作的，所以现在市面上卖的网络设备并没有体现出所谓的分层，不同的网络层之间的界限实际上是很模糊的。在网上搜这方面资料的时候看到有人把路由器称作“3 层交换机”，而常说的那种交换机则称为“2 层交换机”。说实话我也没彻底的搞明白数据链路层和网络层以及路由器和交换机之间的具体区别，不过往简单了说，可以把路由器看成是一个根据 IP 地址在不同的网段之间分发数据的设备，而交换机是通过 MAC 地址，只在一个网段内分发数据的设备，市面上常见的售卖的“路由器”商品实际上是一个真正意义上的路由器 + 交换机 + DHCP服务器和其他组件的组合体，知道这些基本就够用了，再往详细了讲的话我也讲不明白了。</p>
<p>然后修改 PCIE 网卡的 4 个接口 <code>enp3s0</code> - <code>enp6s0</code> 的 MAC 地址和板载网口 <code>enp9s0</code> 的 MAC 地址一致，这样5个网口和虚拟的 <code>br0</code> 网口都使用同一个 MAC 地址，就能实现交换机的功能了，至于为啥要把 5 个网口的 MAC 地址都设置一致这个别问我，我也不到为啥，如果这里有哪些知识点有误，可以评论告诉我。</p>
<p>因为 <code>netctl</code> 好像不支持修改接口 MAC 地址的操作，所以这里还是要用到 <code>systemd-networkd</code> 在开机时自动修改网口的 MAC 地址，在 <code>/etc/systemd/network/</code> 中创建 <code>00-enp3s0.link</code> - <code>00-enp6s0.link</code> 这 4 个配置文件。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"># cat /etc/systemd/network/00-enp3s0.link
</span></span><span class="line"><span class="cl">[Match]
</span></span><span class="line"><span class="cl"># 这个是网口原有的 MAC 地址
</span></span><span class="line"><span class="cl">MACAddress=aa:bb:cc:dd:xx:xx
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">[Link]
</span></span><span class="line"><span class="cl"># 这个是修改后的 MAC 地址
</span></span><span class="line"><span class="cl">MACAddress=f4:bb:22:xx:xx:xx
</span></span><span class="line"><span class="cl">NamePolicy=kernel database onboard slot path
</span></span></code></pre></div><p>确保 <code>/etc/systemd/network/</code> 中没有其他的配置文件后，<code>systemctl enable --now systemd-networkd</code> 启动 <code>systemd-networkd</code>，在重启电脑后 5 个接口的 MAC 地址就都一致了。</p>
<p>至此交换机这部分就配置完了。</p>
<h3 id="配置-samba">配置 Samba</h3>
<p>目前我还没有机械硬盘，只有一个移动硬盘通过 USB 连接到了 NAS 上，目前我使用的是 <code>hd-idle</code> 配置了硬盘的自动启停。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> sudo hd-idle -a /dev/sda -i <span class="m">300</span>
</span></span></code></pre></div><p>然后安装 <code>samba</code>，在 <code>/etc/samba/</code> 目录下创建 <code>smb.conf</code>，具体的过程请参照 <a href="https://wiki.archlinux.org/title/Samba">Wiki</a>。</p>
<p>我把我的 16T 硬盘格式化成 <code>btrfs</code> 后挂载到了 <code>/samba/hdd_16t_1</code> 目录下面，然后对应的 Samba 配置文件为：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">[HDD16T1]
</span></span><span class="line"><span class="cl">  force user = root
</span></span><span class="line"><span class="cl">  comment = HDD 16T 1
</span></span><span class="line"><span class="cl">  path = /samba/hdd_16T_1
</span></span><span class="line"><span class="cl">  valid users = samba
</span></span><span class="line"><span class="cl">  public = no
</span></span><span class="line"><span class="cl">  writable = yes
</span></span><span class="line"><span class="cl">  browsable = yes
</span></span><span class="line"><span class="cl">  printable = no
</span></span><span class="line"><span class="cl">  create mask = 0644
</span></span><span class="line"><span class="cl">  directory mask = 0755
</span></span><span class="line"><span class="cl">  read only = no
</span></span></code></pre></div><p>我打算只在内网访问我的 NAS，我还不打算把它暴露到公网上，所以目前不用太考虑安全的问题。</p>
<p>然后我目前不考虑组 RAID，首先是因为没钱再买硬盘了，其次是 RAID 并不适合作为冗余备份使用，它没办法保证数据的绝对安全，所以如果我要存重要的数据的话，还是要往别的移动硬盘里也拷贝一份的，所以目前来看 RAID 我暂时用不上。</p>
<p>东芝这块盘收货之后，我用 Samba 往里面烤了俩小时文件没遇到失败的情况，速度一直维持在 100MB/s 以上很稳定，至于噪音的话，白天是感觉不出来 NAS 的声音的，晚上因为配置了硬盘自动停转所以只要睡觉时不用它下载东西的话也是听不到声音的。</p>
<h2 id="其他">其他</h2>
<p>之所以买了 8 核 16 线程的 CPU 是因为我除了让它做 Samba 服务器之外还打算在上面跑一些别的服务啥的，目前除了 Samba 之外我在上面跑了 qemu KVM 虚拟机，然后在局域网搞了 Kubernetes 集群，因为公有云价格太贵了我自己租不起长时间的高性能 VPS，所以在本地起几个虚拟机装轻量级的集群用来学习 k8s 还是可以轻松实现的，不过我目前还没想好可以在集群里跑些什么东西。</p>
<p>后续我打算把我的 MineCraft 单机生存的存档也放到 NAS 上面当服务器跑，这样就可以实现一些只有在服务器才能实现的操作了（比如挂个假人 24 小时挂机刷怪之类的）。</p>
<p>以后有时间的话再写个 TeleBot 机器人啥的，用来远程监控 NAS 的状态。</p>
<hr>
<p><strong>STARRY-S</strong></p>]]></content:encoded>
    </item>
    <item>
      <title>食虫植物种植记录 - 2022.11.06</title>
      <link>https://blog.starry-s.moe/posts/2022/carnivorous-plant-221106/</link>
      <pubDate>Sun, 06 Nov 2022 14:58:42 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2022/carnivorous-plant-221106/</guid>
      <description>&lt;p&gt;过了一个多月，多数植物的状态都有好转，期间土瓶草和几个状态很差的茅膏菜挂掉了，猪笼草新结了几个瓶子。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>过了一个多月，多数植物的状态都有好转，期间土瓶草和几个状态很差的茅膏菜挂掉了，猪笼草新结了几个瓶子。</p>
<blockquote>
<p>图片由相机拍完后使用 GIMP 调整了尺寸、色温和曲线。</p>
</blockquote>
<p><img loading="lazy" src="images/1.JPG" alt="" />

</p>
<p><img loading="lazy" src="images/2.JPG" alt="" />

</p>
<p><img loading="lazy" src="images/9.JPG" alt="" />

</p>
<p><img loading="lazy" src="images/3.JPG" alt="" />

</p>
<p><img loading="lazy" src="images/4.JPG" alt="" />

</p>
<p><img loading="lazy" src="images/6.JPG" alt="" />

</p>
<p><img loading="lazy" src="images/5.JPG" alt="" />

</p>
<p><img loading="lazy" src="images/7.JPG" alt="" />

</p>
<p><img loading="lazy" src="images/8.JPG" alt="" />

</p>]]></content:encoded>
    </item>
    <item>
      <title>月姫 -A piece of blue glass moon-</title>
      <link>https://blog.starry-s.moe/posts/2022/tsukihimi/</link>
      <pubDate>Fri, 28 Oct 2022 20:15:42 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2022/tsukihimi/</guid>
      <description>&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;https://blog.starry-s.moe/posts/2022/tsukihimi/images/1.png&#34; alt=&#34;&#34; /&gt;

&lt;/p&gt;
&lt;p&gt;想不懂咱是怎么忍住恐惧在大晚上的玩通关这游戏的公主线的……&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><img loading="lazy" src="/posts/2022/tsukihimi/images/1.png" alt="" />

</p>
<p>想不懂咱是怎么忍住恐惧在大晚上的玩通关这游戏的公主线的……</p>
<meting-js server="netease" type="song" id="1898223599" theme="#233333"></meting-js>
<hr>
<p><img loading="lazy" src="images/2.png" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">呐。那个，让我看看</p>
</p>
<hr>
<meting-js server="netease" type="song" id="1898231769" theme="#233333"></meting-js>
<p><img loading="lazy" src="images/3.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/4.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/12.jpg" alt="" />

</p>
<hr>
<meting-js server="netease" type="song" id="1898230877" theme="#233333"></meting-js>
<p><img loading="lazy" src="images/05.png" alt="" />

</p>
<p><img loading="lazy" src="images/09.jpg" alt="" />

</p>
<hr>
<meting-js server="netease" type="song" id="1898231765" theme="#233333"></meting-js>
<p><img loading="lazy" src="images/11.png" alt="" />

</p>
<p><img loading="lazy" src="images/07.png" alt="" />

</p>
<p><img loading="lazy" src="images/06.png" alt="" />

</p>
<p><img loading="lazy" src="images/08.png" alt="" />

</p>
<hr>
<p><img loading="lazy" src="images/10.png" alt="" />

</p>
<blockquote>
<p><a href="https://tsukihimecn.github.io/">https://tsukihimecn.github.io/</a></p>
</blockquote>]]></content:encoded>
    </item>
    <item>
      <title>yuzu - Switch 虚拟机</title>
      <link>https://blog.starry-s.moe/posts/2022/switch-emulator/</link>
      <pubDate>Sun, 16 Oct 2022 15:48:46 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2022/switch-emulator/</guid>
      <description>&lt;p&gt;一直想玩的游戏没有官方汉化，Switch 的游戏文件没破解的话是没办法装汉化插件的，尽管咱有 Switch OLED 港版主机，但是破解有被封的可能而且过程过于麻烦，破解后就不能联网了，所以咱在买了正版游戏但是看不懂日语后无奈之下选择了虚拟机，正好水一篇博客。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>一直想玩的游戏没有官方汉化，Switch 的游戏文件没破解的话是没办法装汉化插件的，尽管咱有 Switch OLED 港版主机，但是破解有被封的可能而且过程过于麻烦，破解后就不能联网了，所以咱在买了正版游戏但是看不懂日语后无奈之下选择了虚拟机，正好水一篇博客。</p>
<meting-js server="netease" type="song" id="590443" theme="#233333"></meting-js>
<p>目前有两个虚拟机可选，一个是 由 C# 编写的 <a href="https://github.com/Ryujinx/Ryujinx">Ryujinx</a>，另一个是由 C++ 编写的 <a href="https://github.com/yuzu-emu/yuzu">yuzu</a>，这俩都是开源软件而且都能在 Linux 上运行，咱选的是后者。</p>
<hr>
<p>yuzu 提供的教程需要短接器和一张TF卡以及 switch 本体，从 switch 中获取 <code>prod.keys</code> 和 <code>title.keys</code> （非必须），喜欢折腾的可以买个短接器慢慢鼓捣，教程链接：<a href="https://yuzu-emu.org/help/quickstart/">https://yuzu-emu.org/help/quickstart/</a>。如果懒得折腾的话从网上找个现成的 key 文件也能用。</p>
<p>Arch Linux 可以从 archlinuxcn 源或者 AUR 中获取。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo pacman -S yuzu-git
</span></span></code></pre></div><p>第一次启动时会提示缺少 keys，可先忽略，选择左上角 <code>File-&gt;Open yuzu Folder</code>，新建一个 <code>keys</code> 文件夹，将 <code>prod.keys</code> 复制进去后重启软件。</p>
<p>yuzu 可以在不手动安装系统的情况下运行游戏，所以添加了游戏所在的文件夹就能玩了。</p>
<p><img loading="lazy" src="images/1.png" alt="" />

</p>
<p>然后就是在设置里面改一下手柄的按键布局，因为用习惯了 Xbox 手柄，所以咱把 A 和 B 位置对调了一下。</p>
<p><img loading="lazy" src="images/2.png" alt="" />

</p>
<hr>
<p>后续：</p>
<p>Yuzu 2023-05-21 之后的版本对宝可梦做了些优化导致玩月姬时文字花屏乱码，玩月姬的话不要下载 2023-05-21 (1440) 之后的版本。</p>
<p>然后 Arch Linux 最近滚完系统后遇到了 yuzu 的 Qt 启动失败报错：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">Warning: Ignoring XDG_SESSION_TYPE=wayland on Gnome. Use QT_QPA_PLATFORM=wayland to run on Wayland anyway.
</span></span><span class="line"><span class="cl">qt.qpa.plugin: Could not find the Qt platform plugin &#34;wayland&#34; in &#34;&#34;
</span></span><span class="line"><span class="cl">This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Available platform plugins are: xcb.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">zsh: IOT instruction (core dumped)  yuzu
</span></span></code></pre></div><p>查了一阵子找到的解决办法是设置 <code>QT_PLUGIN_PATH=/usr/lib/qt/plugins</code> 环境变量，覆盖掉 yuzu 安装包自带的 Qt 插件而是改用系统的。</p>]]></content:encoded>
    </item>
    <item>
      <title>食虫植物回坑小记</title>
      <link>https://blog.starry-s.moe/posts/2022/carnivorous-plants-1/</link>
      <pubDate>Sun, 18 Sep 2022 13:20:04 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2022/carnivorous-plants-1/</guid>
      <description>&lt;p&gt;重新捡起了弃坑多年的食虫植物系列。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;多图预警。&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <content:encoded><![CDATA[<p>重新捡起了弃坑多年的食虫植物系列。</p>
<blockquote>
<p>多图预警。</p>
</blockquote>
<hr>
<p>咱其实很久以前就入坑了食虫植物，最早可以追溯到我念小学的时候，当时克服重重困难就为了养一两颗草，但是都没怎么养活过。后来念大学的时候因为许多原因不方便养所以弃坑了几年，大一还没有疫情的时候在暑假去了上海和杭州，大中午的顶着炎炎烈日坐了好几个小时的公交车从杭州市区到桐乡的“小虫草堂”实地参观了一番，当时拍了好多照片（感兴趣的可以看看<a href="/posts/2019/shanghai-2/">这篇博客</a>），疫情之后就再也没能去远一点的地方旅行过（两次实习除外），后来食虫植物这个玩意也逐渐被我抛在脑后很长时间没有去想过它。</p>
<p>最近由于一些机缘巧合在研究地理学的太阳高度角与经纬度和节气之间的关系时（确信），无意间想起了我曾经养过的这群对生存条件要求非常苛刻的食虫植物，这个念头挥之不去，加上现在有了一定的养这玩意的基础，于是又重新入坑了这个系列。</p>
<hr>
<p>以下的图片都是植物邮寄到手缓了四五天后用相机拍的直出照片，没调颜色，没裁剪，只对图片压缩了尺寸。光照用的是全光谱的植物补光灯，因为是新到的小苗，很多植物状态并不太好还伴随着许多叶片的枯萎，因为懒加上没有镊子，所以有些植物的枯萎叶子没有修剪。后续如果养的好的话，也许会出新的系列专门记录一下。</p>
<p><img loading="lazy" src="images/1.JPG" alt="&ldquo;布凯茅膏菜&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">布凯茅膏菜</p>
</p>
<p><img loading="lazy" src="images/2.JPG" alt="&ldquo;某不知名的茅膏菜&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">某不知名的茅膏菜</p>
</p>
<p><img loading="lazy" src="images/3.JPG" alt="&ldquo;布凯茅膏菜&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">布凯茅膏菜</p>
</p>
<p><img loading="lazy" src="images/4.JPG" alt="&ldquo;新种的活水苔（左）和不知名的瓶子草（右）&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">新种植的活水苔（左）和不知名的瓶子草（右）</p>
</p>
<p><img loading="lazy" src="images/5.JPG" alt="&ldquo;白瓶子草&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">白瓶子草</p>
</p>
<p><img loading="lazy" src="images/6.JPG" alt="&ldquo;土瓶草&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">土瓶草</p>
</p>
<p><img loading="lazy" src="images/7.JPG" alt="&ldquo;布凯茅膏菜&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">布凯茅膏菜</p>
</p>
<p><img loading="lazy" src="images/8.JPG" alt="&ldquo;Fake Dracula&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">Fake Dracula</p>
</p>
<p><img loading="lazy" src="images/9.JPG" alt="&ldquo;爱斯捕虫堇&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">爱斯捕虫堇</p>
</p>
<p>后面还有几棵猪笼草，但是瓶子基本都枯萎了只剩绿油油的大叶子，所以就不拍照片了，等以后如果养活了再水一篇新的博客吧。</p>]]></content:encoded>
    </item>
    <item>
      <title>FinalFantasy VII Remake 游戏记录</title>
      <link>https://blog.starry-s.moe/posts/2022/finalfantasy-vii/</link>
      <pubDate>Sat, 20 Aug 2022 20:09:04 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2022/finalfantasy-vii/</guid>
      <description>&lt;p&gt;前一阵子通关了最终幻想7重置版，放几张截图到这里水一期博客。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;严重剧透警告&lt;/strong&gt;：如不想被剧透请勿阅读本篇内容！&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <content:encoded><![CDATA[<p>前一阵子通关了最终幻想7重置版，放几张截图到这里水一期博客。</p>
<blockquote>
<p><strong>严重剧透警告</strong>：如不想被剧透请勿阅读本篇内容！</p>
</blockquote>
<meting-js server="netease" type="song" id="1487507457" theme="#233333"></meting-js>
<hr>
<p><img loading="lazy" src="images/000.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">卖花的大姐姐</p>
</p>
<p><img loading="lazy" src="images/001.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">Tifa</p>
</p>
<p><img loading="lazy" src="images/002.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/003.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/004.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/005.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/006.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/007.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/008.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/009.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">Aerith 的千层套路</p>
</p>
<p><img loading="lazy" src="images/010.jpg" alt="" />

</p>
<hr>
<meting-js server="netease" type="song" id="1487507471" theme="#233333"></meting-js>
<p><img loading="lazy" src="images/011.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">Reunited</p>
</p>
<p><img loading="lazy" src="images/012.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">云片好帅</p>
</p>
<p><img loading="lazy" src="images/013.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/014.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/015.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">穿这么少的衣服，干的却是正经生意</p>
</p>
<hr>
<meting-js server="netease" type="song" id="1487508995" theme="#233333"></meting-js>
<p><img loading="lazy" src="images/016.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/017.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">可爱捏</p>
</p>
<p><img loading="lazy" src="images/018.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">爱丽丝老套路了</p>
</p>
<p><img loading="lazy" src="images/019.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">比我想得还要可爱捏</p>
</p>
<p><img loading="lazy" src="images/020.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/021.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/022.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/023.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/024.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/025.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/026.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/027.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/028.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/029.jpg" alt="" />

</p>
<hr>
<meting-js server="netease" type="song" id="1818030780" theme="#233333"></meting-js>]]></content:encoded>
    </item>
    <item>
      <title>移动GM220-S光猫改桥接小记</title>
      <link>https://blog.starry-s.moe/posts/2022/gm220-s-bridge-mode/</link>
      <pubDate>Thu, 07 Jul 2022 17:54:35 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2022/gm220-s-bridge-mode/</guid>
      <description>&lt;p&gt;最近搬了新的住处，用的是移动的宽带，因此尝试着把移动的光猫改成桥接，接到我的软路由上面。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>最近搬了新的住处，用的是移动的宽带，因此尝试着把移动的光猫改成桥接，接到我的软路由上面。</p>
<p>因为我不知道PPPoE拨号的帐号和密码，尽管这个光猫的超级密码网上一搜就能找到，但是我不知道PPPoE帐号的密码就算改了桥接也没办法拨号。
于是先借助网上能搜到的资料尝试把宽带帐号的密码给搞出来。</p>
<hr>
<p>一开始尝试着登录光猫的后台页面，在设置拨号上网的页面那里F12大法，把<code>input</code>元素的<code>type=&quot;password&quot;</code>改成<code>type=&quot;text&quot;</code>，
但是发现这里预填写的密码已经是加密过的<code>******</code>，因此这个方法行不通。</p>
<p>然后看教程有说尝试打开光猫的telnet，把配置文件用ftp传出来，但是试了一下所有的尝试打开telnet的方式在这个光猫上都不好使，于是这个方法也行不通。</p>
<p>之后在光猫登录管理员帐号之后，在“管理-&gt;设备管理-&gt;USB备份配置&quot;这里找到了可以把配置文件备份到U盘的地方，
于是找了一张空内存卡格式化成<code>FAT32</code>，放读卡器里插在光猫上，把配置文件备份到U盘上。</p>
<p><img loading="lazy" src="images/1.png" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">备份至USB</p>
</p>
<blockquote>
<p>这里备份之后不要立即把U盘拔下来，貌似光猫在备份完配置文件后没有立即把数据<code>sync</code>到U盘中，需要等一阵子再拔U盘。
等多久我也不确定，反复试几次直到U盘上出现了<code>e8_Config_Backup</code>文件夹就可以了。</p>
</blockquote>
<p>然后下载<a href="http://www.nirsoft.net/utils/router_password_recovery.html#DownloadLinks">RouterPassView</a>，
用这个工具打开配置文件，就可以找到里面光猫上的所有配置了，包括宽带帐号和密码。</p>
<p>之后在网络设置里面改桥接，就可以用软路由拨号上网辣。</p>
<hr>
<p>使用了一下移动的宽带发现貌似他们把所有的ICMP的ECHO回显请求屏蔽掉了，所以尝试ping任何IP都是不通的。别的貌似没什么问题。</p>]]></content:encoded>
    </item>
    <item>
      <title>在Arch Linux上配置软路由</title>
      <link>https://blog.starry-s.moe/posts/2022/archlinux-router/</link>
      <pubDate>Wed, 08 Jun 2022 00:49:34 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2022/archlinux-router/</guid>
      <description>&lt;p&gt;之前买了个NanoPi R4S，当时给他装了Arch Linux ARM并用&lt;code&gt;systemd-networkd&lt;/code&gt;配置了一个简易的软路由。不过&lt;code&gt;systemd-networkd&lt;/code&gt;不支持PPPoE，所以当时我是把R4S接在租的房子的主路由下做子路由的，然后再给R4S接了一个小米路由器当作无线AP。最近从北京搬回家了所以想直接使用R4S做家里的主路由，因为&lt;a href=&#34;https://blog.starry-s.moe/posts/2022/nanopi-r4s/&#34;&gt;R4S上手体验&lt;/a&gt;的那篇文章已经写完很久了，所以就不打算在那篇博客上做修改了，而是新开（水）了一篇博客。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>之前买了个NanoPi R4S，当时给他装了Arch Linux ARM并用<code>systemd-networkd</code>配置了一个简易的软路由。不过<code>systemd-networkd</code>不支持PPPoE，所以当时我是把R4S接在租的房子的主路由下做子路由的，然后再给R4S接了一个小米路由器当作无线AP。最近从北京搬回家了所以想直接使用R4S做家里的主路由，因为<a href="/posts/2022/nanopi-r4s/">R4S上手体验</a>的那篇文章已经写完很久了，所以就不打算在那篇博客上做修改了，而是新开（水）了一篇博客。</p>
<blockquote>
<p>这里偷偷骂一下长城宽带没人反对吧</p>
</blockquote>
<h2 id="准备工作">准备工作</h2>
<p>按照Arch Wiki的<a href="https://wiki.archlinux.org/title/Router#Connection_sharing">Router页面</a>，你的电脑需要符合安装Arch Linux的基础硬件要求，且至少具备俩物理网口。</p>
<p>个人觉得软路由没必要非得刷*WRT或者其他路由器专用系统，也没必要搞个爱快群辉什么的系统，我只想给他装我喜欢的发行版，然后我自己配置我需要的服务，只要有两个以上的物理网口就可以配置路由功能，给他们配置DHCP和流量转发就完事了，这样搞出来的路由器更符合咱自己的需求，相对来讲也更灵活一些，不用受限于那些路由器/NAS定制的系统，而缺点则是比较折腾，有可能不稳定。</p>
<p>安装系统的步骤咱跳过不讲了，Wiki上有的东西没必要在这里重复一遍。</p>
<h2 id="配置ip地址">配置IP地址</h2>
<p>首先，将你电脑的两个物理网口一个用作WAN口（连接广域网），一个用作LAN口（连接局域网），有需要的可以自行修改网口的名称（通常默认的网卡名字为<code>eth*</code>，或者<code>enp*s*</code>）。
为了和Wiki同步，这里假设WAN口的名字为<code>extern0</code>，用来指连接到广域网的网口，LAN口的名字为<code>intern0</code>，代指连接到局域网的网口。</p>
<p>本篇使用<code>netctl</code>配置网络，在修改配置文件之前，需要先停掉其他配置网络的服务。</p>
<p>给LAN口配置一个静态IP地址。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"># /etc/netctl/intern0-profile
</span></span><span class="line"><span class="cl"># Config file for intern0 (LAN)
</span></span><span class="line"><span class="cl">Description=&#39;Private Interface. (LAN)&#39;
</span></span><span class="line"><span class="cl">Interface=intern0
</span></span><span class="line"><span class="cl">Connection=ethernet
</span></span><span class="line"><span class="cl">IP=&#39;static&#39;
</span></span><span class="line"><span class="cl">Address=(&#39;10.10.10.1/24&#39;)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">IP6=&#39;static&#39;
</span></span><span class="line"><span class="cl">Address6=(&#39;fdaa:aaaa:bbbb::0001/64&#39;)
</span></span><span class="line"><span class="cl">SkipNoCarrier=yes
</span></span></code></pre></div><p>以上配置将为LAN口设定IPv4的地址为<code>10.10.10.1</code>，IPv6的地址为<code>fdaa:aaaa:bbbb::0001</code>。
你可以给这个网口设定任意的局域网IP地址，通常为<code>10.*</code>，<code>172.*</code>，<code>192.168.*</code>这些网段的任意一个地址，
IPv6的局域网网段为<code>fd00::/8</code>，通俗一点讲就是<code>fd**</code>开头的一般都是局域网的IP地址。</p>
<p>之后给WAN口配置DHCP或PPPoE协议。</p>
<blockquote>
<p>配置DHCP的方式自行翻Wiki或者看example，这里不重复讲了。</p>
</blockquote>
<p>在配置PPPoE之前需要安装<code>ppp</code>。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"># /etc/netctl/extern0-profile
</span></span><span class="line"><span class="cl"># Config file for public interface (WAN)
</span></span><span class="line"><span class="cl">Description=&#39;Public Interface. (WAN)&#39;
</span></span><span class="line"><span class="cl">Interface=extern0
</span></span><span class="line"><span class="cl">Connection=pppoe
</span></span><span class="line"><span class="cl">User=&#39;username&#39;
</span></span><span class="line"><span class="cl">Password=&#39;samplepasswd&#39;
</span></span><span class="line"><span class="cl"># IP6=stateless
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Always keep a connection established
</span></span><span class="line"><span class="cl">ConnectionMode=&#39;persist&#39;
</span></span></code></pre></div><p>使用以下命令启动<code>netctl</code>的配置文件。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">netctl enable intern0-profile
</span></span><span class="line"><span class="cl">netctl enable extern0-profile
</span></span></code></pre></div><p>重启路由器，将WAN口与光猫的网口连接，使用<code>ip addr</code>查看网络设备的IP地址，顺利的话，可以看到一个名为<code>ppp0</code>的网口，并获取了一个运营商分给你的IP地址。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">1: intern0: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc fq_codel state UP group default qlen 1000
</span></span><span class="line"><span class="cl">    link/ether fa:97:da:d8:9d:8a brd ff:ff:ff:ff:ff:ff
</span></span><span class="line"><span class="cl">    inet 10.10.10.1/24 brd 10.10.10.255 scope global intern0
</span></span><span class="line"><span class="cl">       valid_lft forever preferred_lft forever
</span></span><span class="line"><span class="cl">    inet6 fdaa:aaaa:bbbb::1/64 scope global nodad
</span></span><span class="line"><span class="cl">       valid_lft forever preferred_lft forever
</span></span><span class="line"><span class="cl">    inet6 fe80::f897:daff:fed8:9d8a/64 scope link
</span></span><span class="line"><span class="cl">       valid_lft forever preferred_lft forever
</span></span><span class="line"><span class="cl">2: extern0: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc mq state UP group default qlen 1000
</span></span><span class="line"><span class="cl">    link/ether ca:1f:4a:9b:29:df brd ff:ff:ff:ff:ff:ff
</span></span><span class="line"><span class="cl">    inet6 fe80::c81f:4aff:fe9b:29df/64 scope link
</span></span><span class="line"><span class="cl">       valid_lft forever preferred_lft forever
</span></span><span class="line"><span class="cl">3: ppp0: &lt;POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP&gt; mtu 1492 qdisc fq_codel state UNKNOWN group default qlen 3
</span></span><span class="line"><span class="cl">    link/ppp
</span></span><span class="line"><span class="cl">    inet 123.123.123.123 peer 123.123.123.1/32 scope global ppp0
</span></span><span class="line"><span class="cl">       valid_lft forever preferred_lft forever
</span></span><span class="line"><span class="cl">    inet6 240e:aaaa:bbbb:cccc:::eeee/64 scope global dynamic mngtmpaddr
</span></span><span class="line"><span class="cl">       valid_lft 259132sec preferred_lft 172732sec
</span></span><span class="line"><span class="cl">    inet6 fe80::aaaa:bbbb:cccc:dddd peer fe80::aaaa:bbbb:cccc:dddd/128 scope link
</span></span><span class="line"><span class="cl">       valid_lft forever preferred_lft forever
</span></span></code></pre></div><p>如果遇到了问题，可以使用<code>systemctl status netctl@extern0\\x2dprofile.service</code>查看一下错误信息。
如果是认证失败的话，重启几次这个service说不定就好了。</p>
<h2 id="配置dns和dhcp">配置DNS和DHCP</h2>
<p>安装<code>dnsmasq</code>，编辑<code>/etc/dnsmasq.conf</code>。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"># Setup listen address
</span></span><span class="line"><span class="cl">listen-address=10.10.10.1,127.0.0.1
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Do not read /etc/resolv.conf
</span></span><span class="line"><span class="cl">no-resolv
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Use following dns servers
</span></span><span class="line"><span class="cl">server=114.114.114.114
</span></span><span class="line"><span class="cl">server=8.8.8.8
</span></span><span class="line"><span class="cl">server=8.8.4.4
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Bind interface
</span></span><span class="line"><span class="cl">interface=intern0
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Setup domain
</span></span><span class="line"><span class="cl">expand-hosts
</span></span><span class="line"><span class="cl">domain=foo.bar
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Setup IPv4 DHCP
</span></span><span class="line"><span class="cl">dhcp-range=10.10.10.100,10.10.10.255,255.255.255.0,12h
</span></span><span class="line"><span class="cl"># Setup IPv6 DHCP
</span></span><span class="line"><span class="cl">dhcp-range=fdaa:aaaa:bbbb::000a, fdaa:aaaa:bbbb::ffff, 64, 12h
</span></span></code></pre></div><p>使用<code>systemctl enable --now dnsmasq.service</code>启动<code>dnsmasq</code>，
之后重启路由器，使用网线连接将电脑连接到路由器的LAN口，顺利的话可以自动获取一个IP地址。</p>
<p>如果没获取到IP地址的话，有可能是DHCP服务器的问题，先尝试在电脑上手动设置一个IP地址，之后尝试ping路由器的IP（<code>10.10.10.1</code>）。
如果还是无法连接到路由器的话，就需要重新检查一下路由器的配置了。</p>
<h2 id="网络共享">网络共享</h2>
<p>首先<a href="https://wiki.archlinux.org/title/Internet_sharing#Enable_packet_forwarding">参照Wiki</a>，开启数据包转发的功能。</p>
<p>之后安装<code>iptables</code>，配置ipv4和ipv6的流量伪装。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">iptables -A FORWARD -i intern0 -j ACCEPT
</span></span><span class="line"><span class="cl">iptables -A FORWARD -o intern0 -j ACCEPT
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">iptables -t nat -A POSTROUTING -o ppp0 -j MASQUERADE
</span></span><span class="line"><span class="cl">iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
</span></span><span class="line"><span class="cl">iptables -A FORWARD -i intern0 -o ppp0 -j ACCEPT
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">iptables -t mangle -A FORWARD -o ppp0 -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">ip6tables -t nat -A POSTROUTING -o ppp0 -j MASQUERADE
</span></span></code></pre></div><p>之后可使用<code>iptables-save -f /etc/iptables/iptables.rules</code>和
<code>ip6tables-save -f /etc/iptables/ip6tables.rules</code>将ip桌子的规则保存下来。</p>
<h2 id="done">Done</h2>
<p>以上配置完成后，按理来说路由器就已经配置好了。</p>
<p>调试的过程为首先在路由器上尝试ping一个广域网的域名或IP地址（<code>8.8.8.8</code>），之后将路由器与电脑用网线连接，
电脑应当通过DHCP自动获取到一个随机的IP地址。
之后在电脑上尝试打开一些理应能打开的网站，应该是能打开的。</p>
<p>如果能电脑可以ping通一个广域网的IP，但是打不开网站的话，就检查一下路由器DNS配置，
如果路由器上能ping通一个广域网的IP，但电脑连IP地址都ping不通，那就去检查一下ip桌子的流量伪装规则，检查一下网口名字有没有写对之类的。</p>
<p>之后如果一切都调试成功的话，就可以把家里的无线路由器改成“有线中继”模式了，这样家里的无线路由器将只作为一个无线AP使用，路由的功能将全部由刚刚配置好的软路由实现。</p>
<p>配置好“有线中继”模式后，电脑连接无线WIFI后获得的IP地址应当是软路由分配的IP地址，网段为刚刚咱们设置的<code>10.10.10.*</code>，
而不再是<code>192.168.*</code>的IP地址了。</p>]]></content:encoded>
    </item>
    <item>
      <title>NanoPi R4S上手 &amp; 安装Arch Linux ARM</title>
      <link>https://blog.starry-s.moe/posts/2022/nanopi-r4s/</link>
      <pubDate>Fri, 13 May 2022 00:32:40 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2022/nanopi-r4s/</guid>
      <description>&lt;p&gt;前两天下单了个Nano Pi R4S，4G内存的版本。通常情况下这玩意别人都把他当软路由用，但是今天咱收到货后想了一会拍大腿一寻思这玩意不就是个ARM架构的小电脑嘛~&lt;/p&gt;
&lt;p&gt;所以咱暂时先不打算给这玩意装OpenWRT或 *WRT这类的路由器系统了，而是把它当成一个超小号的带俩网口的mini主机折腾。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>前两天下单了个Nano Pi R4S，4G内存的版本。通常情况下这玩意别人都把他当软路由用，但是今天咱收到货后想了一会拍大腿一寻思这玩意不就是个ARM架构的小电脑嘛~</p>
<p>所以咱暂时先不打算给这玩意装OpenWRT或 *WRT这类的路由器系统了，而是把它当成一个超小号的带俩网口的mini主机折腾。</p>
<meting-js server="netease" type="song" id="19563215" theme="#233333"></meting-js>
<h2 id="开箱">开箱</h2>
<p><img loading="lazy" src="images/nanopi_1.jpg" alt="&ldquo;USB 3.0、SD卡插槽以及三脚架接口&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">USB 3.0、SD卡插槽以及三脚架接口</p>
</p>
<p><img loading="lazy" src="images/nanopi_2.jpg" alt="&ldquo;供电接口和网口&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">供电接口和网口</p>
</p>
<p><img loading="lazy" src="images/nanopi_3.jpg" alt="&ldquo;正面&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">正面</p>
</p>
<h2 id="拆解">拆解</h2>
<p>咱收到货后第一件事就是找螺丝刀和塑料卡片把这漏油器拆开看看（</p>
<p><img loading="lazy" src="images/nanopi_7.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">主板正面</p>
</p>
<p>芯片的布局可以在<a href="https://www.friendlyelec.com/index.php?route=product/product&amp;product_id=284">R4S的商品页面</a>查到，<a href="https://wiki.friendlyelec.com/wiki/index.php/NanoPi_R4S">官方Wiki</a>上也有更多关于R4S的介绍。</p>
<h2 id="arch-linux-arm">Arch Linux ARM</h2>
<p>在Arch Linux ARM (简称alarm) 官网上没找到对R4S的官方的支持，简单搜了一下armbian有对R4S的官方支持。</p>
<p>因为用惯了<strong>滚动更新</strong>发行版，所以不想用*bian系统，而*WRT系统的软件包相对其他发行版而言更少一些，系统也相当于被魔改过，所以除了做漏油器之外几乎干不了别的，所以这是我想安装Arch Linux的理由。</p>
<p>然后咱搜到了一篇给<a href="https://gist.github.com/larsch/a8f13faa2163984bb945d02efb897e6d">NanoPi R2S安装alarm的教程</a>，评论里有人提到了给R4S安装也是可以的。</p>
<p>所以咱大致把这个教程翻译一下，再修改一些R2S和R4S在安装时的区别。</p>
<p>以下内容需结合alarm的 <a href="https://archlinuxarm.org/platforms/armv8/generic">aarch64通用安装教程</a>食用，像更新pacman-key，ssh的密码之类的部分咱就不在这里重复了。</p>
<h3 id="准备sd卡">准备SD卡</h3>
<ol start="0">
<li>
<p>下载armbian的镜像，下载链接自行谷歌。</p>
<blockquote>
<p>通常下载好的文件是<code>xz</code>格式的压缩文件，需要使用<code>unxz</code>解压成<code>img</code>镜像。</p>
</blockquote>
</li>
<li>
<p>将armbian镜像的<code>bootloader</code>和<code>uboot</code>(32-32767区块的部分)用<code>dd</code>写到SD卡中：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="cl"><span class="c1"># Clean the sector before 32</span>
</span></span><span class="line"><span class="cl"><span class="n">dd</span> <span class="k">if</span><span class="o">=/</span><span class="n">dev</span><span class="o">/</span><span class="n">zero</span> <span class="n">of</span><span class="o">=/</span><span class="n">dev</span><span class="o">/</span><span class="n">sdX</span> <span class="n">bs</span><span class="o">=</span><span class="mi">1</span><span class="n">M</span> <span class="n">count</span><span class="o">=</span><span class="mi">32</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Write uBoot and bootloader</span>
</span></span><span class="line"><span class="cl"><span class="n">dd</span> <span class="k">if</span><span class="o">=</span><span class="n">Armbian_</span><span class="o">*.</span><span class="n">img</span> <span class="n">of</span><span class="o">=/</span><span class="n">dev</span><span class="o">/</span><span class="n">sdX</span> <span class="n">skip</span><span class="o">=</span><span class="mi">31</span> <span class="n">seek</span><span class="o">=</span><span class="mi">31</span> <span class="n">bs</span><span class="o">=</span><span class="mi">512</span> <span class="n">count</span><span class="o">=</span><span class="mi">32736</span>
</span></span></code></pre></div><blockquote>
<p>其实可以直接用<code>dd</code>把armbian的整个镜像写到内存卡中然后插入R4S开机，第一次开机后他会自动重新给内存卡分区，然后只需把<code>/dev/sdX1</code>格式化成ext4就能安装alarm了。</p>
</blockquote>
</li>
<li>
<p>使用<code>fdisk</code>给内存卡分区并格式化文件系统</p>
<p>创建分区时先按<code>o</code>创建个MBR分区表，然后按<code>n</code>添加分区。第一个分区的起始区块(sector)需要设置为32768，通常情况下分一个区就够用了，或者你可以像我这样分俩区，一个给swap，不过实际没啥必要。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">Disk /dev/mmcblk1: 29.72 GiB, 31914983424 bytes, 62333952 sectors
</span></span><span class="line"><span class="cl">Units: sectors of 1 * 512 = 512 bytes
</span></span><span class="line"><span class="cl">Sector size (logical/physical): 512 bytes / 512 bytes
</span></span><span class="line"><span class="cl">I/O size (minimum/optimal): 512 bytes / 512 bytes
</span></span><span class="line"><span class="cl">Disklabel type: dos
</span></span><span class="line"><span class="cl">Disk identifier: 0x33fc535e
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Device         Boot    Start      End  Sectors  Size Id Type
</span></span><span class="line"><span class="cl">/dev/mmcblk1p1         32768 53944319 53911552 25.7G 83 Linux
</span></span><span class="line"><span class="cl">/dev/mmcblk1p2      53944320 62333951  8389632    4G 82 Linux swap / Solaris
</span></span></code></pre></div><p>创建完分区后，把<code>root</code>分区<code>mkfs.ext4</code>格式化成ext4，swap分区用<code>mkswap</code>格式化。</p>
</li>
<li>
<p>解压alarm系统文件到root分区中</p>
</li>
<li>
<p>复制并替换armbian的<code>/boot</code>中的文件到新建分区的<code>/boot</code>文件夹中。</p>
</li>
<li>
<p>编辑<code>/boot/armbianEnv.txt</code>，更新<code>rootdev</code>的UUID
使用<code>blkid</code>或者<code>lsblk -o+UUID</code>可以查看UUID，注意是<strong>UUID</strong>不是PARTUUID。</p>
</li>
<li>
<p>插电，开机 (<del>此处不会出现五安大电牛</del>)，网线连接R4S的WAN口到路由器的LAN口，第一次开机需要生成SSH Key所以时间会久一些，然后就可以ssh到R4S上去辣。</p>
</li>
</ol>
<h3 id="内核">内核</h3>
<p>上述的安装步骤使用的armbian的内核，可以正常开机，但是想用Arch Linux stock aarch64内核的话，得替换一下DTB文件。（DTB文件是啥我目前还不清楚，如果后续弄明白了再更新到博客上吧）</p>
<ol>
<li>
<p>ssh到R4S中，安装<code>linux-aarch64</code>。</p>
</li>
<li>
<p>修改使用的DTB文件：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">cd /boot
</span></span><span class="line"><span class="cl">rm dtb
</span></span><span class="line"><span class="cl">ln -sf dtbs dtb
</span></span></code></pre></div><p>编辑<code>armbianEnv.txt</code>，在末尾添加一行<code>fdtfile=rockchip/rk3399-rockpro64.dtb</code>。</p>
<blockquote>
<p>在<code>/boot/dtb/rockchip</code>目录下是可以找到<code>rk3399-nanopi-r4s.dtb</code>文件的，但是目前用这个DTB的话会导致PCIE不能正常工作，导致LAN口无法使用。
<code>dmesg</code>的输出为：</p>
</blockquote>
<blockquote>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-prolog" data-lang="prolog"><span class="line"><span class="cl"><span class="s">dmesg</span> <span class="p">|</span> <span class="s">grep</span> <span class="s">pci</span>
</span></span><span class="line"><span class="cl"><span class="p">[</span>    <span class="mf">0.538310</span><span class="p">]</span> <span class="s">ehci</span><span class="o">-</span><span class="nn">pci</span><span class="p">:</span> <span class="nv">EHCI</span> <span class="nv">PCI</span> <span class="s">platform</span> <span class="s">driver</span>
</span></span><span class="line"><span class="cl"><span class="p">[</span>    <span class="mf">0.559708</span><span class="p">]</span> <span class="s">ohci</span><span class="o">-</span><span class="nn">pci</span><span class="p">:</span> <span class="nv">OHCI</span> <span class="nv">PCI</span> <span class="s">platform</span> <span class="s">driver</span>
</span></span><span class="line"><span class="cl"><span class="p">[</span>    <span class="mf">2.999933</span><span class="p">]</span> <span class="s">rockchip</span><span class="o">-</span><span class="s">pcie</span> <span class="s">f8000000</span><span class="p">.</span><span class="nn">pcie</span><span class="p">:</span> <span class="s">host</span> <span class="s">bridge</span> <span class="o">/</span><span class="s">pcie@f8000000</span> <span class="nn">ranges</span><span class="p">:</span>
</span></span><span class="line"><span class="cl"><span class="p">[</span>    <span class="mf">2.999974</span><span class="p">]</span> <span class="s">rockchip</span><span class="o">-</span><span class="s">pcie</span> <span class="s">f8000000</span><span class="p">.</span><span class="nn">pcie</span><span class="p">:</span>      <span class="nv">MEM</span> <span class="mh">0x00fa000000</span><span class="p">.</span><span class="mf">.0</span><span class="s">x00fbdfffff</span> <span class="s">-&gt;</span> <span class="mh">0x00fa000000</span>
</span></span><span class="line"><span class="cl"><span class="p">[</span>    <span class="mf">2.999987</span><span class="p">]</span> <span class="s">rockchip</span><span class="o">-</span><span class="s">pcie</span> <span class="s">f8000000</span><span class="p">.</span><span class="nn">pcie</span><span class="p">:</span>       <span class="nv">IO</span> <span class="mh">0x00fbe00000</span><span class="p">.</span><span class="mf">.0</span><span class="s">x00fbefffff</span> <span class="s">-&gt;</span> <span class="mh">0x00fbe00000</span>
</span></span><span class="line"><span class="cl"><span class="p">[</span>    <span class="mf">3.000410</span><span class="p">]</span> <span class="s">rockchip</span><span class="o">-</span><span class="s">pcie</span> <span class="s">f8000000</span><span class="p">.</span><span class="nn">pcie</span><span class="p">:</span> <span class="s">no</span> <span class="s">vpcie12v</span> <span class="s">regulator</span> <span class="s">found</span>
</span></span><span class="line"><span class="cl"><span class="p">[</span>    <span class="mf">3.500881</span><span class="p">]</span> <span class="s">rockchip</span><span class="o">-</span><span class="s">pcie</span> <span class="s">f8000000</span><span class="p">.</span><span class="nn">pcie</span><span class="p">:</span> <span class="nv">PCIe</span> <span class="s">link</span> <span class="s">training</span> <span class="s">gen1</span> <span class="s">timeout</span><span class="p">!</span>
</span></span><span class="line"><span class="cl"><span class="p">[</span>    <span class="mf">3.500944</span><span class="p">]</span> <span class="s">rockchip</span><span class="o">-</span><span class="nn">pcie</span><span class="p">:</span> <span class="s">probe</span> <span class="s">of</span> <span class="s">f8000000</span><span class="p">.</span><span class="s">pcie</span> <span class="s">failed</span> <span class="s">with</span> <span class="s">error</span> <span class="o">-</span><span class="mi">110</span>
</span></span></code></pre></div><p>于是就先用rockpro64的DTB文件了。</p>
</blockquote>
</li>
<li>
<p>创建uBoot镜像和initramfs。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pacman -S uboot-tools
</span></span><span class="line"><span class="cl">mkimage -A arm64 -T ramdisk -n uInitrd -d /boot/initramfs-linux.img /boot/uInitrd-initramfs-linux.img
</span></span><span class="line"><span class="cl">ln -sf /boot/uInitrd-initramfs-linux.img /boot/uInitrd
</span></span></code></pre></div><p>创建个<code>pacman</code>的钩子，在以后更新<code>linux-aarch64</code>的时候自动的重新构建uboot和initramfs。</p>
<p>在<code>mkdir -p /etc/pacman.d/hooks</code>目录下创建<code>/etc/pacman.d/hooks/initramfs.hook</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-pacmanconf" data-lang="pacmanconf"><span class="line"><span class="cl"><span class="k">[Trigger]</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="na">Operation</span> <span class="o">=</span> Install<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="na">Operation</span> <span class="o">=</span> Upgrade<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="na">Type</span> <span class="o">=</span> Package<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="na">Target</span> <span class="o">=</span> linux-aarch64<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">
</span></span></span><span class="line"><span class="cl"><span class="k">[Action]</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="na">Description</span> <span class="o">=</span> Generate uInitrd<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="na">Exec</span> <span class="o">=</span> /usr/bin/mkimage -A arm64 -T ramdisk -n uInitrd -d /boot/initramfs-linux.img /boot/uInitrd-initramfs-linux.img<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="na">When</span> <span class="o">=</span> PostTransaction<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="na">Depends</span> <span class="o">=</span> uboot-tools<span class="err">
</span></span></span></code></pre></div></li>
<li>
<p>重启，<code>uname -a</code>输出的应该是新版本的内核了。</p>
<p>有个细节，用armbian的DTB文件时，开机后SYS LED灯是闪烁的，但是换到rockpro60的DTB文件后只有PWR灯长亮，别的灯都不闪了。</p>
</li>
</ol>
<h2 id="router">Router</h2>
<p>虽然装的是Arch Linux ARM系统，但是这并不代表它不能作为一个路由器使用。</p>
<p>系统默认用的是<code>systemd-networkd</code>管理网络，所以以下内容使用<code>systemd-networkd</code>配置路由器，暂时没遇到问题，如果不行的话我再换别的。</p>
<blockquote>
<p>参考: <a href="https://wiki.archlinux.org/title/Router">Router - ArchWiki</a></p>
</blockquote>
<h3 id="重命名网络接口">重命名网络接口</h3>
<blockquote>
<p>这一步并非必须，但是我有遇到重启系统后网口从<code>eth0</code>变成<code>eth1</code>的情况，所以还是给网口重命个名好一些。</p>
</blockquote>
<p>首先移除并备份<code>/etc/systemd/network</code>中原有的配置文件。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">cd</span> /etc/systemd/network
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># backup config files</span>
</span></span><span class="line"><span class="cl">mv ./* /root/
</span></span></code></pre></div><p>获取WAN口的mac地址。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cat /sys/class/net/eth0/address
</span></span><span class="line"><span class="cl">12:34:56:78:90:ab
</span></span></code></pre></div><p>创建<code>10-extern0.link</code>，重命名<code>eth0</code>到<code>extern0</code>。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-pacmanconf" data-lang="pacmanconf"><span class="line"><span class="cl"><span class="k">[Match]</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="na">MACAddress</span><span class="o">=</span>12:34:56:78:90:ab<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">
</span></span></span><span class="line"><span class="cl"><span class="k">[Link]</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="na">Description</span><span class="o">=</span>WAN<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="na">Name</span><span class="o">=</span>extern0<span class="err">
</span></span></span></code></pre></div><p>另一个网口(LAN)在开机时<code>systemd-networkd</code>会自动给他重命名为<code>enp1s0</code>。</p>
<h3 id="wan口配置dhcp客户端">WAN口配置DHCP客户端</h3>
<p>这里我是把R4S的WAN口接到另一台路由器的LAN上，所以配置的是DHCP客户端。如果你打算直接把路由器接光猫，而且你的猫设置了桥接，那么你可能需要配置PPPOE。</p>
<p>创建<code>20-extern0.network</code>。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-pacmanconf" data-lang="pacmanconf"><span class="line"><span class="cl"><span class="k">[Match]</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="na">Name</span><span class="o">=</span>extern0<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">
</span></span></span><span class="line"><span class="cl"><span class="k">[Network]</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="na">DHCP</span><span class="o">=</span>yes<span class="err">
</span></span></span></code></pre></div><h3 id="lan口配置静态ip和dhcp服务器">LAN口配置静态IP和DHCP服务器</h3>
<p>给LAN口设置成另一个网络的静态IP地址，并配置DHCP服务器，给连接到LAN口的机器分配同一个网络下的其他IP地址。</p>
<p>创建<code>20-enp1s0.network</code>。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-pacmanconf" data-lang="pacmanconf"><span class="line"><span class="cl"><span class="k">[Match]</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="na">Name</span><span class="o">=</span>enp1s0<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">
</span></span></span><span class="line"><span class="cl"><span class="k">[Network]</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="na">Address</span><span class="o">=</span>10.0.0.1/24<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="na">DHCPServer</span><span class="o">=</span>true<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="na">IPMasquerade</span><span class="o">=</span>both<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">
</span></span></span><span class="line"><span class="cl"><span class="k">[DHCPServer]</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="na">PoolOffset</span><span class="o">=</span>100<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="na">PoolSize</span><span class="o">=</span>100<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="na">EmitDNS</span><span class="o">=</span>yes<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c1"># DNS=8.8.8.8</span><span class="err">
</span></span></span></code></pre></div><p>我这个配置是给LAN口设置了静态IP地址<code>10.0.0.1</code>，掩码<code>255.255.255.0</code>，启用了DHCP服务器，
设置了IPv4数据“伪装”(packets forwarded from the network interface will be appear as coming from the local host)。</p>
<p>有关配置文件的参数可以使用<code>man systemd.network</code>查询。</p>
<p>暂时还没搞懂怎么折腾IPv6，如果配置好IPv6的话我再补上……</p>
<h2 id="后续">后续</h2>
<p>之后咱装了JDK以及一堆我常用的小组件。为了测试性能，我把我以前备份的Minecraft服务器复制到R4S上跑了一下试试。我的服务器之前是在疼讯云学生主机上跑的(1核2G)，装了好多性能优化插件(lithium，phosphor，carpet&hellip;)，版本是1.16.4，抱着尝试的心态跑了一下这个服务器结果发现很流畅，一开始区块加载的时候CPU的6个核心全跑满，之后就恢复到正常水平了。刚才尝试了一下长时间的生成区块貌似没什么大的问题，只要别一直用鞘翅跑图就行，应该是内存够用了所以运行效果要好一些，不过单核性能来讲的话肯定还是X86吊打R4S的。</p>
<p>毕竟这就是半个巴掌大小的机器，跑MC的时候CPU温度才不到50度，应该不需要主动散热，功耗才十多瓦……</p>
<p><img loading="lazy" src="images/nanopi_performance.png" alt="&ldquo;MineCraft Server Performance&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">MineCraft Server Performance</p>
</p>
<p><img loading="lazy" src="images/nanopi_neofetch.png" alt="&ldquo;Arch Linux ARM&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">Arch Linux ARM</p>
</p>
<h2 id="参烤链接">参烤链接</h2>
<ul>
<li><a href="https://www.friendlyelec.com/index.php?route=product/product&amp;product_id=284">NanoPi R4S</a></li>
<li><a href="https://wiki.friendlyelec.com/wiki/index.php/NanoPi_R4S">NanoPi R4S - FriendlyELEC WiKi</a></li>
<li><a href="https://gist.github.com/larsch/a8f13faa2163984bb945d02efb897e6d">Installing Arch Linux AArch64 on the NanoPi R2S</a></li>
<li><a href="https://archlinuxarm.org/platforms/armv8/generic">Generic AArch64 Installation | Arch Linux ARM</a></li>
<li><a href="https://wiki.archlinux.org/title/Router">Router - ArchWiki</a></li>
<li><a href="https://wiki.archlinux.org/title/Systemd-networkd">systemd-networkd - ArchWiki</a></li>
<li><a href="https://man.archlinux.org/man/systemd.network.5">systemd.network(5) — Arch manual pages</a></li>
<li><a href="https://zhuanlan.zhihu.com/p/392619184">在 NanoPi R2S 上安装 Archlinuxarm - 知乎</a></li>
</ul>]]></content:encoded>
    </item>
    <item>
      <title>自建RSS服务器：Miniflux</title>
      <link>https://blog.starry-s.moe/posts/2022/miniflux-build/</link>
      <pubDate>Tue, 01 Mar 2022 22:24:17 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2022/miniflux-build/</guid>
      <description>&lt;p&gt;之前咱自己搭过一个Miniflux服务器，不过当时用得并不频繁，逐渐的被咱弃用了。&lt;/p&gt;
&lt;p&gt;最近想订阅一些网站，因为使用RSS订阅的话，能收到更新提醒，不用经常的翻收藏夹去看页面内容有没有更新，使用RSS订阅的话也方便集中管理一些，
而且还能绕开推荐算法，只看自己想看的内容，这点还是蛮重要的。&lt;/p&gt;
&lt;p&gt;思考了几天发现我确实需要一个RSS订阅服务器后，于是决定这次把搭建过程记录下来，省得以后又忘了。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>之前咱自己搭过一个Miniflux服务器，不过当时用得并不频繁，逐渐的被咱弃用了。</p>
<p>最近想订阅一些网站，因为使用RSS订阅的话，能收到更新提醒，不用经常的翻收藏夹去看页面内容有没有更新，使用RSS订阅的话也方便集中管理一些，
而且还能绕开推荐算法，只看自己想看的内容，这点还是蛮重要的。</p>
<p>思考了几天发现我确实需要一个RSS订阅服务器后，于是决定这次把搭建过程记录下来，省得以后又忘了。</p>
<hr>
<h2 id="准备">准备</h2>
<p>Miniflux官方文档（EN）：<a href="https://miniflux.app/docs/index.html">https://miniflux.app/docs/index.html</a></p>
<h2 id="安装">安装</h2>
<p>此部分配合官方文档食用：<a href="https://miniflux.app/docs/installation.html">https://miniflux.app/docs/installation.html</a></p>
<h3 id="配置数据库">配置数据库：</h3>
<p>首先需要安装postgresql数据库。安装方法因发行版而异，网上一搜就有。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"># Switch to the postgres user
</span></span><span class="line"><span class="cl">$ sudo su - postgres
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Create a database user for miniflux
</span></span><span class="line"><span class="cl">$ createuser -P miniflux
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Create a database for miniflux that belongs to our user
</span></span><span class="line"><span class="cl">$ createdb -O miniflux miniflux
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Create the extension hstore as superuser
</span></span><span class="line"><span class="cl">$ psql miniflux -c &#39;create extension hstore&#39;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Change postgres password
</span></span><span class="line"><span class="cl">$ psql
</span></span><span class="line"><span class="cl">&gt; \password
</span></span></code></pre></div><h3 id="安装miniflux">安装Miniflux：</h3>
<p>不同的发行版使用方法不一样，咱的这台服务器为Ubuntu，所以参照<a href="https://miniflux.app/docs/howto.html#apt-repo">这里的教程</a>配置APT源，安装Miniflux。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">curl -s https://apt.miniflux.app/KEY.gpg | sudo apt-key add -
</span></span><span class="line"><span class="cl">echo &#34;deb https://apt.miniflux.app/ /&#34; | sudo tee /etc/apt/sources.list.d/miniflux.list &gt; /dev/null
</span></span><span class="line"><span class="cl">apt update
</span></span><span class="line"><span class="cl">apt install miniflux
</span></span></code></pre></div><h2 id="配置miniflux">配置Miniflux</h2>
<p>默认配置文件为：<code>/etc/miniflux.conf</code>。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"># See https://miniflux.app/docs/configuration.html
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">LISTEN_ADDR=0.0.0.0:8080
</span></span><span class="line"><span class="cl">LOG_DATE_TIME=yes
</span></span><span class="line"><span class="cl">DATABASE_URL=user=postgres password=&lt;YOURPASSWORD&gt; dbname=miniflux sslmode=disable
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Run SQL migrations automatically
</span></span><span class="line"><span class="cl"># RUN_MIGRATIONS=1
</span></span></code></pre></div><p>之后线将刚刚创建的数据库用户<code>miniflux</code>设置为超级用户。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">$ sudo su - postgres
</span></span><span class="line"><span class="cl">$ psql
</span></span><span class="line"><span class="cl">&gt; ALTER USER miniflux WITH SUPERUSER;
</span></span></code></pre></div><p>使用以下指令创建数据库表，并创建用户：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">$ miniflux -c /etc/miniflux.conf -migrate
</span></span><span class="line"><span class="cl">$ miniflux -c /etc/miniflux.conf -create-admin
</span></span></code></pre></div><p>之后将<code>miniflux</code>切换回普通用户。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">$ sudo su - postgres
</span></span><span class="line"><span class="cl">$ psql
</span></span><span class="line"><span class="cl">&gt; ALTER USER miniflux WITH NOSUPERUSER;
</span></span></code></pre></div><p>最后重新启动<code>miniflux</code>。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">$ sudo systemctl restart miniflux
</span></span></code></pre></div><h2 id="配置ssl可选">配置SSL（可选）</h2>
<p>使用nginx转发流量，可以将服务器套在Cloudflare下面。</p>
<p>编辑nginx的服务器配置文件，创建一个端口为443的服务器，并指定SSL key的位置：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">server {
</span></span><span class="line"><span class="cl">	listen 443 ssl default_server;
</span></span><span class="line"><span class="cl">	listen [::]:443 ssl default_server;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	server_name miniflux;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	ssl_certificate /path/to/server.crt;
</span></span><span class="line"><span class="cl">	ssl_certificate_key /path/to/server.key;
</span></span><span class="line"><span class="cl">	ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	location / {
</span></span><span class="line"><span class="cl">    		proxy_pass  http://127.0.0.1:8080;
</span></span><span class="line"><span class="cl">	}
</span></span><span class="line"><span class="cl">}
</span></span></code></pre></div><p>之后执行<code>sudo systemctl restart nginx</code>，访问服务器地址即可。</p>
<h2 id="others">Others</h2>
<p>Miniflux支持Fever和Google Reader等第三方服务，参考<a href="https://miniflux.app/docs/services.html">官方文档</a>，可以在服务器的设置-&gt;集成页面中配置，之后在别的设备中安装客户端，可以阅读订阅的文章，比网页版好用一些。</p>
<p><strong>STARRY-S</strong></p>]]></content:encoded>
    </item>
    <item>
      <title>电脑换壳——先马趣造</title>
      <link>https://blog.starry-s.moe/posts/2022/sama-quzao/</link>
      <pubDate>Sat, 26 Feb 2022 23:07:52 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2022/sama-quzao/</guid>
      <description>&lt;p&gt;年初的时候买了先马趣造这个机箱，买这个机箱的主要的原因是先马鲁班太大了，之前买先马鲁班的时候没有考虑到机箱便携性的问题，因为台式机压根就没啥便携性可言。&lt;/p&gt;
&lt;p&gt;然额因为咱今年上半年要去北京，觉得台式机留在家里不用有些不忍心，所以某天在网上闲逛的时候看到了这款机箱。看了很多网上的评测视频后才决定入手。&lt;/p&gt;
&lt;p&gt;现在距离刚入手这台机箱已经过去一个多月了，前一阵子一直在忙所以没时间更新博客，于是直到现在咱才把换机箱这件事更新到博客上。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>年初的时候买了先马趣造这个机箱，买这个机箱的主要的原因是先马鲁班太大了，之前买先马鲁班的时候没有考虑到机箱便携性的问题，因为台式机压根就没啥便携性可言。</p>
<p>然额因为咱今年上半年要去北京，觉得台式机留在家里不用有些不忍心，所以某天在网上闲逛的时候看到了这款机箱。看了很多网上的评测视频后才决定入手。</p>
<p>现在距离刚入手这台机箱已经过去一个多月了，前一阵子一直在忙所以没时间更新博客，于是直到现在咱才把换机箱这件事更新到博客上。</p>
<meting-js server="netease" type="song" id="1444211740" theme="#233333"></meting-js>
<h2 id="配置">配置</h2>
<p>CPU、主板、显卡、电源、硬盘、CPU散热以及内存这些用的还是上一篇“<a href="/posts/2021/build-desktop/">我们来组装一台电脑吧</a>”里面讲的配置。</p>
<p>除此之外咱买了一个直径8CM的小风扇(ARCTIC P8)，和一个超薄的1.5CM厚的风扇(ID-COOLING TF9125)。</p>
<p>配置方面没有太大改动，因为不打算装侧玻璃板，所以咱这次没有装RGB灯条。</p>
<h2 id="装机">装机</h2>
<p><img loading="lazy" src="images/6.jpg" alt="&ldquo;最终的样子&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">最终的样子</p>
</p>
<p>装机过程还是很顺利的，因为新机箱体积比较小，可供操作的空间有限，所以花了很长时间才装完。一开始还比较担心显卡太长会不会和右侧的ATX电源产生冲突，
不过华硕B550重炮手的主板的PCIE4X16是在第二槽，第一槽是个PCIE1，所以装上显卡后没有影响到右侧全模组电源的电源线，
但是显卡的底部空间很小，没办法塞下一个正常厚度的风扇了。</p>
<hr>
<p><img loading="lazy" src="images/2.jpg" alt="&ldquo;底部风扇&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">机箱底部</p>
</p>
<p>后来咱买了一个1.5cm厚的超薄风扇帮助显卡散热，不过风扇的尺寸咱买小了，本来应该买12cm的风扇咱买了个9cm的，到手后发现把风扇斜过来也能勉强固定住，于是就不打算换了。</p>
<hr>
<p><img loading="lazy" src="images/3.jpg" alt="顶部" />
<p style="margin-bottom: -0.8em;" class="image-title">顶部</p>
</p>
<p><img loading="lazy" src="images/5.jpg" alt="正面" />
<p style="margin-bottom: -0.8em;" class="image-title">正面</p>
</p>
<p>机箱顶部用的是之前买的先马冰洞静音风扇，虽然不支持PWM调速但是很静音。然后因为右侧的空间被ATX电源占用了所以顶部只能装一个风扇。</p>
<p>发热量大户实际上是显卡，CPU产生的热量很容易就能散开。所以所有风扇低速运行时，CPU和显卡的待机温度才比室温高十几度，只有玩游戏时CPU和显卡温度会高一些。</p>
<p>把所有零件全部装上之后，就没有空间装机械硬盘了，不过还好我只有一块2.5寸固态硬盘，勉强塞在了前面板背面的硬盘位中。</p>
<p>因为机箱空间有限，所以理线是个大难题，机箱几乎没有可理线的空间，所以我只好把一大团电线捆在一起。</p>
<h2 id="最终效果">最终效果</h2>
<p><img loading="lazy" src="images/7.jpg" alt="&ldquo;最终效果&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">最终效果</p>
</p>
<p>白色机箱的确比黑色机箱好看很多，而且我反而觉得白色机箱更不容易脏一些，因为之前的黑色机箱手一摸就是油乎乎的大手印，但是这个白色机箱就没有。</p>
<p>在网上看到有人在机箱前面贴苹果贴纸的，不过咱找了一下高中时买的Arch Linux贴纸，贴到了机箱顶部。</p>
<p>趣造的机箱四面八方全是散热孔，所以就算咱把一个M-ATX机箱里面的所有东西全都塞到了这个小盒子里面，温度也并没有上升。要说影响的话，就是没有可以塞机械硬盘和摆手办的地方了（</p>
<p>如果想塞机械硬盘的话，我得把我的ATX电源换成SFX电源。然后把CPU散热换成水冷或者下压式散热之类的，才能倒出空间装机械硬盘。不过咱买这个机箱目的就是为了能放到行李箱里面拉走，所以装机械硬盘的话容易在路上颠坏了。而固态就没这个担心。</p>
<p>然后就是个人感觉先马趣造这机箱设计得还蛮不错的，机箱的所有地方都能拆，装机的时候可以先把机箱拆得只剩下一个铁架子，所以实际上装机没有增添太多难度。就是理线费劲了一些。全模组电源自带的模组线是真的硬。</p>
<p>因为机箱小，去北京实习的时候咱买了个28寸的超大行李箱，于是这个行李箱在塞下这个机箱之后还能塞很多衣服。总之肯定是不会出现像上次那样坐高铁我一个人把机箱和显示器从北京扛回老家的情况了。</p>
<p><strong>STARRY-S</strong></p>]]></content:encoded>
    </item>
    <item>
      <title>Hello 2022</title>
      <link>https://blog.starry-s.moe/posts/2022/hello-2022/</link>
      <pubDate>Sun, 02 Jan 2022 01:09:24 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2022/hello-2022/</guid>
      <description>&lt;p&gt;又到了写年终总结的时候了，说实话今年发生了好多好多好多的事情，我可能要写很久才能把这些事全写完。然而我文笔不好，回忆事情容易写成流水帐，所以不重要的事情我只好简短略过一下。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>又到了写年终总结的时候了，说实话今年发生了好多好多好多的事情，我可能要写很久才能把这些事全写完。然而我文笔不好，回忆事情容易写成流水帐，所以不重要的事情我只好简短略过一下。</p>
<meting-js server="netease" type="song" id="33211167" theme="#233333"></meting-js>
<hr>
<h2 id="gaming">Gaming</h2>
<p>容我翻一下去年的Todo List……</p>
<p>今年在PS4上完成了三刷尼尔：机械纪元，全程没开简单模式和自动芯片，做完了大多数支线，然后除了DLC里的难度极高的斗兽场没有打，其他的DLC剧情全过了一遍，还看了几遍柏拉图的记忆。
顺便在B站会员购上买了两个尼尔的八音盒，曲子分别是<em>Kaine</em>和<em>Weight of the world</em>，尽管八音盒只能无限循环一小段，有种洗脑循环的感觉，但是偶尔听一下还是蛮好听的。
然后我应该是去年就把尼尔原著的短话就看完了，今年看的少年寄叶，不过只看了一半还没读完。长话因为和主线剧情一样所以我买来翻了几页就放起来了。
不得不说日本的游戏剧情支线真的特别多，全打完的话非常的肝，而且很费时间，不过发现了新的隐藏剧情还是很开心的。</p>
<p>然后咱还在PS4上斥巨资买了正版的尼尔：伪装者，也就是机械纪元的前作（钱包在滴血），打通了一周目和二周目，然后因为时间原因加上后面剧情都是重复的，就没玩下去。尼尔：伪装者的游戏内容比机械纪元要少很多，而且可玩性比机械纪元差很多（本来机械纪元的游戏体验就不怎么好），所以这也是我玩不下去的原因之一。</p>
<p>然后上半年的时候通关了传送门2单人模式，之前蛮期待的Nier:Reincarnation手游下载后看了几眼感觉不感兴趣没继续玩下去。然后月姬重置版只在日本发售，其他区服买不到，因为懒得换区加上不会日语，所以玩月姬的计划也被我抛在脑后。</p>
<p>不得不承认咱的大部分时间都拿去打守望先锋了，这游戏玩上就有种停不下来的感觉，所以在守望上花了好多时间，然后经常在竞技模式里被气得卸游戏，过两天又想玩然后把它下载回来……</p>
<p>其余时间还玩了一阵子Minecraft，完成了非常大的工程，在原点空置域修了全物品分类机，不过比较气愤的就是1.17新增了好多新的物品，我旧的全物品分类机装不下，然后1.18又在Y0至-64的高度区域生成新的洞穴，所以我之前的空置域又白炸了，所以我还在继续玩1.16.4，没有升级游戏版本。</p>
<p>这么一回忆起来，怎么有一种我这一年的时间全荒废在打游戏的错觉……</p>
<h2 id="learning">Learning</h2>
<p>上面写的游戏经历只不过是一小部分而已，咱在今年买了Kindle PaperWhite4，看了《自控力》、《黑客与画家》这两本书，还二刷了《空之境界》原著，之后就是用Kindle看了一些技术类书的样章，不过在Kindle上有些书的代码是以图片的形式显示的，所以阅览效果并不好。</p>
<p>咱还买了好多纸质书，都是技术相关的，因为大多都只是简单翻了几页，所以就不列举名字了，今年要是有时间的话就试着看几本吧。</p>
<p>技术方面的话，咱今年靠着自己的努力尝试着写了一些程序，软件工程的大作业为了赶时间，用现学的前端知识和JS写了一个“能用”的问卷调查系统。不过后来我实在看不下去自己当时写的代码太不规范了，所以对代码进行了简单的整理和优化，顺便提高了一些安全性，不过和那些能稳定运行的大型软件相比肯定还是差远了。</p>
<p>然后就是自学了点现代OpenGL，选了学校的计算机图形学选修课，不过老师讲课时用的老版本的GLUT进行画图，然后别的学生基本上就是上网找代码抄一下就交作业了，他们有些人甚至连透视都不知道是什么。</p>
<p>所以我当时花了好多时间学OpenGL的矩阵运算和渲染方面的东西，这东西入门的门槛还真是挺高的。然后简单的了解了一下核心模式写Shader之类的操作，不过时间比较紧碰撞检测那些东西我就没学，所以最后给老师交作业时写了个比较简陋的世界模型，里面放了一个大正方形当作地面，然后渲染了几个正方体当作物体，贴了我的世界的材质…… 没弄碰撞检测和物理引擎之类的东西，用纯C语言写真的太难了。</p>
<p>除此之外，咱学了一点GTK和Qt，用Qt写了软件工程结对编程的作业，然后用GTK写了个很简易的将多条视频合并成一个视频的小程序，挺多部分都是一边抄别人的代码一边学怎么写的，因为自己能力并不强，写的东西也都是勉强能用的程度，然后现在一回忆发现好多东西又都忘掉了。</p>
<h2 id="working">Working</h2>
<p>大三下半年我花了好多时间在网上找实习工作，然后因为我几乎没有准备就去面试，所以被问到算法题或者一些需要靠刷题和背书才能学会的知识时我几乎全答不上来，所以我挂了好多面试。</p>
<p>额……因为我在此之前对秋招和春招之类的没有啥概念，我也没有找学长问这些经验，只靠着自己听老师和别的同学随口说的内容，现在回想起来当时自己纯粹是什么都不懂的状态在瞎找工作，也几乎没怎么考虑待遇和工作内容这类的十分现实的问题，在所有面试全都是被刷的情况下我去了最后一个同意我去实习的公司。</p>
<p>我还记得我第一次面试时紧张得不得了，然后因为什么都没准备所以面试官问我所有的东西都答不上来，面试结束后就觉得我这种表现对面试官很不尊重。</p>
<p>所以别问我实习和找工作相关的东西，我没办法给你任何有价值的参考意见，你看我这么胡扯的做法就能体现出来。</p>
<p>所以要说结果的话，我在实习的时候放弃了继续工作的打算，开始向更多的实际问题思考，国庆假期回到家后，在研究生考试报名截止前两天报了本校的研究生考试，在距离考试不到两个月的时候辞职，12月初回学校，预习考试内容。</p>
<p>很明显，我写这些东西的时候，已经考完了今年的研究生考试，结果可显而知，我得二战了。</p>
<p>（亏我还能如此淡定的说出这些离大谱的东西）</p>
<p>让我以一名阅读这些文字的访客的角度思考一下…… 好了别骂了别骂了，再骂孩子就吓得以后啥都不敢干了，我做了好多的心理准备才敢把这些事情写出来，要是有人骂的话我肯定忍不住要删掉的，毕竟万事开头难，咱已经吃过好多苦头了。</p>
<p>我这只不过是一个什么都不懂的学生想凭借他的努力找到第一个实习工作的比较客观的真实反映而已，你看B乎上那些新编的故事有时候不能反映自己真实的情况，我承认我有些方法确实做得不够成熟……</p>
<p>不过，这次实习我还是有很丰富的收获的，很幸运，我实习的公司对我很好，尽管任务量比较大，让实习生直接进行生产项目的开发，直接解决客户提的问题，但这些都还在能接受的范围内。
可能是因为咱刚接触这些领域吧，对这些还有一些新鲜感。</p>
<p>我用实习的工资组装了一个台式机，就是之前博客里面写的那台电脑，当初从北京回家时因为快递拒收电脑，所以我扛着装电脑机箱和显示器的箱子坐高铁回家，别提有多狼狈了。</p>
<p>在北京的这三个月的生活我可以用惨来形容，不用多说，你看我上面写的这些就能想到为什么惨了。</p>
<p>只不过我没有在之前的几篇博客中提到这些事情而已，因为咱不想把技术类文章和这些琐事混到一起，说出来又解决不了什么实际问题。</p>
<hr>
<p>一提到实习这段经历就不由自主的把气氛搞沉重了，通过三个月的实习对北京这个城市有了一个新的认识，不过从自己眼中看到的事物并非是全面的，毕竟北京城太大了。</p>
<p>因为缺乏生活经验，在北京自己一个人的生活可以用狼狈来形容。北京是个漂泊感很重的城市，在租房的那段时间几乎每天早晨起床时都会感受到一股强烈的孤独感，有一段时间每天都笼罩在几乎窒息一般的孤独当中，这可能是我最不愿回忆的一段经历了。</p>
<p>中秋节假期前一天晚上想坐高铁回家，但是新修的北京朝阳站还没通地铁，节假日前一天晚上堵车非常严重，当时为了赶高铁我愣是从朝阳公园北门附近下公交车拎着公司发的中秋节礼品跑了4公里，不过还是没赶上那天的最后一趟高铁。</p>
<p>国庆的时候抽时间回学校取我剩下的一些东西，回去的时候站在沈阳地铁上忍不住开哭。</p>
<p>实习工作的时候每天都得长时间盯着电脑好几个小时没办法休息，很快我就发现自己起了很重的黑眼圈，有一天早晨坐地铁去公司的路上突然低血糖眼冒金星，到公司后头特别疼，于是不得不又请假，睡了一下午觉后身体十分难受，于是又请了一天假连着周末一起休息。</p>
<p>我其实很不喜欢那种加班的时间去做无意义的摸鱼的事情，明明到了下班时间该收工走人了，却非要在岗位上刷一会手机赖着不走。上班的时间该干什么就干什么，而不是把加班当作一种很光彩的事，给老板创造价值可不是这么创造的，一个人每天能保持的比较旺盛的精力只有几个小时而已，而且人是需要一定时间进行思考的，真实的情况是程序员有时候一天的时间都很难写出来几十行优质的代码的，让一个人全神贯注十多个小时的干活这种事怎么可能呢？只不过是限制人身和思维的自由罢了，这样下去早晚会崩溃的，所以这也就是为什么现在有好多人只是为了完成目的而写代码的原因吧，他们几乎不在乎代码的完整性和可读性之类的东西，实现的功能都是复制粘贴东拼西凑搞出来的，还把代码行数作为编程能力的衡量标准，而且他们甚至连几本专业类的书籍都不愿意去看，嫌这种阅读书籍太浪费时间，而为了所谓的效率去看那些质量低、表面上是以教学为目的而实际上是推销自家编程课程的视频，你看现在随便在某视频平台一搜这类的关键字，有好多视频不都是这样吗？视频一直在推销公众号，评论区置顶了一堆群号和网盘链接，还真有很多人信。</p>
<p>有一些人，看一点网课视频会调几个依赖库就可以写出很多绚丽的功能出来，报一个培训辅导班学几个月就能找工作，而做程序员的目的只不过是因为挣的钱多。所以现在研发人员的地位被搞得越来越低，甚至一说是程序员都被别人瞧不起。有的企业管理人员以为只要招一个会写代码的“农民工”，实现功能就够用了……</p>
<p>算了不说了，果然变得过于敏感并不是什么好事，就算是选择考研也很难摆脱现如今的许多事实，撑不住了就只能开摆了。</p>
<p>所以以上算是我一部分的考研的原因吧。辞职后的那段时间就算英语和政治能勉强速成一下，专业课几乎裸考，但是数学是真没复习完，更何况今年题变难了许多。</p>
<p>不过回学校后内心还是十分沉重的，自己折腾了这么久落反而落得了这样一个结果，我还要和身边同学解释清楚为什么不继续找工作了，还要找些理由解释为什么考研。</p>
<p>在学校待了三年有感情了，在离开学校后才意识到这一点。学校的老师在某些方面其实对我们学生非常好，给了学生很多不错的条件。缺点有是有，但是没必要总把那些问题挂在嘴边成天抱怨。</p>
<h2 id="others">Others</h2>
<p>换个话题吧，今年抽时间看了狼与香辛料，然后因为实在等不起国内的引进所以在网上找了资源看了FSN HF3。
Fate的HF线细节很多，音乐也很好听，剧情的节奏把握得也很不错，身为月厨的我看得十分满意。</p>
<p>咱暑假的时候在B站会员购上预购了世嘉的卫宫家今天的饭的呆毛的手办，之后斥巨资预购了寿屋的两仪式再版手办。</p>
<p>说实话真没想到能在2021年买到再版的空之境界的手办。</p>
<p>11月底的时候付的尾款，组装后拍了几张照片。</p>
<p><img loading="lazy" src="images/IMG_6864-1.jpg" alt="&ldquo;两仪式&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">两仪式</p>
</p>
<p><img loading="lazy" src="images/IMG_6871.jpg" alt="&ldquo;呆毛王&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">呆毛王</p>
</p>
<hr>
<p>以后不打算把太多时间花在游戏上了，至少我不想再像以前那样玩守望先锋了，其他比较缓和的游戏还是可以玩的。
之前在我的腾讯云学生版云主机上搭了一个小型的Minecraft服务器，主要是想和我的同学一起联机生存，年底进服务器发现他们盖了好多建筑，肥肠开心。</p>
<p>其他游戏是真玩不下去了，以后还是得注重身体，从疫情开始到现在体重飙升至200斤了，最近经常熬夜搞得身体很不好，所以以后还是得注重身体。</p>
<p>2022年咱可能要面临更多的问题，我暂时还有些不敢去想。还有半年就要大学毕业，然后还有好多我从来没经历过的事情需要去面对。</p>
<p>所以还是尽可能的保持乐观吧，悲观解决不了任何实际问题。</p>
<p>就写这么多吧，要是以后还想到了哪些东西再来补充。</p>
<h2 id="后续">后续</h2>
<p>在年终总结里加后续总显得有些怪怪的，但是最近这些事情我还是写到2021年的年终总结里吧，后续有什么事我可以再新开一篇文章（我是实在不想把2022年初发生的事情写到22年的年终总结里了）。</p>
<p>事情就是，咱拿到了北京SUSE实习的offer。因为很中意这份工作，所以咱做了很长时间的心理准备，最后决定大四下学期回北京，继续租房子。</p>
<p>嗯，要不是SUSE，俺是绝对不会再回北京的，别的公司联系我的话，我肯定会以考研为理由拒了。</p>
<p>SUSE的面试非常的顺利，既是出乎意料又算是情理之中吧。</p>
<p>所以我又一次花了很长时间，和学校的老师解释，和我以前的室友和新室友解释下学期我不回学校了。</p>
<p>然后我又花了很长时间和家人解释，又一次的拿出很大的资金到北京来租房子。</p>
<p>然后刚到北京就听说辽宁新出了许多的本土确诊病例……</p>
<p>这次租房我坚决不自己整租了，首先是租不起，再就是自己一个人真的很孤独。</p>
<p>然后就是，俺还没入职，所以后续的事我现在也没办法写。</p>
<p>就这些。</p>
<p><strong>STARRY-S</strong></p>]]></content:encoded>
    </item>
    <item>
      <title>使用树莓派搭建一个NAS</title>
      <link>https://blog.starry-s.moe/posts/2021/raspberry_pi_nas/</link>
      <pubDate>Sat, 25 Sep 2021 22:20:03 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2021/raspberry_pi_nas/</guid>
      <description>&lt;p&gt;把吃灰了好久的树莓派带了过来，打算搞个NAS玩一下，简短的记录一下整个过程。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>把吃灰了好久的树莓派带了过来，打算搞个NAS玩一下，简短的记录一下整个过程。</p>
<meting-js server="netease" type="song" id="750905" theme="#233333"></meting-js>
<blockquote>
<p>这歌太魔性了哈哈哈哈哈……</p>
</blockquote>
<h2 id="准备工作">准备工作</h2>
<ul>
<li>树莓派以及所需的电源（废话）</li>
<li>16G以上的高速内存卡用来写系统镜像</li>
<li>硬盘（可以选择买移动硬盘或者直接买硬盘盒和机械硬盘自己组装）</li>
<li>网络设备：路由器、网线</li>
<li>（非必须）键盘、显示器、连接线等</li>
</ul>
<p>树莓派我使用的是高一时买的树莓派3B（满满的回忆），硬盘是上半年为了存照片而买的西数2T移动硬盘，因为暂时闲置所以拿来一起组个云盘玩。</p>
<p>内存卡需要质量好的不容易坏而且读写比较快的卡。</p>
<h2 id="装系统">装系统</h2>
<p>系统咱安装的是<a href="https://archlinuxarm.org/platforms/armv8/broadcom/raspberry-pi-3">ArchLinux ARM</a>，安装教程直接看官方文档即可。</p>
<p>一开始咱为了发挥树莓派3B的64位CPU的性能，我下载了64位的系统镜像，但是在配置无线连接的时候（可能是）驱动问题卡死，因为急着睡觉所以重新格式化内存卡后被迫安装32位的系统。</p>
<p>安装教程咱就不重复写到博客里了，直接翻Wiki，尽管是纯英文的但是不难，都能看懂。咱就不打算在这翻译了。</p>
<p>在格式化<code>root</code>分区时一开始想尝试一下树莓派上跑<code>btrfs</code>，但是开机时进了linux的救援(rescue)模式，懒得折腾还是老老实实换回了<code>ext4</code>，<del>不然我一晚上不用睡觉了</del>。</p>
<p>在分区时除了<code>boot</code>和<code>root</code>之外，我额外分了2G的<code>swap</code>分区，树莓派1G内存有些小不过只是搭个人用的NAS的话实际上是不影响使用的。（<del>这话咋读着这么别扭呢</del>）</p>
<p>因为咱要做NAS肯定得往树莓派上外接个硬盘之类的，树莓派3B只有USB 2.0 + 百兆网口，尽管速度很慢但是作为个人网盘来说不到10MB/S的速度还是比某些恶心网盘快很多的，在线看个1080P视频还是蛮轻松的，BD蓝光想想还是算了。</p>
<p>把移动硬盘接到树莓派后<code>lsblk</code>查看一下分区表。因为咱这是块几乎全新的硬盘所以需要重新分区并格式化一下。</p>
<p>如果你不熟悉在命令行上进行分区格式化的话，建议自行翻阅<a href="https://wiki.archlinux.org/title/fdisk">Wiki (fdisk)</a>，因为往博客上写的话太难理解了别人肯定看不懂。</p>
<p>最后咱把2T移动硬盘格式化成这个样子：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl"># fdisk -l /dev/sda
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Device          Start        End    Sectors  Size Type
</span></span><span class="line"><span class="cl">/dev/sda1        2048 2147485695 2147483648    1T Linux filesystem
</span></span><span class="line"><span class="cl">/dev/sda2  2147485696 3907029133 1759543438  839G Microsoft basic data
</span></span></code></pre></div><p>其中的1T打算格式化为<code>btrfs</code>给Samba用，其余的800G打算格式化为<code>NTFS</code>留着给Windows当个移动硬盘。</p>
<p>创建分区时别忘了更改分区类型，给Linux用的就是<code>Linux filesystem</code>，给Windows用的就是<code>Microsoft basic data</code>，
不然机械硬盘连接到Windows系统中将不显示分区，或者就是一直提醒你：该分区不可用，然后让你格式化，到时候一不小心点错了可是会丢数据的。</p>
<p>安装<code>btrfs-progs</code>和<code>ntfs-3g</code>，之后格式化硬盘（NTFS还是建议到Windows系统中格式化）。</p>
<p>格式化btrfs的时候加个<code>-L</code>参数设置分区的标签，这样方便在fstab中设置开机自动挂载。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ lsblk                                # 一定要看清楚自己格式化的分区名字
</span></span><span class="line"><span class="cl">$ sudo mkfs.btrfs /dev/sdaX -L samba   # -L 参数设置分区的标签
</span></span></code></pre></div><p>最后改一下<code>/etc/fstab</code>让设备在开机时自动挂载交换分区和移动硬盘。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"># &lt;file system&gt; &lt;dir&gt; &lt;type&gt; &lt;options&gt; &lt;dump&gt; &lt;pass&gt;
</span></span><span class="line"><span class="cl">/dev/mmcblk0p1  /boot   vfat    defaults        0       0
</span></span><span class="line"><span class="cl">LABEL=swap      none    swap    defaults        0       0
</span></span><span class="line"><span class="cl">LABEL=samba     /samba  btrfs   defaults        0       0
</span></span></code></pre></div><p>重启系统后如果正常的话，分区会被自动挂载。</p>
<h2 id="配置网络">配置网络</h2>
<blockquote>
<p>配置网络部分不适合在SSH中操作，建议使用显示器和键盘连接到树莓派上操作。</p>
<p>除非你能保证你执行的每个命令都肥肠正确。</p>
</blockquote>
<h3 id="无线网络">无线网络</h3>
<p>因为我电脑离路由器肥肠远，所以为了方便我还要给树莓派配置无线网络。首先照着<a href="https://wiki.archlinux.org/title/Netctl">Wiki上的netctl页面</a>安装了<code>wifi-menu</code>所需要用的<code>dialog</code>，然后就用<code>wifi-menu</code>连接wifi了。不过为了方便以后连接，我需要给他设置静态IP：</p>
<p>首先使用你比较喜欢的文本编辑器打开<code>wifi-menu</code>自动生成的配置文件，并修改成以下的样子</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"># /etc/netctl.d/wlan0-YourWifiName
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Description=&#39;Automatically generated profile by wifi-menu&#39;
</span></span><span class="line"><span class="cl">Interface=wlan0
</span></span><span class="line"><span class="cl">Connection=wireless
</span></span><span class="line"><span class="cl">Security=wpa
</span></span><span class="line"><span class="cl">ESSID=Your Wifi Name
</span></span><span class="line"><span class="cl">IP=static
</span></span><span class="line"><span class="cl">Address=(&#39;192.168.xxx.xxx/24&#39;)
</span></span><span class="line"><span class="cl">Gateway=&#39;192.168.xxx.1&#39;
</span></span><span class="line"><span class="cl">DNS=&#39;8.8.8.8&#39;
</span></span><span class="line"><span class="cl">Key=YOUR WIFI PASSWORD
</span></span></code></pre></div><p>其中修改<code>Address</code>为你想设置的CIDR地址、<code>Gateway</code>为默认网关、以及<code>DNS</code>。</p>
<p>最后修改<code>Key</code>为Wifi密码（明文），如果需要加密的话可以去wiki上找相应方法。</p>
<p>之后<code>sudo netctl enable wlan0-YourWifiName</code>设置好开机自动连接即可。</p>
<p>这时候聪明的小伙伴会想到：我想使用网线联网并配置静态IP，该怎么办呢？</p>
<h3 id="配置有线网络">配置有线网络</h3>
<p>默认情况下，有线接口<code>eth0</code>使用<code>systemd-network</code>配置了<code>DHCP</code>，所以我们不需要改<code>netctl</code>的配置文件，只编辑<code>/etc/systemd/network/eth0.network</code>这个配置文件改成静态IP地址就好了。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">[Match]
</span></span><span class="line"><span class="cl">Name=eth0
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">[Network]
</span></span><span class="line"><span class="cl">Address=192.168.xxx.xxx/24
</span></span><span class="line"><span class="cl">Gateway=192.168.xxx.1
</span></span><span class="line"><span class="cl">DNS=8.8.8.8
</span></span></code></pre></div><p>重启系统后，使用<code>ip addr</code>检查设备的IP地址是否正确。</p>
<h3 id="配置防火墙">配置防火墙</h3>
<p>首先安装<code>ufw</code>。（因为对iptables不是十分熟悉，ufw比ip桌子好用一些，毕竟他叫<strong>Uncomplicated Filewall</strong>，所以咱先用ufw配置防火墙）</p>
<p>食用方法请参见<a href="https://wiki.archlinux.org/title/Uncomplicated_Firewall">Wiki页面</a>。</p>
<p>因为咱打算搭一个Samba服务器，所以别忘了配置防火墙允许Samba的端口，按照Arch Linux Wiki：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"># Create or Edit /etc/ufw/applications.d/samba, add following content:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">[Samba]
</span></span><span class="line"><span class="cl">title=LanManager-like file and printer server for Unix
</span></span><span class="line"><span class="cl">description=Samba
</span></span><span class="line"><span class="cl">ports=137,138/udp|139,445/tcp
</span></span></code></pre></div><p>之后root账户执行<code>ufw app update Samba</code>加载配置文件，然后<code>ufw allow Samba</code>允许Samba的端口。</p>
<p>如果你的树莓派上还装有其他服务（比如http，https等），别忘了<code>ufw allow PORT</code>开放端口，尤其是别忘了开SSH端口。</p>
<p>最后<code>ufw status</code>查看防火墙状态信息，<code>ufw enable</code>开启防火墙。</p>
<h2 id="samba">Samba</h2>
<p>配合<a href="https://wiki.archlinux.org/title/Samba">Arch Wiki</a>食用。</p>
<p>首先我们需要新建一个分组，然后在挂载的分区中新建一个文件夹作为Samba服务器的共享目录：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">$ sudo groupadd -r sambausers          # 新建用户组
</span></span><span class="line"><span class="cl">$ sudo usermod -aG sambausers username # 添加当前用户至分组中
</span></span><span class="line"><span class="cl">$ sudo smbpasswd -a sambausers         # 设置Samba用户的密码
</span></span><span class="line"><span class="cl">$ sudo mkdir /samba/sharefolder        # 新建文件夹用来存储共享的文件
</span></span><span class="line"><span class="cl">$ sudo chown :username /samba/sharefolder   # 修改文件夹的所属分组
</span></span><span class="line"><span class="cl">$ sudo chmod 0770 /samba/sharefolder   # 修改权限
</span></span></code></pre></div><p>（咱写的很详细了吧</p>
<h3 id="配置服务器">配置服务器</h3>
<p>安装好<code>samba</code>安装包后，需要手动去<code>/etc/samba/</code>创建<code>smb.conf</code>配置文件，可以到<a href="https://git.samba.org/samba.git/?p=samba.git;a=blob_plain;f=examples/smb.conf.default;hb=HEAD">Samba git repository</a>中获取样例配置文件，咱只需要把它复制粘贴再简单修改一下就好了。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"># /etc/samba/smb.conf
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">[global]
</span></span><span class="line"><span class="cl"># 修改工作组的名字
</span></span><span class="line"><span class="cl">workgroup = MYGROUP
</span></span><span class="line"><span class="cl"># 服务器描述
</span></span><span class="line"><span class="cl">server string = Raspberry pi Samba Server
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># 在文件末尾添加共享文件夹目录及相关配置
</span></span><span class="line"><span class="cl">[sambashare]
</span></span><span class="line"><span class="cl">comment = Sample share file.
</span></span><span class="line"><span class="cl">path = /path/to/your/samba/folder
</span></span><span class="line"><span class="cl">writable = yes
</span></span><span class="line"><span class="cl">browsable = yes
</span></span><span class="line"><span class="cl">create mask = 0755
</span></span><span class="line"><span class="cl">directory mask = 0755
</span></span><span class="line"><span class="cl">read only = no
</span></span><span class="line"><span class="cl">guest ok = no  # 允许访客随意登录
</span></span></code></pre></div><p>配置好文件后，启动<code>smb.service</code>和<code>nmb.service</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ sudo systemctl <span class="nb">enable</span> --now smb.service
</span></span><span class="line"><span class="cl">$ sudo systemctl <span class="nb">enable</span> --now nmb.service
</span></span></code></pre></div><h3 id="访问服务器">访问服务器</h3>
<p>咱GNOME用户直接打开文件管理器，选择左边的“+ Other Locations”，在底部输入服务器连接<code>smb://192.168.xxx.xxx</code>，
输入用户组、用户名和密码登录就可以访问共享文件夹。</p>
<p>Windows系统中，首先需要到 控制面板-&gt;程序-&gt;启用或关闭Windows功能 里面，选中 SMB1.0/CIFS文件共享直通，保存后等一会安装完，
打开文件资源管理器输入地址<code>\\192.168.xxx.xxx\</code>，登录后就能访问共享文件夹了。</p>
<h2 id="frp内网穿透">Frp内网穿透</h2>
<blockquote>
<p>配合<a href="https://gofrp.org/docs/">frp文档</a>食用更佳</p>
</blockquote>
<p>首先在frp的<a href="https://github.com/fatedier/frp/releases">GitHub Release</a>页面下载安装包。</p>
<p>如果是树莓派用的话就下载<code>arm</code>版本的安装包即可。Arch Linux可以在ArchLinux CN源或AUR中安装<code>frpc</code>和<code>frps</code>作为客户端和服务端。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="c1"># 树莓派上下载编译好的文件</span>
</span></span><span class="line"><span class="cl">$ wget https://github.com/fatedier/frp/releases/download/v0.37.1/frp_0.37.1_linux_arm.tar.gz
</span></span><span class="line"><span class="cl"><span class="c1"># 解压</span>
</span></span><span class="line"><span class="cl">$ tar -zxvf ./frp_0.37.1_linux_arm.tar.gz
</span></span><span class="line"><span class="cl">$ <span class="nb">cd</span> frp_0.37.1_linux_arm/
</span></span><span class="line"><span class="cl"><span class="c1"># 编辑配置文件</span>
</span></span><span class="line"><span class="cl">$ vim ./frpc.ini
</span></span><span class="line"><span class="cl">$ ./frpc -c ./frpc.ini
</span></span></code></pre></div><p>客户端配置文件的格式可参考如下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">[common]
</span></span><span class="line"><span class="cl">server_addr = server ip
</span></span><span class="line"><span class="cl">server_port = 6000
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">[samba]
</span></span><span class="line"><span class="cl">type = tcp
</span></span><span class="line"><span class="cl">local_ip = 127.0.0.1
</span></span><span class="line"><span class="cl">local_port = 445
</span></span><span class="line"><span class="cl">remote_port = 6003
</span></span></code></pre></div><p>其中端口号和<code>token</code>按需要自行更改，Samba服务的<code>tcp</code>端口号为<code>445</code>。</p>
<p>服务端配置文件格式如下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">[common]
</span></span><span class="line"><span class="cl">bind_port = 6000
</span></span></code></pre></div><p>为了安全，别忘了配置<a href="https://gofrp.org/docs/reference/server-configures/#%E6%9D%83%E9%99%90%E9%AA%8C%E8%AF%81">权限验证</a>，同时别忘了修改服务器的防火墙设置。</p>
<h2 id="others">Others</h2>
<p>所以到此为止，咱的Samba服务器就搭建好了。</p>
<p>随便传了个大文件试了一下，内网上传速度在6MB/S左右，有些慢但是还没搞清楚到底是什么原因导致的。</p>]]></content:encoded>
    </item>
    <item>
      <title>我们来组装一台电脑吧</title>
      <link>https://blog.starry-s.moe/posts/2021/build-desktop/</link>
      <pubDate>Mon, 13 Sep 2021 23:20:29 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2021/build-desktop/</guid>
      <description>&lt;p&gt;很久很久以前就想自己组装电脑了，看到别人能有台高性能的电脑别提有多羡慕了。不过考虑到在校期间实在不适合把自己组装的电脑放到寝室中去，要是把电脑放家里的话只能假期玩短短的几星期，其余的时间闲置下来的话又觉得有些浪费，于是组装电脑的计划一直推延至今。最终在大四校外实习自己租房子之后才有了一个短暂而勉强稳定的环境可以让我组装台式机，所以在安置好自己的住处和一系列其他事情之后，我终于可以实现自己这个埋藏心底多年的愿望之一了。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>很久很久以前就想自己组装电脑了，看到别人能有台高性能的电脑别提有多羡慕了。不过考虑到在校期间实在不适合把自己组装的电脑放到寝室中去，要是把电脑放家里的话只能假期玩短短的几星期，其余的时间闲置下来的话又觉得有些浪费，于是组装电脑的计划一直推延至今。最终在大四校外实习自己租房子之后才有了一个短暂而勉强稳定的环境可以让我组装台式机，所以在安置好自己的住处和一系列其他事情之后，我终于可以实现自己这个埋藏心底多年的愿望之一了。</p>
<blockquote>
<p>因包含插图，为了浏览体验我只对图片进行了适量压缩，</p>
<p>网络不好的用户可能需要多花一些时间加载。</p>
</blockquote>
<meting-js server="netease" type="song" id="22689960" theme="#233333"></meting-js>
<hr>
<h2 id="配置清单">配置清单</h2>
<blockquote>
<p>一边翻着某东的购物车一边写</p>
</blockquote>
<ul>
<li>CPU + 主板: AMD Ryzen 5 5600X、华硕TFU GAMING B550M-PLUS (WI-FI) 共2477元</li>
<li>显卡: 撼讯 RX6600XT 红魔 3599元（之前的是实际买了技嘉 GeForce GT 730D3 529元)</li>
<li>内存: 英睿达 美光 DDR4 3200MHz 8Gx4 864元</li>
<li>固态硬盘: 西部数据 SN750 SE 1TB 929元 + 英睿达 美光 MX500 2TB 1299元</li>
<li>机箱: 先马 鲁班1 199元</li>
<li>电源: 长城 金牌电源 V7-700W 475元</li>
<li>CPU散热: 利民AK120 PLUS 169元</li>
<li>机箱散热: 利民 TL-R12 三个12CM风扇 119元 + 先马冰洞 风扇套装 3个12cm风扇49元</li>
<li>显示器：优派 VX2480-HD-PRO-3 165HZ 1143元</li>
<li>其他: 扎带5.5元 理线带 9.9元 追风者CMBO灯带 400mm两条 90元</li>
</ul>
<p>因为我之前用笔记本时已经有一个 ikbc c87 红轴机械键盘和 罗技 G304 无线鼠标 以及之前弄拓展屏时买了 戴尔 U2419HS 显示器，所以这些配件我就不再列入装机清单里面了。</p>
<p>排除显卡的话，整体配置全下来大概花了6.6K左右，有些配件是买完以后京东又降价了于是申请了价格保护退了一些回来。<s>显卡打算以后等价格恢复正常后再买。所以现在买的是英伟达730D，尽管是亮机卡但是我还是决定多花一点预算去自营店买一张靠谱的全新卡，这样就算以后我换显卡了，这张旧的730还可以拿去给亲朋好友的上古时代老电脑“升级”一下。</s></p>
<p>后续在旗舰店买了RX6600XT红魔，这卡可以说是AMD入门级显卡的旗舰款了。除此之外还买了一个高刷1080P显示器，然后买了3个不带灯光的纯白静音风扇和两条ARGB灯带用来装饰。所以整体算下来整机开销大约在12K左右。</p>
<p>主板觉得华硕重炮手B550M就已经蛮符合我自己的需求了，在B站上看了一下B550的评测发现华硕的重炮手要比微星和技嘉的迫击炮强一些（也贵了好几百元），所以决定多花些预算上一张性能强一点的主板，为后续的升级留一些空间（不过我觉得就这配置已经很够用了没啥好升级的了</p>
<p>因为AMD R7 5800X太贵了有点超预算，所以决定买的R5 5600X，除了核心数少了一些（跑编译少几个线程）之外，性能对于现在的日常使用来说已经很足够了。肯定比我笔记本上的R7 4800H强许多。</p>
<p>因为自己打算在电脑装Linux + Windows双系统，所以实际上我给电脑装了2条NVME固态（SN750 SE 1T + 降速的SN550 1T），然后还配了2T的英睿达MX500，以后可能根据需要还会买几个机械硬盘存数据用。</p>
<p>因为CPU散热不带灯光，尽管我不喜欢RGB那种太花里胡哨的效果，但是还是得买几个有光亮的机箱风扇，<s>因为预算有限而且不装显卡暂时对散热要求不高，所以我只买了比较便宜的风扇，不然机箱黑咕隆咚的太不好看了，以后有需要的话再修改。</s></p>
<p>后来咱又买了两条ARGB的灯带，然后花了一段时间把它安装到机箱里面，咱并不喜欢RGB，不过我还是希望机箱能有些观赏性的灯光。</p>
<p>在B站找了几个电源测评介绍的视频看了一下，就买了长城的700W金牌电源，价格比较便宜不是一元一瓦，<s>目前来讲我不装显卡就日常待机的话整体功率都不会超100W，主要是电费太贵了。</s>
不过现在来看RX6600XT的功耗最高也就150W，（应该给这显卡贴个一级能效标识），所以电脑就算满载使用最高也就不到500W的功耗，日常使用的话显卡功耗只有20-30W，超级节能！</p>
<p>从网上挑了很久的机箱发现实在没有比较便宜又顺眼的，一开始想买白色机箱来着，后来发现白色机箱要么就是奇葩风道要么就是超预算，所以最终决定买先马鲁班1黑色机箱，尺寸够大对散热和主板显卡长度几乎没有限制，不用买配件时总计算空间大小了。</p>
<p>配置是周五定下来就从京东上买的，因为基本上都是自营，周六当天就全收到货了。所以周六+周日两天一顿折腾就把电脑装好。一开始还比较担心会不会安错，哪里出问题需要返工这类的情况，实际上装机十分顺利。除了理线花了一些时间以外其他都基本上是一次装齐直接点亮装系统，其实第一次装好机插电源后按开机键怎么也点不亮，当时吓够呛然后拔掉开机跳线检查是不是接线有问题。后来才发现是自己脑残电源开关开反了主板没通电肯定点不亮。</p>
<blockquote>
<p>防呆不防傻， 那么你能帮帮我吗？（逃</p>
</blockquote>
<hr>
<h2 id="装机">装机</h2>
<p>我装机时为了防止装错顺序返工，于是去B站搜了一个教程：【<a href="https://www.bilibili.com/video/BV1jE411e7hw">BV1jE411e7hw</a>】，照着这个顺序安装基本不会出错，而且比较常见的问题视频上都有提到，比如萌新不是十分熟悉的内存插槽优先级以及比较难弄的前面板跳线。</p>
<blockquote>
<p>以下图片除了背部理线的那张图是用手机(iPhone XS)在弱光环境拍摄的，其余都是用相机(佳能EOS 800D + 适马17-50)拍摄，设备不是很好，技术有限，使用RawTherapee进行适量的裁剪调色</p>
</blockquote>
<p><img loading="lazy" src="images/IMG_6800.jpg" alt="主板" />
<p style="margin-bottom: -0.8em;" class="image-title">主板</p>
</p>
<p><img loading="lazy" src="images/IMG_6802.jpg" alt="AMD R5 5600X" />
<p style="margin-bottom: -0.8em;" class="image-title">AMD R5 5600X</p>
</p>
<p><img loading="lazy" src="images/IMG_6804.jpg" alt="内存" />
<p style="margin-bottom: -0.8em;" class="image-title">内存</p>
</p>
<p><img loading="lazy" src="images/IMG_6809.jpg" alt="散热模具" />
<p style="margin-bottom: -0.8em;" class="image-title">散热模具</p>
</p>
<hr>
<p><img loading="lazy" src="images/IMG_6812.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">涂抹硅脂</p>
</p>
<p>因为散热器赠的利民TF7硅脂比较干，所以我直接点9个点然后就扣散热器了。觉得没必要像网上有人那样戴手套把硅质抹匀抹平，也不用特别在意硅脂会不会留气泡，总觉得这些不会对散热有太大影响。</p>
<p>毕竟用一年左右就得拆下来换新硅脂，到时候只要硅脂别太干粘住直接把CPU连根拔起就行。</p>
<hr>
<p><img loading="lazy" src="images/IMG_6814.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">散热器底部</p>
</p>
<p>特地拍了一张照片以确认我把散热器底下的膜撕掉了。</p>
<p><img loading="lazy" src="images/IMG_6816.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">安装散热器</p>
</p>
<hr>
<p><img loading="lazy" src="images/IMG_6821.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">固态硬盘</p>
</p>
<p><img loading="lazy" src="images/IMG_6822.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">亮机卡</p>
</p>
<hr>
<p><img loading="lazy" src="images/IMG_6825.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">装好的效果</p>
</p>
<p><img loading="lazy" src="images/photo_2021-09-14_00-34-22.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">理线后的效果</p>
</p>
<p>背面理线花了好几个小时把不同种类形状的线绑了起来，最顶上的风扇RGB灯光线实在没有空间收纳于是只好捆成一坨放在那。</p>
<h2 id="最终成果展示">最终成果展示</h2>
<p><img loading="lazy" src="images/IMG_6831.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">最终成果演示</p>
</p>
<p>装好Windows，安装华硕主板的软件，把风扇的灯调成了橙色长亮的模式。一开始调成浅蓝色觉得色温太低了有点冷，所以改成了暖色调。</p>
<p><img loading="lazy" src="images/IMG_6839.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">暖色</p>
</p>
<p>给机箱内部拍一张特写，现在正在考虑要不要买一个手办放进去。</p>
<p><img loading="lazy" src="images/IMG_6847.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">少女梦幻粉</p>
</p>
<p>后来买了新显卡和ARGB灯条后，把4根内存插满，设置好显卡的神光同步，把机箱颜色调成了“少女梦幻粉”。</p>
<p>（相机的夜景效果不太好，所以看起来有些晃眼而且丑，实际上这颜色还是很好看的）</p>
<hr>
<p><img loading="lazy" src="images/IMG_6841.jpg" alt="Arch Linux" />
<p style="margin-bottom: -0.8em;" class="image-title">Arch Linux</p>
</p>
<p>装好Arch Linux后的最终效果。</p>
<p>最终我决定还是把机箱放在桌子上好一些，这样不但能透过玻璃“欣赏”内部构造，还不用担心底部电源的风扇“吸尘器”吸进去很多灰尘。</p>
<p>机箱内部风道我也是采用的网上最推荐的风道方式，底部和右边为进风口，顶部和左边为出风口。因为现在没有显卡发热量并不大所以我只安装了左边的一个风扇和顶部的两个风扇。</p>
<p>以后可能再买个手办和灯放到电源仓的上边，再搞点装饰。</p>
<h2 id="为什么买了rx6600xt">为什么买了RX6600XT</h2>
<p>其实咱一开始是想买RTX3060Ti的，但是因为老黄溢价实在太高（NVIDIA f**k you），狗东双十一耍猴，所以去ytb看了一下评测发现这A卡可以通吃1080P全部的游戏（实际上2K很多游戏也全都能玩），而且价格比3060便宜许多，然后一想3060Ti的确有些性能过剩，而且N卡的Linux驱动真的很拉跨（尽管最近一阵子更新了很多东西但是真的就是不想再折腾N卡驱动了）。</p>
<p>然后就是，如果真的玩fps游戏的话，有谁会真的开极高画质上2K144呢。咱玩守望除了把材质调最高之外其他都是最低，3A大作的话咱也就玩过尼尔和底特律，而且咱不玩赛博朋克2077，而且咱主要还是Linux用户。所以真的没必要花首发价2倍的钱去买一个所谓的能在2K分辨率下全高画质流畅运行游戏的显卡。更值得吐槽的是，现在网上一搜显卡评测，给人的感觉就是游戏不开极高画质就好像不能玩了一样，还有很多人还拿A卡开光追后的帧数和N卡作比较，说实话我本人对光追真没什么好感，我玩的这些游戏里没有支持光追的，而且就算玩支持光追的游戏，开光追也看不出多大差距来。</p>
<p>网上有很多评测视频说这显卡显存位宽只有128比特，只支持PCIE4.0x8，核心面积小啊，丐中丐不值三千块钱这个价之类的一大堆缺点，不过这卡实际体验并没有网上传的那么不堪。它实际的游戏效果还是很好的，所以真没必要非拿硬件参数说事，缺点有是有，但影响并不大。</p>
<p>实际上手体验后，这显卡日常待机时风扇转都不转，温度只有40-50度，功耗在20-30W之间肥肠节能。Linux里面卸载掉NVIDIA一大堆驱动后，安装<code>amdgpu</code>驱动直接就能用，没遇到过任何问题。</p>
<p>（要不是因为刚需，谁会在这个时间点上买显卡&hellip;</p>
<h2 id="游戏体验">游戏体验</h2>
<p>守望先锋高画质（我不开极高画质），训练靶场或者进比赛帧数400fps直接跑满，打团时帧数大约下降到320-350之间。（正常人是不会在打团时盯着屏幕左上角看帧数变化的）</p>
<p>Minecraft Java Edition 1.16.4 + Optifine（咱加了这么多定语是因为Minecraft有很多版本，而且还有很多用来改善画质或性能的Mod）：</p>
<p>不知是MC的优化不好还是遇到了CPU瓶颈还是显卡驱动的问题，玩MC时帧数平时会有250帧，但是一到红石机器多的地方或者全物品分类机这类的地方，帧数就掉到了140帧以内，虽然帧数并不高（实际上也不低了），但是已经很流畅了，只不过对生电玩家不太友好。</p>
<p>值得一提的是，这显卡在玩MC时功耗也只有30瓦左右，而且大多数时间风扇都不转，真的太环保了（</p>
<h2 id="arch-linux">Arch Linux</h2>
<p>整个安装Linux的过程十分顺利，不像笔记本那样经常遇到许多奇奇怪怪的问题。加上走的独显直连没有麻烦的双显卡混合交火，基本上是装好自己需要的软件后开机就直接用了。</p>
<p>之前联想R7000P那个笔记本还经常无法调节屏幕亮度，TTY下面的警报“滴”声大得吓人，以前还遇到过nvidia+amdgpu+gnome进wayland就黑屏的问题（现在nvidia升470已经修复了）。</p>
<p>曾经用的那个惠普更是因为英伟达显卡驱动和惠普奇葩的硬件在linux5.0内核之前经常出现黑屏死机这些问题，而且不开启独显的话就没办法连双显示器。</p>
<p>所以还是台式机最香，觉得硬盘不够用了我就可以买块新硬盘扯根线扩容，主板上有4个内存插槽够我插很多根内存。CPU也都是可拆卸的只要主板支持我还可以继续升级，而且我可以选择没有集成显卡的CPU，显示器直连独显不用被那些驱动问题弄得头疼。</p>
<h2 id="others">Others</h2>
<p>其实最初我是想把装机的过程录下来拍成VLOG的，但是租的房子空间有限加上不想露脸，我一说话还会紧张不会组织语言，而且我没带三脚架，所以为了节省体力我只是拍几张照片放到博客上。</p>
<p><del>尽管现在这个电脑显卡很差不能打大型游戏，但是他的强悍的CPU性能已经足够我干很多事情了。</del> 一开始想测试一下CPU性能于是下载了Linux内核，用默认生成的<code>config</code>，<code>make -j12</code>编译。然后找个工具(<code>lm_sensors</code>)查看一下CPU温度。当我还没配好配置文件时突然发现内核他编译好了。</p>
<p>然而除了写代码之外我暂时还没想好什么其他的使用这个电脑的方法。因为有点舍不得电费加上没有公网IP所以我也不打算把它当服务器用。</p>
<blockquote>
<p>可能有人会更关心我在北京实习的相关事情，其实我并不是不想写在博客里面，而是想等过几个月有了一个比较完整的实习体验后再写，因为最近每天都很忙而且心情变化很复杂，毕竟自己一个人离开学校到北京。所以我不想把某一刻的心情写在博客上当作我这一阵子的整体体验，现在还不是做总结的时候（心情好的话，也许我会写在今年年终总结上吧）。</p>
</blockquote>
<hr>
<h2 id="后续">后续</h2>
<p>2022-02-26补充:</p>
<p>咱给这个电脑换了新的机箱，考虑了一下如果把换新机箱的内容添加到这里的话，会对原有的我组装电脑的内容产生影响，所以我新开了一篇博客，感兴趣的可以去看：<a href="/posts/sama-quzao/">电脑换壳——先马趣造</a>。</p>
<p>以后组装电脑相关的博客，都会在标签 <a href="/tags/%E5%8F%B0%E5%BC%8F%E6%9C%BA/">台式机</a> 里面找到。</p>
<br>
<p><strong>STARRY-S</strong></p>]]></content:encoded>
    </item>
    <item>
      <title>NieR:Automata 游戏记录</title>
      <link>https://blog.starry-s.moe/posts/2021/nier_automata/</link>
      <pubDate>Sun, 15 Aug 2021 00:23:55 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2021/nier_automata/</guid>
      <description>&lt;p&gt;这是我入手的第一款RPG游戏，也是我迄今为止买的最贵的游戏，在大一的那个寒假狠下心来用压岁钱花了四百多在Steam上买的正版，后来在今年年初买PS4 Slim时又买了一张二手光碟以及DLC。&lt;/p&gt;
&lt;p&gt;尽管游戏玩了3遍，但是不敢保证说我把尼尔前前后后的每个细节都弄清楚，不过还是打算把我遇到的一些有意思的地方记录到博客中。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>这是我入手的第一款RPG游戏，也是我迄今为止买的最贵的游戏，在大一的那个寒假狠下心来用压岁钱花了四百多在Steam上买的正版，后来在今年年初买PS4 Slim时又买了一张二手光碟以及DLC。</p>
<p>尽管游戏玩了3遍，但是不敢保证说我把尼尔前前后后的每个细节都弄清楚，不过还是打算把我遇到的一些有意思的地方记录到博客中。</p>
<meting-js server="netease" type="song" id="22689961" theme="#233333"></meting-js>
<hr>
<p><img loading="lazy" src="images/NieR_Automata_20210115172747.jpg" alt="&ldquo;遊樂園廢墟&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">遊樂園廢墟</p>
</p>
<p>第一次玩尼尔：机械纪元时除了一些必要的支线任务外，其他支线任务几乎都没有做完就主线通关删档了。</p>
<p>因为PC端没有官方汉化，觉得第三方汉化过于生硬就没有安装汉化包，于是我第一次玩这游戏时几乎全程英文生肉啃下来的，所以尽管游戏结局很令我感动，但是机械纪元究竟讲了怎样一个故事，期间为什么会发生这些事情，以及银莲、A2的过去究竟发生了什么，艾米尔是谁，为什么人们都说他很惨，塑料姐妹花的经历，为什么她们背负着很多罪孽我其实都不知道，所以第一遍玩完这个游戏后我很迫不及待的找了一个时间开始玩第二遍。</p>
<p>第一次打这游戏时全程简单模式+自动芯片，因为没打支线练等级所以三周目时因为等级太低，半天杀不死一个怪物。</p>
<p>然后因为没有手柄，只用鼠标操作，所以9s进行骇入后用鼠标很难控制方向，所以9s打怪时我几乎全程用手打，几乎没怎么进行骇入。</p>
<p>二刷时买了联想拯救者的xbox的有线手柄，就为了打尼尔。因为渣笔记本性能弱，游戏优化很差，GTX1050Ti只能开低画质降低分辨率至1366x768，才勉强30帧“流畅”运行，不过第二次玩尼尔时也是由于时间原因没有做很多支线，E结局到现在也没打完，一方面是因为懒，另一方面是因为后来买了PS4。</p>
<p>入手PS4后才终于才体验到比较流畅高清的画质，加上官方原配的繁体中文字幕(尽管是对照日语字幕翻译的，开英文语音时觉得字幕和语音有些对不上)，能更好的理解一些剧情。然后准确来说去年疫情 <s>超长假期</s> 的时候还买了繁体中文的尼尔短话和长话小说，所以对一些幕后的剧情有了简单的了解，所以我把绝大多数支线都做完了(除了三周目末尾的一个向4s汇报情报的支线以及速度之星这个废手的支线外，别的应该是都做完了)，还攒了全武器，打通了全结局(26结局+dlc结局)，最后在寝室打了E结局然后心满意足的删档。尽管游戏里还有一些特性我没有尝试触发（比如B站上随处可见的卡墙bug，左脚踩右脚上天，如何流畅帅气的打怪之类的），DLC斗兽场的最难级别没有打过（对我这手残党来说真的太难了），所以没有亲自打死SQUARE ENIX社长（大雾）有些可惜。不过我觉得还是可以先把我在这个游戏里的一些回忆记录下来，不然时间久了就又忘了。</p>
<p>想了一下我不太会以叙事的方式讲述游戏的剧情，讲出来的话也就是通篇流水帐，所以我只打算把一些自认为不错的游戏截图贴上来，<s>至于文字描述我暂时先不打算写，以后有时间再补上</s>。</p>
<blockquote>
<p>其实我今年4月份就把游戏就打完了，但是我硬是拖了这么久才把游玩记录写到博客上。</p>
</blockquote>
<hr>
<p><img loading="lazy" src="images/NieR_Automata_20210115175404.jpg" alt="&ldquo;记忆&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">记忆</p>
</p>
<p><img loading="lazy" src="images/NieR_Automata_20210115180846.jpg" alt="&ldquo;八音盒&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">八音盒</p>
</p>
<p><img loading="lazy" src="images/NieR_Automata_20210116183544.jpg" alt="&ldquo;游乐园的月之泪&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">游乐园的月之泪</p>
</p>
<p><img loading="lazy" src="images/NieR_Automata_20210116183651.jpg" alt="&ldquo;充满“干劲”的机器人&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">充满“干劲”的机器人</p>
</p>
<p><img loading="lazy" src="images/NieR_Automata_20210116183741.jpg" alt="&ldquo;游乐园&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">游乐园</p>
</p>
<p><img loading="lazy" src="images/NieR_Automata_20210116184004.jpg" alt="&ldquo;机器人版 罗密欧与朱丽叶&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">机器人版 罗密欧与朱丽叶</p>
</p>
<p><img loading="lazy" src="images/NieR_Automata_20210116184223.jpg" alt="&ldquo;描述人类残暴性的作品&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">描述人类残暴性的作品</p>
</p>
<p><img loading="lazy" src="images/NieR_Automata_20210117155407.jpg" alt="&ldquo;摸头&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">摸头</p>
</p>
<p><img loading="lazy" src="images/NieR_Automata_20210117163441.jpg" alt="&ldquo;NieR:AutoFishing&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">NieR:AutoFishing</p>
</p>
<p><img loading="lazy" src="images/NieR_Automata_20210119163908.jpg" alt="&ldquo;Lunar Tear&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">Lunar Tear</p>
</p>
<p><img loading="lazy" src="images/NieR_Automata_20210119165209.jpg" alt="&ldquo;4s&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">4s</p>
</p>
<p><img loading="lazy" src="images/NieR_Automata_20210124120504.jpg" alt="&ldquo;A2&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">A2</p>
</p>
<p><img loading="lazy" src="images/NieR_Automata_20210124143553.jpg" alt="&ldquo;艾米尔的家&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">艾米尔的家</p>
</p>
<p><img loading="lazy" src="images/NieR_Automata_20210124182334.jpg" alt="&ldquo;森林国王&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">森林国王</p>
</p>
<!-- ![](images/NieR_Automata_20210129002557.jpg) -->
<p><img loading="lazy" src="images/NieR_Automata_20210129010129.jpg" alt="&ldquo;沉没都市&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">沉没都市</p>
</p>
<!-- ![](images/NieR_Automata_20210204010638.jpg) -->
<!-- ![](images/NieR_Automata_20210204012723.jpg) -->
<p><img loading="lazy" src="images/NieR_Automata_20210204015704.jpg" alt="&ldquo;艾米尔的决意&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">艾米尔的决意</p>
</p>
<p><img loading="lazy" src="images/NieR_Automata_20210206020307.jpg" alt="&ldquo;9s 陪寝&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">9s 陪寝</p>
</p>
<p><img loading="lazy" src="images/NieR_Automata_20210214211911.jpg" alt="&ldquo;任务&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">任务</p>
</p>
<p><img loading="lazy" src="images/NieR_Automata_20210214212838.jpg" alt="&ldquo;创造不是那么简单的事了&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">创造不是那么简单的事了</p>
</p>
<p><img loading="lazy" src="images/NieR_Automata_20210219203841.jpg" alt="&ldquo;记好啦&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">记好啦</p>
</p>
<p><img loading="lazy" src="images/NieR_Automata_20210219204035.jpg" alt="&ldquo;最轻松的！&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">最轻松的！</p>
</p>
<p><img loading="lazy" src="images/NieR_Automata_20210219210754.jpg" alt="&ldquo;第6次格式化&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">第6次格式化</p>
</p>
<p><img loading="lazy" src="images/NieR_Automata_20210219210830.jpg" alt="&ldquo;女人真恐怖&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">女人真恐怖</p>
</p>
<p><img loading="lazy" src="images/NieR_Automata_20210220234528.jpg" alt="&ldquo;Morning!&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">Morning!</p>
</p>
<p><img loading="lazy" src="images/NieR_Automata_20210222233545.jpg" alt="&ldquo;Meow&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">Meow</p>
</p>
<p><img loading="lazy" src="images/NieR_Automata_20210223000109.jpg" alt="&ldquo;4s&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">4s</p>
</p>
<!-- ![](images/NieR_Automata_20210309155216.jpg) -->
<p><img loading="lazy" src="images/NieR_Automata_20210309155202.jpg" alt="&ldquo;Hacking A2&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">Hacking A2</p>
</p>
<p><img loading="lazy" src="images/NieR_Automata_20210309204622.jpg" alt="&ldquo;Emil&rsquo;s Memory&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">Emil&#39;s Memory</p>
</p>
<p><img loading="lazy" src="images/NieR_Automata_20210309211227.jpg" alt="&ldquo;Share Happiness!&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">Share Happiness!</p>
</p>
<p><img loading="lazy" src="images/NieR_Automata_20210309220354.jpg" alt="&ldquo;Emil&rsquo;s Home&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">Emil&#39;s Home</p>
</p>
<p><img loading="lazy" src="images/NieR_Automata_20210310140442.jpg" alt="&quot;《没中》&quot;" />
<p style="margin-bottom: -0.8em;" class="image-title">《没中》</p>
</p>
<p><img loading="lazy" src="images/NieR_Automata_20210310141143.jpg" alt="&ldquo;孩子们的核心&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">孩子们的核心</p>
</p>
<p><img loading="lazy" src="images/NieR_Automata_20210314205859.jpg" alt="&ldquo;晚安，好梦！&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">晚安，好梦！</p>
</p>
<p><img loading="lazy" src="images/NieR_Automata_20210314211653.jpg" alt="&ldquo;Brother&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">Brother</p>
</p>
<p><img loading="lazy" src="images/NieR_Automata_20210320213830.jpg" alt="&ldquo;利落地杀掉我&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">利落地杀掉我</p>
</p>
<p><img loading="lazy" src="images/NieR_Automata_20210320225429.jpg" alt="&ldquo;柏拉图&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">柏拉图</p>
</p>
<p><img loading="lazy" src="images/NieR_Automata_20210321090429.jpg" alt="&ldquo;柏拉图的回忆&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">柏拉图的回忆</p>
</p>
<p><img loading="lazy" src="images/NieR_Automata_20210321095116.jpg" alt="&ldquo;谢谢你愿意听我说话&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">谢谢你愿意听我说话</p>
</p>
<p><img loading="lazy" src="images/NieR_Automata_20210321104439.jpg" alt="&ldquo;deb[U]nked&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">deb[U]nked</p>
</p>
<p><img loading="lazy" src="images/NieR_Automata_20210321115643.jpg" alt="&ldquo;好一个逃离现场&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">好一个逃离现场</p>
</p>
<p><img loading="lazy" src="images/NieR_Automata_20210321131750.jpg" alt="&quot;[E]nd&quot;" />
<p style="margin-bottom: -0.8em;" class="image-title">[E]nd</p>
</p>
<hr>
<p><img loading="lazy" src="images/NieR_Automata_20210220231427.jpg" alt="&ldquo;NieR:Automata&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">NieR:Automata</p>
</p>]]></content:encoded>
    </item>
    <item>
      <title>《空之境界》摘抄</title>
      <link>https://blog.starry-s.moe/posts/2021/karanokyokai/</link>
      <pubDate>Sat, 03 Jul 2021 21:01:37 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2021/karanokyokai/</guid>
      <description>&lt;p&gt;很久以前就打算二刷空境原著了，由于种种原因进展一直比较缓慢。&lt;/p&gt;
&lt;p&gt;最终在 Kindle (&lt;del&gt;泡面盖&lt;/del&gt;) 上阅读时标记了一些不错的菌言菌语，整理到博客上。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>很久以前就打算二刷空境原著了，由于种种原因进展一直比较缓慢。</p>
<p>最终在 Kindle (<del>泡面盖</del>) 上阅读时标记了一些不错的菌言菌语，整理到博客上。</p>
<meting-js server="netease" type="song" id="1322356611" theme="#233333"></meting-js>
<hr>
<h2 id="俯瞰风景">俯瞰风景</h2>
<ul>
<li>
<p>飞行这个名词，与坠落这个名词是相连结的。但越是迷恋天空的人，越会欠缺这样的认知，结果死了之后也只能持续朝云端飞行，不会往地面坠落下来，等于是朝着天空坠落。</p>
</li>
<li>
<p>视野并不是眼球看到的景象，而是大脑处理过的景象。我们的视野受到我们的常识保护着，不认为自身的高度叫高，甚至觉得是种常识，没有高这个概念存在。反过来说，凡是人类，都活在俯瞰的视野中。</p>
</li>
<li>
<p>人是活在箱中的生物，也只能在箱中生活。</p>
</li>
<li>
<p>在这短暂存在的密室里，现在无论外界发生什么事都与式没有关联，也无法产生关联。这份实际感受，微微沁入她本应空虚的心。</p>
</li>
<li>
<p>——如果自身的视野就是世界的一切，此刻世界的确正在沉睡。</p>
</li>
<li>
<p>如果城市是深海，夜空就是纯粹的黑暗。在那片黑暗上，星辰就像散落的宝石那般闪闪发光。月亮是洞穴，一个凿穿夜空这张黑色图画纸的巨大洞穴。</p>
</li>
<li>
<p>月亮其实不是反射太阳的镜子，只是在窥视这一侧的景色——在两仪家，式曾听人这么说过。</p>
</li>
<li>
<p>一头宛如以一根根丝线梳就的黑发顺滑无比，只要风势一大，黑发迎风飞舞的模样就散发出无比幽玄之美。</p>
</li>
<li>
<p>刀刃有六寸长，与其说是刀更像是一柄只由白刃构成的凶器。</p>
</li>
<li>
<p>因为天空没有尽头。我认为如果能无拘无束的漫游、能自由飞往任何地方，就可以找到我不讨厌的世界。</p>
</li>
<li>
<p>所谓的 ‘逃’ 有两种，漫无目的的逃以及带有目的的逃。一般将前者称为 ‘漂浮’，后者称为 ‘飞行’。</p>
</li>
<li>
<p>我们并不是根据背负的罪来选择道路，而是先选择道路再背负起自己的罪孽。</p>
</li>
<li>
<p>虽然根本不值得一提，我终究认为自己最后还是应该死于从俯瞰坠落。</p>
</li>
<li>
<p>无论当事人下了什么决定，自杀还是会被视为自杀来处理。</p>
</li>
</ul>
<hr>
<h2 id="杀人考察前">杀人考察（前）</h2>
<ul>
<li>
<p>人要无知一点比较好，黑桐。人在小时候只看得到自己，根本不会察觉别人的恶意。就算是污秽也好，当被爱的感觉转化成经验，人才能够以善意去对待他人——因为人只能展现本身已具有的情感。</p>
</li>
<li>
<p>你要当心点，黑桐同学。不详的预感，会招来不详的现实</p>
</li>
</ul>
<hr>
<h2 id="痛觉残留">痛觉残留</h2>
<ul>
<li>
<p>无法适应社会的人非常多，他们的存在本身却从一开始就无法适应这个社会。他们不应该存在，不，是无法存在。</p>
</li>
<li>
<p>这让她很高兴。因为活下去，就等于痛苦下去。</p>
</li>
<li>
<p>被排除在境界之外的人，也会被彻底剥夺其存在意义。所以，那只不过是肉块罢了。</p>
</li>
<li>
<p>对于想不通的事，不要强迫自己接受比较好。</p>
</li>
<li>
<p>因为缺乏感觉的人一样拥有身体，也能够移动自如，我们就认为他们除了没有感觉之外没什么不同。但这是错误的。没有感觉，就代表什么都接收不到喔，黑桐。</p>
</li>
<li>
<p>黑桐没能赶上是吗？接下来，就看暴风雨是先抵达，还是先被制造出来。式一个人去，有可能反被打败啊，两仪。</p>
</li>
<li>
<p>痛觉可是种好东西，有错的终究是伤口，不可以搞错先后顺序。我们需要痛觉，无论多么痛苦都一样。</p>
</li>
<li>
<p>“没有什么伤是无法痊愈的。不会痊愈的伤口不叫伤口，叫作死亡。”</p>
</li>
</ul>
<hr>
<h2 id="伽蓝之洞">伽蓝之洞</h2>
<ul>
<li>
<p>如果眼睛看得见，我大概正看着他应付的笑容。</p>
</li>
<li>
<p>“你的嗜好还真奇怪。”</p>
<p>“我好高兴。真让人惊讶，没想到橙子小姐也有跟普通人一样的温情和道义精神！”</p>
</li>
<li>
<p>“没错，你无法过着正常生活。要烦恼也该有个限度，两仪式，你该认清现实了。你原本就是属于我们这边的人吧？ 所以——别再梦想什么普通（幸福）的生活了。”</p>
</li>
<li>
<p>这女的——真是专挑我逃避的问题刺人痛楚。</p>
</li>
<li>
<p>正因为失去的事物永不复返，我决定不再后悔。</p>
</li>
</ul>
<hr>
<h2 id="矛盾螺旋">矛盾螺旋</h2>
<ul>
<li>
<p>所谓的变成大人，就是明智地将幻想取代。</p>
<p>自以为早熟的愚昧，让我骄傲地接受了这个事实。</p>
</li>
<li>
<p>追求社会上理所当然的生活就得遭受打击。只要接受我的人生注定如此，就不会觉得自己不幸。这和小时候一样。我以聪明代替幻想，决定一个人活下去。</p>
</li>
<li>
<p>我明明热爱奔跑，奔跑明明曾是我的救赎，到头来我却发现那不过是发生了一些不幸后便可以抛弃的东西，不仅愕然。</p>
</li>
<li>
<p>只要一堕落，人是否就会变得这么婆婆妈妈的？我不禁傻眼。</p>
</li>
<li>
<p>这家伙以前曾说心是看不见的。因此，她绝不会对别人吐露肉眼看不见的烦恼。</p>
</li>
<li>
<p>所以——为了不再做梦，我只不过是在被杀前抢先宰了对方。</p>
</li>
<li>
<p>忍耐明明是你的优点，结果你却选择了痛苦的那条路。第一次见面的时候，胭条巴正要抹杀胭条巴。失去未来，变成空壳的你，也跟现在一样想死是吗？</p>
</li>
<li>
<p>“重点明明在于学到什么，这个国家却本末倒置。只要有真材实料根本不需要什么证据，大家却为了得到资格去学习，而不是透过学习的成果取得资格。一个只剩下用来证明‘我学到这么多！’的资格，不就像契约书一样。”</p>
</li>
<li>
<p>或许，她就是将如暗处鲜花般优美的日本幽灵，与外国童话中的妖精融合而成的结晶。</p>
</li>
<li>
<p>“和他们结识，算是我在伦敦时唯一的疏忽吧。”</p>
</li>
<li>
<p>灵长已经变得太复杂，是过度追求万能，替生命附加种种能力导致的结果。</p>
</li>
<li>
<p>朦胧不定的东西会召唤朦胧不定的言语。事情明明这么简单，方才的平稳气息却已散去，让人难以呼吸。</p>
</li>
<li>
<p>那是因为无论拥有怎样优秀的肉体、素质，对于一个人来说只能把一件事情做到极致。去到高处的话可以，然而除此以外的山便无法去攀登了。</p>
</li>
<li>
<p>“你这是什么表情。让那系统崩坏掉的人就是你吧。所谓的精神异常者呢，由于自以为自己的异常是梦境所以才没有破绽。式过去也是这样的。但是却不由得注意到了名为黑桐干也的人。于是对两仪式的存在方式察觉到了异常。”</p>
</li>
<li>
<p>“哦，离题了。说着与两仪有关的话就没有注意到，似乎是被什么逼迫着一般。不知不觉就说多了。说不定黑桐你明天就死掉了呢。”</p>
<p>“&hellip;&hellip;不敢当。我会小心车子的。”</p>
</li>
<li>
<p>也就是指男性之中的女性部分和女性之中的男性部分。从男性的语气来推断出是阳性，这结论未免下得太早了。无论是谁都会拥有偏向异性的思考模式。男扮女装的怪癖是最为典型的。现在的式毫无疑问是阴性的式。男性的语气，是她为了死掉的织而在无意识下进行的代偿行为。至少，是希望你还能够记得织的事情也说不定。呼呼呼，这不是很可爱吗？</p>
<p>“&hellip;”</p>
</li>
<li>
<p>由于没有任何一点小的异常，所以也就注意不到大的异常。</p>
</li>
<li>
<p>“要打开了。这可是阔别半年的自己的家呦，胭条。”</p>
<p>两仪很开心似的说着。</p>
</li>
<li>
<p>若不在人们身上使用相同的仪式死亡，给你的献祭便不完全。如果死亡之后再次复活的螺旋不完全。没有达到相互交缠且相克的条件，便无法将其联系起来。于是我便准备了他们的尸体作为阴，他们平常的生活作为阳。</p>
</li>
<li>
<p>死者与生者无法兼容。在满是矛盾的这个世界中，个体是没有共通这层意义的。</p>
</li>
<li>
<p>第一次脱离了死的困境，但那只不过是为了迎接第二次、第三次的死所注定的方法。这种有限的死的方式。</p>
</li>
<li>
<p>“&hellip;&hellip;推了煞车坏掉的人一把，这种做法是不对的。”</p>
</li>
<li>
<p>为了消除现象而引起的现象，最终会变成将自己向绝境逼迫的行为。但是果然，即使留下最初的现象不管，也会演变成被逼迫至绝境的情形。无论怎样努力，现象这个词的含义是不会消失的。</p>
</li>
<li>
<p>由于事物总是连带有许多阻碍，所以并不存在完美的事物。</p>
</li>
<li>
<p>橙子小姐的言辞一针见血。</p>
</li>
<li>
<p>不过到了近代这种称呼就不再使用了。文明发达了，人们变得很容易就能够将自身灭绝。</p>
</li>
<li>
<p>“学问和年龄无关，柯尼勒斯，虽然你外表看起来很年轻，但你总是只注意外表，所以内在才会追不上啊。”</p>
</li>
<li>
<p>人类的个体若是完成，生存的意义就会消失。但各种人类却只为了生存下去的欲望而无意识地拒绝它，所有的人类在以人类身份思考时，变成比动物还要不如。明明为了完成而生存，却为了生存而拒绝完成。</p>
</li>
<li>
<p>让一个人了解事物，与其教他，不如让他自己体验来得快。</p>
</li>
<li>
<p>生命的证据不是如何去追求快乐，因为生命的意义，就是要去体会痛苦。</p>
</li>
<li>
<p>——风停了，信号也已响起。</p>
<p>来吧 ——该开始认真地奔跑了——</p>
</li>
<li>
<p>我虽然讨厌别人的同情，但我知道拒绝别人同情的代价，最后报应会发生在自己身上。</p>
</li>
<li>
<p>然后，男人的手伸了过来。</p>
<p>“一个人站不起来的话，我就助你一臂之力吧。”</p>
</li>
<li>
<p>男人“嗯”的一声，毫不做作地笑了。</p>
<p>“我也认为你应该会这么说的。”</p>
<p>那是一股不可思议、连我也想回报的笑容。</p>
</li>
<li>
<p>“你就拿着吧，因为这以后得由你来守护才行。”</p>
<p>我努力露出灿烂的笑容，但不知道是不是顺利笑了出来。</p>
</li>
<li>
<p>“&hellip;&hellip;我也一样，希望有人来帮我，一直希望有人来帮我，但是，我却不知道该把自己从哪里解放出来&hellip;&hellip;而结果也不该知道的，因为根本没有可以帮助我的方法。不管意义如何替换，只有一开始的现象无法消除。”</p>
</li>
<li>
<p>他回头看了一眼，那个被蒸汽和水声包围的地下室非常安静。那是连自己死了都不知道，到今天也还继续梦见日常之轮的脑髓灵魂安置所。</p>
</li>
<li>
<p>但那也是不可能的，扭曲的轮回不会在同一个地方转动，若死者不能亲自结束身为死者的存在，日常生活永远不会到来。</p>
</li>
<li>
<p>&hellip;&hellip;我失败了。不该和这些怪物扯上关系啊！</p>
<p>&hellip;&hellip;那就是，红大衣魔术师最后的思考。</p>
</li>
<li>
<p>从走廊看出去的夜景很安静、很寂寞，公寓周围只存有旁边那栋形状相同的公寓，公寓之间铺着柏油道路，还有绿色的庭院。那光景，与其说是夜景，还不如说是被绿意包围的墓碑。</p>
</li>
<li>
<p>“为什么回来。”</p>
<p>魔术师用沉重的声音问着。</p>
<p>巴无力回答，只是一直看着荒耶。他没有回答的余力，若不是全力集中精神，他连正面看着魔术师也作不到。</p>
</li>
<li>
<p>你的意志只不过是由幻想产生，由幻想所活化的东西而已。在这个世界死亡的胭条巴，已经只能在这里生活了。</p>
</li>
<li>
<p>事情就是因为有尽头，所以才能观测到无限这件事。</p>
</li>
</ul>
<hr>
<h2 id="忘却录音">忘却录音</h2>
<ul>
<li>
<p>在确认自己的记忆时，不可以依靠他人的记忆。毕竟只有名为回忆的自我天平，才能决定过去&hellip;&hellip;</p>
</li>
<li>
<p>所谓的忘记，其实是记忆劣化。回忆是一种不会消失、只会逐渐褪色的废弃物。你不觉得很可惜吗？人们竟然让属于永恒的事物生锈。亲手让身为永恒的事物化为烟尘。</p>
</li>
<li>
<p>污秽由污秽自己解决是最好的作法，因为不管是什么人，想要清除污秽，就一定会受到污秽沾染，这是一个不详的循环，我们称之为 ‘诅咒’。</p>
</li>
<li>
<p>所谓的天才，到最后只是把自己当成对手。</p>
</li>
<li>
<p>妖精很难控制，操纵者常常在不知不觉间，从实现他们自己的愿望变成实现妖精的愿望。鲜花你听清楚了，要注意——使用自己以外的东西所制造出来的使魔，别走到操纵者反被操纵的下场</p>
</li>
<li>
<p>我的能力只能从别人已经走过的道路来获得信息，但你却可以看到接下来的路通往哪里呢&hellip;&hellip;</p>
</li>
<li>
<p>人之所以选择忘却记忆，绝不是因为那些记忆没有必要，而是因为记住那些事很危险。</p>
</li>
<li>
<p>我们刻意忘却过去犯下的种种过错。忘却那些如果记得就会让自己崩溃的记忆。我们靠着这么做——才能守护自己现在是健康而无辜的幻象。</p>
</li>
<li>
<p>“物质是用来消费及磨耗的事物，这个名为地球的世界逐渐走向崩坏也是自然的道理，因为在最后走向死亡是最正确的存在方式，所以谁也不会去解决这个问题。对我们来说，真正的世界只存在于各自的脑髓中而已。”</p>
</li>
</ul>
<hr>
<h2 id="杀人考察后">杀人考察（后）</h2>
<ul>
<li>
<p>本能在表层意识具现化成人格时，将会驱逐所有理性，会凌驾我这个名为白纯里绪的人格。</p>
</li>
<li>
<p>向对方抱有的情感，超出自己的容许量的时候吧，自己能承受的感情量是一定的，有些人容量很大，也有人容量很小，不论是爱情或者是憎恨，当那种感情超过自己所能容纳的量，那么超出的部分会转变为痛苦，如此一来，便不能忍受对方的存在。</p>
</li>
<li>
<p>为了到最后让自己死去，所以我们只有杀一次人的权利。</p>
</li>
<li>
<p>人一辈子只能承受一人份的人生价值，为了原谅无法走到尽头的人生，所以大家才会用尊重的态度看待死亡，因为生命等价，即使是自己的生命，也不是自己所拥有的东西。</p>
</li>
<li>
<p>光是孕育意识的大脑，无法产生人格。虽然只有脑部也可以活下去，但我们必须先拥有肉体才能产生自我意识。有了肉体之后，和肉体一起培养，就有了现在的人格。喜爱自己肉体的人，应该属于社交型人格，而厌恶自己肉体的人，则属于内向型人格。虽然光有意识也可以培养出人格，但那样的人格是无法认识自己的，一般来说，心灵就会长成特别的东西。那样的话，已经不能称之为人格，和计算机没有什么不同。如果有谁只是一个大脑，那个人就必须创造出一个 ‘只有脑的自己’ 的人格。必须舍弃肉体这个大我，而保存意识这个小我。</p>
</li>
<li>
<p>“不是有了知性才有肉体。”</p>
<p>“而是，有了肉体之后，知性才得以诞生。”</p>
</li>
<li>
<p>说到底，人类只不过是在自己这个空壳中做着梦而已。明明是那么地显而易见的。</p>
</li>
<li>
<p>大部分的人并不是出于自己的愿望要过那样的生活。想要成为特别的存在却无法实现，这种形式才是真正的平凡人生。</p>
</li>
</ul>
<hr>
<h2 id="未来福音">未来福音</h2>
<ul>
<li>
<p>她擅长的并非预测未来，而是教人如何避免遭遇到不幸。</p>
</li>
<li>
<p>那是一种希望，希望对方相信她说的话。也是一种绝望，绝望于对方不可能相信。</p>
</li>
<li>
<p>所谓的现实，即为数值尚未确定的公式。公式的数值会不断变动，不用说是求出答案，人们连要求出什么都不知道。然而——一旦这个数值确定下来，答案便再也不会改变。</p>
</li>
<li>
<p>这一切都是醒着的，但没有什么活着的样子。没有任何例外。</p>
</li>
<li>
<p>“就是因为知道得太清楚，才会看不见吗——橙子说得对极了。炸弹魔，你有没有听到？既然你的眼睛什么都没看见，不如废了算了。”</p>
</li>
<li>
<p>“未来并没有不同。‘未来’这种东西，一开始便不存在。不存在的东西，当然没有办法控制。”</p>
</li>
<li>
<p>她依然是老样子，即使自己遇到不幸，也没有显露出来，仍旧表现得潇洒又懒惰，完全就是时下高中女生会有的样子。</p>
</li>
<li>
<p>“没错。那顶多算是憧憬。你就像个看到偶像，拼命叫个不停的幸福小粉丝。所谓的恋爱啊，应该要更轰轰烈烈、更不堪回首、更捉摸不定，如同不是抵达终点，便是发生意外的云霄飞车。总之啊，谈恋爱不可能留下什么美好回忆&hellip;&hellip;”</p>
</li>
<li>
<p>“我漂亮地踩到地雷。”</p>
</li>
<li>
<p>既然我可以用未来视取巧，相对地当然要承担一些代价。</p>
</li>
<li>
<p>不论谁看见她，都会期待她未来的发展，另一方面，又暗暗希望她永远维持现在的样子</p>
</li>
<li>
<p>在这个时代，幸福的未来相当罕见。</p>
</li>
<li>
<p>既然看得见未来，当然也会了解过去。</p>
</li>
<li>
<p>这十年——不，说得更正确，是十二年来，我始终像故事中模仿人类的机器人，融入这个城市的生活罢了。</p>
</li>
<li>
<p>我遇见难得的朋友，之后又失去他。尽管试着承续他的衣钵，却每天被唯一的读者批评。</p>
</li>
<li>
<p>尽管我也为此付出代价，往后的人生满是失败。但我至少还留有什么。</p>
</li>
<li>
<p>“你很快会从这个世界上消失。你的前途一片漆黑，未来完全没有任何希望。你不会留下任何东西，也没有获救的可能&hellip;&hellip;但是，太不可思议了，尽管如此，你的梦想将继续活下去。”</p>
</li>
</ul>
<hr>
<meting-js server="netease" type="song" id="590515" theme="#233333"></meting-js>]]></content:encoded>
    </item>
    <item>
      <title>Learn OpenGL</title>
      <link>https://blog.starry-s.moe/posts/2021/learn_opengl/</link>
      <pubDate>Mon, 10 May 2021 21:33:10 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2021/learn_opengl/</guid>
      <description>&lt;p&gt;其实咱很久很久以前就开始看&lt;a href=&#34;https://learnopengl-cn.github.io/&#34;&gt;LearnOpenGL CN&lt;/a&gt;这个网站了&amp;hellip;&amp;hellip;&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>其实咱很久很久以前就开始看<a href="https://learnopengl-cn.github.io/">LearnOpenGL CN</a>这个网站了&hellip;&hellip;</p>
<meting-js server="netease" type="song" id="1322354976" theme="#233333"></meting-js>
<hr>
<h2 id="前言">前言</h2>
<p>首先需要明白什么是核心模式，什么是立即渲染模式，立即渲染模式的代码中都包含<code>glBegin()</code>和<code>glEnd()</code>，绘图的部分都是在这两个函数之间，比如画一个点就是<code>glVertex2f(x, y)</code>，这种方式画图确实很简单，用户只要提供坐标和颜色就好了，不用知道GPU干了什么，而缺点是性能低，有很多限制，所以新版本的OpenGL为了给开发者提供更多的可操作空间而逐渐废弃了立即渲染改用核心模式。</p>
<p>核心模式提供了很多强大的功能，可以管线编程，代码量增加了很多，所以对初学者不太友好。</p>
<p>LearnOpenGL CN上面的代码基于C++，而OpenGL是用C编写的，教程上的代码除了矩阵运算部分可以使用C++的运算符重载而使代码编写起来变得更简单一些外，其他部分基本没啥区别，而且教程为了便于教学使用的也是面向过程式编程，没有将各个功能封装到一个类里面，所以咱这篇教程用C编写。</p>
<p>要记住C就是C，C++就是C++，C不是C++的子集，只能说C++兼容部分C的代码，不要把C和C++混用，不要因为C++支持面向对象使得一些功能看起来简单很易于使用就轻易迈入C++的坑。</p>
<h2 id="准备工作">准备工作</h2>
<p>有关OpenGL的介绍以及安装GLFW、GLAD以及编译所需的CMake的部分可以直接看<a href="https://learnopengl-cn.github.io/01%20Getting%20started/02%20Creating%20a%20window/">教程</a>，咱尽可能把教程上没有提到的或者是刚开始学OpenGL很难理解的部分记录下来。</p>
<p>大体上就是安装<code>glfw</code>，Arch Linux使用包管理器<code>sudo pacman -S glfw-x11</code> (如果你使用的是wayland，那么安装<code>glfw-wayland</code>)，然后把下载的<code>glad/glad.h</code>复制到<code>/usr/include</code>下，把<code>glad.c</code>复制到工程文件夹的代码目录下。</p>
<p>为使用CMake生成Makefile，编写<code>CMakeLists.txt</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmake" data-lang="cmake"><span class="line"><span class="cl"><span class="nb">cmake_minimum_required</span><span class="p">(</span><span class="s">VERSION</span> <span class="s">3.0</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="nb">project</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Learn OpenGL&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="s">LANGUAGES</span> <span class="s">C</span>
</span></span><span class="line"><span class="cl">    <span class="s">VERSION</span> <span class="s">0.1.0</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="nb">set</span><span class="p">(</span><span class="s">C_FLAGS</span> <span class="s2">&#34;-Wall -lm -ldl -std=c11&#34;</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="nb">set</span><span class="p">(</span><span class="s">CMAKE_C_FLAGS</span> <span class="o">${</span><span class="nv">C_FLAGS</span><span class="o">}</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="nb">set</span><span class="p">(</span><span class="s">OpenGL_GL_PREFERENCE</span> <span class="s">LEGACY</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="nb">find_package</span><span class="p">(</span><span class="s">glfw3</span> <span class="s">REQUIRED</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="nb">find_package</span><span class="p">(</span><span class="s">OpenGL</span> <span class="s">REQUIRED</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="nb">include_directories</span><span class="p">(</span><span class="o">${</span><span class="nv">OPENGL_INCLUDE_DIR</span><span class="o">}</span> <span class="s">src</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="nb">aux_source_directory</span><span class="p">(</span><span class="s2">&#34;src&#34;</span> <span class="s">GLSRC</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="nb">add_executable</span><span class="p">(</span><span class="s">main</span> <span class="o">${</span><span class="nv">GLSRC</span><span class="o">}</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="nb">target_link_libraries</span><span class="p">(</span><span class="s">main</span> <span class="o">${</span><span class="nv">OPENGL_gl_LIBRARY</span><span class="o">}</span> <span class="s">glfw</span><span class="p">)</span><span class="err">
</span></span></span></code></pre></div><p>完成上述步骤后，确保工程文件夹的结构如下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">├── build/
</span></span><span class="line"><span class="cl">├── CMakeLists.txt
</span></span><span class="line"><span class="cl">└── src/
</span></span><span class="line"><span class="cl">    ├── glad.c
</span></span><span class="line"><span class="cl">    └── main.c
</span></span></code></pre></div><h2 id="创建窗口">创建窗口</h2>
<p>编辑<code>main.c</code>，加入所需的头文件</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="c1">// glad.h要加在glfw3.h之前
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;glad/glad.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;GLFW/glfw3.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
</span></span></span></code></pre></div><p>在main函数中初始化OpenGL并创建窗口</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">**</span><span class="n">argv</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">glfwInit</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="cm">/* 使用OpenGL版本为3.3 */</span>
</span></span><span class="line"><span class="cl">        <span class="nf">glfwWindowHint</span><span class="p">(</span><span class="n">GLFW_CONTEXT_VERSION_MAJOR</span><span class="p">,</span> <span class="mi">3</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">glfwWindowHint</span><span class="p">(</span><span class="n">GLFW_CONTEXT_VERSION_MINOR</span><span class="p">,</span> <span class="mi">3</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="cm">/* 使用核心模式 */</span>
</span></span><span class="line"><span class="cl">        <span class="nf">glfwWindowHint</span><span class="p">(</span><span class="n">GLFW_OPENGL_PROFILE</span><span class="p">,</span> <span class="n">GLFW_OPENGL_CORE_PROFILE</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cp">#ifdef __APPLE__
</span></span></span><span class="line"><span class="cl">        <span class="nf">glfwWindowHint</span><span class="p">(</span><span class="n">GLFW_OPENGL_FORWARD_COMPAT</span><span class="p">,</span> <span class="n">GL_TRUE</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="cp">#endif
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="cm">/* 创建窗口 */</span>
</span></span><span class="line"><span class="cl">        <span class="n">GLFWwindow</span> <span class="o">*</span><span class="n">window</span> <span class="o">=</span>
</span></span><span class="line"><span class="cl">                    <span class="nf">glfwCreateWindow</span><span class="p">(</span><span class="mi">800</span><span class="p">,</span> <span class="mi">600</span><span class="p">,</span> <span class="s">&#34;Hello World&#34;</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">window</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nf">fprintf</span><span class="p">(</span><span class="n">stderr</span><span class="p">,</span> <span class="s">&#34;Failed to create window.</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                <span class="nf">glfwTerminate</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">                <span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="nf">glfwMakeContextCurrent</span><span class="p">(</span><span class="n">window</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="cm">/* 初始化glad */</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nf">gladLoadGLLoader</span><span class="p">((</span><span class="n">GLADloadproc</span><span class="p">)</span> <span class="n">glfwGetProcAddress</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nf">fprintf</span><span class="p">(</span><span class="n">stderr</span><span class="p">,</span> <span class="s">&#34;Failed to initialize GLAD</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                <span class="nf">glfwTerminate</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">                <span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="cm">/* prepare render */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="cm">/* main loop */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="nf">glfwTerminate</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>创建窗口的代码比较容易理解，到这里可以尝试编译一下代码检查有没有遇到什么问题，如果编译失败了可以尝试检查GLFW是否安装正确，代码哪里出现了什么问题。</p>
<p>编译代码并运行生成的程序：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">cd build
</span></span><span class="line"><span class="cl">cmake .. &amp;&amp; make -j8
</span></span><span class="line"><span class="cl">./main
</span></span></code></pre></div><p>如果一切正常的话，可以看到窗口一闪而过就消失了，因为到目前为止我们只创建了一个窗口，创建完成后就结束了程序，所以窗口会瞬间消失。</p>
<hr>
<h2 id="准备绘图">准备绘图</h2>
<p>在绘制形状之前，我们需要一些准备工作比如设定视口、回调函数，创建顶点缓冲区等操作，然后在大循环中进行渲染绘制。</p>
<h3 id="视口">视口</h3>
<p>首先要告诉OpenGL咱的窗口尺寸是多少，以便OpenGL根据窗口大小显示数据和坐标。</p>
<p>在<code>/* prepare render */</code>处添加代码：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="nf">glViewport</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">800</span><span class="p">,</span> <span class="mi">600</span><span class="p">);</span>
</span></span></code></pre></div><p>首先需要知道你传给OpenGL的坐标都是<code>-1.0</code>到<code>1.0</code>之间的数，OpenGL再将其转换成屏幕上的像素点坐标，这个过程由GPU运算所得，所以你得告诉OpenGL你的屏幕尺寸，<code>glViewport</code>的前两个参数为<code>0, 0</code>指的是窗口左下角的位置，这个值咱目前不需要修改。</p>
<h3 id="回调函数">回调函数</h3>
<p>我们需要一个窗口被更改的回调函数，这样当窗口尺寸发生变化时，程序可以调用这个函数调整视口。</p>
<p>我们还需要一个<a href="https://www.glfw.org/docs/3.3/input_guide.html#input_key">按键回调函数</a>，当用户按下键盘上的某个按键后会调用这个回调函数处理按键操作。</p>
<p>编写函数<code>framebuffer_size_callback</code>用来处理窗口尺寸更改:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="kt">void</span> <span class="nf">framebuffer_size_callback</span><span class="p">(</span><span class="n">GLFWwindow</span><span class="o">*</span> <span class="n">window</span><span class="p">,</span> <span class="kt">int</span> <span class="n">width</span><span class="p">,</span> <span class="kt">int</span> <span class="n">height</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">glViewport</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">width</span><span class="p">,</span> <span class="n">height</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>这样当你拖拽窗口进行缩放时，窗口里的图像也会跟着窗口尺寸改变而进行缩放。</p>
<p>编写<code>key_callback</code>函数处理用户按键操作：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="kt">void</span> <span class="nf">key_callback</span><span class="p">(</span><span class="n">GLFWwindow</span> <span class="o">*</span><span class="n">window</span><span class="p">,</span> <span class="kt">int</span> <span class="n">key</span><span class="p">,</span> <span class="kt">int</span> <span class="n">s</span><span class="p">,</span> <span class="kt">int</span> <span class="n">action</span><span class="p">,</span> <span class="kt">int</span> <span class="n">m</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">key</span> <span class="o">==</span> <span class="n">GLFW_KEY_ESCAPE</span> <span class="o">&amp;&amp;</span> <span class="n">action</span> <span class="o">==</span> <span class="n">GLFW_PRESS</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nf">glfwSetWindowShouldClose</span><span class="p">(</span><span class="n">window</span><span class="p">,</span> <span class="n">GL_TRUE</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">key</span> <span class="o">==</span> <span class="n">GLFW_KEY_Q</span> <span class="o">&amp;&amp;</span> <span class="n">action</span> <span class="o">==</span> <span class="n">GLFW_PRESS</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nf">glfwSetWindowShouldClose</span><span class="p">(</span><span class="n">window</span><span class="p">,</span> <span class="n">GL_TRUE</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>当用户按下<code>ESC</code>或<code>Q</code>键时，可以结束OpenGL窗口的运行。</p>
<p>然后我们需要注册这两个回调函数，在<code>/* prepare render */</code>处添加下面的代码:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="nf">glfwSetFramebufferSizeCallback</span><span class="p">(</span><span class="n">window</span><span class="p">,</span> <span class="n">framebuffer_size_callback</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nf">glfwSetKeyCallback</span><span class="p">(</span><span class="n">window</span><span class="p">,</span> <span class="n">key_callback</span><span class="p">);</span>
</span></span></code></pre></div><h3 id="大循环">大循环</h3>
<p>在大循环中，每循环一次代表绘制一帧画面。这里利用了双缓冲将绘制的图形放到缓存中，然后将缓存中的图形刷新到显示器上。(因为OpenGL绘图操作是逐行绘制的，如果不先将图形存到缓存中而直接显示在显示器上的话，在画面快速变动时你很可能会遇到画面撕裂，垂直不同步)</p>
<p>大循环部分的代码长这个样子:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="k">while</span><span class="p">(</span><span class="o">!</span><span class="nf">glfwWindowShouldClose</span><span class="p">(</span><span class="n">window</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="cm">/* 清空背景色为深灰色 */</span>
</span></span><span class="line"><span class="cl">        <span class="nf">glClearColor</span><span class="p">(</span><span class="mf">0.1f</span><span class="p">,</span> <span class="mf">0.1f</span><span class="p">,</span> <span class="mf">0.1f</span><span class="p">,</span> <span class="mf">1.0f</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">glClear</span><span class="p">(</span><span class="n">GL_COLOR_BUFFER_BIT</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="cm">/* draw something */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="cm">/* swap buffer */</span>
</span></span><span class="line"><span class="cl">        <span class="nf">glfwSwapBuffers</span><span class="p">(</span><span class="n">window</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">glfwPollEvents</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>每次循环的开始需要使用<code>glClear</code>清空屏幕，这样就不会看到上一次渲染的结果。</p>
<p>这里我们使用<code>glClearColor</code>设定清空屏幕的颜色为接近纯黑色的灰色。</p>
<p>大循环每循环一次，OpenGL就渲染了一帧画面并显示在显示器上，所以你可以计算一秒钟循环的次数而估算游戏的帧率。</p>
<blockquote>
<p>因为我们使用了双缓冲，所以这时游戏的帧数被限制为等于显示的刷新帧数，
比如显示器刷新率为144帧，游戏的帧率就被锁为144，如果想解除这个限制需要改为单缓冲。</p>
</blockquote>
<hr>
<p>到此为止，我们已经创建了一个游戏引擎，但是这个引擎还什么都没有做。</p>
<p>尝试编译代码，可以看到一个黑色（深灰）的窗口，按<code>ESC</code>或<code>Q</code>即可结束运行。</p>
<p><img loading="lazy" src="images/create_window.png" alt="Window" />
<p style="margin-bottom: -0.8em;" class="image-title">创建窗口</p>
</p>
<p>如果你遇到什么问题，可以<a href="/posts/2021/learn_opengl/learn-code-1/main.c">对照一下代码</a>是否有问题。</p>
<hr>
<h2 id="三角形">三角形</h2>
<p>画三角形的过程很简单，首先要确定三角形的三个顶点坐标，然后告诉GPU这三个点坐标就可以了 :)</p>
<blockquote>
<p>如果你看不懂顶点着色器、几何着色器、片段着色器、光栅化这些难懂的知识点，那么你不必急于弄懂这个过程，等把图形绘制出来后再回来看这部分的内容。</p>
</blockquote>
<h3 id="标准化设备坐标">标准化设备坐标</h3>
<p>因为OpenGL的坐标为都是-1.0f到1.0f之间的数（我们先不考虑视口变换这些复杂的情形）</p>
<p>x, y, z的坐标为-1.0f到1.0f的坐标称作标准化设备坐标，坐标原点在窗口的正中央且在窗口的表面上，往右为x正方向，往上为y正方向，往屏幕里面为z正方向（这里是左手系）。</p>
<p>有关标准化设备坐标的介绍请看<a href="https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/#_2">这里</a>。</p>
<p>所以我们先声明三角形的顶点和颜色值到一个一维数组里面（用一维数组是因为顶点坐标值在内存的分布都是连续的，这样方便给GPU传坐标）。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="kt">float</span> <span class="n">vertices</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="o">-</span><span class="mf">0.5f</span><span class="p">,</span> <span class="o">-</span><span class="mf">0.5f</span><span class="p">,</span> <span class="mf">0.0f</span><span class="p">,</span>     <span class="c1">// 左下角
</span></span></span><span class="line"><span class="cl">     <span class="mf">1.0f</span><span class="p">,</span>  <span class="mf">0.0f</span><span class="p">,</span> <span class="mf">0.0f</span><span class="p">,</span>     <span class="c1">// red
</span></span></span><span class="line"><span class="cl">     <span class="mf">0.5f</span><span class="p">,</span> <span class="o">-</span><span class="mf">0.5f</span><span class="p">,</span> <span class="mf">0.0f</span><span class="p">,</span>     <span class="c1">// 右下角
</span></span></span><span class="line"><span class="cl">     <span class="mf">0.0f</span><span class="p">,</span>  <span class="mf">1.0f</span><span class="p">,</span> <span class="mf">0.0f</span><span class="p">,</span>     <span class="c1">// green
</span></span></span><span class="line"><span class="cl">     <span class="mf">0.0f</span><span class="p">,</span>  <span class="mf">0.5f</span><span class="p">,</span> <span class="mf">0.0f</span><span class="p">,</span>     <span class="c1">// y轴正上方
</span></span></span><span class="line"><span class="cl">     <span class="mf">0.0f</span><span class="p">,</span>  <span class="mf">0.0f</span><span class="p">,</span> <span class="mf">1.0f</span>      <span class="c1">// blue
</span></span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><h3 id="顶点缓冲">顶点缓冲</h3>
<p>首先我们需要创建一个顶点缓冲对象，用来存我们的顶点信息，这个对象叫“Vertex Buffer Object”（VBO），之后创建一个顶点数组对象“Vertex Array Object”（VAO），用来存我们创建的VBO。</p>
<p>为便于理解，你可以把VBO当作为开辟了一块GPU上的显存(Buffer)，用来存顶点和颜色信息，然后VAO是一个包含多个VBO的数组(Array)。</p>
<p>绘图时可以把VBO中存的大量顶点信息发送给GPU，因为用CPU给显卡发顶点坐标的速度慢而且没办法一次发送大量的顶点坐标，所以我们把顶点坐标存到显存中，绘图时直接访问显存即可。</p>
<p>生成一个VBO和一个VAO的代码为：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="c1">// VAO和VBO的ID都是非负整型
</span></span></span><span class="line"><span class="cl"><span class="n">GLuint</span> <span class="n">VBO</span><span class="p">,</span> <span class="n">VAO</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// 生成一个VAO和一个VBO
</span></span></span><span class="line"><span class="cl"><span class="nf">glGenVertexArrays</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">VAO</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nf">glGenBuffers</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">VBO</span><span class="p">);</span>
</span></span></code></pre></div><p>我们刚刚创建好了一个缓存对象，我们现在需要告诉这个缓存的数据空间大小以及数据（顶点坐标）。</p>
<p>首先我们绑定刚创建的VAO，然后绑定VBO</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="nf">glBindVertexArray</span><span class="p">(</span><span class="n">VAO</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nf">glBindBuffer</span><span class="p">(</span><span class="n">GL_ARRAY_BUFFER</span><span class="p">,</span> <span class="n">VBO</span><span class="p">);</span>
</span></span></code></pre></div><p>之后给VBO传递数组信息</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="nf">glBufferData</span><span class="p">(</span><span class="n">GL_ARRAY_BUFFER</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">vertices</span><span class="p">),</span> <span class="n">vertices</span><span class="p">,</span> <span class="n">GL_STATIC_DRAW</span><span class="p">);</span>
</span></span></code></pre></div><ul>
<li>
<p>第一个参数是目标缓冲的类型，我们刚刚把VBO绑定到<code>GL_ARRAY_BUFFER</code>上了</p>
</li>
<li>
<p>第二个参数是顶点信息所占的空间大小，单位是字节。三角形一共3个顶点，每个顶点有3个坐标值和3个颜色值，</p>
<p>所以大小是<code>6 * 3 * sizeof(float)</code>，不过我们可以直接用<code>sizeof(vertices)</code>知道整个数组的大小。</p>
</li>
<li>
<p>第三个参数是数组的地址</p>
</li>
<li>
<p>第四个参数告诉GL我们的顶点数据几乎不会改变，所以是<code>GL_STATIC_DRAW</code>。</p>
<p>如果数据会被改变很多次，则为<code>GL_DYNAMIC_DRAW</code></p>
<p>如果数据每次绘制都会更改，则改为<code>GL_STREAM_DRAW</code></p>
</li>
</ul>
<p>然后我们告诉CPU我们给VBO传递的数组都是什么：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="nf">glVertexAttribPointer</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="n">GL_FLOAT</span><span class="p">,</span> <span class="n">GL_FALSE</span><span class="p">,</span> <span class="mi">6</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">float</span><span class="p">),</span> <span class="p">(</span><span class="kt">void</span><span class="o">*</span><span class="p">)</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nf">glVertexAttribPointer</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="n">GL_FLOAT</span><span class="p">,</span> <span class="n">GL_FALSE</span><span class="p">,</span> <span class="mi">6</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">float</span><span class="p">),</span> <span class="p">(</span><span class="kt">void</span><span class="o">*</span><span class="p">)</span> <span class="p">(</span><span class="mi">3</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">float</span><span class="p">)));</span>
</span></span><span class="line"><span class="cl"><span class="nf">glEnableVertexAttribArray</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nf">glEnableVertexAttribArray</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
</span></span></code></pre></div><ul>
<li>
<p>第一个参数为位置，告诉这个数据传递到顶点着色器的哪个位置上</p>
</li>
<li>
<p>第二个参数为大小，我们定义的那个数组一个顶点有3个坐标，所以是3</p>
</li>
<li>
<p>第三个参数为数据类型，这里是float。</p>
</li>
<li>
<p>第四个参数为false，暂时不用管他</p>
</li>
<li>
<p>第五个参数为步长，一个顶点有三个坐标和三个颜色值，所以每传一个顶点，就走<code>6 * sizeof(float)</code>的长度。</p>
</li>
<li>
<p>第六个参数为偏移量，因为我们定义的数组中前三个数字代表顶点坐标，后三个数字代表颜色，所以传递顶点坐标时，偏移量为0，传递颜色时偏移量为<code>3 * sizeof(float)</code>。</p>
</li>
</ul>
<p><img loading="lazy" src="https://learnopengl-cn.github.io/img/01/05/vertex_attribute_pointer_interleaved.png" alt="VBO中内存数据" />
<p style="margin-bottom: -0.8em;" class="image-title">VBO中内存数据</p>
</p>
<p>之后使用<code>glEnableVertexAttribArray</code>告诉OpenGL启用顶点着色器上这个位置的数据。</p>
<p>最后使用完VAO后要记得将其解绑：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="nf">glBindVertexArray</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
</span></span></code></pre></div><h3 id="着色器">着色器</h3>
<p>我们需要写两个着色器程序，分别为顶点着色器和片段着色器。</p>
<blockquote>
<p>开头说的核心模式可编程管线就是指我们可以写着色器程序，手动指定管线都进行什么操作。</p>
</blockquote>
<h4 id="顶点着色器">顶点着色器</h4>
<p>因为OpenGL使用的是标准化设备坐标而不是屏幕上的像素点为坐标，所以GPU需要把标准化设备坐标转换为屏幕上的像素点，这个过程由顶点着色器实现。</p>
<p>而顶点着色器不知道咱们三角形的每个顶点的坐标是多少，所以咱们得往着色器中传我们刚才创建的VBO里面存储的坐标。</p>
<p>着色器程序使用GLSL编写，其代码和C很像</p>
<p>在代码文件夹中新建一个<code>vertex.glsl</code>，编写以下代码：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-glsl" data-lang="glsl"><span class="line"><span class="cl"><span class="cp">#version 330 core</span>
</span></span><span class="line"><span class="cl"><span class="n">layout</span> <span class="p">(</span><span class="n">location</span> <span class="o">=</span> <span class="mo">0</span><span class="p">)</span> <span class="k">in</span> <span class="k">vec3</span> <span class="n">aPos</span><span class="p">;</span>     <span class="c1">// 传入坐标</span>
</span></span><span class="line"><span class="cl"><span class="n">layout</span> <span class="p">(</span><span class="n">location</span> <span class="o">=</span> <span class="mi">1</span><span class="p">)</span> <span class="k">in</span> <span class="k">vec3</span> <span class="n">aColor</span><span class="p">;</span>   <span class="c1">// 传入颜色</span>
</span></span><span class="line"><span class="cl"><span class="k">out</span> <span class="k">vec3</span> <span class="n">color</span><span class="p">;</span>     <span class="c1">// 向片段着色器发送颜色</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">void</span> <span class="n">main</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">gl_Position</span> <span class="o">=</span> <span class="k">vec4</span><span class="p">(</span><span class="n">aPos</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">color</span> <span class="o">=</span> <span class="n">aColor</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><ul>
<li>
<p>第一行代表我们使用OpenGL 3.3 核心模式</p>
</li>
<li>
<p>第二行声明了一个类型为<code>vec3</code>（三维坐标）的变量，该变量的位置为<code>0</code>，需要人为的通过这个位置给他传值，这个变量代表顶点的坐标</p>
</li>
<li>
<p>第三行声明的<code>aColor</code>用来代表颜色，顶点着色器不需要处理颜色，所以我们接收到颜色值后不需要处理，直接传出去即可。</p>
</li>
<li>
<p>main函数中<code>gl_Position</code>代表这个顶点的位置坐标，我们把CPU传给顶点着色器的<code>vec3</code>转换为<code>vec4</code>。</p>
</li>
</ul>
<h4 id="片段着色器">片段着色器</h4>
<p>顶点着色器处理完顶点后，由片段着色器计算每个像素点的颜色，所以如果我们想给三角形上色的话，也是在这个环节进行。</p>
<p>在代码文件夹中新建一个<code>fragment.glsl</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-glsl" data-lang="glsl"><span class="line"><span class="cl"><span class="cp">#version 330 core</span>
</span></span><span class="line"><span class="cl"><span class="k">in</span> <span class="k">vec3</span> <span class="n">color</span><span class="p">;</span>    <span class="c1">// 接收顶点着色器发送的颜色</span>
</span></span><span class="line"><span class="cl"><span class="k">out</span> <span class="k">vec4</span> <span class="n">FragColor</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">void</span> <span class="n">main</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">FragColor</span> <span class="o">=</span> <span class="k">vec4</span><span class="p">(</span><span class="n">color</span><span class="p">,</span> <span class="mf">1.0</span><span class="n">f</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><ul>
<li>
<p>第一行同顶点着色器，代表OpenGL版本为3.3 核心模式</p>
</li>
<li>
<p>第二行表示接收顶点着色器发送的颜色数据到变量<code>color</code></p>
</li>
<li>
<p>第三行声明了一个类型为<code>vec4</code>(4维坐标)的变量，表示向外传递变量<code>FragColor</code>（该像素点的颜色值）</p>
</li>
<li>
<p>main函数中设定变量<code>FragColor</code>的值等于<code>color</code>，用来指颜色。</p>
<p>最后一个值恒为1.0f，我们暂时不需要修改它。</p>
</li>
</ul>
<h3 id="编译着色器">编译着色器</h3>
<p>因为我们只是写了着色器程序的代码，需要让GPU将其编译。</p>
<p>编译着色器的部分对应的LearnOpenGL CN教程<a href="https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/#_4">在这里</a>，本篇不打算重复讲编译着色器部分的代码。</p>
<p>我们可以把编译着色器的代码封装到几个函数里面，这样可以减少main函数中重复代码的数量。</p>
<p>有关这部分的代码我推荐使用<a href="https://sh.alynx.one/posts/Learn-OpenGL-1/#%E7%9D%80%E8%89%B2%E5%99%A8%EF%BC%88Shader%EF%BC%89">这篇文章</a>中讲述的方式从文件中读取glsl代码并将其编译。</p>
<p>最后在main函数的准备阶段处的最下面，插入以下代码，编译你的着色器程序：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="n">GLuint</span> <span class="n">shaderProgram</span> <span class="o">=</span> <span class="nf">load_program</span><span class="p">(</span><span class="s">&#34;vertex.glsl&#34;</span><span class="p">,</span> <span class="s">&#34;fragment.glsl&#34;</span><span class="p">);</span>
</span></span></code></pre></div><h3 id="我们期待的三角形">我们期待的三角形</h3>
<p>经过了前面的一番准备，我们创建了缓冲对象存顶点的坐标和颜色信息，之后编写了着色器程序处理顶点坐标和颜色。</p>
<p>现在我们终于可以在大循环中画三角形了。</p>
<p>在大循环的<code>/* draw something */</code>下面添加如下代码：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="nf">glUseProgram</span><span class="p">(</span><span class="n">shaderProgram</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nf">glBindVertexArray</span><span class="p">(</span><span class="n">VAO</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nf">glDrawArrays</span><span class="p">(</span><span class="n">GL_TRIANGLES</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">3</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nf">glBindVertexArray</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
</span></span></code></pre></div><p>这串代码表示我们首先使用刚编译好的着色器程序，之后绑定VAO，绘制一个三角形，最后解绑。</p>
<hr>
<p>至此我们的工程文件夹结构修改如下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">├── build/
</span></span><span class="line"><span class="cl">├── CMakeLists.txt
</span></span><span class="line"><span class="cl">└── src/
</span></span><span class="line"><span class="cl">    ├── fragment.glsl
</span></span><span class="line"><span class="cl">    ├── glad.c
</span></span><span class="line"><span class="cl">    ├── main.c
</span></span><span class="line"><span class="cl">    ├── shader.c
</span></span><span class="line"><span class="cl">    ├── shader.h
</span></span><span class="line"><span class="cl">    └── vertex.glsl
</span></span></code></pre></div><p>因为我们编写了<code>vertex.glsl</code>和<code>fragment.glsl</code>，需要修改<code>CMakeLists.txt</code>，使得编译时将代码文件夹下的着色器文件复制到<code>build</code>文件夹下。</p>
<p>在<code>CMakeLists.txt</code>的<code>find_package</code>下面添加如下代码：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">configure_file(src/vertex.glsl vertex.glsl COPYONLY)
</span></span><span class="line"><span class="cl">configure_file(src/fragment.glsl fragment.glsl COPYONLY)
</span></span></code></pre></div><p>编译后运行程序，可以看到一个五颜六色的三角形，它的左下角为红色，右下角为绿色，顶点为蓝色。</p>
<p><img loading="lazy" src="images/draw_triangle.png" alt="三角形" />
<p style="margin-bottom: -0.8em;" class="image-title">三角形</p>
</p>
<p>如果你遇到了问题，或者哪里不太明白，可以看咱写好的代码：</p>
<ul>
<li>
<p><a href="/posts/2021/learn_opengl/learn-code-2/main.c">main.c</a></p>
</li>
<li>
<p><a href="/posts/2021/learn_opengl/learn-code-2/shader.h">shader.h</a></p>
</li>
<li>
<p><a href="/posts/2021/learn_opengl/learn-code-2/shader.c">shader.c</a></p>
</li>
<li>
<p><a href="/posts/2021/learn_opengl/learn-code-2/vertex.glsl">vertex.glsl</a></p>
</li>
<li>
<p><a href="/posts/2021/learn_opengl/learn-code-2/fragment.glsl">fragment.glsl</a></p>
</li>
<li>
<p><a href="/posts/2021/learn_opengl/learn-code-2/CMakeLists.txt">CMakeLists.txt</a></p>
</li>
</ul>
<hr>]]></content:encoded>
    </item>
    <item>
      <title>Spring - 4</title>
      <link>https://blog.starry-s.moe/posts/2021/spring-4/</link>
      <pubDate>Mon, 03 May 2021 01:44:57 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2021/spring-4/</guid>
      <description>&lt;p&gt;咱去年忘了更新这个系列&amp;hellip;&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>咱去年忘了更新这个系列&hellip;</p>
<meting-js server="netease" type="song" id="2002645769" theme="#233333"></meting-js>
<p><img loading="lazy" src="images/IMG_6611.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">iso100, 50mm, 1/1000s, f2.8</p>
</p>
<hr>
<p>前一阵子买了二手的适马17-50 f2.8镜头，在网上看这镜头很适合新手学摄影，而且这个焦段+大光圈适用的范围也很广。遂把在家吃灰了一年的相机带回学校，打算找时间拍点风景照片。</p>
<p>实际上买完新镜头后我就拿它拍了几张花的照片后也没怎么出过门，本来打算5月初份去长白岛拍樱花的然而经过了五一小长假的调休，上周末的体侧，这周辽宁省又开始闹疫情，所以只好先拍点校内的风景啦。</p>
<p>如果你想看长白岛的樱花，不妨看一下<a href="/posts/2019/spring-3/">Spring - 3</a>这篇文章。</p>
<blockquote>
<p>仅使用Raw Therapee和Adobe Lightroom对照片进行了裁剪并调整颜色，并由GIMP压缩</p>
</blockquote>
<p><img loading="lazy" src="images/IMG_6616.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">iso100, 33mm, 1/1000s, f2.8</p>
</p>
<p><img loading="lazy" src="images/IMG_6618.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">iso100, 50mm, 1/1000s, f2.8</p>
</p>
<p><img loading="lazy" src="images/IMG_6623.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">iso100, 35mm, 1/1250s, f2.8</p>
</p>
<hr>
<blockquote>
<p>拍摄自：2021-05-18</p>
</blockquote>
<p>在大风天拍蒲公英确实是个不明智的选择。</p>
<p><img loading="lazy" src="images/IMG_6723.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">iso100, 50mm, 1/100s, f2.8</p>
</p>
<p><img loading="lazy" src="images/IMG_6726.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">iso100, 50mm, 1/250s, f2.8</p>
</p>
<p><img loading="lazy" src="images/IMG_6728.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">iso100, 50mm, 1/320s, f2.8</p>
</p>
<p><img loading="lazy" src="images/IMG_6697.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">iso100, 50mm, 1/320s, f2.8</p>
</p>
<h1 id="spring---4">Spring - 4</h1>]]></content:encoded>
    </item>
    <item>
      <title>联想R7000P安装Arch Linux的常见问题</title>
      <link>https://blog.starry-s.moe/posts/2021/lenovo-r7000p/</link>
      <pubDate>Mon, 22 Mar 2021 19:51:32 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2021/lenovo-r7000p/</guid>
      <description>&lt;p&gt;旧电脑坏掉了，因为坏的有些复杂而且不忍心拿到学校的修理店去修于是打算等到暑假有时间自己买零部件修。（就不吐槽惠普的产品设计问题了&amp;hellip;&lt;/p&gt;
&lt;p&gt;于是在网上逛了一会下决心再也不碰惠普了之后买了联想R7000P 2020，满血RTX 2060(这里指的是最大功耗为115W的笔记本显卡) + R7 4800H还是很香的，打守望屁股终于能稳定200+fps了。&lt;/p&gt;
&lt;p&gt;所以隔了这么久我终于更新博客了。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>旧电脑坏掉了，因为坏的有些复杂而且不忍心拿到学校的修理店去修于是打算等到暑假有时间自己买零部件修。（就不吐槽惠普的产品设计问题了&hellip;</p>
<p>于是在网上逛了一会下决心再也不碰惠普了之后买了联想R7000P 2020，满血RTX 2060(这里指的是最大功耗为115W的笔记本显卡) + R7 4800H还是很香的，打守望屁股终于能稳定200+fps了。</p>
<p>所以隔了这么久我终于更新博客了。</p>
<blockquote>
<p>本篇原标题为「联想R7000P上手体验」，因为内容大多在讲安装Linux时遇到的问题及解决方法，所以把标题更改为「联想R7000P安装Arch Linux的常见问题」。</p>
</blockquote>
<hr>
<h2 id="安装arch-linux">安装Arch Linux</h2>
<p>到手后就把之前买的西数SN750 1T固态换到了新电脑上，顺便格式化重装了个系统。</p>
<p>双M2插槽配上1T + 500G NVME，美汁汁。</p>
<p>于是直接给Linux分了150G root，16G SWAP，500G HOME（有点奢侈）， 然后还分了100G用来存steam游戏，剩下的全扔给Windows。</p>
<p>装Linux过程中只遇到了终端的警报声有些大这个问题（插耳机时声音依旧从扬声器输出），别的问题都没遇到。</p>
<p>之前的电脑总是遇到奇葩问题，用旧版本Linux内核关机或者<code>lspci</code>时会卡死，显卡驱动装不好会导致开机死机，声卡驱动一直有问题听歌时音量大一点就爆音，HDMI接口直连的NVIDIA显卡所以显卡驱动没配置好独显不工作时没办法外接显示器(后来才知道type c接口有DP视频输出)。</p>
<p>新电脑买来装完系统后就遇到了一点小问题网上搜一下就解决了。</p>
<h3 id="屏幕亮度不能调节">屏幕亮度不能调节</h3>
<p><del>开个浏览器能把眼睛晃瞎</del></p>
<p>网上查了一下只有在bios设置为独显直连时解决亮度不能调节的方法，在混合显卡模式下，存在<a href="https://bugzilla.opensuse.org/show_bug.cgi?id=1180749">AMD显卡亮度用16位值表示而不是8位值表示</a>的这个BUG (Feature?)所以没办法调节亮度。</p>
<p>所以<code>cat /sys/class/backlight/amdgpu_bl0/actual_brightness</code>得到的是一个大于255的数。</p>
<p>确保内核和显卡驱动都是最新的情况下，编辑内核参数<code>amdgpu.backlight=0</code>和<code>acpi_backlight=vendor</code>，可以解决混合模式下AMD显卡不能调节亮度这个问题。</p>
<p>如果你经常切换混合模式和显卡直连模式的话：</p>
<p>安装显卡驱动<code>xf86-video-amdgpu</code>和<code>nvidia</code>以及按需要安装nvidia的其他组件。</p>
<p>复制<code>/usr/share/X11/xorg.conf.d/10-nvidia-drm-outputclass.conf</code>到<code>/etc/X11/xorg.conf.d/</code></p>
<p>编辑<code>/etc/X11/xorg.conf.d/10-nvidia-drm-outputclass.conf</code>在<code>EndSection</code>前添加一行参数允许nvidia驱动调节亮度。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">Option &#34;RegistryDwords&#34; &#34;EnableBrightnessControl=1&#34;
</span></span></code></pre></div><p>之后编辑<code>/etc/modprobe.d/blacklist.conf</code>禁用闭源驱动<code>nouveau</code>和<code>ideapad_laptop</code>，让显卡驱动调节亮度。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"># /etc/modprobe.d/blacklist.conf
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">blacklist nouveau
</span></span><span class="line"><span class="cl">blacklist ideapad_laptop
</span></span></code></pre></div><p>编辑内核参数添加<code>acpi_backlight=vendor</code>和<code>amdgpu.backlight=0</code>。</p>
<p>以systemd-boot为例:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="cl"><span class="c1"># /boot/loader/entries/arch.conf</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">options</span> <span class="n">acpi_backlight</span><span class="o">=</span><span class="n">vendor</span>
</span></span><span class="line"><span class="cl"><span class="n">options</span> <span class="n">amdgpu</span><span class="o">.</span><span class="n">backlight</span><span class="o">=</span><span class="mi">0</span>
</span></span></code></pre></div><p>最后重启电脑就能调亮度了。</p>
<h3 id="gdm不自启动">GDM不自启动</h3>
<p>开机时GDM不会自动显示出来而是得手动切TTY2再切回TTY1才能显示。</p>
<p>查Wiki得知是因为GDM在显卡驱动被加载之前就启动了。</p>
<p><a href="https://wiki.archlinux.org/index.php/GDM#Black_screen_on_AMD_or_Intel_GPUs_when_an_NVidia_%28e%29GPU_is_present">参照Wiki</a>，设置<a href="https://wiki.archlinux.org/index.php/Kernel_mode_setting#Early_KMS_start">KMS早启动</a>。</p>
<p>编辑<code>/etc/mkinitcpio.conf</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"># /etc/mkinitcpio.conf
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">MODULES=(nvidia nvidia_modeset nvidia_uvm nvidia_drm amdgpu radeon)
</span></span></code></pre></div><p>如果你只使用独显直连模式的话可以去掉<code>amdgpu</code>和<code>radeon</code>。</p>
<p>然后<code>sudo mkinitcpio -p linux</code>重新生成内核镜像，之后重启。</p>
<p>这么做会使Wayland在开机时被禁用，所以在混合模式使用AMD显卡开机时无法使用Wayland，<a href="https://wiki.archlinux.org/index.php/GDM#GDM_ignores_Wayland_and_uses_X.Org_by_default">参见Wiki</a>。</p>
<p>将<code>/usr/lib/udev/rules.d/61-gdm.rules</code>复制到<code>/etc/udev/rules.d/</code>，并编辑<code>61-gdm.rules</code>将下面这一行注释掉：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">DRIVER==&#34;nvidia&#34;, RUN+=&#34;/usr/lib/gdm-disable-wayland&#34;
</span></span></code></pre></div><p>之后重启电脑再开机<code>echo $XDG_SESSION_TYPE</code>就可以检查现在使用的是<code>wayland</code>了。</p>
<h3 id="optimus-manager">Optimus Manager</h3>
<p>因为独显功耗太高了，使用独显直连模式在浏览网页写文档这类的轻度工作时电池待机只能2小时，用混合模式的话能待机4.5小时，外加上我也不打那些对性能要求很高的游戏，
所以日常使用时就在Bios里设置显卡为混合模式。</p>
<p>然后在Linux系统里安装<code>optimus-manager</code>，修改配置为：使用电池开机时关掉NVIDIA显卡，只让AMD集显工作；有外接电源时则使用“hybrid”混合模式，如果需要玩游戏的话用<a href="https://wiki.archlinux.org/index.php/PRIME#PRIME_render_offload">nvidia-prime</a>让独显运行游戏。</p>
<p>Optimus Manager的配置方法和之前我<a href="/posts/2021/archlinux-pavilion-gaming-laptop/">之前配置旧电脑时</a>讲的基本一样，唯一区别就是这电脑是AMD，旧电脑是Intel。</p>
<p>所以编辑配置文件修改了这些地方：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"># /etc/optimus-manager/optimus-manager.conf
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># 设置开机自动切换显卡模式
</span></span><span class="line"><span class="cl">startup_mode=auto
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># 使用电池时关掉独立显卡降低功耗
</span></span><span class="line"><span class="cl">startup_auto_battery_mode=integrated
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># 使用外接电源时为混合模式
</span></span><span class="line"><span class="cl">startup_auto_extpower_mode=hybrid
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">[amd]
</span></span><span class="line"><span class="cl"># 因为安装的xf86-video-amdgpu, 所以修改驱动改为amdgpu
</span></span><span class="line"><span class="cl">driver=amdgpu
</span></span></code></pre></div><p>安装好<code>nvidia-prime</code>后在混合显卡模式（hybrid）下，使用<code>prime-run</code>让独显运行游戏。</p>
<p>如果想让Steam以独显运行游戏，修改启动参数为<code>prime-run %command%</code>。</p>
<h2 id="windows">Windows</h2>
<p>在Windows系统下显卡独连时字体渲染有些蹦，设置了ClearType之后还是没啥改善。</p>
<p>主要是Visual Studio 2019的字体渲染真的瞎眼，最后装了Text Sharp插件换了Jet Brains字体才看起来正常了一些。</p>
<p>最后在NVIDIA控制面板全局设置里把平滑处理全关了，字体的锯齿才消失。</p>
<p>貌似是因为NVIDIA把文本编辑器当游戏渲染了。</p>
<hr>
<h2 id="others">Others</h2>
<ul>
<li>
<p>在Linux系统里会遇到按Fn+Esc键时FnLock的灯没有亮这个问题，不过不影响FnLock的正常使用，所以就忽视了。</p>
</li>
<li>
<p>如果要用诱骗线充电的话，用optimus-manager把独显关掉（用<code>nvidia-smi</code>得知独显在不使用的情况下仍有5W的功耗），然后装一个CPU功率调节的软件，例如<code>cpupower-gui</code><sup>AUR</sup>，设置为节电模式，实测用小米65W GaN充电器给电脑充电，轻度使用没有卡顿掉电的情况。</p>
</li>
<li>
<p>因为之前趁着狗东打折加上买显示器送的100E卡，只花了两百多买了一个紫米20移动电源（<del>板砖</del>），25000毫安且支持100WPD充电。按照上面讲的方法在电池满电的情况下一边轻度使用电脑一边充电，充电宝能用4小时左右，然后笔记本的电池还能续航4至5小时。（实在是因为原装充电器它太沉了）</p>
</li>
<li>
<p>目前来看觉得这电脑还是蛮香的，AMD的CPU性能很强而且比intel版的Y7000P便宜一千块钱。尽管现在已经有二线厂商做AMD 5800系的笔记本了但是4800H的性能依旧够用，RTX3060显卡就当它是空气吧就算发售也是残血而且抢不到。</p>
</li>
<li>
<p>然后就是电脑没有雷电3接口，只有一个支持USB3.2 Gen1的type c接口且支持DP1.2视频输出，不过USB接口倒是挺多的，电脑用到现在没有遇到啥AMD CPU引起的兼容性问题。</p>
</li>
</ul>
<hr>
<p><img loading="lazy" src="images/1.jpg" alt="GNOME 40" />
<p style="margin-bottom: -0.8em;" class="image-title">GNOME 40</p>
</p>]]></content:encoded>
    </item>
    <item>
      <title>小米路由器3G之使用TTL串口刷机救砖</title>
      <link>https://blog.starry-s.moe/posts/2021/xiaomi-r3g-ttl-flash/</link>
      <pubDate>Fri, 22 Jan 2021 22:10:01 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2021/xiaomi-r3g-ttl-flash/</guid>
      <description>&lt;p&gt;本来路由器闲置了一年了都没咋用了，前两天想把OpenWrt系统刷回原厂系统。&lt;/p&gt;
&lt;p&gt;然鹅刷原厂固件时忘记改环境变量了，因为第三方Boot Loader也被我顺带刷回了原厂的所以现在开机无限重启。&lt;/p&gt;
&lt;p&gt;之前买单片机套件时赠了一条usb转ttl线的，但是被我放学校了。只好再从万能的某宝再买一条线，尝试着救砖了。&lt;/p&gt;
&lt;p&gt;(在某宝发现了一家店啥元件都有，还特别便宜。于是我还顺带买了很多杜邦线、电阻、LED灯、面包板等小玩意&amp;hellip;)&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>本来路由器闲置了一年了都没咋用了，前两天想把OpenWrt系统刷回原厂系统。</p>
<p>然鹅刷原厂固件时忘记改环境变量了，因为第三方Boot Loader也被我顺带刷回了原厂的所以现在开机无限重启。</p>
<p>之前买单片机套件时赠了一条usb转ttl线的，但是被我放学校了。只好再从万能的某宝再买一条线，尝试着救砖了。</p>
<p>(在某宝发现了一家店啥元件都有，还特别便宜。于是我还顺带买了很多杜邦线、电阻、LED灯、面包板等小玩意&hellip;)</p>
<meting-js server="netease" type="song" id="409931672" theme="#233333"></meting-js>
<h2 id="砖了">砖了</h2>
<h2 id="拆机">拆机</h2>
<p><img loading="lazy" src="images/1.jpg" alt="&ldquo;路由器主板&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">Are You OK?</p>
</p>
<blockquote>
<p>北方冬季气候干燥，拆机时记得放静电，找根铁丝把自己连地线上 (找个金属外壳是接地的电器和自己连上也行)。</p>
</blockquote>
<p>拆开路由器外壳，卸下主板，顺手拆掉了散热片（屏蔽罩）。</p>
<p>串口在图片上主板的左侧，旁边有标记，从上到下依次是<code>TX</code>、<code>GND</code>、<code>RX</code>、<code>1</code>。需要注意的是主板上的<code>TX</code>要接到usb串口的<code>RX</code>，主板上的<code>RX</code>要接到usb串口的<code>TX</code>，<code>GND</code>连<code>GND</code>，VCC不用连。为了防止接错线，小米还十分贴心的标注了每个接口对应的连接线的颜色。</p>
<p><img loading="lazy" src="images/2.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">I&#39;m very OK!</p>
</p>
<p>找出家里吃灰好几年差点被我扔掉的电烙铁，刮刀头刮了半天电烙铁才上锡，把新买的杜邦线母线焊到主板上，和ttl串口线相连。</p>
<p>(电烙铁太破了根本焊不上锡，焊得很丑，emmm)</p>
<p>实际上可以买个4PIN单排针焊上去，把串口接到排针上就可以，我直接把电线焊到上面以后用起来会很麻烦。</p>
<h2 id="刷机">刷机</h2>
<blockquote>
<p>以下部分基于Arch Linux，其他系统的操作方式可能不一样（例如Windows可能需要超级终端访问串口，再想办法开一个tftp服务器）
刷机的原理: 通过ttl串口线连接路由器的主板，让路由器访问电脑上的tftp服务器，刷第三方Boot Loader。</p>
</blockquote>
<ol>
<li>首先电脑上装一个tftp服务器。</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">$ sudo pacman -S tftp-hpa
</span></span><span class="line"><span class="cl">$ sudo systemctl start tftpd.service
</span></span></code></pre></div><p>tftp的默认目录为<code>/srv/tftp</code>。</p>
<p>这里使用HackPascal制作的Breed（第三方Boot Loader），<a href="https://breed.hackpascal.net/">下载链接</a>，把下载好的Breed重命名为<code>breed.bin</code>，复制到tftp的默认目录。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="cl"><span class="o">$</span> <span class="n">cd</span> <span class="o">/</span><span class="n">srv</span><span class="o">/</span><span class="n">tftp</span>
</span></span><span class="line"><span class="cl"><span class="o">$</span> <span class="n">sudo</span> <span class="n">cp</span> <span class="o">~/</span><span class="n">Downloads</span><span class="o">/</span><span class="n">breed</span><span class="o">-</span><span class="n">mt7620</span><span class="o">-</span><span class="n">xiaomi</span><span class="o">-</span><span class="n">r3g</span><span class="o">.</span><span class="n">bin</span> <span class="n">breed</span><span class="o">.</span><span class="n">bin</span>
</span></span></code></pre></div><ol start="2">
<li>
<p>路由器插网线连电脑，设置静态ip地址192.168.1.3/24，将串口和电脑连接，先不给路由器通电，通常情况下usb串口的设备名称为<code>/dev/ttyUSB0</code>，如果不确定的话可以<code>dmesg</code>查一下。</p>
</li>
<li>
<p>使用<code>screen</code>连接串口，波特率为115200。</p>
</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">$ sudo pacman -S screen
</span></span><span class="line"><span class="cl">$ sudo screen /dev/ttyUSB0 115200
</span></span></code></pre></div><p>路由器通电，终端上会显示路由器启动的信息，等几秒后在选择启动项的时候按9，通过TFTP加载Boot Loader。</p>
<blockquote>
<p>如果串口在连接到电脑的情况下主板通电时没有响应，那就断开usb串口先给主板通电后再连接串口。
(我语文不好别骂我)
如果路由器没有变砖可以正常启动（蓝灯长亮），在正常开机过程中不能选择启动项。此时可以尝试在路由器开机后长按reset按钮7s以上直到主板重启，便可以选择启动项。</p>
</blockquote>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Please choose the operation:
</span></span><span class="line"><span class="cl"> 1: Load system code to SDRAM via TFTP.
</span></span><span class="line"><span class="cl"> 2: Load system code then write to Flash via TFTP.
</span></span><span class="line"><span class="cl"> 3: Boot system code via Flash (default).
</span></span><span class="line"><span class="cl"> 4: Entr boot command line interface.
</span></span><span class="line"><span class="cl"> 7: Load Boot Loader code then write to Flash via Serial.
</span></span><span class="line"><span class="cl"> 9: Load Boot Loader code then write to Flash via TFTP.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">You choosed 9
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">9: System Load Boot Loader then write to Flash via TFTP.
</span></span><span class="line"><span class="cl">Warning!! Erase Boot Loader in Flash then burn new one. Are you sure?(Y/N)y
</span></span></code></pre></div><p>之后设置路由器主机地址（192.168.1.1）和TFTP服务器地址(192.168.1.3)以及文件名称(breed.bin)。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Please Input new ones /or Ctrl-C to discard
</span></span><span class="line"><span class="cl">          Input device IP (192.168.31.1) ==:192.168.1.1
</span></span><span class="line"><span class="cl">          Input server IP (192.168.31.3) ==:192.168.1.3
</span></span><span class="line"><span class="cl">          Input Uboot filename (uboot.bin) ==:breed.bin
</span></span></code></pre></div><p>按回车后开始刷机，过几秒钟后路由器会自动重启，第三方Boot Loader刷写完成。</p>
<p>路由器断电，长按reset键的同时通电开机，灯闪烁后打开浏览器输入网址<code>http://192.168.1.1</code>便可访问breed后台。</p>
<h2 id="done">Done</h2>
<p>第三方Breed刷完后，先用Breed刷小米官方的开发版固件，开启ssh，之后按照<a href="/posts/2019/xiaomi_r3g_openwrt/#%e4%bd%bf%e7%94%a8Breed%e7%9a%84%e5%88%b7%e6%9c%ba%e6%96%b9%e6%b3%95">小米路由器3G折腾之刷OpenWrt记录</a>这篇文章刷OpenWrt。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">BusyBox v1.19.4 (2018-10-29 07:52:03 UTC) built-in shell (ash)
</span></span><span class="line"><span class="cl">Enter &#39;help&#39; for a list of built-in commands.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> -----------------------------------------------------
</span></span><span class="line"><span class="cl">       Welcome to XiaoQiang!
</span></span><span class="line"><span class="cl"> -----------------------------------------------------
</span></span><span class="line"><span class="cl">  $$$$$$\  $$$$$$$\  $$$$$$$$\      $$\      $$\        $$$$$$\  $$\   $$\
</span></span><span class="line"><span class="cl"> $$  __$$\ $$  __$$\ $$  _____|     $$ |     $$ |      $$  __$$\ $$ | $$  |
</span></span><span class="line"><span class="cl"> $$ /  $$ |$$ |  $$ |$$ |           $$ |     $$ |      $$ /  $$ |$$ |$$  /
</span></span><span class="line"><span class="cl"> $$$$$$$$ |$$$$$$$  |$$$$$\         $$ |     $$ |      $$ |  $$ |$$$$$  /
</span></span><span class="line"><span class="cl"> $$  __$$ |$$  __$$&lt; $$  __|        $$ |     $$ |      $$ |  $$ |$$  $$&lt;
</span></span><span class="line"><span class="cl"> $$ |  $$ |$$ |  $$ |$$ |           $$ |     $$ |      $$ |  $$ |$$ |\$$\
</span></span><span class="line"><span class="cl"> $$ |  $$ |$$ |  $$ |$$$$$$$$\       $$$$$$$$$  |       $$$$$$  |$$ | \$$\
</span></span><span class="line"><span class="cl"> \__|  \__|\__|  \__|\________|      \_________/        \______/ \__|  \__|
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">root@XiaoQiang:~#
</span></span></code></pre></div>]]></content:encoded>
    </item>
    <item>
      <title>解决笔记本外接HIDPI显示器的缩放问题</title>
      <link>https://blog.starry-s.moe/posts/2021/laptop-dualscreen-hidpi-scale/</link>
      <pubDate>Sat, 09 Jan 2021 22:12:54 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2021/laptop-dualscreen-hidpi-scale/</guid>
      <description>&lt;p&gt;前天在狗东买了台27寸4K显示器，型号为优派VX2771-4K-HD，分辨率3840x2160，支持HDR 10bit色深（然而电脑只支持DP1.2），因为之前已经&lt;a href=&#34;https://blog.starry-s.moe/posts/2021/archlinux-pavilion-gaming-laptop/&#34;&gt;配置好了optimus-manager&lt;/a&gt;，所以电脑接上显示器就能亮，很幸运没有遇到物理问题。&lt;/p&gt;
&lt;p&gt;然后一看4K屏上的字小得瞎眼。&lt;/p&gt;
&lt;p&gt;如果设置分辨率为1080P，显示文字时会特别糊，根本没法看，效果还没有1080P显示器好。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>前天在狗东买了台27寸4K显示器，型号为优派VX2771-4K-HD，分辨率3840x2160，支持HDR 10bit色深（然而电脑只支持DP1.2），因为之前已经<a href="/posts/2021/archlinux-pavilion-gaming-laptop/">配置好了optimus-manager</a>，所以电脑接上显示器就能亮，很幸运没有遇到物理问题。</p>
<p>然后一看4K屏上的字小得瞎眼。</p>
<p>如果设置分辨率为1080P，显示文字时会特别糊，根本没法看，效果还没有1080P显示器好。</p>
<h2 id="解决方法">解决方法</h2>
<p>首先按照<a href="https://wiki.archlinux.org/index.php/HiDPI">Wiki</a>设置GNOME的HIDPI:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ gsettings set org.gnome.settings-daemon.plugins.xsettings overrides &#34;[{&#39;Gdk/WindowScalingFactor&#39;, &lt;2&gt;}]&#34;
</span></span><span class="line"><span class="cl">$ gsettings set org.gnome.desktop.interface scaling-factor 2
</span></span></code></pre></div><p>在显示设置里将缩放调到200%后，界面被放大了2倍，在4K屏上的字倒是不瞎眼了。</p>
<p>但是因为笔记本是15寸1080P，所以笔记本上显示的字大得离谱。</p>
<p>解决方法是使用xrandr调整笔记本电脑的屏幕缩放，笔记本的分辨率为1920x1080，使用xrandr将画面的分辨率放大2倍，也就是调整为3840x2160，然后显示在分辨率为1920x1080的显示器上。</p>
<p>首先使用<code>xrandr</code>查看每个显示器所对应的设备名称和分辨率。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ xrandr | grep &#34;$extern connected&#34;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  HDMI-0 connected primary 3840x2160+0+0 (normal left inverted right x axis y axis) 597mm x 336mm
</span></span><span class="line"><span class="cl">  eDP-1-1 connected 1920x1080+0+2160 (normal left inverted right x axis y axis) 344mm x 193mm
</span></span></code></pre></div><p>这里HDMI-0是外接的4K显示器，分辨率为3840x2160，位置为(0, 0)。</p>
<p>eDP-1-1是笔记本的显示器，分辨率1920x1080，位置为(0, 2160)，在4K显示器的左下方。</p>
<p>参见<a href="https://wiki.archlinux.org/index.php/HiDPI#Multiple_displays">Wiki设置双显示器部分</a>，用xrandr将笔记本的显示器缩放2倍，分辨率变为 [1920 * 2]x[1080 * 2]=3840x2160，位置还是在4K显示器的正下方。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ xrandr --output eDP-1-1 --scale 2.0x2.0 --panning 3840x2160+0+2160 --output HDMI-0 --auto
</span></span></code></pre></div><p>因为不需要修改4K显示器的分辨率，所以HDMI-0设置为auto。</p>
<p>这样笔记本上的画面也显示正常了。</p>
<p>但是用过一阵子会发现笔记本显示器上的字比4K显示器上的字小很多，看起来不方便，所以把缩放倍数改为1.6（我是从1.5-2.0之间一点点试的，才找到最适合自己的缩放倍数），这样解决了字体大小的问题，不过笔记本画面还是会有模糊（能接受）。</p>
<blockquote>
<p>[1920 * 1.6]x[1080 * 1.6]=3072x1728</p>
</blockquote>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ xrandr --output eDP-1-1 --scale 1.6x1.6 --panning 3072x1728+0+2160 --output HDMI-0 --auto
</span></span></code></pre></div><p>用到现在GNOME的缩放还算是完美，除了玩Minecraft用的HMCL启动器（Java程序）没有被放大之外~~（貌似缺配置，要是配置好了我再补充）~~，Steam界面缩放正常，饥荒、Dota2也都没问题。</p>
<blockquote>
<p>Java8不支持Hidpi缩放，如果想让HMCL支持缩放需要需要安装Java9以上的版本，所以还是算了，又不是不能用。
网易云音乐缩放方法参考<a href="https://ntzyz.io/post/fix-cloud-music-linux-client-hidpi-issue">这篇博客</a>。
qt5设置环境变量<code>QT_SCREEN_SCALE_FACTORS=2</code>。</p>
</blockquote>
<h2 id="others">Others</h2>
<ul>
<li>
<p>仅限GNOME，因为我只用GNOME所以不知道其他DE开HIDPI的效果是什么样。</p>
</li>
<li>
<p>听说Wayland支持不同显示器设置不同的缩放倍数，但是我笔记本的HDMI是独显输出，自带屏幕为集显输出，想启用独显输出画面除了用大黄蜂之外只能Nvidia Optimus，然而Optimus不支持Wayland（F**K NVIDIA），
于是我现在都不知道用Wayland上双显示器的效果是什么样子，只好改用xorg和optimus-manager切换显卡，再用xrandr调显示器的缩放倍数。</p>
<p>或者买一根type-c转DP的线连显示器，我电脑的type-c支持DP1.2，可以输出4K60fps，而且走集显输出。</p>
</li>
<li>
<p>xrandr的指令是我自己试了很多遍试出来的，在我电脑上能用，期间遇到一堆问题(BadMatch)，没想好什么解决方法，所以在别的电脑上可能需要一些修改。</p>
</li>
<li>
<p>如果你正考虑为你的笔记本购买一台新显示器而且你是Linux用户，你的笔记本又是双显卡，不知道HDMI是独显输出还是集显输出的话，建议你买一台和笔记本电脑的分辨率相同的显示器(或者2K)，这样能省去很多麻烦。</p>
</li>
<li>
<p>用了几个月后经常遇到显示器通过HDMI连接到电脑但是没有视频输出的情况，所以现在改用的是type-c转DP的连接线，走集成显卡输出，就没有遇到过这个问题。</p>
</li>
</ul>]]></content:encoded>
    </item>
    <item>
      <title>Hello 2021</title>
      <link>https://blog.starry-s.moe/posts/2021/hello-2021/</link>
      <pubDate>Wed, 06 Jan 2021 01:57:19 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2021/hello-2021/</guid>
      <description>&lt;p&gt;本来我已经戴好耳机准备睡觉了的，突然想起来2020年的年终总结还没有写，恰好脑子里有想写的东西，于是大概构思了一下，便从床上爬起来开灯打开电脑开写年终总结。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>本来我已经戴好耳机准备睡觉了的，突然想起来2020年的年终总结还没有写，恰好脑子里有想写的东西，于是大概构思了一下，便从床上爬起来开灯打开电脑开写年终总结。</p>
<p><img loading="lazy" src="images/1.jpg" alt="Lunar tear" />
<p style="margin-bottom: -0.8em;" class="image-title">Lunar tear</p>
</p>
<meting-js server="netease" type="song" id="26323042" theme="#233333"></meting-js>
<hr>
<p>因为思路不是很稳定，总是写了删删了写，所以隔了这么久才把原本已经放弃了不打算写的年终总结重新写出来。</p>
<p>果然还是深夜适合写这些东西。</p>
<p>今年原本打算能像去年那样在假期外出旅行的，然而疫情爆发后我就一直窝在家待着哪里都没去，不过在1月17号也就是武汉封城前几天，我自己一个人坐飞机跑去北京玩了一圈，当时还不知道有疫情这么一回事，到北京后就是找个地方住下，简单的去了一些景点，后来因为脚疼，很多路远的人多的地方都没有去成。在北京闲逛3天后就坐大客车回家了（一边坐车一遍骂京哈高铁京承段为什么一点进展都没有）。到家后不几天就听说武汉开始封城，然后北京就开始交通管制，现在想想还挺刺激的。</p>
<p>然后暑假本来打算去上海BW2020的，在票都买好了之后，因为外出需要和学校审批（此处省略若干字），最终漫展门票全打水漂，飞机票退票手续费还花了三百多块钱。</p>
<p>上半年宅在家里大半年，几乎是什么都没学，什么事情都没干。不过也借此机会每天都能睡个大懒觉，总之就是狠狠的歇了一顿，尝试着把高中时期欠的觉补回来，然而似乎并不管用。</p>
<p>然后总感觉自己每天都很累，明明什么都没有干但是身体总是一点力气都没有，总之下半年开学后经历了很长一段时间才重新适应了学校的生活，期间心理变化很复杂。</p>
<p>所以大二下学期和大三上学期的期末成绩基本上都是刚好及格。（真的是烤60分比烤80分还高兴）</p>
<hr>
<p>年初看了几集超电磁炮3，看到后来因为它更新太慢了就忘了继续看了。之后还看了格莱普尼尔，看这个番纯粹是因为它的OP是Hikaru唱的，看着看着觉得挺有意思而且官方更新速度很快，于是就把第一季追完了。</p>
<p>然后因为Lacrimosa这首歌，我才看的黑执事，不过自己比较懒只看了第一季，第二季一直咕到现在都没有看，听说剧情挺虐的所以我更不敢看了。（明明第二季的ed比第一季的还好听）</p>
<p>本来还打算看新世纪福音战士的，也是因为懒，看了一集就不继续看了。</p>
<p>除此之外，年末的时候在睡前为了打发时间看了几集非自然死亡。</p>
<hr>
<p>这一年又打了一遍尼尔：机械纪元。和第一次玩不太一样，二刷时我尽可能的多做支线任务，不开简单模式和自动芯片。不过通关后我还没有打E结局，总想找个时间把其他支线任务做完。</p>
<p>年初在逛淘宝时发现了台版的尼尔原著小说，于是毫不犹豫的把少年寄叶、短话和长话全都买了下来。不过截至目前我只看完了短话。在看完艾米尔的回忆那一章后很感动于是打开游戏找到相对应的支线任务，废了很大力气找到那三朵“Lunar Tear”（月之泪）之后，前往种着一大片月之泪的地下室。</p>
<p>在网上看攻略找到了艾米尔居住的家，于是又走了很远的路到地下很深的地方，到他家里面拿（偷）走了面具。</p>
<p>据说去他家偷完东西后会触发艾米尔的boss支线，不过因为在寝室没太多时间所以我没有继续玩下去。</p>
<p>蛮期待2021年4月发行的尼尔续作的，不过有些担心我的笔记本还能不能跑得起来。</p>
<hr>
<p>尝试着二刷空境原著，不过没刷完，所以只好等以后有时间再看了。</p>
<p>2020年下半年花了很大力气尝试玩懂FGO，不过最终因为太肝、自己太非、没时间而劝退，前几章的主线太枯燥乏味了，而且我貌似只对FSN系列和空境系列的人感兴趣，FGO里面的人物基本和他们不沾边而且我也不认识。</p>
<p>很期待月姬重制版，尽管我目前还一点都不了解月姬。既然月姬重置版只在主机平台上映，尼尔续作也有主机版，那么我是不是应该提前准备一台PS4呢？</p>
<p>PS5水货太贵了还是算了，除非等国行，然而等国行又要等到4月份。</p>
<p>关键是我连PS4都买不起。</p>
<blockquote>
<p>2021年1月22日后续：
最终买了二手港版PS4 Slim，考虑了价格、重量、性能、体积等因素后没有买Pro（主要是没钱），因此现在变得非常贫穷。
换了笔记本上拆下来闲置了一年的1T硬盘，尝试着在尼尔：伪装者发售前三刷一遍尼尔：机械纪元，因为PS4太好玩了所以现在在后悔为什么不早点买PS4。</p>
</blockquote>
<hr>
<p>2020年4月份的时候新开了一个Minecraft单机生存的坑，原本是打算看一下新版本（1.15.2）更新了什么新特性的（蜜蜂），结果玩上生存就停不下来了。先是花了两个星期用纯铁镐手挖两个史莱姆区块，用矿车运村民和僵尸照着B站的视频建了简易的刷铁机，之后又用不到一个月的时间解放末地。总之就是玩上了就停不下来了。然后照着B站各大UP主的视频做了末地刷沙机、然后是混凝土固化机，后来挂三向轰炸机肝了3天清了出生点空置域。用了短短几个月的时间一个人建了很多东西，去地狱打了凋零骷髅头，建海上刷怪塔刷火药和骨粉。后来物资储备足够多了之后就开始挖地铁（旧习难改吧），一年的时间挖了两条地铁线路，盖了十几座车站、一座跨海大桥、一栋摩天大楼。</p>
<p>原来单机生存还能玩得这么有意思，B站上有很多生电大佬，然而红科搬的视频我基本都看不懂。</p>
<p>后续可能要再修几条地铁，延长已有的线路，然后再搞一些建筑，炸一个原点空置域修全物品分类机，然后还打算升级1.16.4。</p>
<hr>
<p>就回忆到这里吧，2020是个特别的一年。</p>
<p><img loading="lazy" src="images/2.jpg" alt="" />

</p>
<hr>
<p>犹豫了很久最后还是决定把之前删掉的部分文章恢复回来，不然总给人一种这个博客是在2020年创建的而不是2016年创建的错觉。</p>
<p>2020年博客鸽了很长一段时间，当时尽管并没有放弃博客但是我真的什么都写不出来，因为看到自己以前写的水文觉得很恶心而且十分不适，于是就在某个心情不好的时候删掉了。后来换了一个新的域名之后就再也没和任何人提起“我还有一个博客”这件事情，因为把博客的文章全删除后几乎没人访问我的博客，新的访客量到现在为止才800多，其中绝大多数都是我自己调试页面刷新时点出来的。</p>
<p>对啊博客就是用来记录生活的，所以就让“因为看不惯以前的自己写的啥也不是的水文最终将其全部删掉”也成为博客记录下的一件事吧。</p>]]></content:encoded>
    </item>
    <item>
      <title>51单片机习题整理</title>
      <link>https://blog.starry-s.moe/posts/2020/learn-mcs8051/</link>
      <pubDate>Tue, 03 Nov 2020 20:19:36 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2020/learn-mcs8051/</guid>
      <description>&lt;p&gt;赶在&lt;del&gt;期中考试&lt;/del&gt;(骑磨烤柿)前把单片机的课后习题整理出来&amp;hellip;&lt;/p&gt;
&lt;p&gt;(实在是没什么可拿来更新博客的了, 就干脆写点复习资料吧&amp;hellip;)&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>赶在<del>期中考试</del>(骑磨烤柿)前把单片机的课后习题整理出来&hellip;</p>
<p>(实在是没什么可拿来更新博客的了, 就干脆写点复习资料吧&hellip;)</p>
<hr>
<blockquote>
<p>本篇内容为作者整理资料所得, 仅供学习使用。如需转载请务必遵循CC BY-NC-ND 4.0协议。
请勿将本篇内容作为权威的教学辅导资料使用, 因无法保证100%准确, 仅供参考。</p>
<p>如果发现了本篇存在的错误, 欢迎在页面下方提issue指正。</p>
<p>本篇文章使用MathJax显示数学公式，在使用RSS阅读器时会出现无法正确显示的情况。</p>
</blockquote>
<meting-js server="netease" type="song" id="1429420739" theme="#233333"></meting-js>
<h2 id="硬件结构指令系统">硬件结构&amp;指令系统</h2>
<ol>
<li>
<p>8051复位后从地址<code>0000H</code>开始执行程序, SP的值为<code>07H</code>。</p>
<p>PC: 两字节(16位)寄存器, 也称程序计数器。</p>
<p>SP: 堆栈指针, 指示出堆栈顶部在<strong>内部RAM块</strong>中的位置。</p>
<p>单片机的堆栈设在了内部RAM区, 单片机复位后, SP中的内容为<code>07H</code>(指向第0组工作寄存器的R7), 堆栈实际上从<code>08H</code>开始。</p>
<p>若SP被初始化为<code>39H</code>, 则堆栈实际上是从<code>3AH</code>开始的。</p>
<p>执行<code>LCALL addr16</code>指令时，单片机先将PC的低字节压栈，再将PC的高字节压栈，最后把转移地址送入PC中。</p>
<p>与之类似的<code>LJMP</code>类无条件转移指令，单片机只修改PC的值，并不堆栈保存跳转前的PC中保存的地址。</p>
<p>执行<code>RET</code>指令后, SP值减2 (因为PC为2字节寄存器), 进行两次出栈操作，第一次出栈送PC的高位，第二次出栈送PC的低位。</p>
<p>中断服务程序结束指令<code>RETI</code>不仅将堆栈中保存的2字节地址分别送入PC的高位和低位中，而且复位中断系统。因此<code>RET</code>和<code>RETI</code>不同。</p>
<p><code>RET</code>和<code>RETI</code>对堆栈的操作是相同的。</p>
</li>
<li>
<p>PSW中的<code>RS1 RS0</code>=<code>10B</code>时, R2的RAM地址为<code>12H</code>。</p>
<p>PSW： 程序状态字寄存器, 从<code>PSW.7</code>至<code>PSW.0</code>分别为<code>Cy</code>(进位标志位), <code>Ac</code>(辅助进位标志位), <code>F0</code>(标志位), <code>RS1</code>和<code>RS0</code>(寄存器区选择控制位), <code>OV</code>(溢出位), 保留位, <code>P</code>(奇偶标志位, 奇数为1, 偶数为0)。</p>
<table>
  <thead>
      <tr>
          <th style="text-align: center"></th>
          <th style="text-align: center">D7</th>
          <th style="text-align: center">D6</th>
          <th style="text-align: center">D5</th>
          <th style="text-align: center">D4</th>
          <th style="text-align: center">D3</th>
          <th style="text-align: center">D2</th>
          <th style="text-align: center">D1</th>
          <th style="text-align: center">D0</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: center">PSW</td>
          <td style="text-align: center">Cy</td>
          <td style="text-align: center">Ac</td>
          <td style="text-align: center">F0</td>
          <td style="text-align: center">RS1</td>
          <td style="text-align: center">RS0</td>
          <td style="text-align: center">OV</td>
          <td style="text-align: center"></td>
          <td style="text-align: center">P</td>
      </tr>
  </tbody>
</table>
<p><code>RS1 RS0</code> = <code>10B</code>时, 使用第二组工作寄存器区, 第0组工作寄存器区R0-R7的地址为<code>00H-07H</code>, 第1组为<code>08H-0FH</code>, 第2组为<code>10H-17H</code>, 第3组为<code>18H-1FH</code>(每组长度为8个字节, 每个寄存器占1个字节)。</p>
<p>因此, 当A为<code>01110010B</code>时, PSW中P为0(偶数个1)。</p>
<p><code>INC A</code>指令不改变<code>PSW</code>中的<code>Cy</code>，只有可能改变奇偶标志位<code>P</code>。</p>
<p>假设PSW为<code>18H</code>, 即<code>00011000B</code>, 此时使用第3组工作寄存器, R0地址为<code>18H</code>, R7地址为<code>1FH</code>。</p>
<p>复位后, 默认选择的寄存器区是0区。</p>
</li>
<li>
<p>8051片外数据存储器的寻址空间为<code>0000H~0FFFFH</code></p>
</li>
<li>
<p>位地址<code>07H</code>位于字节地址<code>20H</code>, 位地址<code>7FH</code>位于字节地址<code>2FH</code>。</p>
<p>片内RAM中<code>20H~2FH</code>这16个单元即可进行共128位的位寻址, 也可进行字节寻址。</p>
<p>字节地址及其位地址见下表所示：</p>
<table>
  <thead>
      <tr>
          <th style="text-align: center">字节地址</th>
          <th style="text-align: center">D7</th>
          <th style="text-align: center">D6</th>
          <th style="text-align: center">D5</th>
          <th style="text-align: center">D4</th>
          <th style="text-align: center">D3</th>
          <th style="text-align: center">D2</th>
          <th style="text-align: center">D1</th>
          <th style="text-align: center">D0</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: center">2FH</td>
          <td style="text-align: center">7FH</td>
          <td style="text-align: center">7EH</td>
          <td style="text-align: center">7DH</td>
          <td style="text-align: center">7CH</td>
          <td style="text-align: center">7BH</td>
          <td style="text-align: center">7AH</td>
          <td style="text-align: center">79H</td>
          <td style="text-align: center">78H</td>
      </tr>
      <tr>
          <td style="text-align: center">2EH</td>
          <td style="text-align: center">77H</td>
          <td style="text-align: center">76H</td>
          <td style="text-align: center">75H</td>
          <td style="text-align: center">74H</td>
          <td style="text-align: center">73H</td>
          <td style="text-align: center">72H</td>
          <td style="text-align: center">71H</td>
          <td style="text-align: center">70H</td>
      </tr>
      <tr>
          <td style="text-align: center">2DH</td>
          <td style="text-align: center">6FH</td>
          <td style="text-align: center">6EH</td>
          <td style="text-align: center">6DH</td>
          <td style="text-align: center">6CH</td>
          <td style="text-align: center">6BH</td>
          <td style="text-align: center">6AH</td>
          <td style="text-align: center">69H</td>
          <td style="text-align: center">68H</td>
      </tr>
      <tr>
          <td style="text-align: center">&hellip;</td>
          <td style="text-align: center">&hellip;</td>
          <td style="text-align: center">&hellip;</td>
          <td style="text-align: center">&hellip;</td>
          <td style="text-align: center">&hellip;</td>
          <td style="text-align: center">&hellip;</td>
          <td style="text-align: center">&hellip;</td>
          <td style="text-align: center">&hellip;</td>
          <td style="text-align: center">&hellip;</td>
      </tr>
      <tr>
          <td style="text-align: center">21H</td>
          <td style="text-align: center">0FH</td>
          <td style="text-align: center">0EH</td>
          <td style="text-align: center">0DH</td>
          <td style="text-align: center">0CH</td>
          <td style="text-align: center">0BH</td>
          <td style="text-align: center">0AH</td>
          <td style="text-align: center">09H</td>
          <td style="text-align: center">08H</td>
      </tr>
      <tr>
          <td style="text-align: center">20H</td>
          <td style="text-align: center">07H</td>
          <td style="text-align: center">06H</td>
          <td style="text-align: center">05H</td>
          <td style="text-align: center">04H</td>
          <td style="text-align: center">03H</td>
          <td style="text-align: center">02H</td>
          <td style="text-align: center">01H</td>
          <td style="text-align: center">00H</td>
      </tr>
  </tbody>
</table>
<p>因此<code>00H~07H</code>位于字节地址<code>20H</code>, <code>78H-7FH</code>位于字节地址<code>2FH</code>。</p>
<p>除此之外，一些特殊功能寄存器（SFR）可进行位寻址（字节地址能够被8整除），SFR中位地址有83个（共有88个，5个未用），能够位寻址的SFR的字节地址末位均为<code>0H</code>或<code>8H</code>。</p>
</li>
<li>
<p>访问外部存储器时, P0口用来传输低8位地址和数据, P2口用来传送高8位地址。</p>
</li>
<li>
<p>访问外部存储器时, ALE的输出用于锁存低8位地址。</p>
</li>
<li>
<p>一个机器周期为12个震荡周期</p>
</li>
<li>
<p>为保证读入数据正确, 在读一个端口引脚之前应先向相应的端口锁存器写1。</p>
<p>如果在读一个端口引脚之前向对应的输出锁存器写了0, 将总是读到0。</p>
<p>8051访问片外存储器时, 利用ALE信号锁存来自P0的低8位地址信号。</p>
</li>
<li>
<p>只能用直接寻址方式访问特殊功能寄存器。</p>
<p>如果为8052单片机, 在访问高128字节的RAM时, 只能用间接寻址方式寻址。</p>
<p>访问片外数据存储器64Kbyte时, 使用<code>DPTR</code>做间接寻址寄存器。</p>
<p>使用<code>MOVX @DPTR</code>类指令访问外部扩展存储器时, P2口输出高8位地址, P0口传送低8位地址和数据。</p>
<p>8051中, <code>PC</code>和<code>DPTR</code>都用于提供地址时, <code>PC</code>是<strong>用户程序不可访问的</strong>, <code>DPTR</code>可以分为两个8位寄存器<code>DPH</code>和<code>DPL</code>使用。</p>
</li>
<li>
<p>累加器A的值为<code>30H</code>, 指令<code>MOVC A, @A+PC</code>位于地址<code>3000H</code>。执行该指令时, 程序存储器地址<code>3031H</code>的内容被传送至累加器A。</p>
</li>
</ol>
<p>指令<code>MOVC A, @A+PC</code>长度为1字节, 位于地址<code>3000H</code>, 因此PC值为<code>3001H</code>, 故<code>A+PC</code>为<code>3031H</code>。</p>
<ol start="11">
<li>设SP的值为<code>5FH</code>, 指令<code>LCALL DELAY</code>所在地址为<code>2030H</code>, 子程序DELAY所在地址为<code>20A0H</code>, 则该指令完成的操作是将地址<code>2033H</code>压入堆栈保存, 将地址<code>20A0H</code>送入PC, SP的值应在该指令执行结束后变成<code>61H</code>。</li>
</ol>
<p>单片机执行子程序或中断服务程序时, 需要保护现场, 即将PC当前的值压栈保存, 当子程序或中断服务程序运行结束后再进行出栈。(所以如果子程序修改了栈的内容, 在子程序运行结束后程序有可能会运行错误)。</p>
<p><strong>PC是16字节寄存器</strong>, 所以SP需要加2, 以此分别保存PC的高、低8位的数据。</p>
<p><code>LCALL addr16</code>这条指令占3字节, 其中addr16占两字节, 因此程序可跳转64KB范围内的地址。</p>
<p>(<code>ACALL</code>指令只能跳转当前所在的<strong>2K范围内</strong>的地址, 如果不涉及到片外数据存储器的话, 通常只用<code>SJMP</code>和<code>AJMP</code>)</p>
<blockquote>
<p>在网上搜到的答案「将<code>3500H</code>送入PC」是错误的, 实际是将<code>20A0H</code>送入PC, 因为DELAY所在的地址为<code>20A0H</code>, 和<code>3500H</code>一点关系都没有。</p>
</blockquote>
<ol start="12">
<li><code>MOVC</code>访问 <strong>程序(ROM)</strong> 存储器, <code>MOVX</code>指令访问 <strong>外部数据</strong> 存储器。</li>
</ol>
<p><code>MOVC</code>为查表指令, 只有<code>MOVC @A+PC</code>和<code>MOVC @A+DPTR</code>这两条, 均为单字节指令。</p>
<p><code>MOVX</code>用于累加器A与外部数据存储器进行传送。</p>
<p>可以是<code>MOVX A, @DPTR</code>, <code>MOVX A, @Ri</code>, <code>MOVX @DPTR, A</code>, <code>MOVX @Ri, A</code>，其中i为0或1.</p>
<p>当使用<code>MOVX @Ri</code>类指令时，只有P0口用来传送地址和数据，P2口的状态不会发生改变，因此可使用<code>MOV P2, #12H</code>指令设定高8位的地址。</p>
<p>当采用Ri作间接寻址时, 只能寻找片外256个单元的数据存储器, 此时8位地址和数据均由P0口传送，P2口的状态不发生改变。</p>
<ol start="13">
<li>假设指令<code>DJNZ R7, rel</code>位于<code>005FH</code>, 如果在执行该指令前寄存器R7值为<code>00H</code>, 偏移量rel为<code>02H</code>, 则该指令执行后下一条要执行的指令所在的地址是<code>005FH</code>。</li>
</ol>
<p><code>DJNZ R7, rel</code>位于<code>005FH</code>, 该指令长度为<strong>2字节</strong> (书上写的3字节是错的), 所以在执行这条指令开始时PC值在原来的基础上 + 2 变为<code>0061H</code>。又因为rel为<code>02H</code>, 所以执行这条指令后, PC值变为<code>0063H</code>。</p>
<ol start="14">
<li>分析下面子程序的功能, 假设8051单片机的震荡频率为6MHz。</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-asm" data-lang="asm"><span class="line"><span class="cl"><span class="nl">DL:</span> <span class="nf">MOV</span> <span class="no">R7</span><span class="p">,</span> <span class="c1">#0AH
</span></span></span><span class="line"><span class="cl"><span class="nl">L0:</span> <span class="nf">MOV</span> <span class="no">R6</span><span class="p">,</span> <span class="c1">#250
</span></span></span><span class="line"><span class="cl"><span class="nl">L1:</span> <span class="nf">NOP</span>
</span></span><span class="line"><span class="cl">     <span class="nf">NOP</span>
</span></span><span class="line"><span class="cl">     <span class="nf">DJNZ</span> <span class="no">R6</span><span class="p">,</span> <span class="no">L1</span>
</span></span><span class="line"><span class="cl">     <span class="nf">DJNZ</span> <span class="no">R7</span><span class="p">,</span> <span class="no">L0</span>
</span></span><span class="line"><span class="cl">     <span class="nf">RET</span>
</span></span></code></pre></div><p>R7为10, R6为250, 因此两个<code>NOP</code>加上一个<code>DJNZ</code>一共循环了250次, 该250次的循环一共执行了10次。</p>
<p><code>NOP</code>为1周期指令, <code>DJNZ</code>为2周期指令, 两个<code>NOP</code>加一个<code>DJNZ</code>共4周期。</p>
<p>6MHz下一个机器周期为<span class="has-mathjax">\(12 \div (6 \times 10^6)= 2{\mu}s\)</span>, 12MHz下一个机器周期为1微秒。</p>
<p>故程序该子程序延时了<span class="has-mathjax">\((4 \times 250 \times 10 \times 12) \div (6 \times 10^6) = 20(ms)\)</span></p>
<p>(实际上有10次<code>MOV R6, #250</code>和10次<code>DJNZ R7, L0</code>造成的30个机器周期的约0.6ms的误差)</p>
<hr>
<blockquote>
<p>以下部分写于2020年11月25日</p>
</blockquote>
<h2 id="汇编语言程序">汇编语言程序</h2>
<ol>
<li>
<p>编写一个子程序, 将内部RAM 40H~4FH的内容复制到50H~5FH。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-asm" data-lang="asm"><span class="line"><span class="cl"><span class="nl">COPY:</span> <span class="nf">MOV</span> <span class="no">R0</span><span class="p">,</span> <span class="c1">#40H
</span></span></span><span class="line"><span class="cl">      <span class="nf">MOV</span> <span class="no">R1</span><span class="p">,</span> <span class="c1">#50H
</span></span></span><span class="line"><span class="cl">      <span class="nf">MOV</span> <span class="no">R2</span><span class="p">,</span> <span class="c1">#10H
</span></span></span><span class="line"><span class="cl">      <span class="c1">; 40H~4FH一共复制了16次
</span></span></span><span class="line"><span class="cl"><span class="nl">LOOP:</span>
</span></span><span class="line"><span class="cl">      <span class="nf">MOV</span> <span class="no">A</span><span class="p">,</span> <span class="err">@</span><span class="no">R0</span>
</span></span><span class="line"><span class="cl">      <span class="nf">MOV</span> <span class="err">@</span><span class="no">R1</span><span class="p">,</span> <span class="no">A</span>
</span></span><span class="line"><span class="cl">      <span class="nf">INC</span> <span class="no">R0</span>
</span></span><span class="line"><span class="cl">      <span class="nf">INC</span> <span class="no">R1</span>
</span></span><span class="line"><span class="cl">      <span class="nf">DJNZ</span> <span class="no">R2</span><span class="p">,</span> <span class="no">LOOP</span>
</span></span><span class="line"><span class="cl">      <span class="nf">RET</span>
</span></span></code></pre></div><p>因为没有<code>MOV @RX, @RX</code>这条指令, 所以用A做数据的中转站。</p>
</li>
<li>
<p>将任何无符号8位二进制数转换为BCD码的子程序, 入口参数为内部RAM单元20H, 出口参数为内部RAM单元30H和31H, 30H存放百位数, 31H存放十位数和个位数。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-asm" data-lang="asm"><span class="line"><span class="cl"><span class="nl">CV:</span>
</span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">R0</span><span class="p">,</span> <span class="c1">#20H
</span></span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">A</span><span class="p">,</span> <span class="err">@</span><span class="no">R0</span>
</span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">B</span><span class="p">,</span> <span class="c1">#100
</span></span></span><span class="line"><span class="cl"><span class="nf">DIV</span> <span class="no">AB</span><span class="c1">; A除以100, 得到的百位数存在A中
</span></span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="mi">30</span><span class="no">H</span><span class="p">,</span> <span class="no">A</span>
</span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">A</span><span class="p">,</span> <span class="no">B</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">B</span><span class="p">,</span> <span class="c1">#10
</span></span></span><span class="line"><span class="cl"><span class="nf">DIV</span> <span class="no">AB</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">SWAP</span> <span class="no">A</span>
</span></span><span class="line"><span class="cl"><span class="nf">ADD</span> <span class="no">A</span><span class="p">,</span> <span class="no">B</span><span class="c1">; 也可以用ORL
</span></span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="mi">31</span><span class="no">H</span><span class="p">,</span> <span class="no">A</span>
</span></span><span class="line"><span class="cl"><span class="nf">RET</span>
</span></span></code></pre></div><p><code>DIV AB</code>和<code>MUL AB</code>的A和B之间没有逗号。</p>
</li>
<li>
<p>内部RAM 30H单元存放两位十进制数 (压缩BCD码), 编写将该十进制数转换为对应ASCII码的子程序, 转换结果存放到内部RAM 40H (十位数) 和41H (个位数) 单元。</p>
<blockquote>
<p>ASCII码： <code>30H</code>为<code>0</code>, <code>41H</code>为<code>A</code>, <code>61H</code>为<code>a</code></p>
</blockquote>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-asm" data-lang="asm"><span class="line"><span class="cl"><span class="nl">CV:</span>
</span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">A</span><span class="p">,</span> <span class="mi">30</span><span class="no">H</span>
</span></span><span class="line"><span class="cl"><span class="nf">ANL</span> <span class="no">A</span><span class="p">,</span> <span class="c1">#0FH
</span></span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">DPTR</span><span class="p">,</span> <span class="c1">#DATA; 或者直接用ADD A, #30H即可
</span></span></span><span class="line"><span class="cl"><span class="nf">MOVC</span> <span class="no">A</span><span class="p">,</span> <span class="err">@</span><span class="no">A</span><span class="err">+</span><span class="no">DPTR</span>
</span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="mi">41</span><span class="no">H</span><span class="p">,</span> <span class="no">A</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">A</span><span class="p">,</span> <span class="mi">30</span><span class="no">H</span>
</span></span><span class="line"><span class="cl"><span class="nf">SWAP</span> <span class="no">A</span>
</span></span><span class="line"><span class="cl"><span class="nf">ANL</span> <span class="no">A</span><span class="p">,</span> <span class="c1">#0FH
</span></span></span><span class="line"><span class="cl"><span class="nf">MOVC</span> <span class="no">A</span><span class="p">,</span> <span class="err">@</span><span class="no">A</span><span class="err">+</span><span class="no">DPTR</span>
</span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="mi">40</span><span class="no">H</span><span class="p">,</span> <span class="no">A</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">RET</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nl">DATA:</span>
</span></span><span class="line"><span class="cl"><span class="nf">DB</span> <span class="mi">30</span><span class="no">H</span><span class="p">,</span> <span class="mi">31</span><span class="no">H</span><span class="p">,</span> <span class="mi">32</span><span class="no">H</span><span class="p">,</span> <span class="mi">33</span><span class="no">H</span><span class="p">,</span> <span class="mi">34</span><span class="no">H</span><span class="p">,</span> <span class="mi">35</span><span class="no">H</span><span class="p">,</span> <span class="mi">36</span><span class="no">H</span><span class="p">,</span> <span class="mi">37</span><span class="no">H</span><span class="p">,</span> <span class="mi">38</span><span class="no">H</span><span class="p">,</span> <span class="mi">39</span><span class="no">H</span>
</span></span></code></pre></div></li>
<li>
<p>8个8位数相加, 求平均值, 入口地址为<code>30H</code>~<code>37H</code>, 结果存到<code>40H</code>。</p>
<p>把8位数相加存在溢出, 所以把相加结果以16进制存到R2、R3中, 再除以8( 右移3次), 即可求得不四舍五入的平均值。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-asm" data-lang="asm"><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">R0</span><span class="p">,</span> <span class="c1">#30H
</span></span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">R1</span><span class="p">,</span> <span class="c1">#08H
</span></span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">R2</span><span class="p">,</span> <span class="c1">#00H
</span></span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">R3</span><span class="p">,</span> <span class="c1">#00H
</span></span></span><span class="line"><span class="cl"><span class="c1">; 初始化
</span></span></span><span class="line"><span class="cl"><span class="nl">LOOP:</span>
</span></span><span class="line"><span class="cl"><span class="nf">CLR</span> <span class="no">C</span>
</span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">A</span><span class="p">,</span> <span class="err">@</span><span class="no">R0</span>
</span></span><span class="line"><span class="cl"><span class="nf">ADD</span> <span class="no">A</span><span class="p">,</span> <span class="no">R3</span>
</span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">R3</span><span class="p">,</span> <span class="no">A</span>
</span></span><span class="line"><span class="cl"><span class="nf">JNC</span> <span class="no">SKIP</span>
</span></span><span class="line"><span class="cl"><span class="nf">INC</span> <span class="no">R2</span>
</span></span><span class="line"><span class="cl"><span class="nl">SKIP:</span>
</span></span><span class="line"><span class="cl"><span class="nf">INC</span> <span class="no">R0</span>
</span></span><span class="line"><span class="cl"><span class="nf">DJNZ</span> <span class="no">R1</span><span class="p">,</span> <span class="no">LOOP</span>
</span></span></code></pre></div><p>这样结果被保存到R2、R3中, 然后需要写一个循环右移3位的程序。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-asm" data-lang="asm"><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">R4</span><span class="p">,</span> <span class="c1">#03H
</span></span></span><span class="line"><span class="cl"><span class="nl">LOOP2:</span>
</span></span><span class="line"><span class="cl"><span class="nf">RRC</span> <span class="no">R2</span>
</span></span><span class="line"><span class="cl"><span class="nf">RRC</span> <span class="no">R3</span>
</span></span><span class="line"><span class="cl"><span class="nf">DJNZ</span> <span class="no">R4</span><span class="p">,</span> <span class="no">LOOP2</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="mi">40</span><span class="no">H</span><span class="p">,</span> <span class="no">R3</span>
</span></span><span class="line"><span class="cl"><span class="c1">; 右移3次后R2的低4位为0, 结果保存在R3中
</span></span></span></code></pre></div><p>这样R3中求得的是不带四舍五入的结果。</p>
<p>如果需要带四舍五入的话第一种方法是判断最后一次右移时最低位是否为<span class="has-mathjax">\(1\)</span>，<span class="has-mathjax">\((1 / 2 = 0.5)\)</span>。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-asm" data-lang="asm"><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">R4</span><span class="p">,</span> <span class="c1">#03H
</span></span></span><span class="line"><span class="cl"><span class="nl">LOOP2:</span>
</span></span><span class="line"><span class="cl"><span class="nf">RRC</span> <span class="no">R2</span>
</span></span><span class="line"><span class="cl"><span class="nf">RRC</span> <span class="no">R3</span>
</span></span><span class="line"><span class="cl"><span class="nf">DJNZ</span> <span class="no">R4</span><span class="p">,</span> <span class="no">LOOP2</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">JNC</span> <span class="no">SKIP2</span>
</span></span><span class="line"><span class="cl"><span class="nf">INC</span> <span class="no">R3</span>
</span></span><span class="line"><span class="cl"><span class="nl">SKIP2:</span>
</span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="mi">40</span><span class="no">H</span><span class="p">,</span> <span class="no">R3</span>
</span></span><span class="line"><span class="cl"><span class="c1">; 右移3次后R2肯定为0
</span></span></span></code></pre></div><p>这样是带四舍五入的结果。</p>
<p>第二种方法，8个8位数相加求平均值，要求四舍五入的话，只需要在这8个数求和后再加4（<code>0100B</code>），之后右移3次。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-asm" data-lang="asm"><span class="line"><span class="cl"><span class="nf">ADD</span> <span class="no">A</span><span class="p">,</span> <span class="c1">#04H
</span></span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">R3</span><span class="p">,</span> <span class="no">A</span>
</span></span><span class="line"><span class="cl"><span class="nf">JNC</span> <span class="no">SKIP2</span>
</span></span><span class="line"><span class="cl"><span class="nf">INC</span> <span class="no">R2</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nl">SKIP2:</span>
</span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">R4</span><span class="p">,</span> <span class="c1">#03H
</span></span></span><span class="line"><span class="cl"><span class="nl">LOOP2:</span>
</span></span><span class="line"><span class="cl"><span class="nf">RRC</span> <span class="no">R2</span>
</span></span><span class="line"><span class="cl"><span class="nf">RRC</span> <span class="no">R3</span>
</span></span><span class="line"><span class="cl"><span class="nf">DJNZ</span> <span class="no">R4</span><span class="p">,</span> <span class="no">LOOP2</span>
</span></span></code></pre></div><p>结果保存在R3中。</p>
</li>
</ol>
<hr>
<h2 id="中断系统">中断系统</h2>
<ol>
<li>
<p>8051的外部中断有低电平触发和下降沿触发两种触发方式。外部中断1的中断向量地址是<code>0013H</code>。</p>
<p>在响应中断时, 单片机自动生成一条长调用指令<code>LCALL addr16</code>, 其地址为中断入口地址。</p>
<table>
  <thead>
      <tr>
          <th style="text-align: center">中断源</th>
          <th style="text-align: center">入口地址</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: center">INT0</td>
          <td style="text-align: center">0003H</td>
      </tr>
      <tr>
          <td style="text-align: center">T0</td>
          <td style="text-align: center">000BH</td>
      </tr>
      <tr>
          <td style="text-align: center">INT1</td>
          <td style="text-align: center">0013H</td>
      </tr>
      <tr>
          <td style="text-align: center">T1</td>
          <td style="text-align: center">001BH</td>
      </tr>
      <tr>
          <td style="text-align: center">串行口</td>
          <td style="text-align: center">0023H</td>
      </tr>
  </tbody>
</table>
<p>通常在中断入口地址处放一条<strong>无条件转移指令</strong><code>*JMP</code>。</p>
<p>内部查询顺序同入口地址的顺序，由高到低。</p>
</li>
<li>
<p>执行指令<code>MOV IP, #0BH</code>( <code>#00001011B</code>)后, 中断优先级最高者为<code>PX0</code>, 最低为<code>PS</code>。</p>
<p>IP: 中断优先级寄存器, 其前3为无意义, 后5位( IP.4至IP.0)为：<code>PS</code>, <code>PT1</code>, <code>PX1</code>, <code>PT0</code>, <code>PX0</code>, 分别对应串行口、定时器T1、外部中断1、定时器0、外部中断0。</p>
<table>
  <thead>
      <tr>
          <th style="text-align: center"></th>
          <th style="text-align: center">D7</th>
          <th style="text-align: center">D6</th>
          <th style="text-align: center">D5</th>
          <th style="text-align: center">D4</th>
          <th style="text-align: center">D3</th>
          <th style="text-align: center">D2</th>
          <th style="text-align: center">D1</th>
          <th style="text-align: center">D0</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: center">IP</td>
          <td style="text-align: center"></td>
          <td style="text-align: center"></td>
          <td style="text-align: center"></td>
          <td style="text-align: center">PS</td>
          <td style="text-align: center">PT1</td>
          <td style="text-align: center">PX1</td>
          <td style="text-align: center">PT0</td>
          <td style="text-align: center">PX0</td>
      </tr>
  </tbody>
</table>
<p>单片机复位以后, IP的内容为0, 各个中断源均为低优先级中断。</p>
<p>在同时收到几个同一优先级的中断请求时, 中断响应取决于内部查询顺序, 其顺序由高到低为：<code>INT0</code>、<code>T0</code>、<code>INT1</code>、<code>T1</code>、<code>串行口</code>。</p>
<p>当中断源均为同一优先级时, 当它们同时申请中断时CPU首先响应外部中断0</p>
</li>
<li>
<p>8051的晶振频率为12MHz, 则最短的外部中断响应时间为3<span class="has-mathjax">\(\mu\)</span>s, 最长的外部中断响应时间为12<span class="has-mathjax">\(\mu\)</span>。</p>
</li>
<li>
<p>中断标记位于单片机寄存器的<code>TCON</code>和<code>SCON</code>中。</p>
<table>
  <thead>
      <tr>
          <th style="text-align: center"></th>
          <th style="text-align: center">D7</th>
          <th style="text-align: center">D6</th>
          <th style="text-align: center">D5</th>
          <th style="text-align: center">D4</th>
          <th style="text-align: center">D3</th>
          <th style="text-align: center">D2</th>
          <th style="text-align: center">D1</th>
          <th style="text-align: center">D0</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: center">TCON</td>
          <td style="text-align: center">TF1</td>
          <td style="text-align: center">TR1</td>
          <td style="text-align: center">TF0</td>
          <td style="text-align: center">TR0</td>
          <td style="text-align: center">IE1</td>
          <td style="text-align: center">IT1</td>
          <td style="text-align: center">IE0</td>
          <td style="text-align: center">IT0</td>
      </tr>
      <tr>
          <td style="text-align: center">SCON</td>
          <td style="text-align: center">SM0</td>
          <td style="text-align: center">SM1</td>
          <td style="text-align: center">SM2</td>
          <td style="text-align: center">REN</td>
          <td style="text-align: center">TB8</td>
          <td style="text-align: center">RB8</td>
          <td style="text-align: center">TI</td>
          <td style="text-align: center">RI</td>
      </tr>
  </tbody>
</table>
<p><code>TCON</code>中<code>TF1</code>和<code>TF0</code>为定时器/计数器中断标志位, <code>TR1</code>和<code>TR0</code>为定时/计数启动位, <code>IE1</code>和<code>IE0</code>为外部中断标志位, <code>IT1</code>和<code>IT0</code>为选择外部中断为边沿触发(1)还是电平触发(0)方式。</p>
<p><code>SCON</code>中前几位与串行口有关，第1位和第2位的<code>TI</code>和<code>RI</code>(是大写字母I不是数字1)分别为串行口中断发送中断请求标志位(发送成功后置1)和接受中断请求标志位( 接受成功后置1)。串口中断无法硬件清零, 只能软件清零。</p>
<p>当IE中EA为1、ES为1时，TI或RI为1时，CPU执行无条件转移指令<code>LJMP 0023H</code>, 执行串行口中断服务程序。</p>
</li>
<li>
<p>要使8051能够响应定时器T1的中断和串行口中断, 不响应其他中断, 则中断允许寄存器<code>IE</code>的内容为<code>98H</code>(<code>10011000B</code>)。</p>
<p>中断允许寄存器IE：</p>
<table>
  <thead>
      <tr>
          <th style="text-align: center"></th>
          <th style="text-align: center">D7</th>
          <th style="text-align: center">D6</th>
          <th style="text-align: center">D5</th>
          <th style="text-align: center">D4</th>
          <th style="text-align: center">D3</th>
          <th style="text-align: center">D2</th>
          <th style="text-align: center">D1</th>
          <th style="text-align: center">D0</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: center">IE</td>
          <td style="text-align: center">EA</td>
          <td style="text-align: center"></td>
          <td style="text-align: center"></td>
          <td style="text-align: center">ES</td>
          <td style="text-align: center">ET1</td>
          <td style="text-align: center">EX1</td>
          <td style="text-align: center">ET0</td>
          <td style="text-align: center">EX0</td>
      </tr>
  </tbody>
</table>
<p><code>EA</code>为总控制位, 当<code>EA</code>为1时, CPU才可以响应中断请求。</p>
<p><code>ES</code>为串行口中断允许位, <code>ET1</code>和<code>ET0</code>为定时器中断允许位, <code>EX1</code>和<code>EX0</code>为外部中断允许位。</p>
</li>
</ol>
<hr>
<h2 id="定时器和计数器">定时器和计数器</h2>
<ol>
<li>
<p>使T0工作方式1的溢出周期最长的初值是<code>0000H</code>。</p>
<p>定时器/计数器工作在方式0为13位计数器, 由<code>TLX</code>的低5位和<code>THX</code>的高8位组成13位计数器, 最大值为<span class="has-mathjax">\(2^{13}-1 = 8191\)</span>, 晶振频率12MHz下计时周期最长为8.192ms。工作在方式1时由均为8位的<code>TLX</code>和<code>THX</code>组成16位计数器, 最大值为65535, 晶振频率为12M下最长为65.536ms。</p>
<p>当定时器/计数器工作在方式2时, 可以循环定时/计数。当计数溢出后, 自动将8位的<code>THX</code>装入8位的<code>TLX</code>中, 可省去重装初值的时间, 最大值为<code>255</code>, 晶振频率12M下计时周期最长为0.256ms。</p>
</li>
<li>
<p><code>T1</code>配置为方式3时, 停止计数, 方式3只适用于定时器0。</p>
<p>定时器<code>T0</code>的方式3将其分为两个8位定时器, 其中<code>TH0</code>只能做定时器使用。</p>
</li>
<li>
<p>设8051单片机的晶振频率为12MHz, 定时器作计数器使用时, 其计数输入信号的最高频率为500KHz。</p>
<p>当定时器用作计数器时, 当检测到引脚上的负跳变时计数器的值增一。检测下降沿需要2个机器周期, 即24个震荡周期, 所以输入信号最高频率为 <span class="has-mathjax">\(12M \div 24 = 500KHz\)</span>。</p>
</li>
<li>
<p>用定时器方式2扩展一个下降沿触发的外部中断, 计数初值应为<code>FFH</code>。</p>
<p>此处定时器2以计数器方式运行, 当检测到一个下降沿后, 计数器加一后溢出, 因此会执行定时器中断的中断子程序。</p>
</li>
</ol>
<hr>
<blockquote>
<p>2020年12月3日：
前两天感冒, 休息了几天(<del>打了两天的游戏</del>)后继续。</p>
</blockquote>
<h2 id="串行口">串行口</h2>
<ol>
<li>
<p>串行口TXD为高电平, 表示这是数据位或停止位或空闲状态。</p>
<p>串行口工作在方式1时, <code>TXD</code>用来发送数据、<code>RXD</code>用来接受数据。方式1的一帧数据为10位, 起始为为0, 停止位为1, 数据位和空闲状态均可能为0或1。</p>
</li>
<li>
<p>串行口工作在方式3时, 发送的第9位数据要事先写入寄存器<code>SCON</code>的<code>TB8</code>, 接收的第9位数据被写入同一寄存器的<code>RB8</code>。</p>
<p>串行口控制寄存器SCON：</p>
<table>
  <thead>
      <tr>
          <th style="text-align: center"></th>
          <th style="text-align: center">D7</th>
          <th style="text-align: center">D6</th>
          <th style="text-align: center">D5</th>
          <th style="text-align: center">D4</th>
          <th style="text-align: center">D3</th>
          <th style="text-align: center">D2</th>
          <th style="text-align: center">D1</th>
          <th style="text-align: center">D0</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: center">SCON</td>
          <td style="text-align: center">SM0</td>
          <td style="text-align: center">SM1</td>
          <td style="text-align: center">SM2</td>
          <td style="text-align: center">REN</td>
          <td style="text-align: center">TB8</td>
          <td style="text-align: center">RB8</td>
          <td style="text-align: center">TI</td>
          <td style="text-align: center">RI</td>
      </tr>
  </tbody>
</table>
<p>其中<code>SM0</code>、<code>SM1</code>为串口的工作方式选择位, 方式0为同步移位寄存器方式, 方式1为8位异步收发, 方式2和方式3为9位异步收发。</p>
<p>波特率：串行口每秒钟传送的比特位数，单位bits/s。</p>
<p>方式0的波特率<strong>固定</strong>为 <span class="has-mathjax">\(f_{osc}/12\)</span>, 方式2的波特率为 <span class="has-mathjax">\(f_{osc}/64\)</span> 或 <span class="has-mathjax">\(f_{osc}/32\)</span> (由SMOD控制)。</p>
<p>方式1的波特率和方式3的波特率可变, 公式为 <span class="has-mathjax">\((2^{SMOD} / 32) \times 定时器T1的溢出率\)</span>。</p>
<p>T1的溢出率为定时器T1的周期的倒数，定时器的周期为</p>
<div class="has-mathjax">
  
   $$T = \frac{12\times (2^n - X )}{f\_{osc}}$$
   
</div>
<p>当T1工作在方式2时，n为8，波特率为：</p>
<div class="has-mathjax">
  
   $$波特率=\frac{(2^{SMOD} \div 32) \times f_{osc}}{12 \times (256 - X)}$$
   
</div>
<p><span class="has-mathjax">\(X\)</span>为定时器T1的初值。</p>
<p>串口工作在方式1，波特率为2400，则每秒钟最大能发送/接收 <span class="has-mathjax">\(2400 \div 10 = 240Byte\)</span> 的数据。</p>
<blockquote>
<p>单片机工作在方式1时, 1帧数据为1个起始位、8个数据位、1个停止位共10位。</p>
</blockquote>
<p>8051的UART工作在方式3，要求每秒钟能传送不少于900个字节的数据，则波特率应当大于 <span class="has-mathjax">\(900 \times 8 = 7200bits/s\)</span>。</p>
</li>
<li>
<p>比特率2400Kbits/s，时钟频率12M，PC机发送8个字节的数据存到单片机的<code>30H-37H</code>中，随后单片机发送2个确认字节<code>55H</code>和<code>AAH</code>给PC机，使用查询方式。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-asm" data-lang="asm"><span class="line"><span class="cl"><span class="nf">ORG</span> <span class="mi">0000</span><span class="no">H</span>
</span></span><span class="line"><span class="cl"><span class="nl">MAIN:</span>
</span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">SCON</span><span class="p">,</span> <span class="c1">#50H
</span></span></span><span class="line"><span class="cl"><span class="c1">; 串口使用方式1，且允许接收
</span></span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">PCON</span><span class="p">,</span> <span class="c1">#00H
</span></span></span><span class="line"><span class="cl"><span class="c1">; 波特率不加倍
</span></span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">TMOD</span><span class="p">,</span> <span class="c1">#20H
</span></span></span><span class="line"><span class="cl"><span class="c1">; 定时器1使用方式2
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">TL1</span><span class="p">,</span> <span class="c1">#0F3H
</span></span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">TH1</span><span class="p">,</span> <span class="c1">#0F3H
</span></span></span><span class="line"><span class="cl"><span class="nf">SETB</span> <span class="no">TR1</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nl">LOOP:</span>
</span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">R0</span><span class="p">,</span> <span class="c1">#30H
</span></span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">R1</span><span class="p">,</span> <span class="c1">#08H
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nl">REC:</span>
</span></span><span class="line"><span class="cl"><span class="nf">JNB</span> <span class="no">RI</span><span class="p">,</span> <span class="no">$</span>
</span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="err">@</span><span class="no">R0</span><span class="p">,</span> <span class="no">SBUF</span>
</span></span><span class="line"><span class="cl"><span class="nf">CLR</span> <span class="no">RI</span> <span class="c1">; 软件清零RI
</span></span></span><span class="line"><span class="cl"><span class="nf">INC</span> <span class="no">R0</span>
</span></span><span class="line"><span class="cl"><span class="nf">DJNZ</span> <span class="no">R1</span><span class="p">,</span> <span class="no">REC</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">SBUF</span><span class="p">,</span> <span class="c1">#55H
</span></span></span><span class="line"><span class="cl"><span class="nf">JNB</span> <span class="no">TI</span><span class="p">,</span> <span class="no">$</span>
</span></span><span class="line"><span class="cl"><span class="nf">CLR</span> <span class="no">TI</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">SBUF</span><span class="p">,</span> <span class="c1">#0AAH
</span></span></span><span class="line"><span class="cl"><span class="nf">JNB</span> <span class="no">TI</span><span class="p">,</span> <span class="no">$</span>
</span></span><span class="line"><span class="cl"><span class="nf">CLR</span> <span class="no">TI</span>
</span></span><span class="line"><span class="cl"><span class="nf">AJMP</span> <span class="no">LOOP</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">END</span>
</span></span></code></pre></div><p>单片机接收到数据后，RI被置1，代表SBUF中的数据有效，取走SBUF中的数据后需要软件清零RI（串口中断不会自动清零RI）。</p>
<p>当单片机执行写SBUF操作时，串口发送开始，TI被自动置1，在发送完成后TI被自动清0。</p>
</li>
</ol>
<hr>
<blockquote>
<p>2020年12月7日：</p>
</blockquote>
<h2 id="io口--外部存储器拓展">I/O口 &amp; 外部存储器拓展</h2>
<ol>
<li>
<p>8051使用指令<code>MOVX A, @R0</code>读外部数据存储器时，不起作用的信号是<code>WR</code>。</p>
<p>8051在使用<code>MOVX @DPTR</code>类指令读外部数据存储器时，P0和P2先输出外部数据的地址，单片机发出的<code>ALE</code>信号的负跳沿将P0口输出的地址锁存在地址锁存器(74HC573)里，之后单片机发出<code>RD</code>信号，读取外部数据存储器（RAM）的数据到总线再传送到P0口上。最后单片机从P0口读取数据保存到A中。</p>
<p>如果是写外部数据存储器时，单片机将不发出<code>RD</code>信号而是<code>WR</code>信号，将A中的数据写入外部数据存储器中。</p>
</li>
<li>
<p>存储器芯片6264需要13根地址线。</p>
<p>62256的容量为 <span class="has-mathjax">\(256Kbit \div 8 = 32KB = 2^{15}\)</span>, 需要15根地址线。</p>
<p>6264的容量为 <span class="has-mathjax">\(64Kbit \div 8 = 8KB = 2^{13}\)</span>，所以需要13根地址线。</p>
<p>6116容量为 <span class="has-mathjax">\(16Kbit \div 8 = 2KB = 2^{11}\)</span>, 需要11根地址线。</p>
<p>计算方法是62 <span class="has-mathjax">\(X\)</span> 的 <span class="has-mathjax">\(容量=X \div 8(K)\)</span>。</p>
</li>
<li>
<p>使用16位地址模式时，8051的外部数据存储器寻址空间为64KB。</p>
</li>
<li>
<p>基于8051的单片机系统能拓展的外部数据存储器容量无限制。</p>
<p>有的书上说最大只能拓展64KB，实际是最大寻址空间为64KB，拓展的外部数据存储器容量无限制。</p>
</li>
<li>
<p>8051的程序存储器可用来存放用户程序和数据。 例如使用<code>DW</code>或<code>DB</code>指令用来定义数据。</p>
<p>所以应用程序也可以使用<code>MOVC A, @A+DPTR</code>访问程序存储器中的数据。</p>
</li>
</ol>
<blockquote>
<p>除此之外LED数码管和外部存储器拓展、外部I/O设备拓展部分有很多需要根据线路图计算地址和DA转换的题，因为线路图源自老师提供的PDF，而老师为PDF加了密码，意味着不允许外传，所以我就不放到博客上面了，<del>除非我自己用Porteus画一个类似的</del></p>
</blockquote>
<ol start="6">
<li>
<p>LED段码入口地址8004H，位选入口地址8002H，将30H-32H保存的6个压缩BCD码发送到6位共阴极数码管上显示， 要求编写延时1ms的子程序。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-asm" data-lang="asm"><span class="line"><span class="cl"><span class="nl">MAIN:</span>
</span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">R0</span><span class="p">,</span> <span class="c1">#30H
</span></span></span><span class="line"><span class="cl"><span class="c1">; R2用来计数
</span></span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">R2</span><span class="p">,</span> <span class="c1">#03H
</span></span></span><span class="line"><span class="cl"><span class="c1">; R3用来位选
</span></span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">R3</span><span class="p">,</span> <span class="c1">#01H
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nl">LOOP:</span>
</span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">A</span><span class="p">,</span> <span class="err">@</span><span class="no">R0</span>
</span></span><span class="line"><span class="cl"><span class="nf">SWAP</span> <span class="no">A</span>
</span></span><span class="line"><span class="cl"><span class="nf">ANL</span> <span class="no">A</span><span class="p">,</span> <span class="c1">#0FH
</span></span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">DPTR</span><span class="p">,</span> <span class="c1">#TAB
</span></span></span><span class="line"><span class="cl"><span class="nf">MOVC</span> <span class="no">A</span><span class="p">,</span> <span class="err">@</span><span class="no">A</span><span class="err">+</span><span class="no">DPTR</span>
</span></span><span class="line"><span class="cl"><span class="c1">; 取高4位BCD码对应的段码
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">DPTR</span><span class="p">,</span> <span class="c1">#8004H
</span></span></span><span class="line"><span class="cl"><span class="nf">MOVX</span> <span class="no">A</span><span class="p">,</span> <span class="err">@</span><span class="no">A</span><span class="err">+</span><span class="no">DPTR</span>
</span></span><span class="line"><span class="cl"><span class="c1">; 先送段码
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">A</span><span class="p">,</span> <span class="no">R3</span>
</span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">DPTR</span><span class="p">,</span> <span class="c1">#8002H
</span></span></span><span class="line"><span class="cl"><span class="nf">MOVX</span> <span class="err">@</span><span class="no">DPTR</span><span class="p">,</span> <span class="no">A</span>
</span></span><span class="line"><span class="cl"><span class="nf">RL</span> <span class="no">A</span>
</span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">R3</span><span class="p">,</span> <span class="no">A</span>
</span></span><span class="line"><span class="cl"><span class="c1">; 再送位选
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">LCALL</span> <span class="no">DELAY</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">A</span><span class="p">,</span> <span class="err">@</span><span class="no">R0</span>
</span></span><span class="line"><span class="cl"><span class="nf">ANL</span> <span class="no">A</span><span class="p">,</span> <span class="c1">#0FH
</span></span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">DPTR</span><span class="p">,</span> <span class="c1">#TAB
</span></span></span><span class="line"><span class="cl"><span class="nf">MOVC</span> <span class="no">A</span><span class="p">,</span> <span class="err">@</span><span class="no">A</span><span class="err">+</span><span class="no">DPTR</span>
</span></span><span class="line"><span class="cl"><span class="c1">; 取低4位BCD码对应的段码
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">DPTR</span><span class="p">,</span> <span class="c1">#8004H
</span></span></span><span class="line"><span class="cl"><span class="nf">MOVX</span> <span class="no">A</span><span class="p">,</span> <span class="err">@</span><span class="no">A</span><span class="err">+</span><span class="no">DPTR</span>
</span></span><span class="line"><span class="cl"><span class="c1">; 送段码
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">A</span><span class="p">,</span> <span class="no">R3</span>
</span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">DPTR</span><span class="p">,</span> <span class="c1">#8002H
</span></span></span><span class="line"><span class="cl"><span class="nf">MOVX</span> <span class="err">@</span><span class="no">DPTR</span><span class="p">,</span> <span class="no">A</span>
</span></span><span class="line"><span class="cl"><span class="nf">RL</span> <span class="no">A</span>
</span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">R3</span><span class="p">,</span> <span class="no">A</span>
</span></span><span class="line"><span class="cl"><span class="c1">; 送位选
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">LCALL</span> <span class="no">DELAY</span>
</span></span><span class="line"><span class="cl"><span class="nf">INC</span> <span class="no">R0</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">DJNZ</span> <span class="no">R2</span><span class="p">,</span> <span class="no">LOOP</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nl">DELAY:</span>
</span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">R7</span><span class="p">,</span> <span class="c1">#250
</span></span></span><span class="line"><span class="cl"><span class="nl">DL:</span>
</span></span><span class="line"><span class="cl"><span class="nf">NOP</span>
</span></span><span class="line"><span class="cl"><span class="nf">NOP</span>
</span></span><span class="line"><span class="cl"><span class="nf">DJNZ</span> <span class="no">R7</span><span class="p">,</span> <span class="no">DL</span>
</span></span><span class="line"><span class="cl"><span class="nf">RET</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nl">TAB:</span>
</span></span><span class="line"><span class="cl"><span class="nf">DB</span> <span class="mi">3</span><span class="no">FH</span><span class="p">,</span> <span class="mi">06</span><span class="no">H</span><span class="p">,</span> <span class="mi">5</span><span class="no">BH</span><span class="p">,</span> <span class="mi">4</span><span class="no">FH</span><span class="p">,</span> <span class="mi">66</span><span class="no">H</span><span class="p">,</span> <span class="mi">6</span><span class="no">DH</span><span class="p">,</span> <span class="mi">7</span><span class="no">DH</span><span class="p">,</span> <span class="mi">07</span><span class="no">H</span><span class="p">,</span> <span class="mi">7</span><span class="no">FH</span><span class="p">,</span> <span class="mi">6</span><span class="no">FH</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">END</span>
</span></span></code></pre></div></li>
<li>
<p>打印机数据输入接口和P1直接相连，STB接口和P3.4相连，BUSY接口和P3.3相连，不使用ACK应答信号，编写将外部存储器<code>1000H-100FH</code>的数据发送到打印机打印的子程序。</p>
<p><img loading="lazy" src="images/1.jpg#center" alt="" />

</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-asm" data-lang="asm"><span class="line"><span class="cl"><span class="nl">PRINT:</span>
</span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">DPTR</span><span class="p">,</span> <span class="c1">#1000H
</span></span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">R2</span><span class="p">,</span> <span class="c1">#10H
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nl">LOOP:</span>
</span></span><span class="line"><span class="cl"><span class="nf">JB</span> <span class="no">P3.3</span><span class="p">,</span> <span class="no">$</span>
</span></span><span class="line"><span class="cl"><span class="c1">; 确保打印机处于空闲状态
</span></span></span><span class="line"><span class="cl"><span class="nf">MOVX</span> <span class="no">A</span><span class="p">,</span> <span class="err">@</span><span class="no">DPTR</span>
</span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">P1</span><span class="p">,</span> <span class="no">A</span>
</span></span><span class="line"><span class="cl"><span class="nf">CLR</span> <span class="no">P3.4</span>
</span></span><span class="line"><span class="cl"><span class="nf">SETB</span> <span class="no">P3.4</span>
</span></span><span class="line"><span class="cl"><span class="nf">INC</span> <span class="no">DPTR</span>
</span></span><span class="line"><span class="cl"><span class="nf">DJNZ</span> <span class="no">R2</span><span class="p">,</span> <span class="no">LOOP</span>
</span></span><span class="line"><span class="cl"><span class="nf">RET</span>
</span></span></code></pre></div><p>如果打印机没有和P1直接相连，而是连接到数据锁存器上（74HC374），锁存器的时钟信号的入口地址为<code>A000H</code>。</p>
<p>使用堆栈保护间接寻址寄存器的值。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-asm" data-lang="asm"><span class="line"><span class="cl"><span class="nl">PRINT:</span>
</span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">R2</span><span class="p">,</span> <span class="c1">#10H
</span></span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">DPTR</span><span class="p">,</span> <span class="c1">#1000H
</span></span></span><span class="line"><span class="cl"><span class="nl">LOOP:</span>
</span></span><span class="line"><span class="cl"><span class="nf">JB</span> <span class="no">P3.3</span><span class="p">,</span> <span class="no">$</span>
</span></span><span class="line"><span class="cl"><span class="c1">; 确保打印机处于空闲状态
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">MOVX</span> <span class="no">A</span><span class="p">,</span> <span class="err">@</span><span class="no">DPTR</span>
</span></span><span class="line"><span class="cl"><span class="nf">PUSH</span> <span class="no">DPL</span>
</span></span><span class="line"><span class="cl"><span class="nf">PUSH</span> <span class="no">DPH</span>
</span></span><span class="line"><span class="cl"><span class="nf">MOV</span> <span class="no">DPTR</span><span class="p">,</span> <span class="c1">#0A000H
</span></span></span><span class="line"><span class="cl"><span class="nf">MOVX</span> <span class="err">@</span><span class="no">DPTR</span><span class="p">,</span> <span class="no">A</span>
</span></span><span class="line"><span class="cl"><span class="nf">POP</span> <span class="no">DPH</span>
</span></span><span class="line"><span class="cl"><span class="nf">POP</span> <span class="no">DPL</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">CLR</span> <span class="no">P3.4</span>
</span></span><span class="line"><span class="cl"><span class="nf">SETB</span> <span class="no">P3.4</span>
</span></span><span class="line"><span class="cl"><span class="nf">INC</span> <span class="no">DPTR</span>
</span></span><span class="line"><span class="cl"><span class="nf">DJNZ</span> <span class="no">R2</span><span class="p">,</span> <span class="no">LOOP</span>
</span></span><span class="line"><span class="cl"><span class="nf">RET</span>
</span></span></code></pre></div></li>
</ol>
<hr>
<h2 id="sfr列表">SFR列表</h2>
<table>
  <thead>
      <tr>
          <th style="text-align: center">87H</th>
          <th style="text-align: center">D7</th>
          <th style="text-align: center">D6</th>
          <th style="text-align: center">D5</th>
          <th style="text-align: center">D4</th>
          <th style="text-align: center">D3</th>
          <th style="text-align: center">D2</th>
          <th style="text-align: center">D1</th>
          <th style="text-align: center">D0</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: center">PCON</td>
          <td style="text-align: center">SMOD</td>
          <td style="text-align: center">-</td>
          <td style="text-align: center">-</td>
          <td style="text-align: center">-</td>
          <td style="text-align: center">-</td>
          <td style="text-align: center">-</td>
          <td style="text-align: center">PD</td>
          <td style="text-align: center">IDL</td>
      </tr>
  </tbody>
</table>
<table>
  <thead>
      <tr>
          <th style="text-align: center">88H</th>
          <th style="text-align: center">D7</th>
          <th style="text-align: center">D6</th>
          <th style="text-align: center">D5</th>
          <th style="text-align: center">D4</th>
          <th style="text-align: center">D3</th>
          <th style="text-align: center">D2</th>
          <th style="text-align: center">D1</th>
          <th style="text-align: center">D0</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: center">TCON</td>
          <td style="text-align: center">TF1</td>
          <td style="text-align: center">TR1</td>
          <td style="text-align: center">TF0</td>
          <td style="text-align: center">TR0</td>
          <td style="text-align: center">IE1</td>
          <td style="text-align: center">IT1</td>
          <td style="text-align: center">IE0</td>
          <td style="text-align: center">IT0</td>
      </tr>
  </tbody>
</table>
<table>
  <thead>
      <tr>
          <th style="text-align: center">89H</th>
          <th style="text-align: center">D7</th>
          <th style="text-align: center">D6</th>
          <th style="text-align: center">D5</th>
          <th style="text-align: center">D4</th>
          <th style="text-align: center">D3</th>
          <th style="text-align: center">D2</th>
          <th style="text-align: center">D1</th>
          <th style="text-align: center">D0</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: center">TMOD</td>
          <td style="text-align: center">GATE</td>
          <td style="text-align: center">C/T</td>
          <td style="text-align: center">M1</td>
          <td style="text-align: center">M0</td>
          <td style="text-align: center">GATE</td>
          <td style="text-align: center">C/T</td>
          <td style="text-align: center">M1</td>
          <td style="text-align: center">M0</td>
      </tr>
  </tbody>
</table>
<table>
  <thead>
      <tr>
          <th style="text-align: center">98H</th>
          <th style="text-align: center">D7</th>
          <th style="text-align: center">D6</th>
          <th style="text-align: center">D5</th>
          <th style="text-align: center">D4</th>
          <th style="text-align: center">D3</th>
          <th style="text-align: center">D2</th>
          <th style="text-align: center">D1</th>
          <th style="text-align: center">D0</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: center">SCON</td>
          <td style="text-align: center">SM0</td>
          <td style="text-align: center">SM1</td>
          <td style="text-align: center">SM2</td>
          <td style="text-align: center">REN</td>
          <td style="text-align: center">TB8</td>
          <td style="text-align: center">RB8</td>
          <td style="text-align: center">TI</td>
          <td style="text-align: center">RI</td>
      </tr>
  </tbody>
</table>
<table>
  <thead>
      <tr>
          <th style="text-align: center">A8H</th>
          <th style="text-align: center">D7</th>
          <th style="text-align: center">D6</th>
          <th style="text-align: center">D5</th>
          <th style="text-align: center">D4</th>
          <th style="text-align: center">D3</th>
          <th style="text-align: center">D2</th>
          <th style="text-align: center">D1</th>
          <th style="text-align: center">D0</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: center">IE</td>
          <td style="text-align: center">EA</td>
          <td style="text-align: center">-</td>
          <td style="text-align: center">-</td>
          <td style="text-align: center">ES</td>
          <td style="text-align: center">ET1</td>
          <td style="text-align: center">EX1</td>
          <td style="text-align: center">ET0</td>
          <td style="text-align: center">EX0</td>
      </tr>
  </tbody>
</table>
<table>
  <thead>
      <tr>
          <th style="text-align: center">B8H</th>
          <th style="text-align: center">D7</th>
          <th style="text-align: center">D6</th>
          <th style="text-align: center">D5</th>
          <th style="text-align: center">D4</th>
          <th style="text-align: center">D3</th>
          <th style="text-align: center">D2</th>
          <th style="text-align: center">D1</th>
          <th style="text-align: center">D0</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: center">IP</td>
          <td style="text-align: center">-</td>
          <td style="text-align: center">-</td>
          <td style="text-align: center">-</td>
          <td style="text-align: center">PS</td>
          <td style="text-align: center">PT1</td>
          <td style="text-align: center">PX1</td>
          <td style="text-align: center">PT0</td>
          <td style="text-align: center">PX0</td>
      </tr>
  </tbody>
</table>
<table>
  <thead>
      <tr>
          <th style="text-align: center">D0H</th>
          <th style="text-align: center">D7</th>
          <th style="text-align: center">D6</th>
          <th style="text-align: center">D5</th>
          <th style="text-align: center">D4</th>
          <th style="text-align: center">D3</th>
          <th style="text-align: center">D2</th>
          <th style="text-align: center">D1</th>
          <th style="text-align: center">D0</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: center">PSW</td>
          <td style="text-align: center">Cy</td>
          <td style="text-align: center">Ac</td>
          <td style="text-align: center">F0</td>
          <td style="text-align: center">RS1</td>
          <td style="text-align: center">RS0</td>
          <td style="text-align: center">OV</td>
          <td style="text-align: center">-</td>
          <td style="text-align: center">P</td>
      </tr>
  </tbody>
</table>
<hr>]]></content:encoded>
    </item>
    <item>
      <title>惠普光影精灵4在Arch Linux下使用Optimus Manager配置双显卡</title>
      <link>https://blog.starry-s.moe/posts/2021/archlinux-pavilion-gaming-laptop/</link>
      <pubDate>Sun, 19 Jul 2020 15:29:57 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2021/archlinux-pavilion-gaming-laptop/</guid>
      <description>&lt;p&gt;自从高中毕业至今这台笔记本用了将近两年, 已经记不清当初为什么买了这台笔记本, 刚买来电脑第一次装Arch Linux时遇到了一些坑, 不过大多数的问题Google折腾一会或随着后续的软件更新基本上就都解决了, 唯独配置双显卡这个问题在用了两年后才算是找到了比较满意的解决方法（大概是）, &lt;del&gt;尽管现在这电脑已经停产了, 就算有人买了这台电脑也不一定会拿他装Arch Linux, 不过我还是打算把这个问题的解决过程记录一下&lt;/del&gt;（本篇讲的方法应该是适用于大多数N卡+i卡的笔记本电脑的, 只是有些细节不一样）, 以备我后续重装系统时有个参考。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>自从高中毕业至今这台笔记本用了将近两年, 已经记不清当初为什么买了这台笔记本, 刚买来电脑第一次装Arch Linux时遇到了一些坑, 不过大多数的问题Google折腾一会或随着后续的软件更新基本上就都解决了, 唯独配置双显卡这个问题在用了两年后才算是找到了比较满意的解决方法（大概是）, <del>尽管现在这电脑已经停产了, 就算有人买了这台电脑也不一定会拿他装Arch Linux, 不过我还是打算把这个问题的解决过程记录一下</del>（本篇讲的方法应该是适用于大多数N卡+i卡的笔记本电脑的, 只是有些细节不一样）, 以备我后续重装系统时有个参考。</p>
<hr>
<h2 id="先描述一下踩坑经过">先描述一下踩坑经过</h2>
<blockquote>
<p>本段略微有些废话, 可以跳过</p>
</blockquote>
<p>记得在刚买来这台笔记本（2018年夏）, Linux内核还没升到5.0的时候, 使用live CD装系统时会遇到<code>lspci</code>卡死, 关机的时候会卡死的问题, 查系统日记都是一堆ACPI的报错。当时网上查了一下大概是内核和驱动一些bug, 没找到解决办法, Google到论坛的帖子说是在关机/重启发生卡死时只能直接长按电源关机就（找不到搜的回答了）</p>
<p>装完系统后安装显卡驱动时想通过Bumblebee + bbswitch切换双显卡, 于是装了Gnome然后照着wiki配置完Bumblebee和bbswitch后重启电脑直接死机。</p>
<p>经过多次重装系统的折磨后, 发现只装Bumblebee不装bbswitch不<code>systemctl enable bumblebeed.service</code>时, 能正常开机, 然后之前遇到的两个问题也莫名其妙就好了, 即系统重启关机不会卡死, <code>lspci</code>也正常了（迷）</p>
<p>之后, 在不装bbswitch的情况下, 启动<code>bumblebeed.service</code>再用<code>optirun</code>和<code>primusrun</code>这种方式用独显运行程序都没有问题。</p>
<p>当时因为电脑不装Bumblebee的话就没法正常关机, 于是就一直用着Bumblebee切换双显卡, 玩游戏性能比Windows下差一点, 别的都没啥问题。</p>
<p>今年年初买了一块拓展屏想搞双显示器, 本来显示器应该插上HDMI直接就能用的, 但是因为这电脑的HDMI走的独显输出, Bumblebee不能直接用, wiki上教的创建个intel的虚拟输出啥的方法有试过但是没成功, 于是又Google了一下后卸了Bumblebee改用<a href="https://wiki.archlinux.org/index.php/NVIDIA_Optimus#Use_NVIDIA_graphics_only">NVIDIA Optimus 只使用独显</a>的方式, 这样双显示器倒是能用了, 但是如果笔记本只用电池没连着拓展屏的时候还跑着独显这也太费电了。</p>
<p>所以最后找到了能切换显卡的<a href="https://github.com/Askannz/optimus-manager">Optimus Manager</a>。</p>
<blockquote>
<p>查了一下这款电脑的type-c接口支持DP1.2视频输出，和HDMI 2.0一样支持4K 60fps，走的是intel集成显卡，可以在独显不通电的时候输出画面到第三方显示器。所以买一根type-c转DP线就可以点亮第三方显示器（前提是你的显示器有DP接口），但是切换显卡还是得依靠Bunblebee或Optimus Manager这类的软件。</p>
</blockquote>
<h2 id="安装过程">安装过程</h2>
<p>照着<a href="https://wiki.archlinux.org/index.php/NVIDIA_Optimus">Wiki</a>和Optimus Manager的<a href="https://github.com/Askannz/optimus-manager#optimus-manager">README</a>。首先安装好显卡驱动相关的软件, 如果有Bumblebee的话使用<code>systemctl disable bumblebeed</code>停用。</p>
<p>首先清除（记得备份）<code>/etc/X11/xorg.conf.d/</code>下的配置文件, 并删掉（记得备份）<code>/etc/X11/xorg.conf</code>（如果有的话）, 因为Optimus Manager会自动生成配置文件存放到<code>/etc/X11/xorg.conf.d/</code>里面, 所以建议安装前把显示配置相关的文件都清除掉。</p>
<p>使用Arch Linux CN源或者通过AUR Helper安装<code>optimus-manager</code>。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl"># Arch Linux CN
</span></span><span class="line"><span class="cl">$ sudo pacman -S optimus-manager
</span></span><span class="line"><span class="cl"># AUR
</span></span><span class="line"><span class="cl">$ yay -S optimus-manager
</span></span></code></pre></div><p>因为我用的Gnome, 参照<a href="https://github.com/Askannz/optimus-manager#important--gnome-and-gdm-users">README中说的</a>卸载掉<code>gdm</code>并安装<code>gdm-prime</code><sup>AUR</sup>。（国内下载源代码的速度极慢建议挂梯子, 或者挂梯子克隆<a href="https://gitlab.gnome.org/GNOME/gdm">GDM的代码</a>到<code>~/.cache/yay/gdm-prime/gdm</code>下。）</p>
<p>修改<code>/etc/gdm/custom.conf</code>, 移除<code>WaylandEnable=false</code>一行前面的<code>#</code>禁用Wayland而使用X。</p>
<h3 id="修改配置文件">修改配置文件</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">$ sudo cp /usr/share/optimus-manager.conf /etc/optimus-manager/optimus-manager.conf
</span></span></code></pre></div><p>不要编辑<code>/usr/share/</code>下的文件, 编辑<code>/etc/optimus-manager/optimus-manager.conf</code>, 将切换方式设为<code>switching=none</code>, 不推荐使用bbswitch（见<a href="/posts/2021/archlinux-pavilion-gaming-laptop/#Others">后续第一条</a>）, 设置<code>pci_power_control=yes</code>让PCI Power Management切换显卡。</p>
<p>之后根据需求来修改开机自动选择显卡：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">startup_mode=auto
</span></span><span class="line"><span class="cl">startup_auto_battery_mode=intel
</span></span><span class="line"><span class="cl">startup_auto_extpower_mode=nvidia
</span></span></code></pre></div><p>这里我设置的是用电池时使用集成显卡, 用电源时使用独显。</p>
<p>最后贴一下全部的配置文件, 除了上述的几处修改以外其他均为默认值, 仅供参考。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="cl"><span class="p">[</span><span class="n">optimus</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># This parameter defines the method used to power switch the Nvidia card. See the documentation</span>
</span></span><span class="line"><span class="cl"><span class="c1"># for a complete description of what each value does. Possible values :</span>
</span></span><span class="line"><span class="cl"><span class="c1">#</span>
</span></span><span class="line"><span class="cl"><span class="c1"># - nouveau : load the nouveau module on the Nvidia card.</span>
</span></span><span class="line"><span class="cl"><span class="c1"># - bbswitch : power off the card using the bbswitch module (requires the bbswitch dependency).</span>
</span></span><span class="line"><span class="cl"><span class="c1"># - acpi_call : try various ACPI method calls to power the card on and off (requires the acpi_call dependency)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># - custom: use custom scripts at /etc/optimus-manager/nvidia-enable.sh and /etc/optimus-manager/nvidia-disable.sh</span>
</span></span><span class="line"><span class="cl"><span class="c1"># - none : do not use an external module for power management. For some laptop models it&#39;s preferable to</span>
</span></span><span class="line"><span class="cl"><span class="c1">#          use this option in combination with pci_power_control (see below).</span>
</span></span><span class="line"><span class="cl"><span class="n">switching</span><span class="o">=</span><span class="n">none</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Enable PCI power management in Intel mode.</span>
</span></span><span class="line"><span class="cl"><span class="c1"># This option is incompatible with acpi_call and bbswitch, so it will be ignored in those cases.</span>
</span></span><span class="line"><span class="cl"><span class="n">pci_power_control</span><span class="o">=</span><span class="n">yes</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Remove the Nvidia card from the PCI bus.</span>
</span></span><span class="line"><span class="cl"><span class="c1"># May prevent crashes caused by power switching.</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Ignored if switching=nouveau or switching=bbswitch.</span>
</span></span><span class="line"><span class="cl"><span class="n">pci_remove</span><span class="o">=</span><span class="n">yes</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Reset the Nvidia card at the PCI level before reloading the nvidia module.</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Ensures the card is in a fresh state before reloading the nvidia module.</span>
</span></span><span class="line"><span class="cl"><span class="c1"># May fix some switching issues. Possible values :</span>
</span></span><span class="line"><span class="cl"><span class="c1">#</span>
</span></span><span class="line"><span class="cl"><span class="c1"># - no : does not perform any reset</span>
</span></span><span class="line"><span class="cl"><span class="c1"># - function_level : perform a light &#34;function-level&#34; reset</span>
</span></span><span class="line"><span class="cl"><span class="c1"># - hot_reset : perform a &#34;hot reset&#34; of the PCI bridge. ATTENTION : this method messes with the hardware</span>
</span></span><span class="line"><span class="cl"><span class="c1">#         directly, please read the online documentation of optimus-manager before using it.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#         Also, it will perform a PCI remove even if pci_remove=no.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#</span>
</span></span><span class="line"><span class="cl"><span class="n">pci_reset</span><span class="o">=</span><span class="n">no</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Automatically log out the current desktop session when switching GPUs.</span>
</span></span><span class="line"><span class="cl"><span class="c1"># This feature is currently supported for the following DE/WM :</span>
</span></span><span class="line"><span class="cl"><span class="c1"># KDE Plasma, GNOME, XFCE, LXDE, Deepin, i3, Openbox, AwesomeWM, bspwm</span>
</span></span><span class="line"><span class="cl"><span class="c1"># If this option is disabled or you use a different desktop environment,</span>
</span></span><span class="line"><span class="cl"><span class="c1"># GPU switching only becomes effective at the next graphical session login.</span>
</span></span><span class="line"><span class="cl"><span class="n">auto_logout</span><span class="o">=</span><span class="n">yes</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># GPU mode to use at computer startup. Possible values: nvidia, intel, hybrid, auto</span>
</span></span><span class="line"><span class="cl"><span class="c1"># &#34;auto&#34; is a special mode that auto-detects if the computer is running on battery</span>
</span></span><span class="line"><span class="cl"><span class="c1"># and selects a proper GPU mode. See the other options below.</span>
</span></span><span class="line"><span class="cl"><span class="n">startup_mode</span><span class="o">=</span><span class="n">auto</span>
</span></span><span class="line"><span class="cl"><span class="c1"># GPU mode to select when startup_mode=auto and the computer is running on battery.</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Possible values: nvidia, intel, hybrid</span>
</span></span><span class="line"><span class="cl"><span class="n">startup_auto_battery_mode</span><span class="o">=</span><span class="n">intel</span>
</span></span><span class="line"><span class="cl"><span class="c1"># GPU mode to select when startup_mode=auto and the computer is running on external power.</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Possible values: nvidia, intel, hybrid</span>
</span></span><span class="line"><span class="cl"><span class="n">startup_auto_extpower_mode</span><span class="o">=</span><span class="n">nvidia</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="n">intel</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Driver to use for the Intel GPU. Possible values : modesetting, intel</span>
</span></span><span class="line"><span class="cl"><span class="c1"># To use the intel driver, you need to install the package &#34;xf86-video-intel&#34;.</span>
</span></span><span class="line"><span class="cl"><span class="n">driver</span><span class="o">=</span><span class="n">modesetting</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Acceleration method (corresponds to AccelMethod in the Xorg configuration).</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Only applies to the intel driver.</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Possible values : sna, xna, uxa</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Leave blank for the default (no option specified)</span>
</span></span><span class="line"><span class="cl"><span class="n">accel</span><span class="o">=</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Enable TearFree option in the Xorg configuration.</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Only applies to the intel driver.</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Possible values : yes, no</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Leave blank for the default (no option specified)</span>
</span></span><span class="line"><span class="cl"><span class="n">tearfree</span><span class="o">=</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># DRI version. Possible values : 2, 3</span>
</span></span><span class="line"><span class="cl"><span class="n">DRI</span><span class="o">=</span><span class="mi">3</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Whether or not to enable modesetting for the nouveau driver.</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Does not affect modesetting for the Intel GPU driver !</span>
</span></span><span class="line"><span class="cl"><span class="c1"># This option only matters if you use nouveau as the switching backend.</span>
</span></span><span class="line"><span class="cl"><span class="n">modeset</span><span class="o">=</span><span class="n">yes</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="n">nvidia</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Whether or not to enable modesetting. Required for PRIME Synchronization (which prevents tearing).</span>
</span></span><span class="line"><span class="cl"><span class="n">modeset</span><span class="o">=</span><span class="n">yes</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Whether or not to enable the NVreg_UsePageAttributeTable option in the Nvidia driver.</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Recommended, can cause poor CPU performance otherwise.</span>
</span></span><span class="line"><span class="cl"><span class="n">PAT</span><span class="o">=</span><span class="n">yes</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># DPI value. This will be set using the Xsetup script passed to your login manager.</span>
</span></span><span class="line"><span class="cl"><span class="c1"># It will run the command</span>
</span></span><span class="line"><span class="cl"><span class="c1"># xrandr --dpi &lt;DPI&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Leave blank for the default (the above command will not be run).</span>
</span></span><span class="line"><span class="cl"><span class="n">DPI</span><span class="o">=</span><span class="mi">96</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># If you&#39;re running an updated version of xorg-server (let&#39;s say to get PRIME Render offload enabled),</span>
</span></span><span class="line"><span class="cl"><span class="c1"># the nvidia driver may not load because of an ABI version mismatch. Setting this flag to &#34;yes&#34;</span>
</span></span><span class="line"><span class="cl"><span class="c1"># will allow the loading of the nvidia driver.</span>
</span></span><span class="line"><span class="cl"><span class="n">ignore_abi</span><span class="o">=</span><span class="n">no</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Set to yes if you want to use optimus-manager with external Nvidia GPUs (experimental)</span>
</span></span><span class="line"><span class="cl"><span class="n">allow_external_gpus</span><span class="o">=</span><span class="n">no</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Comma-separated list of Nvidia-specific options to apply.</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Available options :</span>
</span></span><span class="line"><span class="cl"><span class="c1"># - overclocking : enable CoolBits in the Xorg configuration, which unlocks clocking options</span>
</span></span><span class="line"><span class="cl"><span class="c1">#   in the Nvidia control panel. Note: does not work in hybrid mode.</span>
</span></span><span class="line"><span class="cl"><span class="c1"># - triple_buffer : enable triple buffering.</span>
</span></span><span class="line"><span class="cl"><span class="n">options</span><span class="o">=</span><span class="n">overclocking</span>
</span></span></code></pre></div><h2 id="食用方法">食用方法</h2>
<p><code>optimus-manager --switch nvidia</code>切换到独显（nvidia）</p>
<p><code>optimus-manager --switch intel</code>切换到集显（intel）</p>
<p>安装<code>mesa-demos</code>后, 使用<code>glxinfo | grep &quot;OpenGL renderer&quot;</code>查看当前正使用的显卡</p>
<p>其他用法参见<a href="https://github.com/Askannz/optimus-manager#usage">Usage</a>。</p>
<p>注意：</p>
<ul>
<li>
<p>切换显卡的过程中会自动注销登录, 所以记得<strong>保存并关掉电脑正在运行的程序</strong>。</p>
</li>
<li>
<p>你可以在配置文件中修改<code>auto_logout=false</code>禁止自动注销以手动注销切换显卡。</p>
</li>
</ul>
<h2 id="others">Others</h2>
<ul>
<li>
<p>之所以不推荐使用<code>bbswitch</code>是因为容易遇到<strong>ACPI锁死</strong>的问题, <a href="https://wiki.archlinux.org/index.php/NVIDIA_Optimus#Lockup_issue_%28lspci_hangs%29">参考Wiki</a>, 需要添加<a href="https://wiki.archlinux.org/index.php/Kernel_parameters">内核参数</a><code>acpi_osi=! acpi_osi=&quot;Windows 2009&quot;</code>或<code>acpi_osi=&quot;!Windows 2015&quot;</code>启动, 如果你遇到了锁死可以通过开机时在<a href="https://wiki.archlinux.org/index.php/Arch_boot_process#Boot_loader">启动加载器界面</a>编辑添加内核参数来正常进入系统, 如果你用的是efistub或者没办法编辑内核参数的话就只能用live CD救你的电脑了。</p>
</li>
<li>
<p>如果用不了<code>lspci</code>, 电脑没法正常关机的话, 是nouveau的问题, 可添加内核参数<code>modprobe.blacklist=nouveau</code>禁用。</p>
</li>
<li>
<p>因为前几天改配置文件时又踩了一遍锁死的坑, 于是用最新的(2020.07.01)live CD救砖时, 惊喜的发现在live环境下<code>lspci</code>和关机都不会卡死了, 貌似是新版内核修复了这个bug</p>
</li>
<li>
<p>在切换显卡自动注销后, gdm界面有时不会自动加载出来而是一直黑屏, 这时需要手动切换到tty2再切回tty1才能加载出来。</p>
</li>
<li>
<p>如果显示器支持<a href="https://en.wikipedia.org/wiki/Display_Data_Channel#DDC.2FCI">DDC/DI</a>，可以参考<a href="https://wiki.archlinux.org/index.php/Backlight#External_monitors">Wiki</a>使用命令调节显示器亮度。</p>
</li>
<li>
<p>(本条与配置显卡无关) 因为电脑用的intel网卡, 如果遇到蓝牙耳机无法连接的情况, 安装<code>pulseaudio</code>和<code>pulseaudio-modues-bt</code>等耳机需要的蓝牙组件, 照着Wiki上的<a href="https://wiki.archlinux.org/index.php/Wireless_network_configuration#Bluetooth_coexistence">禁用Bluetooth coexistence</a>解决此问题。</p>
</li>
</ul>
<hr>
<p><img loading="lazy" src="images/1.png" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">neofetch</p>
</p>]]></content:encoded>
    </item>
    <item>
      <title>近期日常：2019.6-2019.8</title>
      <link>https://blog.starry-s.moe/posts/2019/shanghai-2/</link>
      <pubDate>Fri, 02 Aug 2019 23:49:39 +0000</pubDate>
      <guid>https://blog.starry-s.moe/posts/2019/shanghai-2/</guid>
      <description>&lt;p&gt;好久没有更新博客了，大一下半年一直在忙各种考试，除此之外还有一堆乱七八糟的事情要忙，更新博客的事就只好一点点往后拖了，于是拖更到了8月，后决定不继续拖更但实在没什么拿得出手的技术类文章可写，正好最近还遇到许多有趣的事情，就整理成日常写在这里吧。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>好久没有更新博客了，大一下半年一直在忙各种考试，除此之外还有一堆乱七八糟的事情要忙，更新博客的事就只好一点点往后拖了，于是拖更到了8月，后决定不继续拖更但实在没什么拿得出手的技术类文章可写，正好最近还遇到许多有趣的事情，就整理成日常写在这里吧。</p>
<meting-js server="netease" type="song" id="493911" theme="#233333"></meting-js>
<hr>
<p><img loading="lazy" src="images/dianche-1.jpg" alt="有轨电车——沈阳南站站" />
<p style="margin-bottom: -0.8em;" class="image-title">有轨电车——沈阳南站站</p>
</p>
<blockquote>
<p>现在是00:00， 很适合吹空调听音乐写博客，毕竟白天睡了一天了。</p>
</blockquote>
<p>首先是6月末去了沈阳META·TRON（梅塔·特隆）电音节，听说这是东北第一次举办的电音节，从AlanWalker入坑的电音，但Meta没请Alan，不过得知有百大DJ前十的DonDiablo（大！菠！萝！）遂非常激动觉得机会难得于是毫不犹豫的就把票买了。</p>
<p>本来临近期末了除了高数和英语和程序这几科没结课就没有别的课了，但实际上因为要准备高数省统考某组织多加了几节高数课搞得每天都要上两三大节的高数，在得知考试提前到6月29号后某部门下通知要求周末不许离校必须参加全天的自习和新增的高数课和高数模拟考试（草），于是我差点成为了一个花700多块钱打水漂的带傻子。</p>
<p>于是6月23号下午考完高数模拟考试就直奔地铁站，临时买了一张沈阳北站到沈阳南站的高铁票，逃掉了晚自习，踏上了追(露)逐(宿)梦(街)想(头)的追(流)梦(浪)之旅。</p>
<p>买高铁票时只剩一张商务座，于是用29元超低价格体验了一次12分钟的商务舱之旅，还享有了商务舱专属候车区的服务，实在没敢问有没有免费咖啡因为怕被骂不要脸（逃</p>
<p>检票时乘务员阿姨像提醒小朋友一样严肃的提醒我下一站就下车，没有返程，要是不下车就坐到大连了哈哈哈哈哈哈（噗</p>
<p>没通地铁于是坐高铁去苏家屯&hellip;然后从沈阳南站一路狂奔到有鬼电车一号线的站台，等了将近20分钟才等到车（班次真少），14:00就开始的电音节我20:10才迟到赶到苏家屯的会展中心&hellip;&hellip;</p>
<p>结果完美错过了回沈北的大巴车，于是真要露宿街头了。</p>
<p>实际是打车到了沈阳站太原街附近找个旅店住了一晚上第二天早起坐地铁然后换乘共享单车（迷）才赶上了第一节的高数课。</p>
<hr>
<p><img loading="lazy" src="images/westlake.jpg" alt="WestLake" />
<p style="margin-bottom: -0.8em;" class="image-title">WestLake</p>
</p>
<p>暑假二刷上海，二刷了迪士冶和地铁二号线、地铁四号线、10号线、11号线、16号线和磁悬浮等景点。看了梦寐以求的BiliBili Macro Link，说实话BML的现场和看直播/回放的效果真的不一样，反正·<strong>爱死这破站</strong> 就完了。</p>
<p>总之就是以后绝对不会这么热的天去迪士冶了，要出人命的。</p>
<p>不过说回来还是喜欢一个人玩，人多了就是特别累，尤其是夏天这个要命的季节。</p>
<hr>
<p>最后附上几张在杭州小虫草堂拍的照片，当时为了去那个地方愣是顶着大太阳坐了两个多小时公交车。</p>
<p>当时比较匆忙外加上好久没有用相机了一直都在用手机拍照所以拍的不是很好，光圈太大了对焦有些不准，凑和着看一下吧。</p>
<p><img loading="lazy" src="images/IMG_4604.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">瓶子草</p>
</p>
<p><img loading="lazy" src="images/IMG_4605.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">捕蝇草</p>
</p>
<p><img loading="lazy" src="images/IMG_4641.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">猪笼草</p>
</p>
<p><img loading="lazy" src="images/IMG_4658.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">猪笼草</p>
</p>]]></content:encoded>
    </item>
    <item>
      <title>Spring - 3</title>
      <link>https://blog.starry-s.moe/posts/2019/spring-3/</link>
      <pubDate>Fri, 03 May 2019 23:20:43 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2019/spring-3/</guid>
      <description></description>
      <content:encoded><![CDATA[<meting-js server="netease" type="song" id="1322356616" theme="#233333"></meting-js>
<hr>
<p><img loading="lazy" src="images/IMG_4238-1.JPG" alt="" />

</p>
<p><img loading="lazy" src="images/IMG_4150-1.JPG" alt="" />

</p>
<p><img loading="lazy" src="images/IMG_4204-1.JPG" alt="" />

</p>
<p><img loading="lazy" src="images/IMG_4245-1.JPG" alt="" />

</p>
<p><img loading="lazy" src="images/IMG_4242-1.JPG" alt="" />

</p>
<p><img loading="lazy" src="images/IMG_4216-1.JPG" alt="" />

</p>]]></content:encoded>
    </item>
    <item>
      <title>一个人的旅行 —— 上海</title>
      <link>https://blog.starry-s.moe/posts/2019/shanghai/</link>
      <pubDate>Tue, 09 Apr 2019 13:24:44 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2019/shanghai/</guid>
      <description>&lt;p&gt;不要问我为什么过了这么久才把这篇文章码出来…&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>不要问我为什么过了这么久才把这篇文章码出来…</p>
<p><img loading="lazy" src="images/shanghai.jpg" alt="" />

</p>
<hr>
<p><img loading="lazy" src="images/IMG_4138-1.jpg" alt="" />

</p>
<meting-js server="netease" type="song" id="1322354965" theme="#233333"></meting-js>
<p>很久以前就有想去上海的念头了。</p>
<p>最开始是打算在下一次BW举办的时候去上海的，不过想了想要是一整个寒假哪里都没去实在是不甘心，遂定机票简单的制定了计划和预算，开始了这趟旅行。</p>
<p>准确点说不全是我一个人的旅行，只有去除掉和朋友一同坐了一趟飞机和磁悬浮（火车就不算了）后就剩下的就是一人的旅行了。</p>
<p>上飞机前的晚上，我硬是没买卧铺票在火车的硬座上坐着熬了一晚上…全靠看Fate Zero熬过来的…</p>
<p>之所以来上海是因为我很想看一下这么一座拥有B站总部和巨多B站大佬的城市是什么样子的，某些东西可能在大多数人眼里很平淡普通吧，但是我觉得很好玩。所以去迪士尼玩、登上海之巅、到野生动物园近距离看大老虎（fǔ）吃肉这些只能被我称作是本次旅行的附赠品，我当然不是只奔着这些去上海的，但别人问你你花了那么多钱出去旅游了一趟什么也没玩的话肯定会骂我的对吧。毕竟门票不贵买了也不吃亏。</p>
<hr>
<p><img loading="lazy" src="images/IMG_3617-1.jpg" alt="龙阳路" />
<p style="margin-bottom: -0.8em;" class="image-title">龙阳路</p>
</p>
<meting-js server="netease" type="song" id="409931671" theme="#233333"></meting-js>
<p>没急着用Metro大都会手机刷码捶（雾）地铁是因为我更想试一下Apple Pay是怎么操作的（所以这也是我没有办为游客准备的地铁卡的原因），现在觉得我花了60块钱充Apple Pay真的是交智商税，无数次的被卡在炸鸡口出不去有一次手机出bug双击电源键弹不出钱包界面后面排了很长的队伍是怎样的感受可想而知。</p>
<p>在此之前只坐过北京地铁和沈阳地铁，在地铁上站两个小时是什么感受只在上海体验过…</p>
<p>墙裂推荐看一下<a href="https://www.bilibili.com/video/av2625588">上海轨交的鬼畜（雾）</a>，不然第一次坐地铁很容易迷路的（谜一样的逻辑）。</p>
<p>第一次自己外出，或多或少会有失误的地方，除了像算错火车票时间退票改票多花点手续费之外，旅店选在了嘉定区着实麻烦了许多…</p>
<p>因为当时我只知道市区的旅店贵，又不想住青年旅舍，就选个不在市中心但是离地铁站不太远的地方（事实上需要走三十分钟）…</p>
<p>到后来才知道我住的地方离江苏好近啊，要是坐11号线支线就江苏花桥了，简直…</p>
<p><img loading="lazy" src="images/IMG_3259-1.jpg" alt="猫！猫！猫！快看！！！" />
<p style="margin-bottom: -0.8em;" class="image-title">cat</p>
</p>
<p>不过旅店的窗子对面有猫！！！每天早晨拉开窗帘看到对面房顶有猫在晒太阳的感觉真的开心死了！</p>
<hr>
<p><img loading="lazy" src="images/IMG_3266-1.jpg" alt="嘉定西站" />
<p style="margin-bottom: -0.8em;" class="image-title">嘉定西站</p>
</p>
<hr>
<p><img loading="lazy" src="images/5.jpg" alt="迪士尼" />
<p style="margin-bottom: -0.8em;" class="image-title">迪士尼</p>
</p>
<meting-js server="netease" type="song" id="468490569" theme="#233333"></meting-js>
<p>所以最漫长的就是坐11号线花了很长的时间才能从上海的嘉定到上海浦东的迪士尼…</p>
<p>一个人去迪士尼的好处就是排队排到最后遇到需要一人凑数的时候我就能合法插队了啊好高兴啊当时（后来我才知道有些地方我一个人是可以直接走单人通道根本不用排队…）</p>
<p><img loading="lazy" src="images/IMG_3382-1.jpg" alt="玩具总动员" />
<p style="margin-bottom: -0.8em;" class="image-title">玩具总动员</p>
</p>
<p>和一个小姐姐一起坐的创极速光轮，小姐姐被吓够呛，我全程没敢睁眼睛死死抓住把手猫在最后一排…</p>
<p>在B站上看到有人用MC还原了迪士尼的创极速光轮：<a href="https://www.bilibili.com/video/av13333369">av13333369</a></p>
<p>迪士尼的吃的真的香！就算是80多一根的大鸡腿还是有人排很长的队伍去买，就是真香，花了150买了一些吃的。</p>
<p>排的最长的队是七个小矮人矿车，足足站了70分钟。</p>
<p>除了泡泡龙冲天赛车我没敢坐，别的知名项目基本上都玩了一遍，但很可惜只玩了一遍。</p>
<p><img loading="lazy" src="images/IMG_3536-1.jpg" alt="" />

</p>
<p>知道晚上有烟花的时候就决定了这一天待在迪士尼里不走了。</p>
<p>夜光幻影秀真的是那种能看哭的震撼。</p>
<p>印象最深的就是20：30幻影秀结束后好多人站在那里不想走，还有好多人在拍照。</p>
<hr>
<p><img loading="lazy" src="images/IMG_3388-1.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">迪士尼</p>
</p>
<p>机智的我选择站在地铁门口等下一班车在屏蔽门打开后跑进去抢一个座，说着看起来很不要脸的行为，但是我的腿真的站不动了…</p>
<p><del>如果是两个人一起去玩的话会更有意思么？</del></p>
<hr>
<p><img loading="lazy" src="images/IMG_3610-1.jpg" alt="" />

</p>
<p>上海之巅的风景很好，我去的那天不是很晴，远处的建筑看起来有些朦胧，不过不影响观景。</p>
<p>VR体验区放着AlanWalker的Routine，听着自己喜欢的歌逛观光厅，很有意思的。</p>
<p><img loading="lazy" src="images/IMG_3600-1.jpg" alt="" />

</p>
<p>陆家嘴附近的吃的是真贵，饿着肚子跑到别的地方吃的午饭&hellip;</p>
<hr>
<p><img loading="lazy" src="images/IMG_3906-1.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">野生动物园站</p>
</p>
<p>下午坐地铁去野生动物园。</p>
<p>又回到了龙阳路地铁站。</p>
<p>16号线分大站车小站车，大站车不停野生动物园站，当时差一点上错车。</p>
<p>之前在知乎上见过有人吐槽16号线车厢内座椅设计的，等坐上车后才感受到这是真的挤…</p>
<p>在16号线上查了英语四级的成绩，压线过，成绩不高不是很满意，不过想了想考试时自己在生病住院，算作是自己为自己找的一个借口吧。</p>
<p>野生动物园站离动物园门口炒鸡远，走着去太耽误时间了。</p>
<p><img loading="lazy" src="images/IMG_3646-1.jpg" alt="" />

</p>
<p>等走到动物园的时候已经下午2点多了，开电瓶车的司机总在提醒他们马上就下班了让我们赶紧逛剩下的景点这个很烦，所以最后我又走着在园区溜达了一遍。</p>
<p>基本上把所有的动物都看了一边，表示我已经见过这些动物了，在公园里溜达一大圈。</p>
<p>等逛完一大圈之后动物园里已经没几个人了，</p>
<p><img loading="lazy" src="images/IMG_3824-1.jpg" alt="" />

</p>
<p>站在桥上看下面的天鹅，附近的音响在放AlanWalker的remix过的Faded，真的我很喜欢这首歌。</p>
<p>最后花五块钱买包饲料喂天鹅，又买点玉米喂鸽子…</p>
<p><img loading="lazy" src="images/IMG_3875-1.jpg" alt="" />

</p>
<hr>
<p><img loading="lazy" src="images/IMG_3987-1.jpg" alt="" />

</p>
<p>晚上开始下雨，坐地铁回陆家嘴去了东方明珠，由于下雨玻璃上有很多水珠所以最初隔着窗子拍的照片都有些模糊。</p>
<p>东方明珠还没有学生票…</p>
<p>最开始觉得好亏，花了好多钱还拍不了照片，直到后来我到了下面的空中走廊&hellip;</p>
<p><del>在迪士尼和别人撞衫就算了，第二天在东方明珠又撞衫，还是同一个人，啊世界真小。</del></p>
<p>在东方明珠上待了很久，参观了一些其他项目，坐了一把VR过山车（正好赶上最后一趟），很晚才回到旅店。</p>
<p><img loading="lazy" src="images/IMG_4071-1.jpg" alt="" />

</p>
<hr>
<p><img loading="lazy" src="images/4.jpg" alt="bilibili" />

</p>
<p>第三天是先坐11号线到江苏路换二号线然后换四号线然后换十号线…</p>
<p><del>欢迎您乘坐轨道交通四号线，本线环线本线环线本线环线本线环线哈哈哈哈哈哈哈哈哈哈哈哈……</del></p>
<p>傻乎乎的骑共享单车跑去B站总部逛了一大圈，问了一下前台小姐姐只能参观一楼大厅后去了咖啡厅花了50块钱买一杯小电视咖啡（忘记叫什么名字了）后含泪把它喝掉…</p>
<p><img loading="lazy" src="images/3.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">Costa Coffee</p>
</p>
<p>太好吃了呜呜呜呜呜…</p>
<p>还真不是Bilibili会员购骗死肥宅钱…</p>
<p>所以我什么都没有买。</p>
<p><img loading="lazy" src="images/2.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">武康大楼</p>
</p>
<p>下午开始下雨，懒得拿相机，也没怎么用手机拍照，就顺着地铁线路依次去了趟同济，交大，顺路去了武康大楼，参观了宋庆龄故居。最后去了师范大，没有去复旦因为时间不够了。</p>
<p>逛那些校园的时候只是打着伞进去走了一圈代表哦我已经来过这里了，一张照片都没有拍。</p>
<p>去同济时正赶上开学，去看了一眼樱花树还没有开，就匆匆走了。</p>
<p>只不过是多抽出一些时间让自己走在街上闲逛罢了…</p>
<p>好多时间都是在地铁上度过的。</p>
<p><img loading="lazy" src="images/IMG_4125-1.jpg" alt="" />
<p style="margin-bottom: -0.8em;" class="image-title">城隍庙</p>
</p>
<p>想了想明天就要坐飞机回家了所以内心很是不舍晚上又坐地铁去了城隍庙（豫园），吃了豫园卖的特别难吃的小吃后（终于明白为什么上海本地人几乎不去豫园这个地方了）回旅店早早睡觉为明天早起做准备…</p>
<p>因为起床晚了为了赶时间坐的磁悬浮去的普通国际姬场（雾），没有坐二号线广兰路到浦东国际机场的延线有些可惜。</p>
<p><img loading="lazy" src="images/6.jpg" alt="" />

</p>
<p>飞机即将降落时在城市上空绕了好几圈，体验了好几次失重的感觉（太好玩了我还要玩 #啪）</p>]]></content:encoded>
    </item>
    <item>
      <title>小米路由器3G折腾之刷OpenWrt记录</title>
      <link>https://blog.starry-s.moe/posts/2019/xiaomi_r3g_openwrt/</link>
      <pubDate>Tue, 19 Feb 2019 00:49:20 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2019/xiaomi_r3g_openwrt/</guid>
      <description>&lt;p&gt;生命不息, 折腾不止&amp;hellip;&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>生命不息, 折腾不止&hellip;</p>
<hr>
<meting-js server="netease" type="song" id="22802176" theme="#233333"></meting-js>
<blockquote>
<p>本篇由于创作时间过于久远，部分下载链接可能失效，因长期未更新，教程仅供参考。</p>
</blockquote>
<h2 id="准备工作">准备工作</h2>
<p>你至少需要以下工具：</p>
<ul>
<li>路由器本体</li>
<li>网线</li>
<li>一个已格式化为FAT/FAT32格式的U盘, 用来刷开发者固件和ssh激活工具</li>
<li>一根怼<code>Reset</code>钮的针</li>
<li>Windows用户需要一个SSH软件（例如：<a href="https://putty.org">putty</a>）</li>
<li><a href="http://www1.miwifi.com/miwifi_download.html">小米路由器客户端</a>, 用来绑定你的小米账号</li>
</ul>
<h2 id="ssh到路由器">SSH到路由器</h2>
<blockquote>
<p>路由器重启后指示灯会变为蓝色, 若变为红色则为刷机失败。</p>
</blockquote>
<p>安装开发者固件并开启SSH权限：</p>
<ol>
<li>
<p>在<a href="http://www1.miwifi.com/miwifi_download.html">MiWiFi下载页面</a>下载所需要的路由器开发者固件（ROM -&gt; ROM for R3G开发版）,命名为<code>miwifi.bin</code>。</p>
</li>
<li>
<p>路由器断电, 将下载好的开发者固件放入U盘插入路由器USB接口, 捅住reset扭接上电源后待指示灯为黄色闪烁时松开, 数分钟后路由器会自动重启, 此过程不要乱动路由器。</p>
</li>
<li>
<p>小米路由器客户端登陆小米账号绑定路由器设备, 此过程需要路由器联网。</p>
</li>
<li>
<p><a href="http://www1.miwifi.com/miwifi_open.html">MiWiFi开放平台</a>登陆小米账号下载ssh激活工具命名为<code>miwifi_ssh.bin</code>, 记下root密码。</p>
</li>
<li>
<p>操作方式同第二步骤, 刷入ssh激活工具。</p>
</li>
</ol>
<p>SSH到路由器:</p>
<ul>
<li>
<p>Windows系统用putty, ip为<code>191.168.31.1</code>, 用户名：<code>root</code>, 密码为下载激活ssh工具时记下的密码。</p>
</li>
<li>
<p>Unix/Linux系统终端执行：<code>ssh root@191.168.31.1</code></p>
<p>如果在ssh到路由器时遇到no matching key exchange method found错误，编辑<code>~/.ssh/config</code>, 加入下面两行：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">Host *
</span></span><span class="line"><span class="cl">    KexAlgorithms +diffie-hellman-group1-sha1
</span></span></code></pre></div></li>
</ul>
<h2 id="刷入bootloader推荐可选">刷入Bootloader（推荐/可选）</h2>
<blockquote>
<p>该步骤可选是因为Breed不支持直接刷入Openwrt固件, 可参考<a href="https://www.right.com.cn/forum/forum.php?mod=viewthread&amp;tid=267455">这篇帖子</a>, 不过为了防止变砖, 还是推荐刷Breed。</p>
</blockquote>
<p>Breed原作者为hackpascal, <a href="https://breed.hackpascal.net/">此处为下载地址</a>, 文件名为<code>breed-mt7621-xiaomi-r3g.bin</code></p>
<p>第一种方法是ssh到路由器后通过<code>wget</code>下载breed文件再刷入（需要确保路由器联网）, 下载地址为https所以需要加上<code>--no-check-certificate</code>参数。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="cl"><span class="n">cd</span> <span class="o">/</span><span class="n">tmp</span>
</span></span><span class="line"><span class="cl"><span class="n">wget</span> <span class="o">--</span><span class="n">no</span><span class="o">-</span><span class="n">check</span><span class="o">-</span><span class="n">certificate</span> <span class="n">https</span><span class="p">:</span><span class="o">//</span><span class="n">breed</span><span class="o">.</span><span class="n">hackpascal</span><span class="o">.</span><span class="n">net</span><span class="o">/</span><span class="n">breed</span><span class="o">-</span><span class="n">mt7621</span><span class="o">-</span><span class="n">xiaomi</span><span class="o">-</span><span class="n">r3g</span><span class="o">.</span><span class="n">bin</span> <span class="o">-</span><span class="n">O</span> <span class="n">breed</span><span class="o">.</span><span class="n">bin</span>
</span></span><span class="line"><span class="cl"><span class="n">mtd</span> <span class="o">-</span><span class="n">r</span> <span class="n">write</span> <span class="n">breed</span><span class="o">.</span><span class="n">bin</span> <span class="n">Bootloader</span>
</span></span></code></pre></div><p>另一种方法是将breed通过U盘拷贝到路由器再刷入。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="cl"><span class="n">mkdir</span> <span class="o">/</span><span class="n">tmp</span><span class="o">/</span><span class="n">sdcard</span>
</span></span><span class="line"><span class="cl"><span class="n">mount</span> <span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">sda1</span> <span class="o">/</span><span class="n">tmp</span><span class="o">/</span><span class="n">sdcard</span>
</span></span><span class="line"><span class="cl"><span class="n">mtd</span> <span class="o">-</span><span class="n">r</span> <span class="n">write</span> <span class="o">/</span><span class="n">tmp</span><span class="o">/</span><span class="n">sdcard</span><span class="o">/</span><span class="n">breed</span><span class="o">.</span><span class="n">bin</span> <span class="n">Bootloader</span>
</span></span></code></pre></div><p>路由器刷写完毕后会自动重启, <strong>在写入Breed的过程中不要动路由器</strong>。</p>
<h2 id="刷机">刷机</h2>
<p>下载所需固件：</p>
<ul>
<li>
<p>tuna镜像站的OpenWrt下载地址：<a href="https://mirrors.tuna.tsinghua.edu.cn/lede/releases/">https://mirrors.tuna.tsinghua.edu.cn/lede/releases/</a></p>
</li>
<li>
<p>官方下载地址: <a href="https://downloads.openwrt.org/releases/">https://downloads.openwrt.org/releases/</a></p>
</li>
</ul>
<h3 id="使用breed的刷机方法">使用Breed的刷机方法</h3>
<p>按照hackpascal的说法是：</p>
<blockquote>
<p>如果kernel0存在kernel1不存在, 那么启动kernel0
如果kernel1存在kernel0不存在, 那么启动kernel1
如果kernel0和kernel1都存在, 那么检查环境变量 <code>xiaomi.r3g.bootfw</code> 的值, 如果存在且值为 2, 那么启动kernel1, 否则启动kernel0</p>
</blockquote>
<p>简单来说就是：路由器有两个内核, 需要在Breed里设置环境变量让路由器启动kernel1。</p>
<ol>
<li>刷入Openwrt固件到Kernel1</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">mtd write openwrt-18.06.2-ramips-mt7621-mir3g-squashfs-kernel1.bin kernel1
</span></span><span class="line"><span class="cl">mtd write openwrt-18.06.2-ramips-mt7621-mir3g-squashfs-rootfs0.bin rootfs0
</span></span></code></pre></div><ol start="2">
<li>
<p>路由器断电, 捅住reset按钮后通电, 待指示灯变为蓝色闪烁后用网线连接路由器到电脑, 浏览器打开网址<code>192.168.1.1</code>, 进入Breed界面。</p>
</li>
<li>
<p>在环境变量编辑里添加<code>xiaomi.r3g.bootfw</code>字段, 值为<code>2</code>,保存后重启即可进入Openwrt。</p>
</li>
</ol>
<h3 id="没有刷入breed的刷机方法">没有刷入Breed的刷机方法</h3>
<blockquote>
<p><a href="https://openwrt.org/toh/xiaomi/mir3g">OpenWrt官网提供的教程</a>是在没有刷入Breed的情况下刷入OpenWrt固件的。</p>
</blockquote>
<p>ssh到路由器, 导入固件后刷机。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">mtd write openwrt-18.06.2-ramips-mt7621-mir3g-squashfs-kernel1.bin kernel1
</span></span><span class="line"><span class="cl">mtd write openwrt-18.06.2-ramips-mt7621-mir3g-squashfs-rootfs0.bin rootfs0
</span></span><span class="line"><span class="cl">nvram set flag_try_sys1_failed=1
</span></span><span class="line"><span class="cl">nvram commit
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">reboot
</span></span></code></pre></div><hr>
<h2 id="others">Others</h2>
<ol>
<li>
<p>如果刷了Breed + Pandavan/PandoraBox后想换回OpenWrt的话, 首先在Breed中刷回小米路由器开发版的官方固件, 然后SSH到路由器按照上述的使用Breed的刷机方法再刷机。</p>
</li>
<li>
<p>USB3.0会对路由器的2.4G频段信号造成干扰。</p>
</li>
<li>
<p>OpenWrt默认语言为英文, 可安装<code>luci-i18n-base-zh-cn</code>,<code>luci-i18n-base-zh-tw</code>安装简体/繁体中文。</p>
</li>
<li>
<p><a href="http://openwrt-dist.sourceforge.net">OpenWrt-dist</a>可拓展更多功能。</p>
</li>
<li>
<p>为优化软件包安装速度, 可将opkg源改为国内：</p>
</li>
</ol>
<p>LuCI -&gt; System -&gt; Software -&gt; Configuration 中将 Distribution feeds 的<code>http://downloads.openwrt.org</code>替换为<code>http://mirrors.tuna.tsinghua.edu.cn/lede</code>。</p>
<ol start="6">
<li>OpenWrt可安装软件包<code>libustream-openssl</code> <code>libustream-mbedtls</code>解决<code>wget</code>无法访问https服务器问题。</li>
</ol>
<p>然后建议把opkg源能改为https的都改为https。</p>]]></content:encoded>
    </item>
    <item>
      <title>Automata</title>
      <link>https://blog.starry-s.moe/posts/2019/nier-automata/</link>
      <pubDate>Sun, 17 Feb 2019 15:34:50 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2019/nier-automata/</guid>
      <description>&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;images/20190216233002_1.jpg&#34; alt=&#34;&#34; /&gt;

&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><img loading="lazy" src="images/20190216233002_1.jpg" alt="" />

</p>
<blockquote>
<p>Everything that lives is designed to end.</p>
<p>We are perpetually trapped</p>
<p>in a never-ending spiral of life and death.</p>
<p>Is this a curse?</p>
<p>Or some kind of punishment?</p>
<p>I often think about the God who blessed us with this cryptic puzzle</p>
<p>and wonder if we&rsquo;ll ever have the chance to kill him.</p>
</blockquote>
<meting-js server="netease" type="song" id="468490595" theme="#233333"></meting-js>
<p><img loading="lazy" src="images/20190214220213_1.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/20190217002539_1.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/20190214223059_1.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/20190217002156_1.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/20190208180203_1.jpg" alt="" />

</p>]]></content:encoded>
    </item>
    <item>
      <title>Lifeline</title>
      <link>https://blog.starry-s.moe/posts/2018/life-in-sau/</link>
      <pubDate>Mon, 01 Oct 2018 22:25:49 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2018/life-in-sau/</guid>
      <description>&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;images/trip.jpg&#34; alt=&#34;Trip&#34; /&gt;

&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><img loading="lazy" src="images/trip.jpg" alt="Trip" />

</p>
<meting-js server="netease" type="song" id="467164552" theme="#233333"></meting-js>
<hr>
<p><img loading="lazy" src="images/trip2.jpg" alt="Trip" />

</p>]]></content:encoded>
    </item>
    <item>
      <title>使用HMCL启动Minecraft</title>
      <link>https://blog.starry-s.moe/posts/2018/hmcl-minecraft/</link>
      <pubDate>Sun, 29 Jul 2018 17:26:35 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2018/hmcl-minecraft/</guid>
      <description>&lt;p&gt;终于到了暑假可以开一个Minecraft服务器和小伙伴联机的时候了！在发现我对吃鸡那类的游戏除了觉得好玩以外没有任何天赋( &lt;del&gt;没钱买鼠标？&lt;/del&gt; )后打算继续玩我的世界，所以为了方便那些想玩Minecraft（非网易代理版）但是还不知道怎么操作的那些小伙伴们，这篇教程就这么诞生了。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>终于到了暑假可以开一个Minecraft服务器和小伙伴联机的时候了！在发现我对吃鸡那类的游戏除了觉得好玩以外没有任何天赋( <del>没钱买鼠标？</del> )后打算继续玩我的世界，所以为了方便那些想玩Minecraft（非网易代理版）但是还不知道怎么操作的那些小伙伴们，这篇教程就这么诞生了。</p>
<blockquote>
<p>本篇由于创作时间过于久远，部分下载链接可能失效，因长期未更新，教程仅供参考。</p>
</blockquote>
<blockquote>
<p>本教程将分为二个部分，第一部分将指导你如何使用HMCL启动器安装并使用Minecraft（非网易代理版），第二部分处理一些常见的问题。
本教程主要写给Windows用户，我想Linux用户不太需要我的教程了吧。。
可能有人是刚接触电脑的新人，所以我尽可能的把每一步都写详细清楚，方便大家的理解。</p>
</blockquote>
<p>本教程仅提供所述内容的下载连接，游戏的版权归Mojang所有。</p>
<p>顺便说一下网易版并不支持MacOS哈哈哈哈。。。。</p>
<h2 id="第一部分">第一部分：</h2>
<p><strong>如果你尚未在官网购买正版的Minecraft，你可以使用HMCL（<a href="https://github.com/huanghongxun/HMCL">Hello Minecraft Launcher</a> )启动器下载并运行MC。</strong></p>
<ol>
<li></li>
</ol>
<p>你需要确保你的电脑已安装java8。下载java：http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html</p>
<ol start="2">
<li>接下来你需要下载HMCL启动器。</li>
</ol>
<p>我建议去<a href="https://ci.huangyuhui.net/job/HMCL/">HMCL的官方服务器</a> 下载所需的<code>exe</code>文件，最好不要从贴吧/论坛下载来路不明/非最新版本的HMCL启动器。</p>
<p>选择最新版本的后缀为<code>.exe</code>的文件下载即可。或者你可以下载后缀为<code>.jar</code>的文件（用于Linux，MacOS）</p>
<ol start="3">
<li>新建一个文件夹（放在你喜欢的位置例如桌面），将下载的<code>exe/jar</code>文件复制到这里。</li>
</ol>
<p>Windows用户直接双击exe文件运行即可，</p>
<p>Linux用户你下载的是jar文件所以如果你想运行你需要新建一个可执行文件：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">$ cd 你的文件夹名称
</span></span><span class="line"><span class="cl">$ echo &#34;java -jar hmcl-对应的版本号.jar&#34; &gt; ./start.sh
</span></span><span class="line"><span class="cl">$ chmod 744 ./start.sh
</span></span></code></pre></div><p>然后在终端执行<code>./start.sh</code>。</p>
<p>或者直接在终端执行<code>java -jar hmcl-*.jar</code>即可。</p>
<ol start="4">
<li>设置一个你喜欢的名字并一直使用它，选择离线模式</li>
</ol>
<p>在HMCL里点击右下角的加号下载游戏文件,建议将游戏下载到你启动器所在的文件夹内。</p>
<p><strong>如果你已购买正版Minecraft，在确保java安装好后直接去官网下载启动器再到启动器中下载游戏即可，可能会因网络原因速度慢，这是正常现象。</strong></p>
<p><strong>请支持正版，RMB165，可使用支付宝。</strong></p>
<h2 id="第二部分">第二部分：</h2>
<ol>
<li>笔记本双显卡用户请确保你正确的安装了显卡驱动（新买的笔记本不知道什么是显卡驱动就默认已经安装好了），NVIDA显卡用户在桌面右键选择NVIDIA控制面板（没有这个选项可能是你的显卡驱动没安装好），选择 <strong>3D设置-&gt;管理3D设置</strong> ，选择添加，找到你安装的JAVA目录下的java.exe，将其设置为高性能处理器。然后保存设置并退出。（ps现在市面上新出的笔记本用集成显卡玩这个游戏也已经很流畅了）</li>
</ol>
<p>在游戏中按F3，若右侧有NVIDIA字样就说明你已成功的使用独显运行游戏了，在左侧可以观察帧数。</p>
<ol start="2">
<li>
<p>Linux用户如果可以启动HMCL但是无法启动游戏错误报告是有关OpenGL的话在确定显卡驱动配置正常后检查一下是否安装了<code>xorg-xrandr</code>。</p>
</li>
<li>
<p>64位系统的用户不要安装32位的java，不仅影响性能而且对可使用的内存大小也有限制。</p>
</li>
<li>
<p>Linux用户如果想用独显玩Minecraft而日常使用时切换回省电的集显，请参考 <a href="https://sh.alynx.one/posts/Steam-with-Bumblebee/">Linux下使Steam调用Bumblebee使用独显</a>,当然不同的Linux Distribution方法会有不同，请linux用户自行摸索。</p>
</li>
</ol>
<h2 id="相关内容">相关内容：</h2>
<ul>
<li><a href="https://minecraft-zh.gamepedia.com/index.php?title=%E6%95%99%E7%A8%8B/%E6%88%90%E5%8A%9F%E5%9C%B0%E5%90%AF%E5%8A%A8%E6%B8%B8%E6%88%8F&amp;variant=zh-cn">教程/成功地启动游戏-Minecraft Wiki</a></li>
<li><a href="https://minecraft.net/zh-hans/">官方网站|Minecraft</a></li>
<li><a href="https://blog.starry-s.moe/posts/2016/raspberrypi-mc-server/">树莓派之我的世界服务器</a></li>
</ul>
<p>如果你感兴趣想一起联机我的世界的话欢迎联系我，大家一起来Van游戏。</p>]]></content:encoded>
    </item>
    <item>
      <title>Spring - 2</title>
      <link>https://blog.starry-s.moe/posts/2018/spring-2/</link>
      <pubDate>Thu, 05 Apr 2018 18:05:11 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2018/spring-2/</guid>
      <description></description>
      <content:encoded><![CDATA[<p><img loading="lazy" src="images/05.jpeg" alt="Spring" />
<p style="margin-bottom: -0.8em;" class="image-title">Flowers</p>
</p>
<h2 id="春天来了">春天来了。</h2>
<p><img loading="lazy" src="images/01.jpeg" alt="Spring 1" />

</p>
<p><img loading="lazy" src="images/02.jpeg" alt="Spring 2" />

</p>
<p><img loading="lazy" src="images/03.jpeg" alt="Spring 3" />

</p>
<p><img loading="lazy" src="images/04.jpeg" alt="Spring 4" />

</p>
<p><img loading="lazy" src="images/06.jpeg" alt="Spring 6" />

</p>
<p><img loading="lazy" src="images/07.jpeg" alt="Spring 7" />

</p>
<p><img loading="lazy" src="images/08.jpeg" alt="Spring 8" />

</p>
<hr>
<h2 id="spring---2">Spring - 2</h2>
<blockquote>
<p>拍摄自2018-3-28</p>
</blockquote>]]></content:encoded>
    </item>
    <item>
      <title>Hello 2018</title>
      <link>https://blog.starry-s.moe/posts/2018/hello-2018/</link>
      <pubDate>Tue, 16 Jan 2018 22:58:06 +0000</pubDate>
      <guid>https://blog.starry-s.moe/posts/2018/hello-2018/</guid>
      <description>&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;images/5cm_per_second.jpg&#34; alt=&#34;秒速5厘米&#34; /&gt;

&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><img loading="lazy" src="images/5cm_per_second.jpg" alt="秒速5厘米" />

</p>
<meting-js server="netease" type="song" id="1939762974" theme="#233333"></meting-js>
<meting-js server="netease" type="song" id="408277643" theme="#233333"></meting-js>
<meting-js server="netease" type="song" id="460628799" theme="#233333"></meting-js>
<meting-js server="netease" type="song" id="1939762883" theme="#233333"></meting-js>
<hr>
<meting-js server="netease" type="song" id="529659114" theme="#233333"></meting-js>
<p><img loading="lazy" src="images/5cm_per_second_2.jpg" alt="秒速5厘米" />
<p style="margin-bottom: -0.8em;" class="image-title">秒速五厘米</p>
</p>]]></content:encoded>
    </item>
    <item>
      <title>尽可能的写点什么</title>
      <link>https://blog.starry-s.moe/posts/2017/just-a-small-update/</link>
      <pubDate>Tue, 27 Jun 2017 22:47:30 +0000</pubDate>
      <guid>https://blog.starry-s.moe/posts/2017/just-a-small-update/</guid>
      <description>&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;images/image4.jpg&#34; alt=&#34;&#34; /&gt;

&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><img loading="lazy" src="images/image4.jpg" alt="" />

</p>
<p><img loading="lazy" src="images/image1.jpg" alt="Misaka Mikoto" />

</p>
<p><img loading="lazy" src="images/image2.jpg" alt="Misaka Mikoto" />

</p>
<hr>
<p><img loading="lazy" src="images/image3.jpg" alt="Love" />

</p>
<iframe src="//player.bilibili.com/player.html?aid=810872&cid=1176840&page=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true" width="100%" height="420px"></iframe>
<h2 id="just-a-small-update">Just A Small Update.</h2>]]></content:encoded>
    </item>
    <item>
      <title>Spring</title>
      <link>https://blog.starry-s.moe/posts/2017/spring/</link>
      <pubDate>Fri, 05 May 2017 22:28:00 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2017/spring/</guid>
      <description>&lt;p&gt;前一阵子拍的一些花，简单地用手机和电脑调了颜色，感觉蛮好看的。感觉有的照片可以当作壁纸了。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>前一阵子拍的一些花，简单地用手机和电脑调了颜色，感觉蛮好看的。感觉有的照片可以当作壁纸了。</p>
<hr>
<h2 id="拍摄自ipod-touch6">拍摄自ipod touch6</h2>
<p><img loading="lazy" src="images/1491917117692.jpeg" alt="Spring_1" />

</p>
<p><img loading="lazy" src="images/1491917129991.jpeg" alt="Spring_3" />

</p>
<p><img loading="lazy" src="images/1491917137076.jpeg" alt="Spring_4" />

</p>
<p><img loading="lazy" src="images/1491917142441.jpeg" alt="Spring_5" />

</p>
<hr>
<h2 id="拍摄自佳能powershot-s100">拍摄自佳能powershot s100</h2>
<p><img loading="lazy" src="images/IMG_0009.jpg" alt="Spring_6" />

</p>
<p><img loading="lazy" src="images/IMG_0011.jpg" alt="Spring_7" />

</p>
<p><img loading="lazy" src="images/IMG_0012.jpg" alt="Spring_8" />

</p>
<p><img loading="lazy" src="images/IMG_0018.jpg" alt="Spring_9" />

</p>
<p><img loading="lazy" src="images/IMG_0025.jpg" alt="Spring_10" />

</p>]]></content:encoded>
    </item>
    <item>
      <title>山地车刹车换血</title>
      <link>https://blog.starry-s.moe/posts/2017/brake-bleed/</link>
      <pubDate>Tue, 24 Jan 2017 14:57:44 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2017/brake-bleed/</guid>
      <description>&lt;p&gt;这是一篇不太正经的文章，因为它的内容与计算机以及正常生活中所能遇到的问题全都无关······ 前几天我的寨车又一次的粗毛病了，起因是刹车来令片沾油发出了驴叫声，更换碟片和来令片后发现油刹漏油进气了最终导致刹车毫无力气，好似液体被掏空···(噗) 油刹换油是唯一的解决方法了（土豪可以买新的刹车），所以这一阵子又一次的和自行车轮胎来了个亲密接触······ 换好油后想了想还是把过程记录下来吧，以后一旦自己需要再次换油时能帮帮自己。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>这是一篇不太正经的文章，因为它的内容与计算机以及正常生活中所能遇到的问题全都无关······ 前几天我的寨车又一次的粗毛病了，起因是刹车来令片沾油发出了驴叫声，更换碟片和来令片后发现油刹漏油进气了最终导致刹车毫无力气，好似液体被掏空···(噗) 油刹换油是唯一的解决方法了（土豪可以买新的刹车），所以这一阵子又一次的和自行车轮胎来了个亲密接触······ 换好油后想了想还是把过程记录下来吧，以后一旦自己需要再次换油时能帮帮自己。</p>
<hr>
<p>我用的是禧玛诺入门级刹车，现在大多数常见的山地车也都在用这款，型号为禧玛诺M355，我用的油是禧玛诺矿物质油。 这篇文章是针对禧玛诺部分类型的刹车写的，其他类型的刹车就不要乱按照本教程乱尝试了。</p>
<h2 id="所需工具">所需工具</h2>
<ol>
<li>换油工具。（我买的易捷EZ换油工具，网上很多都在卖这个工具）</li>
<li>刹车油。（技术好的人50毫升就够一整车的。）</li>
<li>手套，防护工具，清洁工具。</li>
<li>内六角扳手一套。（便宜内六角扳手害死人，一定要买贵的，不然大力出奇迹 #滑稽）</li>
<li>空矿泉水瓶用来存废弃的油。<br>
说实话当地的车店挺坑的，一瓶60ml禧玛诺矿物质油卖我80。给他个眼神让他自己体会去吧······</li>
</ol>
<p><img loading="lazy" src="images/brake-bleed-kit.jpgbleed-tools.jpg" alt="" />


<img loading="lazy" src="images/brake-bleed-kit.jpgIMG_20170122_220057.jpg" alt="" />

</p>
<h2 id="准备工作">准备工作</h2>
<p>首先脑补一些刹车换油的视频。</p>
<p>先熟悉一下刹车。刹把上有一个圆形的注油孔（需要用到2mm扳手），按照禧玛诺官方推荐的换油方法这里在注油时需要安装注油漏斗。刹车夹器上有一个换油孔， 注：禧玛诺M3XX系列的刹车的夹器上的注油孔和放油的螺丝是分开的，螺丝需要内六角拧开，油孔藏在一个防尘帽内。其他类型的刹车的夹器上的注油孔和放油螺丝是一体的，需要扳手拧开。 放油和注油和排气都需要调节夹器的注油螺丝。</p>
<p><img loading="lazy" src="images/brake-bleed-kit.jpgIMG_20170122_213246.jpg" alt="" />

</p>
<p>换油前需要将刹车内的油全部排空，放油过程需要一个容器将油收集起来避免流的哪都是。准备两个矿泉水瓶，将矿泉水瓶盖上钻两个洞，一个和注油管粗细一致，另一个小一点用于排气。 为避免刹车油弄脏来令片和碟刹片，换油前趁着你的爪还干净，把来令片和轮子全都卸下来，并往刹车夹器内装入垫片。 接下来 把刹把位置调整为水平，然后用2mm扳手将刹把的调节刹车松紧度的螺丝调节到最松。 准备手套，清洁用品留到换油后使用，还有纸巾～</p>
<h2 id="换油">换油</h2>
<p><img loading="lazy" src="images/brake-bleed-kit.jpgIMG_20170122_215350.jpg" alt="" />

</p>
<p>将准备好的矿泉水瓶接到刹车夹器的注油孔，用扳手拧松注油孔螺丝1/4圈左右（不要全部拧下来），然后将刹把的换油螺丝拧下来保存好，油会顺着重力流入矿泉水瓶中，该过程可以轻捏刹把让油排得更彻底一点。</p>
<p>油全部排尽后关闭刹车夹器的油孔，取下矿泉水瓶，将漏斗拧到刹把上面，不需要拧很紧此处不需要很高的密封。将注射器内抽入20ml左右的刹车油，连接到夹器的注油孔中。此处建议两人合作。打开夹器的换油螺丝，将刹车油缓慢的推入刹车内（不必担心有气泡进入，因为过后会排气）。等漏斗内出现一些油后停止注油（漏斗内需要留一些油的，多一点最好）。关闭夹器的换油孔，取另一个干净的矿泉水瓶，接到夹器的油孔上。</p>
<p><img loading="lazy" src="images/brake-bleed-kit.jpgIMG_20170122_215641.jpg" alt="" />

</p>
<p>接下来需要排气，取新矿泉水瓶的目的就是排出来的油可以回收再利用，而且油不要弄到皮肤上，对皮肤不好。 先松开夹器的换油螺丝，让油流出一部分，此时不但要注意流出来的油还有没有气泡而且要注意漏斗内的油是否排净，一定要保持漏斗内有一定的刹车油。等没有气泡排出后就可以关闭了。 握紧刹把，快速的打开夹器上的换油螺丝放油（0.5～1秒），然后慢慢地松开刹把，反复的捏几次刹把，注意是否有气泡排出。再持续重复该过程几次，直到感觉刹把手感恢复正常为止。此时若还有气泡冒出，则还需要重复上述过程几次。（重复捏刹把放油的过程中，可以拍打油管让气泡尽可能地全部排到上面)。然后将刹把倾斜30度角，再反复按压几次刹把，尽可能的排清全部的空气。 这个过程非常重要，将直接决定换油质量的好坏。等静置很久后没有气泡冒出，并且刹车手感恢复得差不多就能说明气泡排得差不多了。</p>
<p>拧紧夹器的换油螺丝，防止有油会再漏出来。用换油工具附带的塞子将漏斗底部堵住，防止拆漏斗时油全部都漏出来浪费掉。拆除连接在换油孔的导管，将油清洁干净并将防尘塞塞回原来的位置。 拆掉漏斗，将多余的刹车油回收再利用，滴几滴刹车油到刹把的注油孔让里面的液体溢出再拧紧刹把的螺丝，这样就防止拧螺丝时再混入气泡了。</p>
<p>确保项操作无误后就可以收工了。</p>
<p>刹车油换好后，小心翼翼地把漏斗里的油倒回油瓶内回收利用，原刹车排出的废弃刹车油是不能二次利用的，确保不污染环境的情况下处理掉即可。用清洁剂擦好你的爱车然后再把来令片和车轮装回去。注意一定不要让碟片和来令片沾油。接下来可以根据刹车习惯适当调节刹把内侧的调节刹车松紧的螺丝，手感最好即可。</p>
<blockquote>
<p>本篇文章仅根据个人经验写出，不同型号的刹车会有些许不同，若有错误请在评论区指出，以便我及时改正。
换油看起来好像很难的实际上找个视频看看再动手操作就可以了，我写的教程并不是很清楚所以总是把简单的问题复杂化了。</p>
</blockquote>]]></content:encoded>
    </item>
    <item>
      <title>Hello 2017!</title>
      <link>https://blog.starry-s.moe/posts/2017/hello-2017/</link>
      <pubDate>Sun, 01 Jan 2017 14:59:17 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2017/hello-2017/</guid>
      <description>&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;images/image.jpg&#34; alt=&#34;Hello 2017&#34; /&gt;

&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><img loading="lazy" src="images/image.jpg" alt="Hello 2017" />

</p>
<hr>
<h2 id="hello-2017">Hello 2017!</h2>]]></content:encoded>
    </item>
    <item>
      <title>树莓派之我的世界服务器</title>
      <link>https://blog.starry-s.moe/posts/2016/raspberrypi-mc-server/</link>
      <pubDate>Fri, 25 Nov 2016 15:01:28 +0000</pubDate>
      <guid>https://blog.starry-s.moe/posts/2016/raspberrypi-mc-server/</guid>
      <description>&lt;p&gt;刚买树莓派3B时就想弄一个我的世界服务器，但是那时不懂Linux，服务器也就没建成。这一阵子打算把这个计划再捡起来，和同学一起联机的话树莓派还是能做到的。ARM系列的CPU性能肯定比X86_64电脑的CPU弱许多，外加上树莓派1G的运行内存，让它跑大型服务器肯定是不可能的，所以只能弄一个几个人联机的小服务器了。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>刚买树莓派3B时就想弄一个我的世界服务器，但是那时不懂Linux，服务器也就没建成。这一阵子打算把这个计划再捡起来，和同学一起联机的话树莓派还是能做到的。ARM系列的CPU性能肯定比X86_64电脑的CPU弱许多，外加上树莓派1G的运行内存，让它跑大型服务器肯定是不可能的，所以只能弄一个几个人联机的小服务器了。</p>
<blockquote>
<p>本篇由于创作时间过于久远，部分下载链接可能失效，因长期未更新，教程仅供参考。</p>
</blockquote>
<blockquote>
<p>终于可以继续更新博客了！原计划是11月之前更新博客的。不巧电脑出了问题，重装了一遍Arch Linux,后来又发生了一些事一直没能用电脑。由于开学到现在能放假供我写博客的时间少之又少，这篇原本在11月初就能发表的文章就这样被硬生生推迟到了11月末了。</p>
</blockquote>
<h2 id="材料以及工具">材料以及工具</h2>
<ul>
<li>树莓派一只 （包括所需要的C10的内存卡以及刷好的固件以及5v，2.5A充电器）</li>
<li>一个支持DDNS的路由器（非必须）</li>
</ul>
<blockquote>
<p>如果不想用树莓派建服务器的话也可以按照本教程在电脑上搭建服务器。</p>
</blockquote>
<h2 id="配置树莓派">配置树莓派</h2>
<h3 id="调整gpu可使用的内存">调整GPU可使用的内存：</h3>
<p>毕竟用树莓派开服务器不需要占用GPU，直接调低。</p>
<p>Raspbian: <code>raspi-config</code>中将GPU内存分为16M。</p>
<p>Arch Linux ARM: <code>/boot/config.txt</code>中更改GPU显存为16</p>
<h2 id="安装java">安装JAVA</h2>
<p>Archlinux ARM:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">$ sudo pacman -S jre8-openjdk
</span></span></code></pre></div><p>通用方法：
前往 <strong><a href="http://www.oracle.com/technetwork/cn/java/javase/downloads/jdk8-downloads-2133151-zhs.html">JAVA下载页</a></strong> 下载树莓派（ARM架构，32位系统）所支持的JDK。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">$ mkdir ./java
</span></span><span class="line"><span class="cl">$ tar -zxvf 你所下载的JDK.jar.gz -C ./java/
</span></span></code></pre></div><p>在终端输入<code>./java/jdk1.8.0/bin/java -version</code>显示java的版本号。</p>
<h2 id="部署服务器">部署服务器</h2>
<p>本篇使用<a href="https://github.com/PaperMC/Paper">Paper MC</a>部署服务器。
除了Paper之外还有Bukkit和Spigot以及原版可选。</p>
<p>使用以下命令启动服务器。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">$ echo eula=True &gt; eula.txt
</span></span><span class="line"><span class="cl">$ ./java/jdk*/bin/java -Xms512M -Xmx1024M -jar ./spigot.jar
</span></span></code></pre></div><p>第一次运行会下载一些文件需要一定的时间。</p>
<p>网上有很多服务器插件和Mod什么的。本章中不做过多介绍.</p>
<h2 id="用screen-保持服务器一直运行而不被关掉">用screen 保持服务器一直运行而不被关掉。</h2>
<p>首先安装好screen。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">$ screen
</span></span><span class="line"><span class="cl">$ ./java/jdk1.8.0/bin/java -Xms512M -Xmx1024M -jar./paperclip.jar
</span></span></code></pre></div><p>在配置好你的服务器后就可以和小伙伴一起联机了。经过测试几个人简单的联机运行蛮正常的。</p>]]></content:encoded>
    </item>
    <item>
      <title>Everything is based on “Hello World”</title>
      <link>https://blog.starry-s.moe/posts/2016/hello-world/</link>
      <pubDate>Mon, 22 Aug 2016 14:22:54 +0800</pubDate>
      <guid>https://blog.starry-s.moe/posts/2016/hello-world/</guid>
      <description>&lt;p&gt;你好，世界！&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>你好，世界！</p>
<p><img loading="lazy" src="images/image.jpg" alt="&ldquo;Hello World&rdquo;" />
<p style="margin-bottom: -0.8em;" class="image-title">Hello World</p>
</p>
<meting-js server="netease" type="song" id="593814" theme="#233333"></meting-js>
<!-- <meting-js server="netease" type="song" id="594295" theme="#233333"></meting-js> -->
<hr>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="n">argv</span><span class="p">[])</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">printf</span><span class="p">(</span><span class="s">&#34;Hello World!</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div>]]></content:encoded>
    </item>
  </channel>
</rss>
