<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>绯雪札记</title><description>Hiyuki Notes</description><link>https://fenlyin.dpdns.org/</link><language>zh_CN</language><item><title>用 KVM 虚拟机在 Linux 上跑原生 Windows 应用</title><link>https://fenlyin.dpdns.org/posts/kvm-windows-remoteapp/</link><guid isPermaLink="true">https://fenlyin.dpdns.org/posts/kvm-windows-remoteapp/</guid><description>一份可复现的部署记录：Linux 桌面上直接点出 Word 窗口，而不是一块远程桌面。</description><pubDate>Mon, 26 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;一份可复现的部署记录：KVM 虚拟机跑一台通用 Windows 11（主要用途是原生 Office），Linux 桌面上表现为一个个独立的应用窗口（不是一块远程桌面）。宿主侧脚本、域、磁盘统一叫 &lt;code&gt;win11&lt;/code&gt;。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;0. 为什么是这个方案&lt;/h2&gt;
&lt;p&gt;先排除掉不合适的路：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方案&lt;/th&gt;
&lt;th&gt;结论&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Wine / CrossOver 直接跑 Office&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;不可用&lt;/strong&gt;。CodeWeavers 从 CrossOver 26 起停止 Office 2016 / Microsoft 365 支持（官方兼容表变成 &lt;em&gt;Installs, Will Not Run&lt;/em&gt;），理由是微软每次更新就破一次；社区 fork 与 Bottles 实验 runner 只覆盖 Word/Excel 基础编辑，VBA、Outlook、受保护文档全在雷区&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LibreOffice / OnlyOffice&lt;/td&gt;
&lt;td&gt;日常够用，但「和别人交换文档必须 1:1」这种场景不行&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;网页版 Office&lt;/td&gt;
&lt;td&gt;离线和大文件不行，桌面版功能缺失&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;KVM 虚拟机 + RDP RemoteApp&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;本方案&lt;/strong&gt;。真 Windows、真 Office、真 VBA；Linux 侧一个应用一个窗口&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;架构：&lt;code&gt;Windows 虚拟机（无桌面窗口概念）&lt;/code&gt; → &lt;code&gt;RDP RemoteApp（RAIL 协议）&lt;/code&gt; → &lt;code&gt;FreeRDP 客户端把每个 Windows 顶层窗口映射成一个原生 X11 窗口&lt;/code&gt;。Windows 本体藏在后台，用户在 Linux 应用菜单里点「Word」就直接出 Word 窗口。&lt;/p&gt;
&lt;p&gt;等价于 WinApps 的思路，但不依赖 Docker，全部用 libvirt + xfreerdp3 原生组件。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;1. 前置条件与检查&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;# CPU 虚拟化 + KVM 设备
grep -oE &apos;vmx|svm&apos; /proc/cpuinfo | head -1
ls -l /dev/kvm

# 资源参考值（本机部署用 4 vCPU / 8 GB / 120 GB）
nproc; free -g; df -h /var/lib
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;要点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;不需要 GPU 直通&lt;/strong&gt;。Office 用 virtio/qxl 软件渲染足够，独显留在宿主跑桌面。&lt;/li&gt;
&lt;li&gt;虚拟机常驻约占 2–4 GB 内存。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;2. 主机侧准备&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sudo apt-get update
sudo apt-get install -y qemu-kvm qemu-utils libvirt-daemon-system libvirt-clients \
                        virtinst virt-manager ovmf swtpm swtpm-tools virt-viewer freerdp3-x11
sudo systemctl enable --now libvirtd

# 普通用户加入组（重新登录后生效；没生效时可用 sg libvirt -c &apos;...&apos; 临时访问）
sudo usermod -aG libvirt,kvm &quot;$USER&quot;

# 介质目录：必须让 libvirt-qemu 能读到，不要放在 ~ 下
sudo mkdir -p /var/lib/libvirt/images/iso
sudo chown libvirt-qemu:libvirt /var/lib/libvirt/images/iso

# 默认 NAT 网络
virsh -c qemu:///system net-list --all     # default 应为 active
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;验证：&lt;code&gt;sg libvirt -c &apos;virsh -c qemu:///system list --all&apos;&lt;/code&gt; 能返回空列表即为通。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3. 获取 Windows ISO（微软官方直链）&lt;/h2&gt;
&lt;p&gt;不要用第三方镜像站。微软的下载页需要浏览器会话和反爬，但它的 &lt;em&gt;software-download-connector&lt;/em&gt; API 可以用脚本走通（Fido 脚本的原理）：先向 &lt;code&gt;vlscppe.microsoft.com/tags&lt;/code&gt; + &lt;code&gt;ov-df.microsoft.com&lt;/code&gt; 白名单化 session，再请求 SKU 信息与下载链接。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;~/.local/share/win11/get-win11-iso.py&lt;/code&gt; 就是这段流程的最小实现（部署脚本都放在那个目录，本文只记录方法）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./get-win11-iso.py zh-CN            # 参数是 locale，可用 en-US / zh-TW
# 内部 edition id：3813 = x64 客户端（3816 是 arm64，别选错）
# 输出即为 software.download.prss.microsoft.com 上的官方直链
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;拿到链接后：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -L -C - --retry 5 -o /var/lib/libvirt/images/iso/win11.iso &apos;&amp;lt;上一步输出的 URL&amp;gt;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;链接带签名和有效期，取到后立刻下载；过期就重跑脚本（&lt;strong&gt;注意会被 Sentinel 反爬拦一段时间&lt;/strong&gt;，别连续重试，隔几小时再来）。&lt;/li&gt;
&lt;li&gt;大小为 9 GB 左右（26H2 / build 26300 量级）。校验方式：&lt;code&gt;Content-Length&lt;/code&gt; 与本地文件大小一致即可，再 &lt;code&gt;mount -o loop&lt;/code&gt; 看 &lt;code&gt;sources/install.wim&lt;/code&gt; 是否在。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Windows ISO 的载荷几乎全在 UDF 里&lt;/strong&gt;，&lt;code&gt;xorriso -indev -ls /&lt;/code&gt; 只会看到 &lt;code&gt;README.TXT&lt;/code&gt;，误以为空盘。要用 &lt;code&gt;sudo mount -o loop,ro win11.iso /mnt/x&lt;/code&gt; 检查。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;4. 无人值守安装介质&lt;/h2&gt;
&lt;h3&gt;4.1 交付方式&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;autounattend.xml&lt;/code&gt; 放在一张&lt;strong&gt;单独的小 ISO&lt;/strong&gt;（第二块光驱）里，Windows Setup 会自动在可移动介质根目录找它。同时把 &lt;code&gt;post-install.ps1&lt;/code&gt; 一起放进去，供首次登录时调用。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir -p ~/.local/share/win11/seed
sed -e &quot;s|__WIN_PASS__|$WIN_VM_PASS|g&quot; -e &quot;s|__COMPUTER_NAME__|WIN11|g&quot; \
    ~/.local/share/win11/autounattend.xml.tmpl &amp;gt; ~/.local/share/win11/seed/autounattend.xml
cp ~/.local/share/win11/post-install.ps1 ~/.local/share/win11/seed/
cd ~/.local/share/win11/seed &amp;amp;&amp;amp; xorriso -as mkisofs -quiet -J -R -V OEMDRV \
    -o /var/lib/libvirt/images/iso/win11-seed.iso .
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.2 autounattend.xml 全文&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;
&amp;lt;unattend xmlns=&quot;urn:schemas-microsoft-com:unattend&quot;&amp;gt;
  &amp;lt;settings pass=&quot;windowsPE&quot;&amp;gt;
    &amp;lt;component name=&quot;Microsoft-Windows-International-Core-WinPE&quot; processorArchitecture=&quot;amd64&quot; publicKeyToken=&quot;31bf3856ad364e35&quot; language=&quot;neutral&quot; versionScope=&quot;nonSxS&quot;&amp;gt;
      &amp;lt;SetupUILanguage&amp;gt;
        &amp;lt;UILanguage&amp;gt;zh-CN&amp;lt;/UILanguage&amp;gt;
      &amp;lt;/SetupUILanguage&amp;gt;
      &amp;lt;InputLocale&amp;gt;0804:00000804&amp;lt;/InputLocale&amp;gt;
      &amp;lt;SystemLocale&amp;gt;zh-CN&amp;lt;/SystemLocale&amp;gt;
      &amp;lt;UILanguage&amp;gt;zh-CN&amp;lt;/UILanguage&amp;gt;
      &amp;lt;UserLocale&amp;gt;zh-CN&amp;lt;/UserLocale&amp;gt;
    &amp;lt;/component&amp;gt;
    &amp;lt;component name=&quot;Microsoft-Windows-Setup&quot; processorArchitecture=&quot;amd64&quot; publicKeyToken=&quot;31bf3856ad364e35&quot; language=&quot;neutral&quot; versionScope=&quot;nonSxS&quot;&amp;gt;
      &amp;lt;DiskConfiguration&amp;gt;
        &amp;lt;Disk wcm:action=&quot;add&quot; xmlns:wcm=&quot;http://schemas.microsoft.com/WMIConfig/2002/State&quot;&amp;gt;
          &amp;lt;CreatePartitions&amp;gt;
            &amp;lt;CreatePartition wcm:action=&quot;add&quot;&amp;gt;
              &amp;lt;Order&amp;gt;1&amp;lt;/Order&amp;gt;&amp;lt;Type&amp;gt;EFI&amp;lt;/Type&amp;gt;&amp;lt;Size&amp;gt;300&amp;lt;/Size&amp;gt;
            &amp;lt;/CreatePartition&amp;gt;
            &amp;lt;CreatePartition wcm:action=&quot;add&quot;&amp;gt;
              &amp;lt;Order&amp;gt;2&amp;lt;/Order&amp;gt;&amp;lt;Type&amp;gt;MSR&amp;lt;/Type&amp;gt;&amp;lt;Size&amp;gt;16&amp;lt;/Size&amp;gt;
            &amp;lt;/CreatePartition&amp;gt;
            &amp;lt;CreatePartition wcm:action=&quot;add&quot;&amp;gt;
              &amp;lt;Order&amp;gt;3&amp;lt;/Order&amp;gt;&amp;lt;Type&amp;gt;Primary&amp;lt;/Type&amp;gt;&amp;lt;Extend&amp;gt;true&amp;lt;/Extend&amp;gt;
            &amp;lt;/CreatePartition&amp;gt;
          &amp;lt;/CreatePartitions&amp;gt;
          &amp;lt;ModifyPartitions&amp;gt;
            &amp;lt;ModifyPartition wcm:action=&quot;add&quot;&amp;gt;
              &amp;lt;Order&amp;gt;1&amp;lt;/Order&amp;gt;&amp;lt;PartitionID&amp;gt;1&amp;lt;/PartitionID&amp;gt;&amp;lt;Format&amp;gt;FAT32&amp;lt;/Format&amp;gt;&amp;lt;Label&amp;gt;System&amp;lt;/Label&amp;gt;
            &amp;lt;/ModifyPartition&amp;gt;
            &amp;lt;ModifyPartition wcm:action=&quot;add&quot;&amp;gt;
              &amp;lt;Order&amp;gt;2&amp;lt;/Order&amp;gt;&amp;lt;PartitionID&amp;gt;3&amp;lt;/PartitionID&amp;gt;&amp;lt;Format&amp;gt;NTFS&amp;lt;/Format&amp;gt;&amp;lt;Label&amp;gt;Windows&amp;lt;/Label&amp;gt;&amp;lt;Letter&amp;gt;C&amp;lt;/Letter&amp;gt;
            &amp;lt;/ModifyPartition&amp;gt;
          &amp;lt;/ModifyPartitions&amp;gt;
          &amp;lt;WillWipeDisk&amp;gt;true&amp;lt;/WillWipeDisk&amp;gt;
          &amp;lt;DiskID&amp;gt;0&amp;lt;/DiskID&amp;gt;
        &amp;lt;/Disk&amp;gt;
        &amp;lt;WillShowUI&amp;gt;OnError&amp;lt;/WillShowUI&amp;gt;
      &amp;lt;/DiskConfiguration&amp;gt;
      &amp;lt;ImageInstall&amp;gt;
        &amp;lt;OSImage&amp;gt;
          &amp;lt;InstallFrom&amp;gt;
            &amp;lt;MetaData wcm:action=&quot;add&quot; xmlns:wcm=&quot;http://schemas.microsoft.com/WMIConfig/2002/State&quot;&amp;gt;
              &amp;lt;Key&amp;gt;/IMAGE/NAME&amp;lt;/Key&amp;gt;
              &amp;lt;Value&amp;gt;Windows 11 Pro&amp;lt;/Value&amp;gt;
            &amp;lt;/MetaData&amp;gt;
          &amp;lt;/InstallFrom&amp;gt;
          &amp;lt;InstallTo&amp;gt;
            &amp;lt;DiskID&amp;gt;0&amp;lt;/DiskID&amp;gt;
            &amp;lt;PartitionID&amp;gt;3&amp;lt;/PartitionID&amp;gt;
          &amp;lt;/InstallTo&amp;gt;
          &amp;lt;WillShowUI&amp;gt;OnError&amp;lt;/WillShowUI&amp;gt;
        &amp;lt;/OSImage&amp;gt;
      &amp;lt;/ImageInstall&amp;gt;
      &amp;lt;UserData&amp;gt;
        &amp;lt;AcceptEula&amp;gt;true&amp;lt;/AcceptEula&amp;gt;
        &amp;lt;FullName&amp;gt;office&amp;lt;/FullName&amp;gt;
        &amp;lt;Organization&amp;gt;local&amp;lt;/Organization&amp;gt;
        &amp;lt;ProductKey&amp;gt;
          &amp;lt;Key&amp;gt;VK7JG-NPHTM-C97JM-9MPGT-3V66T&amp;lt;/Key&amp;gt;
          &amp;lt;WillShowUI&amp;gt;Never&amp;lt;/WillShowUI&amp;gt;
        &amp;lt;/ProductKey&amp;gt;
      &amp;lt;/UserData&amp;gt;
    &amp;lt;/component&amp;gt;
  &amp;lt;/settings&amp;gt;

  &amp;lt;settings pass=&quot;specialize&quot;&amp;gt;
    &amp;lt;component name=&quot;Microsoft-Windows-Shell-Setup&quot; processorArchitecture=&quot;amd64&quot; publicKeyToken=&quot;31bf3856ad364e35&quot; language=&quot;neutral&quot; versionScope=&quot;nonSxS&quot;&amp;gt;
      &amp;lt;ComputerName&amp;gt;__COMPUTER_NAME__&amp;lt;/ComputerName&amp;gt;
      &amp;lt;TimeZone&amp;gt;China Standard Time&amp;lt;/TimeZone&amp;gt;
    &amp;lt;/component&amp;gt;
    &amp;lt;component name=&quot;Microsoft-Windows-Deployment&quot; processorArchitecture=&quot;amd64&quot; publicKeyToken=&quot;31bf3856ad364e35&quot; language=&quot;neutral&quot; versionScope=&quot;nonSxS&quot;&amp;gt;
      &amp;lt;RunSynchronous&amp;gt;
        &amp;lt;RunSynchronousCommand wcm:action=&quot;add&quot; xmlns:wcm=&quot;http://schemas.microsoft.com/WMIConfig/2002/State&quot;&amp;gt;
          &amp;lt;Order&amp;gt;1&amp;lt;/Order&amp;gt;&amp;lt;Description&amp;gt;enable RDP&amp;lt;/Description&amp;gt;
          &amp;lt;Path&amp;gt;reg add &quot;HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server&quot; /v fDenyTSConnections /t REG_DWORD /d 0 /f&amp;lt;/Path&amp;gt;
        &amp;lt;/RunSynchronousCommand&amp;gt;
        &amp;lt;RunSynchronousCommand wcm:action=&quot;add&quot; xmlns:wcm=&quot;http://schemas.microsoft.com/WMIConfig/2002/State&quot;&amp;gt;
          &amp;lt;Order&amp;gt;2&amp;lt;/Order&amp;gt;&amp;lt;Description&amp;gt;enable RemoteApp&amp;lt;/Description&amp;gt;
          &amp;lt;Path&amp;gt;reg add &quot;HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Terminal Server\TSAppAllowList&quot; /v fDisabledAllowList /t REG_DWORD /d 1 /f&amp;lt;/Path&amp;gt;
        &amp;lt;/RunSynchronousCommand&amp;gt;
        &amp;lt;RunSynchronousCommand wcm:action=&quot;add&quot; xmlns:wcm=&quot;http://schemas.microsoft.com/WMIConfig/2002/State&quot;&amp;gt;
          &amp;lt;Order&amp;gt;3&amp;lt;/Order&amp;gt;&amp;lt;Description&amp;gt;disable NLA requirement&amp;lt;/Description&amp;gt;
          &amp;lt;Path&amp;gt;reg add &quot;HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp&quot; /v UserAuthentication /t REG_DWORD /d 0 /f&amp;lt;/Path&amp;gt;
        &amp;lt;/RunSynchronousCommand&amp;gt;
        &amp;lt;RunSynchronousCommand wcm:action=&quot;add&quot; xmlns:wcm=&quot;http://schemas.microsoft.com/WMIConfig/2002/State&quot;&amp;gt;
          &amp;lt;Order&amp;gt;4&amp;lt;/Order&amp;gt;&amp;lt;Description&amp;gt;disable fast startup&amp;lt;/Description&amp;gt;
          &amp;lt;Path&amp;gt;reg add &quot;HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Power&quot; /v HiberbootEnabled /t REG_DWORD /d 0 /f&amp;lt;/Path&amp;gt;
        &amp;lt;/RunSynchronousCommand&amp;gt;
        &amp;lt;RunSynchronousCommand wcm:action=&quot;add&quot; xmlns:wcm=&quot;http://schemas.microsoft.com/WMIConfig/2002/State&quot;&amp;gt;
          &amp;lt;Order&amp;gt;5&amp;lt;/Order&amp;gt;&amp;lt;Description&amp;gt;no sleep no hibernate&amp;lt;/Description&amp;gt;
          &amp;lt;Path&amp;gt;cmd /c powercfg /h off &amp;amp;amp; powercfg -change -standby-timeout-ac 0 &amp;amp;amp; powercfg -change -monitor-timeout-ac 0 &amp;amp;amp; powercfg -change -disk-timeout-ac 0&amp;lt;/Path&amp;gt;
        &amp;lt;/RunSynchronousCommand&amp;gt;
        &amp;lt;RunSynchronousCommand wcm:action=&quot;add&quot; xmlns:wcm=&quot;http://schemas.microsoft.com/WMIConfig/2002/State&quot;&amp;gt;
          &amp;lt;Order&amp;gt;6&amp;lt;/Order&amp;gt;&amp;lt;Description&amp;gt;rdp firewall rule&amp;lt;/Description&amp;gt;
          &amp;lt;Path&amp;gt;powershell -NoProfile -Command &quot;Set-NetFirewallRule -Name RemoteDesktop-UserMode-In-TCP,RemoteDesktop-UserMode-In-UDP -Enabled True -ErrorAction SilentlyContinue&quot;&amp;lt;/Path&amp;gt;
        &amp;lt;/RunSynchronousCommand&amp;gt;
      &amp;lt;/RunSynchronous&amp;gt;
    &amp;lt;/component&amp;gt;
  &amp;lt;/settings&amp;gt;

  &amp;lt;settings pass=&quot;oobeSystem&quot;&amp;gt;
    &amp;lt;component name=&quot;Microsoft-Windows-Shell-Setup&quot; processorArchitecture=&quot;amd64&quot; publicKeyToken=&quot;31bf3856ad364e35&quot; language=&quot;neutral&quot; versionScope=&quot;nonSxS&quot;&amp;gt;
      &amp;lt;OOBE&amp;gt;
        &amp;lt;HideEULAPage&amp;gt;true&amp;lt;/HideEULAPage&amp;gt;
        &amp;lt;HideOEMRegistrationScreen&amp;gt;true&amp;lt;/HideOEMRegistrationScreen&amp;gt;
        &amp;lt;HideOnlineAccountScreens&amp;gt;true&amp;lt;/HideOnlineAccountScreens&amp;gt;
        &amp;lt;HideWirelessSetupInOOBE&amp;gt;true&amp;lt;/HideWirelessSetupInOOBE&amp;gt;
        &amp;lt;ProtectYourPC&amp;gt;3&amp;lt;/ProtectYourPC&amp;gt;
      &amp;lt;/OOBE&amp;gt;
      &amp;lt;UserAccounts&amp;gt;
        &amp;lt;LocalAccounts&amp;gt;
          &amp;lt;LocalAccount wcm:action=&quot;add&quot; xmlns:wcm=&quot;http://schemas.microsoft.com/WMIConfig/2002/State&quot;&amp;gt;
            &amp;lt;Password&amp;gt;
              &amp;lt;Value&amp;gt;__WIN_PASS__&amp;lt;/Value&amp;gt;
              &amp;lt;PlainText&amp;gt;true&amp;lt;/PlainText&amp;gt;
            &amp;lt;/Password&amp;gt;
            &amp;lt;Description&amp;gt;office&amp;lt;/Description&amp;gt;
            &amp;lt;DisplayName&amp;gt;office&amp;lt;/DisplayName&amp;gt;
            &amp;lt;Group&amp;gt;Administrators&amp;lt;/Group&amp;gt;
            &amp;lt;Name&amp;gt;office&amp;lt;/Name&amp;gt;
          &amp;lt;/LocalAccount&amp;gt;
        &amp;lt;/LocalAccounts&amp;gt;
      &amp;lt;/UserAccounts&amp;gt;
      &amp;lt;AutoLogon&amp;gt;
        &amp;lt;Password&amp;gt;
          &amp;lt;Value&amp;gt;__WIN_PASS__&amp;lt;/Value&amp;gt;
          &amp;lt;PlainText&amp;gt;true&amp;lt;/PlainText&amp;gt;
        &amp;lt;/Password&amp;gt;
        &amp;lt;Enabled&amp;gt;true&amp;lt;/Enabled&amp;gt;
        &amp;lt;LogonCount&amp;gt;3&amp;lt;/LogonCount&amp;gt;
        &amp;lt;Username&amp;gt;office&amp;lt;/Username&amp;gt;
      &amp;lt;/AutoLogon&amp;gt;
      &amp;lt;FirstLogonCommands&amp;gt;
        &amp;lt;SynchronousCommand wcm:action=&quot;add&quot; xmlns:wcm=&quot;http://schemas.microsoft.com/WMIConfig/2002/State&quot;&amp;gt;
          &amp;lt;Order&amp;gt;1&amp;lt;/Order&amp;gt;
          &amp;lt;Description&amp;gt;win11 post install&amp;lt;/Description&amp;gt;
          &amp;lt;CommandLine&amp;gt;powershell -NoProfile -ExecutionPolicy Bypass -Command &quot;Get-ChildItem D:\,E:\,F:\,G:\ -Filter post-install.ps1 -Depth 2 -ErrorAction SilentlyContinue | Select-Object -First 1 | ForEach-Object { &amp;amp;amp; $_.FullName }&quot;&amp;lt;/CommandLine&amp;gt;
          &amp;lt;RequiresUserInput&amp;gt;false&amp;lt;/RequiresUserInput&amp;gt;
        &amp;lt;/SynchronousCommand&amp;gt;
      &amp;lt;/FirstLogonCommands&amp;gt;
    &amp;lt;/component&amp;gt;
  &amp;lt;/settings&amp;gt;
&amp;lt;/unattend&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.3 这份 XML 里每处「不这么写就不行」的地方&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;XSD 序列顺序&lt;/strong&gt;。&lt;code&gt;DiskConfiguration&lt;/code&gt; 里 &lt;code&gt;Disk&lt;/code&gt; 必须在 &lt;code&gt;WillShowUI&lt;/code&gt; 之前；&lt;code&gt;OSImage&lt;/code&gt; 必须是 &lt;code&gt;InstallFrom → InstallTo → WillShowUI&lt;/code&gt;。顺序错 → &lt;strong&gt;整份答案文件被静默忽略&lt;/strong&gt;，安装退回图形界面（表现为又出现语言页/密钥页）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;International-Core-WinPE&lt;/code&gt; 不能省&lt;/strong&gt;。少这个组件，即使 Setup 组件正确，安装程序仍会停在「选择语言设置」页。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;产品密钥用微软公开的通用安装密钥&lt;/strong&gt;。消费级多版本 ISO 不给密钥会停在「输入产品密钥」页。&lt;code&gt;VK7JG-NPHTM-C97JM-9MPGT-3V66T&lt;/code&gt; 是 Win11 Pro 的公开通用安装密钥，作用是&lt;strong&gt;选版本 + 跳过该页&lt;/strong&gt;，与激活无关。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;/IMAGE/NAME&lt;/code&gt; 显式指定版本&lt;/strong&gt;。中文消费级 ISO 里 Home 系列排在前面，不指定会装成 Home——Home 不能当 RDP 服务端，整个方案作废。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;ModifyPartitions&lt;/code&gt; 不要给 MSR 分区做 Format&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FirstLogonCommands&lt;/code&gt; 里用「扫 D:–G: 找脚本」而不是写死盘符，因为光驱盘符顺序会变。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;5. 虚拟机定义&lt;/h2&gt;
&lt;p&gt;关键片段（完整见 &lt;code&gt;win11.xml.tmpl&lt;/code&gt;）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;os&amp;gt;
  &amp;lt;type arch=&apos;x86_64&apos; machine=&apos;q35&apos;&amp;gt;hvm&amp;lt;/type&amp;gt;
  &amp;lt;!-- .ms. 版本 = 已内建微软密钥的 Secure Boot 变量 --&amp;gt;
  &amp;lt;loader readonly=&apos;yes&apos; type=&apos;pflash&apos;&amp;gt;/usr/share/OVMF/OVMF_CODE_4M.ms.fd&amp;lt;/loader&amp;gt;
  &amp;lt;nvram template=&apos;/usr/share/OVMF/OVMF_VARS_4M.ms.fd&apos;&amp;gt;/var/lib/libvirt/qemu/nvram/win11_VARS.fd&amp;lt;/nvram&amp;gt;
&amp;lt;/os&amp;gt;
&amp;lt;features&amp;gt;
  &amp;lt;acpi/&amp;gt;&amp;lt;apic/&amp;gt;&amp;lt;smm state=&apos;on&apos;/&amp;gt;
  &amp;lt;hyperv mode=&apos;custom&apos;&amp;gt;                       &amp;lt;!-- Windows 客户机性能，缺了会明显慢 --&amp;gt;
    &amp;lt;relaxed state=&apos;on&apos;/&amp;gt;&amp;lt;vapic state=&apos;on&apos;/&amp;gt;&amp;lt;spinlocks state=&apos;on&apos; retries=&apos;8191&apos;/&amp;gt;
    &amp;lt;vpindex state=&apos;on&apos;/&amp;gt;&amp;lt;runtime state=&apos;on&apos;/&amp;gt;&amp;lt;synic state=&apos;on&apos;/&amp;gt;&amp;lt;stimer state=&apos;on&apos;/&amp;gt;
    &amp;lt;reset state=&apos;on&apos;/&amp;gt;&amp;lt;frequencies state=&apos;on&apos;/&amp;gt;&amp;lt;tlbflush state=&apos;on&apos;/&amp;gt;&amp;lt;ipi state=&apos;on&apos;/&amp;gt;
  &amp;lt;/hyperv&amp;gt;
&amp;lt;/features&amp;gt;
&amp;lt;cpu mode=&apos;host-passthrough&apos; check=&apos;none&apos; migratable=&apos;on&apos;/&amp;gt;
&amp;lt;clock offset=&apos;localtime&apos;&amp;gt;
  &amp;lt;timer name=&apos;rtc&apos; tickpolicy=&apos;catchup&apos;/&amp;gt;
  &amp;lt;timer name=&apos;hpet&apos; present=&apos;no&apos;/&amp;gt;
  &amp;lt;timer name=&apos;hypervclock&apos; present=&apos;yes&apos;/&amp;gt;
&amp;lt;/clock&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;设备侧：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!-- 系统盘：SATA（AHCI）——Windows 自带驱动，免装驱动即可安装 --&amp;gt;
&amp;lt;disk type=&apos;file&apos; device=&apos;disk&apos;&amp;gt;
  &amp;lt;driver name=&apos;qemu&apos; type=&apos;qcow2&apos; cache=&apos;none&apos; discard=&apos;unmap&apos;/&amp;gt;
  &amp;lt;source file=&apos;/var/lib/libvirt/images/win11.qcow2&apos;/&amp;gt;
  &amp;lt;target dev=&apos;sda&apos; bus=&apos;sata&apos;/&amp;gt;
  &amp;lt;boot order=&apos;1&apos;/&amp;gt;
&amp;lt;/disk&amp;gt;
&amp;lt;!-- 安装 ISO：放第二位，装完不删也不影响（无按键即跳过） --&amp;gt;
&amp;lt;disk type=&apos;file&apos; device=&apos;cdrom&apos;&amp;gt; ... &amp;lt;target dev=&apos;sdb&apos; bus=&apos;sata&apos;/&amp;gt;&amp;lt;readonly/&amp;gt;&amp;lt;boot order=&apos;2&apos;/&amp;gt;&amp;lt;/disk&amp;gt;
&amp;lt;!-- 无人值守介质 --&amp;gt;
&amp;lt;disk type=&apos;file&apos; device=&apos;cdrom&apos;&amp;gt; ... &amp;lt;target dev=&apos;sdc&apos; bus=&apos;sata&apos;/&amp;gt;&amp;lt;readonly/&amp;gt;&amp;lt;/disk&amp;gt;

&amp;lt;!-- 网卡：e1000e 同样是 Windows 内置驱动；用 virtio-net 就得先灌驱动 --&amp;gt;
&amp;lt;interface type=&apos;network&apos;&amp;gt;&amp;lt;source network=&apos;default&apos;/&amp;gt;&amp;lt;model type=&apos;e1000e&apos;/&amp;gt;&amp;lt;/interface&amp;gt;

&amp;lt;tpm model=&apos;tpm-crb&apos;&amp;gt;&amp;lt;backend type=&apos;emulator&apos; version=&apos;2.0&apos;/&amp;gt;&amp;lt;/tpm&amp;gt;
&amp;lt;graphics type=&apos;spice&apos; autoport=&apos;yes&apos; listen=&apos;127.0.0.1&apos;/&amp;gt;
&amp;lt;video&amp;gt;&amp;lt;model type=&apos;qxl&apos; ram=&apos;65536&apos; vram=&apos;65536&apos; vgamem=&apos;32768&apos; heads=&apos;1&apos; primary=&apos;yes&apos;/&amp;gt;&amp;lt;/video&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;为什么不用 virtio&lt;/strong&gt;：virtio-blk/virtio-net 需要事先把 virtio-win 驱动塞进安装介质（DriverPaths 或 &lt;code&gt;vioscsi&lt;/code&gt; 技巧），多一条出错链。Office 场景 SATA + e1000e 没有任何可感知差异。以后要提速再挂 &lt;code&gt;virtio-win.iso&lt;/code&gt; 装驱动、改 3 行 XML 即可。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;6. 一键建机与安装&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;# ~/.local/share/win11/media.conf
#   WIN_ISO=/var/lib/libvirt/images/iso/win11.iso

sudo -v
sg libvirt -c ~/.local/share/win11/create-vm.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;create-vm.sh&lt;/code&gt; 做四件事：生成随机密码存 &lt;code&gt;credentials.env&lt;/code&gt;（600）→ 渲染 &lt;code&gt;autounattend.xml&lt;/code&gt; 并打成 seed ISO → 建 120 GB qcow2 → &lt;code&gt;virsh define&lt;/code&gt; + &lt;code&gt;start&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;6.1 引导时的两个必知点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;「Press any key to boot from CD or DVD」&lt;/strong&gt;：Windows 安装介质默认要按键才引导。自动化时用&lt;pre&gt;&lt;code&gt;for i in $(seq 1 30); do
    sg libvirt -c &apos;virsh -c qemu:///system send-key win11 KEY_SPACE&apos; &amp;gt;/dev/null; sleep 0.4
done
&lt;/code&gt;&lt;/pre&gt;
装完重启后不要再按键，它会自动回落到硬盘引导。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;进度观察&lt;/strong&gt;（纯无头也能看）：&lt;pre&gt;&lt;code&gt;virsh -c qemu:///system screenshot win11 /tmp/wo.ppm   # 实际是 PNG
&lt;/code&gt;&lt;/pre&gt;
文件大小是个廉价信号：几 KB ≈ 文本/黑屏，几百 KB–1 MB ≈ 图形界面。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;全过程约 25–35 分钟（装系统 15 分钟 + Office 在线安装 8–12 分钟），中间不需要任何点击。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;7. 安装后置：post-install.ps1&lt;/h2&gt;
&lt;p&gt;首次登录（AutoLogon）后由 &lt;code&gt;FirstLogonCommands&lt;/code&gt; 执行，&lt;strong&gt;运行在提权上下文&lt;/strong&gt;，所以能直接写 HKLM。它做两件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;RDP + RemoteApp 解锁：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Terminal Server\fDenyTSConnections = 0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Terminal Server\TSAppAllowList\fDisabledAllowList = 1&lt;/code&gt; ← &lt;strong&gt;没有这一条，RAIL 的 &lt;code&gt;/app:&lt;/code&gt; 会直接被服务端拒绝&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RDP-Tcp\UserAuthentication = 0&lt;/code&gt;（关掉 NLA 要求，客户端用密码直连更省事）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Set-NetFirewallRule -Name RemoteDesktop-UserMode-In-TCP/-In-UDP -Enabled True&lt;/code&gt;（用规则名而不是显示名，避免中文系统本地化差异）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Microsoft 365 在线安装：向 &lt;code&gt;c2rsetup.officeapps.live.com&lt;/code&gt; 的官方引导地址取安装器（6.8 MB 引导器，运行后自己下 ~2.5 GB 载荷），静默安装 &lt;code&gt;O365HomePremRetail&lt;/code&gt;（个人版/家庭版）；商业版是 &lt;code&gt;O365ProPlusRetail&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;脚本里两个细节值得保留：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;先等网络&lt;/strong&gt;（轮询 &lt;code&gt;c2rsetup.officeapps.live.com:443&lt;/code&gt;，最多 120 秒），否则首次登录时 DHCP 还没好，下载直接失败。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;两轮重试 + 校验 &lt;code&gt;WINWORD.EXE&lt;/code&gt; 是否存在&lt;/strong&gt;，装完把结果和 &lt;code&gt;ready.txt&lt;/code&gt; 落在 &lt;code&gt;C:\win11\&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;必踩坑：给 Windows 用的 .ps1 必须带 UTF-8 BOM。&lt;/strong&gt; PowerShell 5.1 在无 BOM 时按 ANSI 解码，中文注释会吞掉后面的代码行，症状是变量莫名变 &lt;code&gt;$null&lt;/code&gt;、&lt;code&gt;Test-Path&lt;/code&gt; 参数绑定失败、注册表写入报 PermissionDenied，&lt;strong&gt;而且不报错&lt;/strong&gt;。写文件时用 &lt;code&gt;encoding=&apos;utf-8-sig&apos;&lt;/code&gt;（Python）或 &lt;code&gt;:set bomb&lt;/code&gt;（vim）。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;8. Linux 侧：把一个 Windows 程序变成原生窗口&lt;/h2&gt;
&lt;h3&gt;8.1 最小验证&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;xfreerdp3 /v:&amp;lt;guest-ip&amp;gt; /u:office /p:&amp;lt;pass&amp;gt; /d: /cert:ignore \
          /app:&quot;program:C:\Windows\System32\notepad.exe&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;就会弹出一个标题为「无标题 - 记事本」的原生 X11 窗口。&lt;/p&gt;
&lt;p&gt;三个语法要点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/app:&lt;/code&gt; 的值是 &lt;code&gt;program:&amp;lt;完整 exe 路径&amp;gt;&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不要加 &lt;code&gt;||&lt;/code&gt; 前缀&lt;/strong&gt;（即别写 &lt;code&gt;program:||C:\...&lt;/code&gt;）。加了会被当成字面路径，服务端返回 &lt;code&gt;RAIL_EXEC_E_FILE_NOT_FOUND (NtError=0x2)&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;路径含空格时整体用双引号包住 &lt;code&gt;/app:&quot;program:C:\Program Files\...&quot;&lt;/code&gt;，不要再在内部加引号。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;/sec:tls&lt;/code&gt; 往往是必需的&lt;/strong&gt;。&lt;code&gt;UserAuthentication=0&lt;/code&gt; 只是让服务端「接受」TLS，但 FreeRDP 3 默认仍先按 NLA 发起；guest 侧 CredSSP 这条路不通时，会在 &lt;code&gt;nla_recv_pdu&lt;/code&gt; 处报 &lt;code&gt;ERRCONNECT_LOGON_FAILURE [0x00020014]&lt;/code&gt; —— 看起来像密码错，实际换个认证层就通。补上 &lt;code&gt;/sec:tls&lt;/code&gt; 即可（仍是 TLS 加密，NAT 内网可接受）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;断连 ≠ 失败&lt;/strong&gt;。会话被别的连接接管或正常关窗时，xfreerdp3 退出码同样非 0、日志里同样有 &lt;code&gt;[ERROR]&lt;/code&gt;。判断「真失败」要语义化：日志里&lt;strong&gt;没有&lt;/strong&gt; &lt;code&gt;gdi_init_ex&lt;/code&gt; / &lt;code&gt;Logon Info V2&lt;/code&gt; 才算出错，否则会满屏假警报。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;多个应用&lt;strong&gt;不能并发&lt;/strong&gt; —— 这是 Windows 客户端的硬限制，不是客户端的问题：Windows 11 客户端版（Pro 也一样）只允许&lt;strong&gt;一个&lt;/strong&gt;交互式 RDP 会话，第二个连接会把第一个从服务端踢掉，旧会话收到 &lt;code&gt;ERRINFO_RPC_INITIATED_DISCONNECT&lt;/code&gt;（&quot;initiated by an administrative tool on the server in another session&quot;）。实测依次启动 Excel → Outlook → Word，每个都登录成功、窗口正常弹出，但前一个在 10–40 秒内被踢下线。所谓「RemoteApp 能多会话」是 Windows Server / RDS 才有的行为。&lt;/p&gt;
&lt;p&gt;单会话下的三种编排：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;编排&lt;/th&gt;
&lt;th&gt;做法&lt;/th&gt;
&lt;th&gt;代价&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;串行（默认）&lt;/td&gt;
&lt;td&gt;每个应用一个图标，一次用一个&lt;/td&gt;
&lt;td&gt;点新图标会关掉旧应用，&lt;strong&gt;未保存文档会丢&lt;/strong&gt; → 启动器里加一步 zenity 确认&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;会话宿主&lt;/td&gt;
&lt;td&gt;只连一次，&lt;code&gt;/app:&lt;/code&gt; 给 &lt;code&gt;explorer.exe&lt;/code&gt; 或 PowerShell，从里面再启动 Office&lt;/td&gt;
&lt;td&gt;点图标不再直达应用，需两步&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;真并发&lt;/td&gt;
&lt;td&gt;换 Windows Server（RDS）&lt;/td&gt;
&lt;td&gt;许可成本；RDPWrap 一类多会话补丁违反客户端许可，24H2 上还频繁失效&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;关键一点：&lt;code&gt;/app:&lt;/code&gt; 只决定&lt;strong&gt;自动启动哪个程序&lt;/strong&gt;，不限制一个会话里跑多少程序 —— RAIL 会把这个会话内所有顶层窗口都映射成原生窗口。所以「会话宿主」编排理论上也能把 Word/Excel/PPT 全变成独立窗口（需实测确认，因为客户端只监视被明确要求的窗口集）。&lt;/p&gt;
&lt;h3&gt;8.2 启动器脚本&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;win11-run&lt;/code&gt; 接受应用名或任意 exe 路径，负责：VM 没开就开 → 从 libvirt DHCP 租约拿 guest IP → 等 3389 可连 → 起 xfreerdp3。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 关键片段
VQ() { sg libvirt -c &quot;virsh -c qemu:///system $*&quot; 2&amp;gt;/dev/null; }   # 组权限没生效时的兜底
vm_running() { VQ domstate &quot;$VM&quot; | grep -q running; }
guest_ip() { VQ domifaddr &quot;$VM&quot; --source lease | awk &apos;/ipv4/ &amp;amp;&amp;amp; $4 ~ /^192\.168\./ {split($4,a,&quot;/&quot;); print a[1]; exit}&apos;; }

declare -A APPS=(
    [word]=&apos;C:\Program Files\Microsoft Office\root\Office16\WINWORD.EXE&apos;
    [excel]=&apos;C:\Program Files\Microsoft Office\root\Office16\EXCEL.EXE&apos;
    [powerpoint]=&apos;C:\Program Files\Microsoft Office\root\Office16\POWERPNT.EXE&apos;
    [outlook]=&apos;C:\Program Files\Microsoft Office\root\Office16\OUTLOOK.EXE&apos;
    [explorer]=&apos;C:\Windows\explorer.exe&apos;
)
exec xfreerdp3 /v:&quot;$ip&quot; /u:&quot;$user&quot; /p:&quot;$pass&quot; /d: /cert:ignore /sec:tls \
     /dynamic-resolution /clipboard +auto-reconnect \
     /app:&quot;program:${APPS[$target]:-$target}&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;两个实测出来的补充（完整脚本见 &lt;code&gt;~/.local/share/win-office/win-office-run&lt;/code&gt;）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/sec:tls&lt;/code&gt; 不能省，否则默认 NLA 路径会在 &lt;code&gt;nla_recv_pdu&lt;/code&gt; 死掉。&lt;/li&gt;
&lt;li&gt;起客户端&lt;strong&gt;之前&lt;/strong&gt;先检查有没有别的 &lt;code&gt;xfreerdp3&lt;/code&gt; 在跑，有就弹 zenity 确认再 &lt;code&gt;kill -TERM&lt;/code&gt; + &lt;code&gt;kill -9&lt;/code&gt;（因为新会话会把它踢掉，不收拾就留孤儿窗口）。退出码非 0 不一定算失败，见 §8.1 最后一条。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;冷启动链路：点图标 → VM 从关机状态拉起 → 约 60 秒出窗口（Windows 启动占大头）。&lt;/p&gt;
&lt;h3&gt;8.3 桌面图标&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;install-launchers.sh&lt;/code&gt; 在 &lt;code&gt;~/.local/share/applications/win11/&lt;/code&gt; 生成 5 个 .desktop。图标用系统主题里确定存在的（&lt;code&gt;libreoffice-writer/calc/impress&lt;/code&gt;、&lt;code&gt;internet-mail&lt;/code&gt;、&lt;code&gt;folder&lt;/code&gt;），不要指望 &lt;code&gt;x-office-*&lt;/code&gt; 这类旧名在所有主题里都有。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;9. 排障手册&lt;/h2&gt;
&lt;h3&gt;9.1 一个可复用的「无头排障通道」&lt;/h3&gt;
&lt;p&gt;把自己的工作目录通过 RDP 磁盘重定向映射进 Windows，然后起一个 RemoteApp PowerShell：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;xfreerdp3 /v:&quot;$ip&quot; /u:&quot;$user&quot; /p:&quot;$pass&quot; /d: /cert:ignore /clipboard /drive:host,&quot;$HOME/.local/share/win11&quot; \
          /app:&quot;program:C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Windows 里就能 &lt;code&gt;\\tsclient\host\xxx.ps1&lt;/code&gt; 读写宿主文件 → &lt;strong&gt;脚本和日志双向流转，不用截图读文本&lt;/strong&gt;。这是排查的主力手段。&lt;/p&gt;
&lt;p&gt;驱动输入时注意两件事：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;中文 IME 会把 &lt;code&gt;xdotool type&lt;/code&gt; 打的反斜杠变成中文候选&lt;/strong&gt;（&lt;code&gt;\tsclient&lt;/code&gt; 打成 &lt;code&gt;敌人、同时client&lt;/code&gt;）。可靠做法：&lt;code&gt;xclip -selection clipboard &amp;lt; 命令文件&lt;/code&gt;，然后在窗口里 &lt;code&gt;Ctrl+V&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;xdotool windowactivate&lt;/code&gt; 前先确认真实窗口标题；焦点了浏览器会把命令粘到地址栏。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;9.2 窗口卡死关不掉（&lt;code&gt;RAIL:0006020E&lt;/code&gt; 之类）&lt;/h3&gt;
&lt;p&gt;现象：一个标题像 &lt;code&gt;RAIL:xxxx&lt;/code&gt; 或空标题的窗口，点叉没反应。&lt;/p&gt;
&lt;p&gt;原因：FreeRDP 客户端进程僵死，占着若干孤儿窗口，连 SIGTERM 都不理。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pgrep -ax xfreerdp3          # 找 PID
kill -9 &amp;lt;pid&amp;gt;                # SIGTERM 无效时直接 -9
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不建议用 &lt;code&gt;pkill -f &amp;lt;模式&amp;gt;&lt;/code&gt;：模式串常常也出现在&lt;strong&gt;你自己这条命令行&lt;/strong&gt;里，会把自己的 shell 一起杀掉（表现为命令返回 143）。同理，排查时不要对同一个 Windows 会话开第二个「整桌面」RDP 连接——它会挤掉正在用的那个，并留下孤儿窗口。&lt;/p&gt;
&lt;h3&gt;9.3 Office 登录卡在「正在加载…」&lt;/h3&gt;
&lt;p&gt;不是网络问题（先确证：&lt;code&gt;Invoke-WebRequest https://login.microsoftonline.com/&lt;/code&gt; 返回 200 即可排除 DNS/代理）。真实原因是这些对话框以&lt;strong&gt;独立顶层窗口&lt;/strong&gt;出现，RAIL 有时先不映射它们：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;《接受许可协议》&lt;/strong&gt;——Office 首次运行的第一道弹窗，它没被映射出来时，Word 看起来就像死在那儿；&lt;/li&gt;
&lt;li&gt;登录容器本身。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;处理：等它把弹窗映射出来（一般十几秒到一分钟）、依次点掉协议与隐私设置，再登录。判断是否已登录成功，最直接的证据是 Word 开始页「最近」列表刷出了 OneDrive 里的文件。&lt;/p&gt;
&lt;h3&gt;9.4 其他&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;症状&lt;/th&gt;
&lt;th&gt;处理&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Failed to open ... Permission denied&lt;/code&gt;（seed ISO 在 &lt;code&gt;~&lt;/code&gt; 下）&lt;/td&gt;
&lt;td&gt;介质放到 &lt;code&gt;/var/lib/libvirt/images/iso/&lt;/code&gt;；libvirt-qemu 进不去家目录&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;xorriso FAILURE ... Permission denied&lt;/code&gt; 重新打包时&lt;/td&gt;
&lt;td&gt;上次生成的 ISO 已被 libvirt chown 给 qemu，先 &lt;code&gt;rm -f&lt;/code&gt; 再打&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;安装程序停在语言页/密钥页&lt;/td&gt;
&lt;td&gt;见 4.3 的 1、2、3 条&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;装完每次重启都等 5 秒&lt;/td&gt;
&lt;td&gt;正常：安装光盘仍挂在第一位，无按键即回落硬盘。想彻底避免就把 &lt;code&gt;&amp;lt;boot order&amp;gt;&lt;/code&gt; 交换&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;10. 已知限制&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Windows 未激活&lt;/strong&gt;：右下角水印、不能改外观，其余功能与更新正常，不会强制关机。要激活就在设置里输密钥或用绑定微软账号的数字授权。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;会话与并发的边界&lt;/strong&gt;：Windows 客户端版只支持&lt;strong&gt;一个&lt;/strong&gt; RDP 会话。既不能 RemoteApp 多应用并发，也不能再叠加一个「整桌面 RDP 连接」——新连接一律踢掉旧会话（&lt;code&gt;ERRINFO_RPC_INITIATED_DISCONNECT&lt;/code&gt;）。被踢的那一端进程常常不退出，只留下点不掉的孤儿窗口，见 §9.2。想真并发只能上 Windows Server / RDS。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Windows 更新可能在你用时重启虚拟机&lt;/strong&gt;。要稳就改成仅通知。&lt;/li&gt;
&lt;li&gt;365 登录只需做一次（令牌留在 VM 的用户配置里，之后重启依然有效）；换 VM 就得重来。&lt;/li&gt;
&lt;li&gt;Office 装在 VM 里算一台设备，受订阅的 5 台设备上限约束。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;11. 文件清单（&lt;code&gt;~/.local/share/win11/&lt;/code&gt;）&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;文件&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get-win11-iso.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;走微软官方 API 取 Windows 11 ISO 直链&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;media.conf&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Windows ISO 路径（Office 走在线安装，无需 ISO）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;autounattend.xml.tmpl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无人值守答案文件模板（含 &lt;code&gt;__WIN_PASS__&lt;/code&gt; / &lt;code&gt;__COMPUTER_NAME__&lt;/code&gt; 占位）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;post-install.ps1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;首次登录后置：RDP/RemoteApp/电源 + 365 静默安装（&lt;strong&gt;UTF-8 BOM&lt;/strong&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;create-vm.sh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;生成 seed ISO → 建盘 → 渲染 XML → 定义并启动&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;win11.xml.tmpl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;域定义模板&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;credentials.env&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;生成的 Windows 账户与密码（600，勿外传）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;win11-run&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;RemoteApp 启动器（应用名 → 原生窗口）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;install-launchers.sh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;生成 5 个桌面图标&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rdp-desktop&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;整桌面窗口（一次性调试/排障用，&lt;strong&gt;不要与日常窗口同时用&lt;/strong&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;win11-shell&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;带宿主共享盘的 RemoteApp PowerShell（排障主力）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;seed/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;渲染后的答案文件与脚本（打包用）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;重建流程：删掉 &lt;code&gt;win11.qcow2&lt;/code&gt;、&lt;code&gt;credentials.env&lt;/code&gt;，跑 &lt;code&gt;create-vm.sh&lt;/code&gt;，然后按 §6.1 发按键；装完执行一次 &lt;code&gt;install-launchers.sh&lt;/code&gt;。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;12. 与上游方案的关系&lt;/h2&gt;
&lt;p&gt;WinApps 做的是同一件事，但把 FreeRDP 包进 Docker 容器里再加一层 &lt;code&gt;winapps&lt;/code&gt; 命令行。本方案省掉容器层，代价是所有细节要自己掌握——也就是本文档存在的理由。若哪天需要 &lt;code&gt;winapps&lt;/code&gt; 的模块化配置（多应用清单、&lt;code&gt;/app:&lt;/code&gt; 参数自动推导），可以迁过去，虚拟机和 Windows 侧配置全部复用。&lt;/p&gt;
</content:encoded></item><item><title>AIC8800 USB 网卡在 6.8 内核上启用 WiFi + 蓝牙</title><link>https://fenlyin.dpdns.org/posts/aic8800-wifi-driver/</link><guid isPermaLink="true">https://fenlyin.dpdns.org/posts/aic8800-wifi-driver/</guid><description>从「插上看不见网卡」到 WiFi 与蓝牙同时可用，每一步都带命令、预期输出和失败分支。</description><pubDate>Sat, 24 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;从&quot;插上看不见网卡&quot;到&quot;WiFi + BT 同时可用&quot;的完整过程。每一步都给出命令、预期输出和失败分支，照着走可复现。&lt;/p&gt;
&lt;h2&gt;0. 硬件与环境指纹（决定后面所有配置）&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;项目&lt;/th&gt;
&lt;th&gt;值&lt;/th&gt;
&lt;th&gt;怎么确认&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;设备三段 PID&lt;/td&gt;
&lt;td&gt;&lt;code&gt;a69c:5730&lt;/code&gt;（U 盘）→ &lt;code&gt;a69c:8d80&lt;/code&gt;（bootrom）→ &lt;code&gt;2604:0023&lt;/code&gt;（WiFi+BT）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lsusb&lt;/code&gt;，每次拔插看一眼&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;芯片族/变种&lt;/td&gt;
&lt;td&gt;&lt;code&gt;chip_id=7, chip_mcu_id = 1&lt;/code&gt;（&lt;strong&gt;MCU1&lt;/strong&gt;，关键）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dmesg | grep chip_mcu_id&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;接口拓扑（正常态）&lt;/td&gt;
&lt;td&gt;3 个：&lt;code&gt;e0/01/01&lt;/code&gt;×2 → &lt;code&gt;btusb&lt;/code&gt;，&lt;code&gt;ff/ff/ff&lt;/code&gt; → &lt;code&gt;aic8800_fdrv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lsusb -t&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;内核&lt;/td&gt;
&lt;td&gt;Ubuntu 24.04，&lt;code&gt;6.8.0-147-generic&lt;/code&gt;，gcc-13&lt;/td&gt;
&lt;td&gt;&lt;code&gt;uname -r&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secure Boot&lt;/td&gt;
&lt;td&gt;关闭（否则未签名模块拒载）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;mokutil --sb-state&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;驱动上游&lt;/td&gt;
&lt;td&gt;&lt;code&gt;goecho/aic8800_linux_drvier&lt;/code&gt;（厂商 dump + 少量社区修补）&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;前置依赖：&lt;code&gt;linux-headers-$(uname -r)&lt;/code&gt;、&lt;code&gt;gcc-13&lt;/code&gt;、&lt;code&gt;make&lt;/code&gt;、&lt;code&gt;eject&lt;/code&gt;、&lt;code&gt;bluez&lt;/code&gt;（&lt;code&gt;bluetoothctl&lt;/code&gt;）。&lt;/p&gt;
&lt;h2&gt;1. 复现步骤&lt;/h2&gt;
&lt;h3&gt;Step 0：确认设备阶段&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;lsusb | grep -iE &quot;a69c|2604&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;只有 &lt;code&gt;a69c:5730&lt;/code&gt; → 还在 U 盘模式，&lt;code&gt;sudo eject /dev/sdX&lt;/code&gt;（盘标 TENDA）切出来&lt;/li&gt;
&lt;li&gt;出现 &lt;code&gt;a69c:8d80&lt;/code&gt; → bootrom，可以往下走&lt;/li&gt;
&lt;li&gt;已是 &lt;code&gt;2604:0023&lt;/code&gt; → 芯片里已有固件在跑，改动后需冷插拔才生效&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Step 1：取源码、开分支&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;git clone https://github.com/goecho/aic8800_linux_drvier
cd aic8800_linux_drvier
git switch -c dev          # main 保持纯净，所有改动放 dev
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Step 2：四处代码改动&lt;/h3&gt;
&lt;p&gt;四个改动互相独立，缺任一处都表现为&quot;WiFi 正常、蓝牙没有&quot;，所以建议按顺序改、按顺序验证。&lt;/p&gt;
&lt;h4&gt;2.1 cfg80211 签名守卫：&lt;code&gt;&amp;gt;= 6.0&lt;/code&gt; 是错的，应为 &lt;code&gt;6.17&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;satic&lt;/code&gt; 检查过 v6.8/v6.9/6.10/6.12/6.13/6.15/6.16（旧签名）与 v6.17/v6.19（新签名）：&lt;code&gt;radio_idx&lt;/code&gt; / &lt;code&gt;link_id&lt;/code&gt; 参数&lt;strong&gt;只有 6.17 才出现&lt;/strong&gt;。上游写成 &lt;code&gt;&amp;gt;= 6.0&lt;/code&gt; 导致 6.8–6.16 全部编译失败（&lt;code&gt;incompatible pointer type&lt;/code&gt; / &lt;code&gt;too many arguments&lt;/code&gt;）。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;drivers/aic8800/aic8800_fdrv/rwnx_compat.h&lt;/code&gt; 增加：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* These specific cfg80211 entry points only gained radio_idx / link_id in
 * 6.17; other ops such as stop_ap carry link_id much earlier, so keep this
 * per call site. link_id is the last arg of cfg80211_cac_event() but comes
 * before gfp in cfg80211_rx_*_frame(), hence two helpers. */
#if LINUX_VERSION_CODE &amp;gt;= KERNEL_VERSION(6, 17, 0)
#define RWNX_RADIO_IDX_ARG      int radio_idx,
#define RWNX_LINK_ID_ARG        -1,
#define RWNX_CAC_LINK_ID_ARG    , 0
#else
#define RWNX_RADIO_IDX_ARG
#define RWNX_LINK_ID_ARG
#define RWNX_CAC_LINK_ID_ARG
#endif
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;替换调用/定义：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* rwnx_main.c */
static int rwnx_cfg80211_set_wiphy_params(struct wiphy *wiphy, RWNX_RADIO_IDX_ARG u32 changed)
                                      RWNX_RADIO_IDX_ARG enum nl80211_tx_power_setting type, int mbm)
/* start_radar_detection 的 #if 判断从 6,0,0 改成 6,17,0；新形参改名为 int link_id */

/* rwnx_radar.c */
cfg80211_cac_event(..., GFP_KERNEL RWNX_CAC_LINK_ID_ARG);

/* rwnx_rx.c */
cfg80211_rx_spurious_frame(vif-&amp;gt;ndev, hdr-&amp;gt;addr2, RWNX_LINK_ID_ARG GFP_ATOMIC);
cfg80211_rx_unexpected_4addr_frame(vif-&amp;gt;ndev, sta-&amp;gt;mac_addr, RWNX_LINK_ID_ARG GFP_ATOMIC);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2.2 补 Tenda &lt;code&gt;2604:0023&lt;/code&gt;，且只匹配 WiFi 接口&lt;/h4&gt;
&lt;p&gt;这颗是 combo，三条 interface 里两条是蓝牙。用 &lt;code&gt;USB_DEVICE&lt;/code&gt; 会连带匹配蓝牙接口（靠枚举顺序才没抢到，不可依赖）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* aicwf_usb.h */
#define USB_PRODUCT_ID_TENDA_D80        0x0023

/* aicwf_usb.c : aicwf_usb_id_table[] */
{USB_DEVICE_AND_INTERFACE_INFO(USB_VENDOR_ID_TENDA, USB_PRODUCT_ID_TENDA_D80,
                               0xff, 0xff, 0xff)},
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;aicwf_usb_chipmatch()&lt;/code&gt; 已有 &lt;code&gt;vid == USB_VENDOR_ID_TENDA&lt;/code&gt; → &lt;code&gt;PRODUCT_ID_AIC8800D81&lt;/code&gt;，不用改。&lt;/p&gt;
&lt;h4&gt;2.3 打开 BT 补丁上传&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;drivers/aic8800/aic_load_fw/aic_compat_8800d80.c&lt;/code&gt; 的 8800D80 U02 路径里，四步被 &lt;code&gt;#if 0&lt;/code&gt; 关着。删掉外层 &lt;code&gt;#if 0&lt;/code&gt; 与其配套 &lt;code&gt;#endif&lt;/code&gt;（连同包住 patch table 的内层 &lt;code&gt;#if 0/#else/#endif&lt;/code&gt;），使其变成：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if(rwnx_plat_bin_fw_upload_android(usb_dev, patch_info.addr_adid,  FW_ADID_BASE_NAME_8800D80_U02))  return -1;
if(rwnx_plat_bin_fw_upload_android(usb_dev, patch_info.addr_patch, FW_PATCH_BASE_NAME_8800D80_U02)) return -1;
if (aicbt_ext_patch_data_load(usb_dev, &amp;amp;patch_info, FW_PATCH_BASE_NAME_8800D80_U02_EXT))            return -1;
if (aicbt_patch_table_load(usb_dev, head))                                                          return -1;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2.4 MCU1 必须开 cache（最隐蔽的一处）&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;drivers/aic8800/aic_load_fw/aic_compat_8800d80.c&lt;/code&gt; 的 &lt;code&gt;system_config_8800d80()&lt;/code&gt; 里，打印 &lt;code&gt;chip_mcu_id&lt;/code&gt; 之后、写 syscfg 表之前插入：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const u32 cache_mem_addr = 0x40100020;
if (chip_mcu_id) {
    ret = rwnx_send_dbg_mem_read_req(usb_dev, cache_mem_addr, &amp;amp;rd_mem_addr_cfm);
    if (ret) { printk(&quot;%x rd fail: %d\n&quot;, cache_mem_addr, ret); return ret; }
    rd_mem_addr_cfm.memdata |= 0x01;
    ret = rwnx_send_dbg_mem_write_req(usb_dev, cache_mem_addr, rd_mem_addr_cfm.memdata);
    if (ret) { printk(&quot;%x write fail: %d\n&quot;, cache_mem_addr, ret); return ret; }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;MCU1 的 BT 核从 cache 取指，没有这一步 WiFi 固件照跑、BT 核一执行就死。&lt;/p&gt;
&lt;h4&gt;2.5 U 盘模式自动 eject（可选但强烈建议）&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;tools/aic.rules&lt;/code&gt; 追加一行（&lt;strong&gt;必须单行&lt;/strong&gt;，udev 不支持续行）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;KERNEL==&quot;sd*&quot;, ATTRS{idVendor}==&quot;a69c&quot;, ATTRS{idProduct}==&quot;5730&quot;,  SYMLINK+=&quot;tendaudisk&quot;, RUN+=&quot;/usr/bin/eject /dev/%k&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Step 3：固件必须成对替换&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;fw/aic8800D80/&lt;/code&gt; 里旧的一套&lt;strong&gt;没有 BT 描述项&lt;/strong&gt;。&lt;code&gt;fw_patch_table&lt;/code&gt; 是 BT 的&quot;描述表&quot;：旧版 984 字节、&lt;code&gt;info_len=4&lt;/code&gt;，没有任何 &lt;code&gt;ext_patch&lt;/code&gt; 项，于是 &lt;code&gt;aicbt_ext_patch_data_load()&lt;/code&gt; 循环 0 次、&lt;code&gt;ext0.bin&lt;/code&gt; 永远不会被上传，&lt;strong&gt;日志里连错误都没有&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;替换后的 18 个文件（&lt;code&gt;fw/aic8800D80/&lt;/code&gt;）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;aic_powerlimit_8800d80.txt            3688
aic_userconfig_8800d80.txt            2724     ← 旧 2448
aic_userconfig_8800d80_u11.txt        2724
aic_userconfig_8800d80_u11_cus.txt    2724
aic_userconfig_8800d80_u11_pro.txt    2724
calibmode_8800d80.bin                17076
fmacfw_8800d80_h_u02.bin            358640
fmacfw_8800d80_h_u02_ipc.bin        352268
fmacfw_8800d80_u02.bin              358072     ← 旧 327620
fmacfw_8800d80_u02_ipc.bin          351828     ← 旧 326033
fw_adid_8800d80_u02.bin               1708
fw_ble_scan_ad_filter.bin           371284     ← 旧 328508
fw_patch_8800d80_u02.bin             32700     ← 旧 25300
fw_patch_8800d80_u02_ext0.bin        16136     ← 新增（BT 补丁本体）
fw_patch_8800d80_u04.bin              4800
fw_patch_table_8800d80_u02.bin        1384     ← 旧 984（关键）
fw_patch_table_8800d80_u04.bin         416
lmacfw_rf_8800d80_u02.bin           283846     ← 旧 227839
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不能只换 &lt;code&gt;fw_patch_table&lt;/code&gt;：表里的 BT 地址（如 &lt;code&gt;0x1e7e9c&lt;/code&gt;）属于配套的 &lt;code&gt;fw_patch_8800d80_u02.bin&lt;/code&gt; 布局，混版本会静默错位。&lt;/p&gt;
&lt;h3&gt;Step 4：编译&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;make -C drivers/aic8800 clean
make -C drivers/aic8800
ls -l drivers/aic8800/*/*.ko
modinfo -F vermagic drivers/aic8800/aic8800_fdrv/aic8800_fdrv.ko   # 必须等于 uname -r
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;预期两个 &lt;code&gt;.ko&lt;/code&gt; 产出，无 &lt;code&gt;error:&lt;/code&gt;。常见编译报错都对应 2.1 的四处签名。&lt;/p&gt;
&lt;h3&gt;Step 5：安装&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;sudo make install          # 必须在仓库根目录（脚本用相对路径 ./fw ./tools）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;它做三件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;两个 &lt;code&gt;.ko&lt;/code&gt; → &lt;code&gt;/lib/modules/$(uname -r)/kernel/drivers/net/wireless/aic8800/&lt;/code&gt; + &lt;code&gt;depmod&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fw/aic8800D80/&lt;/code&gt; → &lt;code&gt;/lib/firmware/&lt;/code&gt;，&lt;code&gt;tools/aic.rules&lt;/code&gt; → &lt;code&gt;/etc/udev/rules.d/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;udevadm trigger&lt;/code&gt; / &lt;code&gt;control --reload&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;开机自加载：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;echo aic8800_fdrv | sudo tee /etc/modules-load.d/aic8800.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;aic8800_fdrv&lt;/code&gt; 的依赖是 &lt;code&gt;cfg80211,aic_load_fw&lt;/code&gt;，一条 &lt;code&gt;modprobe aic8800_fdrv&lt;/code&gt; 就够了。&lt;/p&gt;
&lt;h3&gt;Step 6：冷插拔（必须物理拔插）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;sudo modprobe -r aic8800_fdrv aic_load_fw
sudo modprobe cfg80211        # 注意：modprobe -r 会连带卸掉 cfg80211
sudo modprobe aic_load_fw     # 预加载 loader，保证设备出现在 8d80 前它就绪
# 现在拔下网卡、等 3 秒、插回
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;不能用 &lt;code&gt;/sys/bus/usb/devices/*/authorized = 0/1&lt;/code&gt; 代替&lt;/strong&gt;：它只让设备重新枚举，不给芯片断电，固件仍在上一次状态里跑，测不出任何东西。&lt;/p&gt;
&lt;h3&gt;Step 7：验证&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;lsusb -t | sed -n &apos;/Port [0-9]*, Class=Wireless\|Vendor/p&apos;   # 2 个 Wireless + 1 个 Vendor
bluetoothctl show | head -4                                   # Manufacturer / Version 有值
ip -br link | grep -E &quot;wlx|wlan&quot;                              # WiFi 接口存在
rfkill list | grep -A2 -i bluetooth                           # 未阻塞
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一次成功时 &lt;code&gt;journalctl -k&lt;/code&gt; 里的关键行（顺序即真实流程）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;chip_id=7, chip_mcu_id = 1
### Upload fw_patch_table_8800d80_u02.bin fw_patch_table, size=1384
aicbt_patch_info_unpack ext patch id:0 addr:20b43c
### Upload fw_adid_8800d80_u02.bin firmware, @ = 201940  size=1708
### Upload fw_patch_8800d80_u02.bin firmware, @ = 1e0000  size=32700
aicbt_ext_patch_data_load fw_patch_8800d80_u02_ext0.bin id:0 addr:20b43c
### Upload fw_patch_8800d80_u02_ext0.bin firmware, @ = 20b43c  size=16136
aicbt_patch_table_load bt btmode[4]:5
patch version - ...
### Upload fmacfw_8800d80_u02.bin firmware, @ = 120000  size=358072
usb 1-7: Product: AIC 8800D80
usbcore: registered new interface driver btusb
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. 三个坑的判据（症状几乎一样，根因完全不同）&lt;/h2&gt;
&lt;p&gt;三种故障都表现为&quot;WiFi 能用、蓝牙没有&quot;，只看结果无法区分，必须按下面顺序逐层看：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;层&lt;/th&gt;
&lt;th&gt;判据&lt;/th&gt;
&lt;th&gt;根因&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;① 代码&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lsusb -t&lt;/code&gt; 只有 &lt;strong&gt;1&lt;/strong&gt; 个接口（&lt;code&gt;ff/ff/ff&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;BT 上传那段 &lt;code&gt;#if 0&lt;/code&gt; 没打开&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;② 固件&lt;/td&gt;
&lt;td&gt;3 个接口，但日志&lt;strong&gt;没有&lt;/strong&gt; &lt;code&gt;ext_patch&lt;/code&gt; / &lt;code&gt;size=984&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;patch table 是旧版，缺 ext_patch 描述项&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;③ 硬件变种&lt;/td&gt;
&lt;td&gt;3 个接口 + &lt;code&gt;ext0&lt;/code&gt; 已上传 + &lt;code&gt;btusb&lt;/code&gt; 已绑，仍报 &lt;code&gt;hci0: Opcode 0x0c03 failed: -110&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;chip_mcu_id=1&lt;/code&gt; 未开 cache&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;0x0c03&lt;/code&gt; = HCI_Reset，&lt;code&gt;-110&lt;/code&gt; = ETIMEDOUT；此时 &lt;code&gt;bluetoothctl&lt;/code&gt; 报 &lt;code&gt;No default controller available&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;3. 排障速查&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;现象&lt;/th&gt;
&lt;th&gt;原因 / 处理&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;too many arguments&lt;/code&gt; / &lt;code&gt;incompatible pointer type&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;cfg80211 签名版本边界，见 2.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;固件下载成功但无 &lt;code&gt;wlan*&lt;/code&gt;，&lt;code&gt;lsusb&lt;/code&gt; 显示新 PID 且 &lt;code&gt;driver=none&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;USB ID 表缺该 PID，见 2.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;上电停在 &lt;code&gt;a69c:5730&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;没 eject；&lt;code&gt;sudo eject /dev/sdX&lt;/code&gt; 或补 udev 规则&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Unknown symbol cfg80211_*&lt;/code&gt;（&lt;code&gt;insmod&lt;/code&gt; 时报）&lt;/td&gt;
&lt;td&gt;先 &lt;code&gt;modprobe cfg80211&lt;/code&gt;，或改用 &lt;code&gt;modprobe&lt;/code&gt; 而非 &lt;code&gt;insmod&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hci0: Opcode 0x0c03 failed: -110&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;见 2.4（MCU1 cache）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;蓝牙能用、重启后又没了&lt;/td&gt;
&lt;td&gt;&lt;code&gt;modules-load.d&lt;/code&gt; 没配，或 loader 未更新&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;加载成功但无射频/连不上&lt;/td&gt;
&lt;td&gt;板级配置：&lt;code&gt;aic8800_fdrv/Makefile&lt;/code&gt; 的 &lt;code&gt;CONFIG_VRF_DCDC_MODE&lt;/code&gt; 与 &lt;code&gt;CONFIG_PMIC_SETTING&lt;/code&gt; 要对调试&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;换内核后模块消失&lt;/td&gt;
&lt;td&gt;out-of-tree，需重新 &lt;code&gt;make &amp;amp;&amp;amp; sudo make install&lt;/code&gt;，长期方案 DKMS&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;日志开关：&lt;code&gt;aic_load_fw&lt;/code&gt; / &lt;code&gt;aic8800_fdrv&lt;/code&gt; 都有 &lt;code&gt;aicwf_dbg_level&lt;/code&gt;（默认 &lt;code&gt;LOGERROR|LOGINFO&lt;/code&gt;）；参考实现默认开到 &lt;code&gt;LOGDEBUG|LOGTRACE&lt;/code&gt;，排查时可 &lt;code&gt;insmod ... aicwf_dbg_level=31&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;两个不能当判据的东西&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/sys/class/bluetooth/hci0/{address,type}&lt;/code&gt; 在这套 btusb 组合下恒为空文件，用 &lt;code&gt;bluetoothctl show&lt;/code&gt; 才准；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;authorized 0/1&lt;/code&gt; 不等于断电重启，验证&quot;改动是否生效&quot;必须物理拔插。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;4. 验收清单&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;lsmod&lt;/code&gt;：&lt;code&gt;aic8800_fdrv&lt;/code&gt; + &lt;code&gt;aic_load_fw&lt;/code&gt;（后者的 srcversion 必须等于 &lt;code&gt;drivers/aic8800/aic_load_fw/aic_load_fw.ko&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lsusb -t&lt;/code&gt;：&lt;code&gt;e0/01/01&lt;/code&gt;×2 → &lt;code&gt;btusb&lt;/code&gt;，&lt;code&gt;ff/ff/ff&lt;/code&gt; → &lt;code&gt;aic8800_fdrv&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bluetoothctl show&lt;/code&gt;：有 &lt;code&gt;Manufacturer&lt;/code&gt; / &lt;code&gt;Version&lt;/code&gt;（本机为 &lt;code&gt;0x0b3b&lt;/code&gt; / &lt;code&gt;0x0d&lt;/code&gt;，即 AIC、HCI 5.3）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bluetoothctl scan on&lt;/code&gt; 能看到设备；&lt;code&gt;ip -br link&lt;/code&gt; 有 WiFi 接口，&lt;code&gt;nmcli device wifi list&lt;/code&gt; 能扫到 AP（2.4G + 5G）&lt;/li&gt;
&lt;li&gt;未连 AP 时 &lt;code&gt;ip link&lt;/code&gt; 显示 &lt;code&gt;DORMANT&lt;/code&gt; / &lt;code&gt;NO-CARRIER&lt;/code&gt; 属正常&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;5. 内核升级：能升到多新&lt;/h2&gt;
&lt;p&gt;实测（容器里用真实内核头文件编译，不动宿主）：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;内核&lt;/th&gt;
&lt;th&gt;结果&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;6.8.0-147（Ubuntu 24.04）&lt;/td&gt;
&lt;td&gt;✅ 生产使用中&lt;/td&gt;
&lt;td&gt;WiFi + BT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6.17.0-41（Ubuntu 25.10）&lt;/td&gt;
&lt;td&gt;✅ 编译通过，两个 &lt;code&gt;.ko&lt;/code&gt; 都产出&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;gt;=6.17&lt;/code&gt; 守卫生效，6.8 那套补丁在 6.17 上是惰性的&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7.0.0-38（Ubuntu 26.04）&lt;/td&gt;
&lt;td&gt;✅ 编译通过&lt;/td&gt;
&lt;td&gt;&lt;code&gt;add_key&lt;/code&gt; 等仍是 &lt;code&gt;struct net_device *&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7.1.x&lt;/td&gt;
&lt;td&gt;❌ 编译失败&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cfg80211_ops&lt;/code&gt; 从 7.1 起改用 &lt;code&gt;struct wireless_dev *&lt;/code&gt;（已核 v7.1 头文件）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7.2.11（Arch / Fedora 43、44）&lt;/td&gt;
&lt;td&gt;❌ 编译失败&lt;/td&gt;
&lt;td&gt;在 7.1 基础上，&lt;code&gt;strncpy&lt;/code&gt; 也从 &lt;code&gt;include/linux/string.h&lt;/code&gt; 移除&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;结论：&lt;strong&gt;6.8 → 7.0 都不需改代码&lt;/strong&gt;，分界线在 &lt;strong&gt;7.1&lt;/strong&gt;（ops 签名）和 &lt;strong&gt;7.2&lt;/strong&gt;（&lt;code&gt;strncpy&lt;/code&gt;）。&lt;/p&gt;
&lt;p&gt;复现方法（宿主零改动）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git archive dev | tar -x -C /tmp/k17check
docker run --rm -v /tmp/k17check:/src -w /src ubuntu:25.10 bash -lc \
  &apos;apt-get update -qq &amp;amp;&amp;amp; apt-get install -yqq linux-headers-generic build-essential &amp;amp;&amp;amp; \
   K=$(ls -d /usr/src/linux-headers-*generic | tail -1) &amp;amp;&amp;amp; \
   make -C drivers/aic8800 KDIR=$K&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;升到 6.17 只需重装模块（固件在 &lt;code&gt;/lib/firmware&lt;/code&gt; 不受内核升级影响）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;make -C drivers/aic8800 clean &amp;amp;&amp;amp; make -C drivers/aic8800 &amp;amp;&amp;amp; sudo make install
sudo systemctl restart NetworkManager      # 见第 6 节
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;真要上 7.1+ 时的改动备忘&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;cfg80211_ops&lt;/code&gt; 的 &lt;code&gt;struct net_device *&lt;/code&gt; → &lt;code&gt;struct wireless_dev *&lt;/code&gt;（7.1 起）：&lt;code&gt;add_key&lt;/code&gt; / &lt;code&gt;get_key&lt;/code&gt; / &lt;code&gt;del_key&lt;/code&gt; / &lt;code&gt;set_default_key&lt;/code&gt; / &lt;code&gt;set_default_mgmt_key&lt;/code&gt; / &lt;code&gt;add_station&lt;/code&gt; / &lt;code&gt;del_station&lt;/code&gt; / &lt;code&gt;change_station&lt;/code&gt; / &lt;code&gt;get_station&lt;/code&gt; / &lt;code&gt;dump_station&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cfg80211_new_sta()&lt;/code&gt; / &lt;code&gt;cfg80211_del_sta()&lt;/code&gt; 首参改为 &lt;code&gt;struct wireless_dev *&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;get_channel&lt;/code&gt; 多一个参数&lt;/li&gt;
&lt;li&gt;&lt;code&gt;strncpy&lt;/code&gt; → &lt;code&gt;strscpy&lt;/code&gt;（7.2 起 &lt;code&gt;string.h&lt;/code&gt; 不再声明，&lt;code&gt;rwnx_msg_tx.c:3761&lt;/code&gt; 等处）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;照 6.17 的做法全包在 &lt;code&gt;#if LINUX_VERSION_CODE &amp;gt;= KERNEL_VERSION(7, 1, 0)&lt;/code&gt;（&lt;code&gt;strncpy&lt;/code&gt; 部分用 &lt;code&gt;7, 2, 0&lt;/code&gt;）里，旧内核行为不变。&lt;/p&gt;
&lt;h2&gt;6. DKMS（选了暂不做，仅备忘）&lt;/h2&gt;
&lt;p&gt;DKMS 让模块跟着内核自动重编：源码登在 &lt;code&gt;/var/lib/dkms/&amp;lt;包&amp;gt;/&amp;lt;版本&amp;gt;/&lt;/code&gt;，内核安装/删除时由 dpkg hook 针对新内核重编一次。本仓库落地形状：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PACKAGE_NAME=&quot;aic8800&quot;
PACKAGE_VERSION=&quot;1.0&quot;
MAKE[0]=&quot;make -C drivers/aic8800 KVER=${kernelver} KDIR=/lib/modules/${kernelver}/build&quot;
BUILT_MODULE_NAME[0]=&quot;aic_load_fw&quot;
BUILT_MODULE_NAME[1]=&quot;aic8800_fdrv&quot;
DEST_MODULE_LOCATION[0..1]=&quot;/kernel/drivers/net/wireless/aic8800&quot;
AUTOINSTALL=&quot;yes&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;三个局限：① &lt;strong&gt;只管模块不管固件&lt;/strong&gt;（&lt;code&gt;/lib/firmware/aic8800D80/&lt;/code&gt; 要单独放，内核升级不影响它）；② 需要对应内核的 &lt;code&gt;linux-headers&lt;/code&gt;，自编内核没装 headers 时会静默跳过（&lt;code&gt;journalctl -u dkms&lt;/code&gt;）；③ &lt;strong&gt;不解决版本兼容&lt;/strong&gt;——7.x 上照样失败，只是失败发生在内核安装时。&lt;/p&gt;
&lt;h2&gt;7. 热点（AP 模式）“能开但扫不到”&lt;/h2&gt;
&lt;p&gt;现象：蓝牙正常、WiFi 能扫能连，但开热点后手机搜不到该 SSID，&lt;code&gt;nmcli&lt;/code&gt; 里设备却显示热点已激一。&lt;/p&gt;
&lt;h3&gt;根因：userspace 的能力缓存过期，不是驱动/固件问题&lt;/h3&gt;
&lt;p&gt;关键日志（&lt;code&gt;journalctl -u wpa_supplicant&lt;/code&gt;）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wlx...: Driver does not support AP mode
wlx...: Reject scan trigger since one is already pending
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;而同一时间 &lt;code&gt;nmcli -f WIFI-PROPERTIES device show &amp;lt;if&amp;gt;&lt;/code&gt; 报 &lt;code&gt;WIFI-PROPERTIES.AP: yes&lt;/code&gt;。一个说支持、一个说不支持，因为它们是两个不同的读法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;NM 读内核 wiphy 的 &lt;code&gt;interface_modes&lt;/code&gt;（现在时）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;wpa_supplicant&lt;/code&gt; 在&lt;strong&gt;自己启动时&lt;/strong&gt;读一次 nl80211 能力位（&lt;code&gt;WPA_DRIVER_FLAGS_AP&lt;/code&gt;）并缓存&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本机上 supplicant 是两天前启动的，而驱动今天被反复卸载/重装多次 → 缓存与现状不一致，supplicant 直接拒绝创建 AP；此时&lt;strong&gt;内核日志零记录&lt;/strong&gt;（&lt;code&gt;START_AP&lt;/code&gt; 从未到达驱动），这条就是判断“问题在 userspace 还是内核”的分水岭。&lt;/p&gt;
&lt;h3&gt;修法&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl restart NetworkManager      # ← 必须重启 NM，不只 supplicant
nmcli connection up Hotspot
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;只 &lt;code&gt;systemctl restart wpa_supplicant&lt;/code&gt; &lt;strong&gt;不够&lt;/strong&gt;（实测失败，报 &lt;code&gt;No suitable device found&lt;/code&gt;），因为 NM 自己也持有一层设备/能力状态。重启 NM 后立即可用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;内核: usb … AP started: ch=0, bcmc_idx=33 channel=2462 bw=1
nmcli: wlx…:connected:Hotspot        ip: &amp;lt;热点网关&amp;gt;/24
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;运维规则：每次重装/重载该驱动（&lt;code&gt;make install&lt;/code&gt;、拔插、换内核）后，重启一次 NetworkManager。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;8. 未完成 / 后续&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;上游 PR&lt;/strong&gt;：已向上游提两个（6.8 兼容、Tenda ID）；BT 那套（含 18 个固件文件）未提，要提的话注意二进制固件体量大。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DKMS&lt;/strong&gt;：暂不做（见第 6 节）。&lt;/li&gt;
&lt;li&gt;本文不写入 MAC / 主机名 / 局域网地址等环境信息。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;9. 这次踩坑的方法论&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;症状相同不代表根因相同&lt;/strong&gt;：三层问题都表现为“无 hci0”，只有按 ①接口数 → ②ext0 是否上传 → ③chip_mcu_id/cache 逐层取证才能定位。先建立判据，再动手。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;日志的时间顺序会骗人&lt;/strong&gt;：参考实现默认开 DEBUG，几百行 printk 拖慢了流程，一度让“代码完全相同却结果不同”看起来像时序竞争；用 &lt;code&gt;aicwf_dbg_level=31&lt;/code&gt; 复现同样的输出后，才能排除打印带来的影响。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;代码相同 ≠ 二进制相同&lt;/strong&gt;：最后是靠“函数级机器码大小差异”（&lt;code&gt;objdump -d --disassemble=&amp;lt;fn&amp;gt;&lt;/code&gt;）反查到 &lt;code&gt;system_config_8800d80&lt;/code&gt; 少了 14 行，再回到源码定位 cache 使能。源码 diff 只比函数名/结构行是不够的。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不要相信单一判据&lt;/strong&gt;：&lt;code&gt;hci0&lt;/code&gt; 文件存在不等于控制器可用（曾据此误判 PASS），&lt;code&gt;address&lt;/code&gt; 为空也不等于没控制器。判据要能区分“设备存在”和“设备可用”。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一个症状先定位到层&lt;/strong&gt;：热点走的是 userspace 栈（NM → supplicant → nl80211）。判断方法很简单——&lt;strong&gt;看内核日志有没有记录&lt;/strong&gt;：没有记录说明请求没到内核，别去改驱动；有记录再看返回值。&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>MCP 完整研究指南</title><link>https://fenlyin.dpdns.org/posts/mcp-guide/</link><guid isPermaLink="true">https://fenlyin.dpdns.org/posts/mcp-guide/</guid><description>配置文件在哪、连接怎么建立、自定义 MCP 服务器怎么部署——一次把协议摸清。</description><pubDate>Thu, 22 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;研究时间：2026-03-10&lt;/p&gt;
&lt;p&gt;本文档整理了 MCP 配置文件位置、连接原理以及自定义 MCP 服务器的部署方法。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;目录&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;#1-%E6%A6%82%E8%BF%B0&quot;&gt;概述&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#2-%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E4%BD%8D%E7%BD%AE&quot;&gt;配置文件位置&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#3-%E8%BF%9E%E6%8E%A5%E5%8E%9F%E7%90%86&quot;&gt;连接原理&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#4-%E5%A6%82%E4%BD%95%E9%83%A8%E7%BD%B2%E8%87%AA%E5%AE%9A%E4%B9%89-mcp&quot;&gt;如何部署自定义 MCP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#5-%E6%8E%A5%E5%85%A5-opencode&quot;&gt;接入 OpenCode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#6-%E6%B5%8B%E8%AF%95%E4%B8%8E%E8%B0%83%E8%AF%95&quot;&gt;测试与调试&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#7-%E5%AE%98%E6%96%B9%E8%B5%84%E6%BA%90&quot;&gt;官方资源&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;1. 概述&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;MCP (Model Context Protocol)&lt;/strong&gt; 是 Anthropic 推出的开放协议，用于连接 AI 助手与外部工具和数据源。&lt;/p&gt;
&lt;h3&gt;核心概念&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;概念&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tools&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;可调用的函数，AI 可以执行操作&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Resources&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;可读取的数据源，类似文件系统&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Prompts&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;预定义的提示模板&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Transport&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;通信层，支持 stdio 或 HTTP&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;当前已连接的 MCP&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;MCP 名称&lt;/th&gt;
&lt;th&gt;用途&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;websearch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Exa 网络搜索&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;context7&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;技术文档查询&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;grep_app&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;GitHub 代码搜索&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;2. 配置文件位置&lt;/h2&gt;
&lt;h3&gt;2.1 OpenCode 配置目录&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;~/.config/opencode/
├── opencode.json          # OpenCode 主配置（AI Provider 设置）
├── oh-my-opencode.json    # 插件配置（Agent 模型映射）
├── skills/
│   └── mcp-builder/       # MCP 构建技能
│       ├── SKILL.md       # 开发指南
│       └── reference/     # 参考文档
│           ├── python_mcp_server.md
│           ├── node_mcp_server.md
│           └── mcp_best_practices.md
└── node_modules/oh-my-opencode/
    └── dist/
        ├── mcp/           # 内置 MCP 客户端
        ├── features/
        │   ├── skill-mcp-manager/   # MCP 连接管理器
        │   └── claude-code-mcp-loader/  # Claude MCP 加载器
        └── tools/skill-mcp/  # skill_mcp 工具
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.2 Claude Desktop 配置&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;配置文件位置&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;macOS: &lt;code&gt;~/Library/Application Support/Claude/claude_desktop_config.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Windows: &lt;code&gt;%APPDATA%\Claude\claude_desktop_config.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Linux: &lt;code&gt;~/.claude.json&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;配置示例&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;mcpServers&quot;: {
    &quot;server-name&quot;: {
      &quot;type&quot;: &quot;stdio&quot;,
      &quot;command&quot;: &quot;npx&quot;,
      &quot;args&quot;: [&quot;-y&quot;, &quot;@your/mcp-server&quot;],
      &quot;env&quot;: {
        &quot;API_KEY&quot;: &quot;your-api-key&quot;
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.3 配置字段说明&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;字段&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;type&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;传输类型：&lt;code&gt;stdio&lt;/code&gt; 或 &lt;code&gt;http&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;command&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;启动命令（stdio 模式）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;args&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string[]&lt;/td&gt;
&lt;td&gt;命令参数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;env&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;object&lt;/td&gt;
&lt;td&gt;环境变量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;url&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;服务器地址（http 模式）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;headers&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;object&lt;/td&gt;
&lt;td&gt;HTTP 请求头（http 模式）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;oauth&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;object&lt;/td&gt;
&lt;td&gt;OAuth 配置&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;3. 连接原理&lt;/h2&gt;
&lt;h3&gt;3.1 架构图&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;┌─────────────────┐     JSON-RPC      ┌──────────────────┐
│   AI Client     │ ◄───────────────► │   MCP Server     │
│  (OpenCode/     │                   │  (Python/Node)   │
│   Claude)       │                   │                  │
└─────────────────┘                   └──────────────────┘
        │                                       │
        │            Transport Layer            │
        ├─ stdio    ◄────────────────────────►  │ 
        │   stdin /stdout                       │
        │                                       │
        └─ Streamable HTTP   ◄───────────────►  │
            POST / mcp                     
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.2 通信流程&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;客户端初始化&lt;/strong&gt;：创建 MCP Client 实例&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;建立连接&lt;/strong&gt;：通过 Transport 层连接服务器&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;能力协商&lt;/strong&gt;：交换支持的 Tools/Resources/Prompts&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;调用工具&lt;/strong&gt;：通过 JSON-RPC 调用服务器提供的功能&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;3.3 核心组件&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;组件&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;th&gt;TypeScript 导入&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Transport&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;通信层&lt;/td&gt;
&lt;td&gt;&lt;code&gt;StdioServerTransport&lt;/code&gt; 或 &lt;code&gt;StreamableHTTPServerTransport&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Client&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;MCP 客户端&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@modelcontextprotocol/sdk/client&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Server&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;MCP 服务器&lt;/td&gt;
&lt;td&gt;&lt;code&gt;McpServer&lt;/code&gt; 类&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tool&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;可调用函数&lt;/td&gt;
&lt;td&gt;通过 &lt;code&gt;registerTool&lt;/code&gt; 注册&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Resource&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;数据源&lt;/td&gt;
&lt;td&gt;通过 &lt;code&gt;registerResource&lt;/code&gt; 注册&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Prompt&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;提示模板&lt;/td&gt;
&lt;td&gt;通过 &lt;code&gt;registerPrompt&lt;/code&gt; 注册&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;3.4 连接代码示例&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// stdio 传输（本地）
import { StdioServerTransport } from &quot;@modelcontextprotocol/sdk/server/stdio.js&quot;;

const transport = new StdioServerTransport();
await server.connect(transport);

// HTTP 传输（远程）
import { StreamableHTTPServerTransport } from &quot;@modelcontextprotocol/sdk/server/streamableHttp.js&quot;;

const transport = new StreamableHTTPServerTransport({
  sessionIdGenerator: undefined,
  enableJsonResponse: true
});
await server.connect(transport);
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;4. 如何部署自定义 MCP&lt;/h2&gt;
&lt;h3&gt;4.1 方案 A：Python FastMCP（推荐入门）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;安装依赖&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install &quot;mcp[cli]&quot; httpx pydantic
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;完整示例&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python3
# my_mcp_server.py

from mcp.server.fastmcp import FastMCP
from pydantic import BaseModel, Field, ConfigDict
from typing import Optional
import httpx

# 初始化 MCP 服务器
mcp = FastMCP(&quot;my_service_mcp&quot;)

# 定义输入模型
class SearchInput(BaseModel):
    model_config = ConfigDict(
        str_strip_whitespace=True,
        validate_assignment=True
    )

    query: str = Field(
        ...,
        description=&quot;搜索关键词&quot;,
        min_length=2,
        max_length=200
    )
    limit: Optional[int] = Field(
        default=20,
        description=&quot;返回结果数量限制&quot;,
        ge=1,
        le=100
    )

# 注册工具
@mcp.tool(
    name=&quot;my_search&quot;,
    annotations={
        &quot;readOnlyHint&quot;: True,
        &quot;destructiveHint&quot;: False,
        &quot;idempotentHint&quot;: True,
        &quot;openWorldHint&quot;: True
    }
)
async def my_search(params: SearchInput) -&amp;gt; str:
    &quot;&quot;&quot;执行搜索操作，返回匹配的结果。

    Args:
        params: 搜索参数
            - query: 搜索关键词
            - limit: 返回结果数量

    Returns:
        str: JSON 格式的搜索结果
    &quot;&quot;&quot;
    async with httpx.AsyncClient() as client:
        response = await client.get(
            &quot;https://api.example.com/search&quot;,
            params={&quot;q&quot;: params.query, &quot;limit&quot;: params.limit},
            timeout=30.0
        )
        response.raise_for_status()
        return response.text

# 启动服务器
if __name__ == &quot;__main__&quot;:
    # stdio 模式（本地）
    mcp.run()

    # HTTP 模式（远程）
    # mcp.run(transport=&quot;streamable_http&quot;, port=8000)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;运行&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python my_mcp_server.py
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.2 方案 B：Node/TypeScript SDK（推荐生产）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;项目结构&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;my-service-mcp-server/
├── package.json
├── tsconfig.json
├── src/
│   ├── index.ts          # 主入口
│   ├── types.ts          # 类型定义
│   ├── tools/            # 工具实现
│   │   └── search.ts
│   └── services/         # API 客户端
│       └── api.ts
└── dist/                 # 编译输出
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;package.json&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;my-service-mcp-server&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;type&quot;: &quot;module&quot;,
  &quot;main&quot;: &quot;dist/index.js&quot;,
  &quot;scripts&quot;: {
    &quot;build&quot;: &quot;tsc&quot;,
    &quot;start&quot;: &quot;node dist/index.js&quot;,
    &quot;dev&quot;: &quot;tsx watch src/index.ts&quot;
  },
  &quot;dependencies&quot;: {
    &quot;@modelcontextprotocol/sdk&quot;: &quot;^1.6.1&quot;,
    &quot;axios&quot;: &quot;^1.7.9&quot;,
    &quot;zod&quot;: &quot;^3.23.8&quot;
  },
  &quot;devDependencies&quot;: {
    &quot;@types/node&quot;: &quot;^22.10.0&quot;,
    &quot;tsx&quot;: &quot;^4.19.2&quot;,
    &quot;typescript&quot;: &quot;^5.7.2&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;tsconfig.json&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;compilerOptions&quot;: {
    &quot;target&quot;: &quot;ES2022&quot;,
    &quot;module&quot;: &quot;Node16&quot;,
    &quot;moduleResolution&quot;: &quot;Node16&quot;,
    &quot;outDir&quot;: &quot;./dist&quot;,
    &quot;rootDir&quot;: &quot;./src&quot;,
    &quot;strict&quot;: true,
    &quot;esModuleInterop&quot;: true,
    &quot;skipLibCheck&quot;: true
  },
  &quot;include&quot;: [&quot;src/**/*&quot;],
  &quot;exclude&quot;: [&quot;node_modules&quot;, &quot;dist&quot;]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;src/index.ts&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env node
import { McpServer } from &quot;@modelcontextprotocol/sdk/server/mcp.js&quot;;
import { StdioServerTransport } from &quot;@modelcontextprotocol/sdk/server/stdio.js&quot;;
import { z } from &quot;zod&quot;;
import axios from &quot;axios&quot;;

// 常量配置
const API_BASE_URL = &quot;https://api.example.com/v1&quot;;

// 创建 MCP 服务器
const server = new McpServer({
  name: &quot;my-service-mcp-server&quot;,
  version: &quot;1.0.0&quot;
});

// Zod 输入验证 Schema
const SearchSchema = z.object({
  query: z.string()
    .min(2, &quot;关键词至少 2 个字符&quot;)
    .max(200, &quot;关键词最多 200 个字符&quot;)
    .describe(&quot;搜索关键词&quot;),
  limit: z.number()
    .int()
    .min(1)
    .max(100)
    .default(20)
    .describe(&quot;返回结果数量限制&quot;)
}).strict();

// 工具注册
server.registerTool(
  &quot;my_search&quot;,
  {
    title: &quot;搜索工具&quot;,
    description: `执行搜索操作，返回匹配的结果。

Args:
  - query: 搜索关键词
  - limit: 返回结果数量 (默认 20)

Returns:
  JSON 格式的搜索结果`,
    inputSchema: SearchSchema,
    annotations: {
      readOnlyHint: true,
      destructiveHint: false,
      idempotentHint: true,
      openWorldHint: true
    }
  },
  async (params) =&amp;gt; {
    try {
      const response = await axios.get(`${API_BASE_URL}/search`, {
        params: { q: params.query, limit: params.limit },
        timeout: 30000
      });

      return {
        content: [{
          type: &quot;text&quot;,
          text: JSON.stringify(response.data, null, 2)
        }],
        structuredContent: response.data
      };
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      return {
        content: [{
          type: &quot;text&quot;,
          text: `Error: ${message}`
        }]
      };
    }
  }
);

// 启动服务器
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error(&quot;MCP server running via stdio&quot;);
}

main().catch(console.error);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;构建和运行&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install
npm run build
npm start
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.3 Transport 选择指南&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;场景&lt;/th&gt;
&lt;th&gt;推荐 Transport&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;本地开发工具&lt;/td&gt;
&lt;td&gt;&lt;code&gt;stdio&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;桌面应用集成&lt;/td&gt;
&lt;td&gt;&lt;code&gt;stdio&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;云端部署&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Streamable HTTP&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;多客户端服务&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Streamable HTTP&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;需要 OAuth&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Streamable HTTP&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;5. 接入 OpenCode&lt;/h2&gt;
&lt;h3&gt;5.1 方法 1：作为 Skill MCP（推荐）&lt;/h3&gt;
&lt;p&gt;创建 skill 目录：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;~/.config/opencode/skills/my-mcp/
├── SKILL.md           # 技能描述
├── mcp.json           # MCP 服务器配置
└── scripts/
    └── run.sh         # 启动脚本
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;SKILL.md&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
name: my-mcp
description: 我的自定义 MCP 服务
---

# My MCP Service

提供 XXX 功能的 MCP 服务器。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;mcp.json&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;my_service&quot;: {
    &quot;type&quot;: &quot;stdio&quot;,
    &quot;command&quot;: &quot;python&quot;,
    &quot;args&quot;: [&quot;/home/fenlyin/my_mcp_server.py&quot;],
    &quot;env&quot;: {
      &quot;API_KEY&quot;: &quot;your-api-key&quot;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.2 方法 2：项目级配置&lt;/h3&gt;
&lt;p&gt;编辑 &lt;code&gt;~/.claude.json&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;projects&quot;: {
    &quot;/home/fenlyin/your-project&quot;: {
      &quot;mcpServers&quot;: {
        &quot;my-custom-mcp&quot;: {
          &quot;type&quot;: &quot;stdio&quot;,
          &quot;command&quot;: &quot;node&quot;,
          &quot;args&quot;: [&quot;/path/to/dist/index.js&quot;],
          &quot;env&quot;: {
            &quot;MY_API_KEY&quot;: &quot;xxx&quot;
          }
        }
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.3 方法 3：远程 HTTP MCP&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;mcpServers&quot;: {
    &quot;my-remote-mcp&quot;: {
      &quot;type&quot;: &quot;http&quot;,
      &quot;url&quot;: &quot;https://your-server.com/mcp&quot;,
      &quot;headers&quot;: {
        &quot;Authorization&quot;: &quot;Bearer your-token&quot;
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;6. 测试与调试&lt;/h2&gt;
&lt;h3&gt;6.1 使用 MCP Inspector&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 测试本地 Python 服务器
npx @modelcontextprotocol/inspector python my_mcp_server.py

# 测试本地 Node 服务器
npx @modelcontextprotocol/inspector node dist/index.js

# 测试远程 HTTP 服务器
npx @modelcontextprotocol/inspector http://localhost:8000/mcp
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6.2 调试技巧&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Python 日志&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import logging
logging.basicConfig(level=logging.DEBUG)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Node.js 日志&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 使用 console.error 而不是 console.log（stdout 用于通信）
console.error(&quot;Debug message:&quot;, data);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6.3 常见问题&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;问题&lt;/th&gt;
&lt;th&gt;解决方案&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;连接超时&lt;/td&gt;
&lt;td&gt;检查 command 路径是否正确&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;环境变量未生效&lt;/td&gt;
&lt;td&gt;确认 env 配置格式正确&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;工具未显示&lt;/td&gt;
&lt;td&gt;检查工具注册代码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;参数验证失败&lt;/td&gt;
&lt;td&gt;检查 Zod/Pydantic schema&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;7. 官方资源&lt;/h2&gt;
&lt;h3&gt;7.1 文档与规范&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;资源&lt;/th&gt;
&lt;th&gt;链接&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;官方文档&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;https://modelcontextprotocol.io/docs/&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;协议规范&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;https://modelcontextprotocol.io/specification/&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;最佳实践&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;https://modelcontextprotocol.io/docs/best-practices/&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;快速开始&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;https://modelcontextprotocol.io/docs/quickstart/server/&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;7.2 SDK 与工具&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;资源&lt;/th&gt;
&lt;th&gt;链接&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Python SDK (PyPI)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;https://pypi.org/project/mcp/&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Python SDK (GitHub)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;https://github.com/modelcontextprotocol/python-sdk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TypeScript SDK&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;https://github.com/modelcontextprotocol/typescript-sdk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MCP Inspector&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;https://github.com/modelcontextprotocol/inspector&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;7.3 示例与生态&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;资源&lt;/th&gt;
&lt;th&gt;链接&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;官方服务器示例&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;https://github.com/modelcontextprotocol/servers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MCP Registry&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;https://modelcontextprotocol.io/tools/registry/&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Awesome MCP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;https://github.com/punkpeye/awesome-mcp-servers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;附录：Tool Annotations 说明&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Annotation&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;readOnlyHint&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;boolean&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;工具不会修改环境&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;destructiveHint&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;boolean&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;工具可能执行破坏性操作&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;idempotentHint&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;boolean&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;重复调用无额外效果&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;openWorldHint&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;boolean&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;工具与外部实体交互&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;blockquote&gt;
&lt;p&gt;文档生成于 2026-03-10&lt;/p&gt;
&lt;p&gt;如有问题，请参考官方文档或提交 GitHub Issue&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;OpenCode MCP 集成完整指南&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;MCP 配置文件查找目录（优先级从低到高）
优先级
1
2
3
4
5
6
7 (最高)
OAuth tokens 存储位置: ~/.local/share/opencode/mcp-auth.json&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;MCP 自动启动机制
是的，OpenCode 在启动时自动启动所有 MCP 服务器。
关键代码 (mcp/index.ts:185-218):
const state = Instance.state(
async () =&amp;gt; {
const cfg = await Config.get()
const config = cfg.mcp ?? {}&lt;/p&gt;
&lt;p&gt;// 并行启动所有 MCP
await Promise.all(
Object.entries(config).map(async ([key, mcp]) =&amp;gt; {&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; // enabled: false 则跳过
 if (mcp.enabled === false) {
   status[key] = { status: &quot;disabled&quot; }
   return
 }
 // 创建连接
 const result = await create(key, mcp)
 // ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}),
)
}
)
启动流程：&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;读取配置 → 提取 mcp 字段&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;并行启动 → Promise.all 并发连接&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;连接类型：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;local → StdioClientTransport 启动子进程&lt;/li&gt;
&lt;li&gt;remote → StreamableHTTPClientTransport 或 SSEClientTransport&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;默认超时：30秒（可通过 timeout 字段配置）&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li&gt;MCP 配置格式
{
&quot;mcp&quot;: {
&quot;local-filesystem&quot;: {
&quot;type&quot;: &quot;local&quot;,
&quot;command&quot;: [&quot;npx&quot;, &quot;-y&quot;, &quot;@modelcontextprotocol/server-filesystem&quot;, &quot;/path&quot;],
&quot;environment&quot;: { &quot;MY_VAR&quot;: &quot;value&quot; },
&quot;enabled&quot;: true,
&quot;timeout&quot;: 5000
},
&quot;remote-sentry&quot;: {
&quot;type&quot;: &quot;remote&quot;,
&quot;url&quot;: &quot;https://mcp.sentry.dev/mcp&quot;,
&quot;headers&quot;: { &quot;Authorization&quot;: &quot;Bearer {env:SENTRY_TOKEN}&quot; },
&quot;oauth&quot;: {},
&quot;enabled&quot;: true
}
}
}&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li&gt;CLI 命令
opencode mcp add              # 交互式添加 MCP
opencode mcp list             # 列出所有 MCP 状态
opencode mcp auth &amp;lt;name&amp;gt;      # OAuth 认证
opencode mcp logout &amp;lt;name&amp;gt;    # 移除 OAuth 凭证
opencode mcp debug &amp;lt;name&amp;gt;     # 调试连接问题&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li&gt;禁用自动启动
{
&quot;mcp&quot;: {
&quot;my-mcp&quot;: {
&quot;type&quot;: &quot;local&quot;,
&quot;command&quot;: [&quot;...&quot;],
&quot;enabled&quot;: false
}
}
}&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li&gt;按 Agent 控制 MCP
{
&quot;mcp&quot;: { &quot;my-mcp&quot;: { &quot;type&quot;: &quot;local&quot;, &quot;command&quot;: [&quot;...&quot;], &quot;enabled&quot;: true } },
&quot;tools&quot;: { &quot;my-mcp*&quot;: false },
&quot;agent&quot;: {
&quot;my-agent&quot;: { &quot;tools&quot;: { &quot;my-mcp*&quot;: true } }
}
}&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>Linux 一切皆文件：文件系统详解</title><link>https://fenlyin.dpdns.org/posts/linux-filesystem/</link><guid isPermaLink="true">https://fenlyin.dpdns.org/posts/linux-filesystem/</guid><description>从 VFS 抽象到 inode，把「一切皆文件」这句话拆开看清楚。</description><pubDate>Tue, 20 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Linux的一切皆文件&lt;/h2&gt;
&lt;p&gt;Linux 中的各种事物比如像文档、目录（Mac OS X 和 Windows 系统下称之为文件夹）、键盘、监视器、硬盘、可移动媒体设备、打印机、调制解调器、虚拟终端，还有进程间通信（IPC）和网络通信等输入/输出资源都是定义在文件系统空间下的字节流。
一切都可看作是文件，其最显著的好处是对于上面所列出的输入/输出资源，只需要相同的一套 Linux 工具、实用程序和 API。你可以使用同一套api(read, write)和工具(cat , 重定向, 管道)来处理unix中大多数的资源.
设计一个系统的终极目标往往就是要找到原子操作，一旦锁定了原子操作，设计工作就会变得简单而有序。“文件”作为一个抽象概念，其原子操作非常简单，只有读和写，这无疑是一个非常好的模型。通过这个模型，API的设计可以化繁为简，用户可以使用通用的方式去访问任何资源，自有相应的中间件做好对底层的适配。
现代操作系统为解决信息能独立于进程之外被长期存储引入了文件，文件作为进程创建信息的逻辑单元可被多个进程并发使用。在 UNIX 系统中，操作系统为磁盘上的文本与图像、鼠标与键盘等输入设备及网络交互等 I/O 操作设计了一组通用 API，使他们被处理时均可统一使用字节流方式。换言之，UNIX 系统中除进程之外的一切皆是文件，而 Linux 保持了这一特性。为了便于文件的管理，Linux 还引入了目录（有时亦被称为文件夹）这一概念。目录使文件可被分类管理，且目录的引入使 Linux 的文件系统形成一个层级结构的目录树&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在Linux系统中，一切都是文件，理解文件系统，对于学习Linux来说，是一个非常有必要的前提&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Linux上的文件系统一般来说就是EXT2或EXT3，但这篇文章并不准备一上来就直接讲它们，而希望结合Linux操作系统并从文件系统建立的基础——硬盘开始，一步步认识Linux的文件系统。&lt;/p&gt;
&lt;h2&gt;1. 机械硬盘的物理存储机制&lt;/h2&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;现代计算机大部分文件存储功能都是由机械硬盘这种设备提供的。（现在的SSD和闪存从概念和逻辑上都部分继承自机械硬盘，所以使用机械硬盘来进行理解也是没有问题的）&lt;/li&gt;
&lt;li&gt;机械硬盘能实现信息存储的功能基于：磁性存储介质能够被磁化，且磁化后会长久保留被磁化的状态，这种被磁化状态能够被读取出来，同时这种磁化状态还能够不断被修改，磁化正好有两个方向，所以可以表示0和1。于是硬盘就是把这种磁性存储介质做成一个个盘片，每一个盘片上都分布着数量巨大的磁性存储单位，使用磁性读写头对盘片进行写入和读取（从原理上类似黑胶唱片的播放）。&lt;/li&gt;
&lt;li&gt;一个硬盘中的磁性存储单位数以亿计（1T硬盘就有约80亿个），所以需要一套规则来规划信息如何存取（比如一本存储信息的书我们还会分为页，每一页从上到下从左到右读取，同时还有章节目录）于是就有了这些物理、逻辑概念：
1、一个硬盘有多张盘片叠成，不同盘片有编号
2、每张盘片上的存储颗粒成环形一圈圈地排布，每一圈称为磁道，有编号
3、每条磁道上都有一圈存储颗粒，每512*8（512字节，0.5KB）个存储颗粒作为一个扇区，&lt;strong&gt;扇区是硬盘上存储的最小物理单位&lt;/strong&gt;
4、N个扇区可以组成簇，N取决于不同的文件系统或是文件系统的配置，&lt;strong&gt;簇是此文件系统中的最小存储单位&lt;/strong&gt;
5、所有盘面上的同一磁道构成一个圆柱，称为柱面，柱面是系统分区的最小单位&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;磁头读写文件的时候，首先是分区读写的，由inode编号（区内唯一的编号后面介绍）找到对应的磁道和扇区，然后一个柱面一个柱面地进行读写。机械硬盘的读写控制系统是一个令人叹为观止的精密工程（一个盘面上有几亿个存储单位，每个磁道宽度不到几十纳米，磁盘每分钟上万转），同时关于读写的逻辑也是有诸多细节（比如扇区的编号并不是连续的），非常有意思，可以自行搜索文章拓展阅读。
有了硬盘并不意味着LInux可以立刻把它用来存储，还需要组合进Linux的文件体系才能被Linux使用。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/87457873/127275223-4e6d656a-bf3b-440c-966d-c5d366a8c2ab.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;2.Linux文件体系&lt;/h2&gt;
&lt;p&gt;Linux以文件的形式对计算机中的数据和硬件资源进行管理，也就是彻底的一切皆文件，反映在Linux的文件类型上就是：普通文件、目录文件（也就是文件夹）、设备文件、链接文件、管道文件、套接字文件（数据通信的接口）等等。而这些种类繁多的文件被Linux使用目录树进行管理， 所谓的目录树就是以根目录（/）为主，向下呈现分支状的一种文件结构。不同于纯粹的ext2之类的文件系统，我把它称为文件体系，一切皆文件和文件目录树的资源管理方式一起构成了Linux的文件体系，让Linux操作系统可以方便使用系统资源。所以文件系统比文件体系涵盖的内容少很多，Linux文件体系主要在于把操作系统相关的东西用文件这个载体实现：文件系统挂载在操作系统上，操作系统整个系统又放在文件系统里。但本文中文件体系的相关内容不是很多，大部分地方都可以用文件系统代替文件体系。&lt;/p&gt;
&lt;h3&gt;1. Linux中的文件类型:&lt;/h3&gt;
&lt;h4&gt;1.1. 普通文件（-）&lt;/h4&gt;
&lt;p&gt;从Linux的角度来说，类似mp4、pdf、html这样应用层面上的文件类型都属于普通文件Linux用户可以根据访问权限对普通文件进行查看、更改和删除&lt;/p&gt;
&lt;h4&gt;1.2. 目录文件（d，directory file）&lt;/h4&gt;
&lt;p&gt;目录文件对于用惯Windows的用户来说不太容易理解，目录也是文件的一种目录文件包含了各自目录下的文件名和指向这些文件的指针，打开目录事实上就是打开目录文件，只要有访问权限，你就可以随意访问这些目录下的文件（普通文件的执行权限就是目录文件的访问权限），但是只有内核的进程能够修改它们虽然不能修改，但是我们能够通过vim去查看目录文件的内容&lt;/p&gt;
&lt;h4&gt;1.3. 符号链接（l，symbolic link）&lt;/h4&gt;
&lt;p&gt;这种类型的文件类似Windows中的快捷方式，是指向另一个文件的间接指针，也就是我们常说的软链接&lt;/p&gt;
&lt;h4&gt;1.4. 块设备文件（b，block）和字符设备文件（c，char）&lt;/h4&gt;
&lt;p&gt;这些文件一般隐藏在/dev目录下，在进行设备读取和外设交互时会被使用到比如磁盘光驱就是块设备文件，串口设备则属于字符设备文件系统中的所有设备要么是块设备文件，要么是字符设备文件，无一例外&lt;/p&gt;
&lt;h4&gt;1.5. FIFO（p，pipe）&lt;/h4&gt;
&lt;p&gt;管道文件主要用于进程间通讯。比如使用mkfifo命令可以创建一个FIFO文件，启用一个进程A从FIFO文件里读数据，启动进程B往FIFO里写数据，先进先出，随写随读。&lt;/p&gt;
&lt;h4&gt;1.6. 套接字（s，socket）&lt;/h4&gt;
&lt;p&gt;用于进程间的网络通信，也可以用于本机之间的非网络通信这些文件一般隐藏在/var/run目录下，证明着相关进程的存在
Linux 的文件是没有所谓的扩展名的，一个 Linux文件能不能被执行与它是否可执行的属性有关，只要你的权限中有 x ，比如[ -rwx-r-xr-x ] 就代表这个文件可以被执行，与文件名没有关系。跟在 Windows下能被执行的文件扩展名通常是 .com .exe .bat 等不同。不过，可以被执行跟可以执行成功不一样。比如在 root 主目彔下的 install.log 是一个文本文件，修改权限成为 -rwxrwxrwx 后这个文件能够真的执行成功吗？ 当然不行，因为它的内容根本就没有可以执行的数据。所以说，这个 x 代表这个文件具有可执行的能力， 但是能不能执行成功，当然就得要看该文件的内容了。
虽然如此，不过我们仍然希望能从扩展名来了解该文件是什么东西，所以一般我们还是会以适当的扩展名来表示该文件是什么种类的。所以Linux 系统上的文件名真的只是让你了解该文件可能的用途而已， 真正的执行与否仍然需要权限的规范才行。比如常见的/bin/ls 这个显示文件属性的指令要是权限被修改为无法执行，那么ls 就变成不能执行了。这种问题最常发生在文件传送的过程中。例如你在网络上下载一个可执行文件，但是偏偏在你的 Linux 系统中就是无法执行，那就可能是档案的属性被改变了。而且从网络上传送到你 的 Linux 系统中，文件的属性权限确实是会被改变的&lt;/p&gt;
&lt;h3&gt;2. Linux目录树&lt;/h3&gt;
&lt;p&gt;对Linux系统和用户来说，所有可操作的计算机资源都存在于目录树这个逻辑结构中，对计算机资源的访问都可以认为是目录树的访问。就硬盘来说，所有对硬盘的访问都变成了对目录树中某个节点也就是文件夹的访问，访问时不需要知道它是硬盘还是硬盘中的文件夹。目录树的逻辑结构也非常简单，就是从根目录（/）开始，不断向下展开各级子目录。&lt;/p&gt;
&lt;h2&gt;3.硬盘分区&lt;/h2&gt;
&lt;p&gt;硬盘分区是硬盘结合到文件体系的第一步，本质是「硬盘」这个物理概念转换成「区」这个逻辑概念，为下一步格式化做准备。所以分本身并不是必须的，你完全可以把一整块硬盘作为一个区。但从数据的安全性以及系统性能角度来看，分区还是有很多用处的，所以一般都会对硬盘进行分区。&lt;/p&gt;
&lt;p&gt;讲分区就不得不先提每块硬盘上最重要的第一扇区，这个扇区中有硬盘主引导记录(Master boot record, MBR) 及分区表(partition table)， 其中 MBR 占有 446 bytes，而分区表占有 64 bytes。硬盘主引导记录放有最基本的引导加载程序，是系统开机启动的关键环节，在附录中有更详细的说明。而分区表则跟分区有关，它记录了硬盘分区的相关信息，但因分区表仅有 64bytes ， 所以最多只能记彔四块分区（分区本身其实就是对分区表进行设置）。在MBR分区下，第一扇区不属于任何分区。&lt;/p&gt;
&lt;p&gt;只能分四个区实在太少了，于是就有了扩展分区的概念，既然第一个扇区所在的分区表只能记录四条数据， 那我可否利用额外的扇区来记录更多的分区信息。
把普通可以访问的分区称为主分区，扩展分区不同于主分区，它本身并没有内容，它作为一一种容器，为逻辑分区提供空间的。在某块分区指定为扩展分区后，就可以对这块扩展分区进一步分成多个逻辑分区。操作系统规定：&lt;/p&gt;
&lt;p&gt;1、四块分区每块都可以是主分区或扩展分区
2、扩展分区最多只能有一个（也没必要有多个）
3、扩展分区可以进一步分割为多个逻辑分区
4、扩展分区只是逻辑概念，本身不能被访问，也就是不能被格式化后作为数据访问的分区，能够作为数据访问的分区只有主分区和逻辑分区
5、逻辑分区的数量依操作系统而不同，在 Linux 系统中，IDE 硬盘最多有 59 个逻辑分区(5 号到 63 号)， SATA 硬盘则有 11 个逻辑分区(5 号到 15 号)，1到4号为主分区保留&lt;/p&gt;
&lt;p&gt;一般给硬盘进行分区时，一个主分区一个扩展分区，然后把扩展分区划分为N个逻辑分区是最好的&lt;/p&gt;
&lt;p&gt;是否可以不要主分区呢？不知道，但好像不用管，你创建分区的时候会自动给你配置类型特殊的，你最好单独分一个swap区（内存置换空间），它独为一类，功能是：当有数据被存放在物理内存里面，但是这些数据又不是常被 CPU 所取用时，那么这些不常被使用的程序将会被丢到硬盘的 swap 置换空间当中， 而将速度较快的物理内存空间释放出来给真正需要的程序使用。（虚拟内存）&lt;/p&gt;
&lt;h2&gt;4.格式化&lt;/h2&gt;
&lt;p&gt;我们知道Linux操作系统支持很多不同的文件系统，比如ext2、ext3、XFS、FAT等等，而Linux把对不同文件系统的访问交给了VFS（虚拟文件系统），VFS能访问和管理各种不同的文件系统。所以有了区之后就需要把它格式化成具体的文件系统以便VFS访问。&lt;/p&gt;
&lt;p&gt;标准的Linux文件系统Ext2是使用「基于inode的文件系统」&lt;/p&gt;
&lt;p&gt;1、我们知道一般操作系统的文件数据除了文件实际内容外， 还带有很多属性，例如 Linux 操作系统的文件权限(rwx)与文件属性(拥有者、群组、 时间参数等)，文件系统通常会将属性和实际内容这两部分数据分别存放在不同的区块
2、在基于inode的文件系统中，权限与属性放置到 inode 中，实际数据放到 data block 区块中，而且inode和data block都有编号&lt;/p&gt;
&lt;p&gt;Ext2 文件系统在此基础上&lt;/p&gt;
&lt;p&gt;1、文件系统最前面有一个启动扇区(boot sector)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这个启动扇区可以安装开机管理程序， 这个设计让我们能将不同的引导装载程序安装到个别的文件系统前端，而不用覆盖整个硬盘唯一的MBR， 也就是这样才能实现多重引导的功能&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;2、把每个区进一步分为多个块组 (block group)，每个块组有独立的inode/block体系&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果文件系统高达数百 GB 时，把所有的 inode 和block 通通放在一起会因为 inode 和 block的数量太庞大，不容易管理&lt;/li&gt;
&lt;li&gt;这其实很好理解，因为分区是用户的分区，实际计算机管理时还有个最适合的大小，于是计算机会进一步的在分区中分块&lt;/li&gt;
&lt;li&gt;（但这样岂不是可能出现大文件放不了的问题？有什么机制善后吗？）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;3、每个块组实际还会分为分为6个部分，除了inode table 和 data block外还有4个附属模块，起到优化和完善系统性能的作用&lt;/p&gt;
&lt;p&gt;所以，在MBR分区下，linux ext2文件系统的一个分区组织大概如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/87457873/127280770-5acec908-89d3-4fa1-94a0-79eab26eafb4.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;inode table&lt;/h3&gt;
&lt;p&gt;1.主要记录文件的属性以及该文件实际数据是放置在哪些block中，它记录的信息至少有这些：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;大小、真正内容的block号码（一个或多个）&lt;/li&gt;
&lt;li&gt;访问模式(read/write/excute)&lt;/li&gt;
&lt;li&gt;拥有者与群组(owner/group)&lt;/li&gt;
&lt;li&gt;各种时间：建立或状态改变的时间、最近一次的读取时间、最近修改的时间&lt;/li&gt;
&lt;li&gt;没有文件名！文件名在目录的block中！&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;2、一个文件占用一个 inode，每个inode有编号
3、Linux 系统存在 inode 号被用完但磁盘空间还有剩余的情况
4、注意，这里的文件不单单是普通文件，目录文件也就是文件夹其实也是一个文件，还有其他的也是
5、inode 的数量与大小在格式化时就已经固定了，每个inode 大小均固定为128 bytes (新的ext4 与xfs 可设定到256 bytes)
6、文件系统能够建立的文件数量与inode 的数量有关，存在空间还够但inode不够的情况
7、系统读取文件时需要先找到inode，并分析inode 所记录的权限与使用者是否符合，若符合才能够开始实际读取 block 的内容
8、inode 要记录的资料非常多，但偏偏又只有128bytes ， 而inode 记录一个block 号码要花掉4byte ，假设我一个文件有400MB 且每个block 为4K 时， 那么至少也要十万条block 号码的记录！inode 哪有这么多空间来存储？为此我们的系统很聪明的将inode 记录block 号码的区域定义为12个直接，一个间接, 一个双间接与一个三间接记录区（详细见附录）&lt;/p&gt;
&lt;h3&gt;data block&lt;/h3&gt;
&lt;p&gt;1、放置文件内容数据的地方
2、在格式化时block的大小就固定了，且每个block都有编号，以方便inode的记录&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;原则上，block 的大小与数量在格式化完就不能够再改变了(除非重新格式化)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;3、在Ext2文件系统中所支持的block大小有1K, 2K及4K三种，由于block大小的区别，会导致该文件系统能够支持的最大磁盘容量与最大单一文件容量各不相同：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Block 大小 1KB 2KB 4KB&lt;/li&gt;
&lt;li&gt;最大单一档案限制 16GB 256GB 2TB&lt;/li&gt;
&lt;li&gt;最大档案系统总容量 2TB 8TB 16TB&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;4、每个block 内最多只能够放置一个文件的资料，但一个文件可以放在多个block中（大的话）
5、若文件小于block ，则该block 的剩余容量就不能够再被使用了(磁盘空间会浪费)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;所以如果你的档案都非常小，但是你的block 在格式化时却选用最大的4K 时，可能会产生容量的浪费&lt;/li&gt;
&lt;li&gt;既然大的block 可能会产生较严重的磁碟容量浪费，那么我们是否就将block 大小定为1K ？这也不妥，因为如果block 较小的话，那么大型档案将会占用数量更多的block ，而inode 也要记录更多的block 号码，此时将可能导致档案系统不良的读写效能&lt;/li&gt;
&lt;li&gt;事实上现在的磁盘容量都太大了，所以一般都会选择4K 的block 大小&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;superblock&lt;/h3&gt;
&lt;p&gt;1、记录整个文件系统相关信息的地方，一般大小为1024bytes，记录的信息主要有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;block 与inode 的总量&lt;/li&gt;
&lt;li&gt;未使用与已使用的inode / block 数量&lt;/li&gt;
&lt;li&gt;一个valid bit 数值，若此文件系统已被挂载，则valid bit 为0 ，若未被挂载，则valid bit 为1&lt;/li&gt;
&lt;li&gt;block 与inode 的大小 (block 为1, 2, 4K，inode 为128bytes 或256bytes)；&lt;/li&gt;
&lt;li&gt;其他各种文件系统相关信息：filesystem 的挂载时间、最近一次写入资料的时间、最近一次检验磁碟(fsck) 的时间&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;2、Superblock是非常重要的， 没有Superblock ，就没有这个文件系统了，因此如果superblock死掉了，你的文件系统可能就需要花费很多时间去挽救
3、每个块都可能含有superblock，但是我们也说一个文件系统应该仅有一个superblock 而已，那是怎么回事？事实上除了第一个块内会含有superblock 之外，后续的块不一定含有superblock，而若含有superblock则该superblock主要是做为第一个块内superblock的备份，这样可以进行superblock的救援&lt;/p&gt;
&lt;h3&gt;Filesystem Description&lt;/h3&gt;
&lt;p&gt;1、文件系统描述
2、这个区段可以描述每个block group的开始与结束的block号码，以及说明每个区段(superblock, bitmap, inodemap, data block)分别介于哪一个block号码之间&lt;/p&gt;
&lt;h3&gt;block bitmap&lt;/h3&gt;
&lt;p&gt;1、块对照表
2、如果你想要新增文件时要使用哪个block 来记录呢？当然是选择「空的block」来记录。那你怎么知道哪个block 是空的？这就得要通过block bitmap了，它会记录哪些block是空的，因此我们的系统就能够很快速的找到可使用的空间来记录
3、同样在你删除某些文件时，那些文件原本占用的block号码就得要释放出来， 此时在block bitmap 中对应该block号码的标志位就得要修改成为「未使用中」&lt;/p&gt;
&lt;h3&gt;inode bitmap&lt;/h3&gt;
&lt;p&gt;1、与block bitmap 是类似的功能，只是block bitmap 记录的是使用与未使用的block 号码， 至于inode bitmap 则是记录使用与未使用的inode 号码&lt;/p&gt;
&lt;h2&gt;5.挂载&lt;/h2&gt;
&lt;p&gt;在一个区被格式化为一个文件系统之后，它就可以被Linux操作系统使用了，只是这个时候Linux操作系统还找不到它，所以我们还需要把这个文件系统「注册」进Linux操作系统的文件体系里，这个操作就叫「挂载」 (mount)。挂载是利用一个目录当成进入点（类似选一个现成的目录作为代理），将文件系统放置在该目录下，也就是说，进入该目录就可以读取该文件系统的内容，类似整个文件系统只是目录树的一个文件夹（目录）。这个进入点的目录我们称为「挂载点」。&lt;/p&gt;
&lt;p&gt;由于整个 Linux 系统最重要的是根目录，因此根目录一定需要挂载到某个分区。 而其他的目录则可依用户自己的需求来给予挂载到不同的分去。&lt;/p&gt;
&lt;p&gt;到这里Linux的文件体系的构建过程其实已经大体讲完了，总结一下就是：硬盘经过分区和格式化，每个区都成为了一个文件系统，挂载这个文件系统后就可以让Linux操作系统通过VFS访问硬盘时跟访问一个普通文件夹一样。这里通过一个在目录树中读取文件的实际例子来细讲一下目录文件和普通文件。&lt;/p&gt;
&lt;h2&gt;6.目录树的读取过程&lt;/h2&gt;
&lt;p&gt;首先我们要知道&lt;/p&gt;
&lt;p&gt;1、每个文件（不管是一般文件还是目录文件）都会占用一个inode
2、依据文件内容的大小来分配一个或多个block给该文件使用
3、创建一个文件后，文件完整信息分布在3处地方，生成2个新文件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;文件名记录在该文件所在目录的目录文件的block中，没有新文件生成&lt;/li&gt;
&lt;li&gt;文件属性、权限信息、记录具体内容的block编号记录在inode中，inode是新生成文件&lt;/li&gt;
&lt;li&gt;文件具体内存记录在block中，block是新生成文件&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;4、因为文件名的记录是在目录的block当中，「新增/删除/更名文件名」与目录的w权限有关所以在Linux/Unix中，文件名称只是文件的一个属性，叫别名也好，叫绰号也罢，仅为了方便用户记忆和使用，但系统内部并不需要用文件名来定为文件位置，这样处理最直观的好处就是，你可以对正在使用的文件改名，换目录，甚至放到废纸篓，都不会影响当前文件的使用，这在Windows里是无法想象的。比如你打开个Word文件，然后对其进行重命名操作，Windows会告诉你门儿都没有，关闭文件先！但在Mac里就毫无压力，因为Mac的操作系统同样采用了inode的设计。&lt;/p&gt;
&lt;h3&gt;创建文件过程&lt;/h3&gt;
&lt;p&gt;当在ext2下建立一个一般文件时， ext2 会分配一个inode 与相对于该文件大小的block 数量给该文件&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;例如：假设我的一个block 为4 Kbytes ，而我要建立一个100 KBytes 的文件，那么linux 将分配一个inode 与25 个block 来储存该文件&lt;/li&gt;
&lt;li&gt;但同时请注意，由于inode 仅有12 个直接指向，因此还要多一个block 来作为区块号码的记录&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;创建目录过程&lt;/h3&gt;
&lt;p&gt;当在ext2文件系统建立一个目录时（就是新建了一个目录文件），文件系统会分配一个inode与至少一块block给该目录&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;inode记录该目录的相关权限与属性，并记录分配到的那块block号码&lt;/li&gt;
&lt;li&gt;而block则是记录在这个目录下的文件名与该文件对应的inode号&lt;/li&gt;
&lt;li&gt;block中还会自动生成两条记录，一条是.文件夹记录，inode指向自身，另一条是..文件夹记录，inode指向父文件夹&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;从目录树中读取某个文件过程&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;因为文件名是记录在目录的block当中，因此当我们要读取某个文件时，就一定会经过目录的inode与block ，然后才能够找到那个待读取文件的inode号码，最终才会读到正确的文件的block内的资料。&lt;/li&gt;
&lt;li&gt;由于目录树是由根目录开始，因此操作系统先通过挂载信息找到挂载点的inode号，由此得到根目录的inode内容，并依据该inode读取根目录的block信息，再一层一层的往下读到正确的文件。举例来说，如果我想要读取/etc/passwd 这个文件时，系统是如何读取的呢？先看一下这个文件以及有关路径文件夹的信息：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;1$ ll -di / /etc /etc/passwd
2     128 dr-xr-x r-x . 17 root root 4096 May 4 17:56 /
333595521 drwxr-x r-x . 131 root root 8192 Jun 17 00:20 /et
c436628004 -rw-r-- r-- . 1 root root 2092 Jun 17 00:20 /etc/passwd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;于是该文件的读取流程为：&lt;/p&gt;
&lt;p&gt;1、/的inode：
通过挂载点的信息找到inode号码为128的根目录inode，且inode规定的权限让我们可以读取该block的内容(有r与x)
2、/的block：
经过上个步骤取得block的号码，并找到该内容有etc/目录的inode号码(33595521)
3、etc/的inode：
读取33595521号inode得知具有r与x的权限，因此可以读取etc/的block内容
4、etc/的block：
经过上个步骤取得block号码，并找到该内容有passwd文件的inode号码(36628004)
5、passwd的inode：
读取36628004号inode得知具有r的权限，因此可以读取passwd的block内容
6、passwd的block：
最后将该block内容的资料读出来&lt;/p&gt;
</content:encoded></item><item><title>心理表征：大脑怎么存一个概念</title><link>https://fenlyin.dpdns.org/posts/cognitive-science/</link><guid isPermaLink="true">https://fenlyin.dpdns.org/posts/cognitive-science/</guid><description>它不是脑子里的一张小图片，而是一种多模态的激活模式。</description><pubDate>Sun, 18 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;心理表征&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;心理表征&lt;/strong&gt;​ 指的是信息或知识在头脑中的存储和呈现方式。它不是大脑中的一个“小图片”，而是一种&lt;strong&gt;神经层面的激活模式&lt;/strong&gt;，编码了该概念的各种属性、关系、感觉经验、情感色彩和行为倾向。
心理表征是&lt;strong&gt;多模态和混合的&lt;/strong&gt;，可能包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;感觉意象&lt;/strong&gt;：视觉形象（想到“苹果”时脑海里出现的红色圆形）、声音、味道、触感。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;语言标签&lt;/strong&gt;：概念的词汇名称本身。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;属性与特征&lt;/strong&gt;：关于该概念的事实和特性（“苹果是水果”、“可以吃”、“生长在树上”）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;情境与关联&lt;/strong&gt;：与概念相关的个人记忆、社会场景或相关概念（想到“婚礼”，可能同时激活“婚纱”、“戒指”、“喜庆”）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;情感与评价&lt;/strong&gt;：对该概念的情感反应（喜欢、厌恶、恐惧等）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;动作与程序&lt;/strong&gt;：与概念相关的动作倾向（想到“锤子”，手部可能有无意识的轻微抓握倾向）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;需要注意的点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;心理表征 ≠ 单次激活的印象&lt;/strong&gt;：心理表征是&lt;strong&gt;长期、稳定&lt;/strong&gt;的记忆结构。而“立即出现的印象”是它在&lt;strong&gt;特定时刻、特定语境下&lt;/strong&gt;被激活的&lt;strong&gt;实例&lt;/strong&gt;。就像字典的词条是长期存储，而你每次看到这个词时想到的具体释义和例子，会受到当下语境、心情的影响。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不完整性与选择性&lt;/strong&gt;：一次激活通常不会调取该概念的全部信息，而是&lt;strong&gt;最相关、最典型、最可及&lt;/strong&gt;的部分。例如，提起“狗”，你可能首先想到自家宠物狗的形象，而不是狗的所有生物学分类特征。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;存在无意识表征&lt;/strong&gt;：有些心理表征（尤其是程序性知识、内隐偏见）的激活可能不完全进入意识层面，但仍在影响你的判断和行为。这时，“立即出现的印象”可能只是意识到的冰山一角。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;个体与文化差异&lt;/strong&gt;：不同的人对同一个概念的“立即印象”可能差异巨大，这取决于个人的经验、文化背景和专业知识。专家和新手对同一概念（如“量子”）的心理表征和即时印象截然不同。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;刻板印象&lt;/h2&gt;
&lt;p&gt;“刻板印象”是一个重要的社会心理学概念，指的是&lt;strong&gt;人们对某个特定社会群体（如性别、种族、职业、地域、年龄群体等）所持有的简单化、固定化、概括化的看法和信念&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;核心特征&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;过度概括&lt;/strong&gt;：将某个群体中&lt;strong&gt;个别成员&lt;/strong&gt;的特征，推广到该群体的&lt;strong&gt;所有成员&lt;/strong&gt;身上。例如，“某地的人都精明”、“程序员都不善社交”。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;固定僵化&lt;/strong&gt;：这种看法一旦形成，往往难以改变，即使面对反例也倾向于坚持。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;常常包含评价&lt;/strong&gt;：刻板印象通常与&lt;strong&gt;先入为主的价值判断&lt;/strong&gt;（正面或负面）和情感态度相联系，而不仅仅是中性的认知分类。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;忽略个体差异&lt;/strong&gt;：它抹杀了群体内部成员之间的多样性和独特性。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;形成原因&lt;/h3&gt;
&lt;p&gt;刻板印象的形成是人类认知功能的一种“副产品”，根源在于：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;认知简化&lt;/strong&gt;：大脑为了高效处理海量社会信息，会采用“分类”策略。将人分门别类，贴上“标签”，可以节省认知资源，快速做出判断。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;社会学习&lt;/strong&gt;：主要通过家庭、学校教育、媒体宣传、社会文化传统等渠道习得。媒体对某些群体的模式化描绘（如影视剧中的角色定型）是重要来源。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;社会分类与认同&lt;/strong&gt;：人们通过区分“我们”（内群体）和“他们”（外群体）来建立自我认同，往往对内群体评价更积极，对外群体更容易形成刻板印象。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;为现有社会结构辩护&lt;/strong&gt;：某些刻板印象（如种族或性别刻板印象）可以被用来合理化社会不平等和歧视现状。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;主要类型与例子&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;性别刻板印象&lt;/strong&gt;：“男性应该坚强、养家”、“女性应该温柔、顾家”、“女孩不擅长理科”。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;种族/民族刻板印象&lt;/strong&gt;：历史上各种基于种族的错误偏见，如认为某些种族天生具有特定品质。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;职业刻板印象&lt;/strong&gt;：“老师都很严肃”、“艺术家都邋遢”、“商人唯利是图”。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;地域刻板印象&lt;/strong&gt;：针对国内不同省份、城市居民的笼统看法。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;年龄刻板印象&lt;/strong&gt;：“年轻人浮躁”、“老年人保守固执”。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;影响与后果&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;消极影响（主要方面）&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;导致偏见与歧视&lt;/strong&gt;：是偏见（态度）和歧视（行为）的重要认知基础。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;影响判断与决策&lt;/strong&gt;：使人基于群体标签而非个人实际情况做出判断，导致不公。例如，招聘、升学中的隐性偏见。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;自我实现预言&lt;/strong&gt;：当被刻板印象的个体感知到他人对自己的低期望时，可能真的表现出符合该 stereotype 的行为或成绩下滑（如“ stereotype threat ”，刻板印象威胁）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;限制个人发展&lt;/strong&gt;：固化的社会期望会限制个体对教育、职业、生活方式的选择。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;阻碍社会交往&lt;/strong&gt;：制造群体间的隔阂与误解。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;可能的“积极”或中性功能&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;在某些&lt;strong&gt;非常有限&lt;/strong&gt;的、基于统计规律的语境下（例如，快速的风险初步评估），可能有一定的预测作用，但其&lt;strong&gt;错误率极高，且极不道德、不准确&lt;/strong&gt;，不应作为依据。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;为个体提供初步的、粗糙的社会认知地图。&lt;strong&gt;但关键在于，成熟的社会成员必须学会超越这种粗糙的初步印象。&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;如何减少刻板印象的影响&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;增强意识&lt;/strong&gt;：首先认识到自己和他人都可能存在无意识的刻板印象。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;寻求个体化信息&lt;/strong&gt;：有意识地去了解个体，用具体信息取代群体标签。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;增加群体间接触&lt;/strong&gt;：在平等、合作、共同目标条件下，与不同群体成员进行深入、个性化的接触（接触假说）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;反思媒体信息&lt;/strong&gt;：批判性地看待媒体中对不同群体的描绘。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;教育与宣传&lt;/strong&gt;：通过教育传播平等观念，展示反例，打破固有印象。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;总结来说&lt;/strong&gt;，刻板印象是一种普遍存在但问题重重的认知捷径。它虽然帮助我们快速理解复杂的社会环境，但其代价往往是&lt;strong&gt;简单化、不准确，并常常导致不公和伤害&lt;/strong&gt;。在现代社会中，认识到刻板印象的存在，并有意识地克服其负面影响，是促进个人理性思考和社会公平和谐的重要一步。&lt;/p&gt;
&lt;h3&gt;优缺点的记忆方法&lt;/h3&gt;
&lt;p&gt;记忆某个方法的优缺点，直接投身于具体情景使用该方法，让自己感到舒服的点就是优点，让自己感到不爽的点就是缺点。&lt;/p&gt;
</content:encoded></item></channel></rss>