<?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>Do one thing and do it well.</description><link>https://haoyn231.github.io/</link><language>zh_CN</language><item><title>多元函数求极值 | 使用Hessian矩阵</title><link>https://haoyn231.github.io/posts/%E9%AB%98%E7%AD%89%E6%95%B0%E5%AD%A6/%E5%A4%9A%E5%85%83%E5%87%BD%E6%95%B0%E6%B1%82%E6%9E%81%E5%80%BC--%E4%BD%BF%E7%94%A8hessian%E7%9F%A9%E9%98%B5/</link><guid isPermaLink="true">https://haoyn231.github.io/posts/%E9%AB%98%E7%AD%89%E6%95%B0%E5%AD%A6/%E5%A4%9A%E5%85%83%E5%87%BD%E6%95%B0%E6%B1%82%E6%9E%81%E5%80%BC--%E4%BD%BF%E7%94%A8hessian%E7%9F%A9%E9%98%B5/</guid><description>使用Hessian矩阵构建判别公式</description><pubDate>Tue, 09 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;hr /&gt;
&lt;h2&gt;一、题目&lt;/h2&gt;
&lt;p&gt;$$
f(x,y)=x^4+y^4-(x+y)^2
$$&lt;/p&gt;
&lt;p&gt;已知 $(1,1)$ 与 $(-1,-1)$ 是两个驻点，判断它们处函数值是极大还是极小。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;二、多元函数极值的一般步骤&lt;/h2&gt;
&lt;p&gt;对于二元函数 $f(x,y)$，求极值通常分三步：&lt;/p&gt;
&lt;h3&gt;第一步：求一阶偏导，找驻点&lt;/h3&gt;
&lt;p&gt;驻点满足：&lt;/p&gt;
&lt;p&gt;$$
f&apos;_x=0,\qquad f&apos;_y=0
$$&lt;/p&gt;
&lt;p&gt;也就是梯度为零：&lt;/p&gt;
&lt;p&gt;$$
\nabla f=(f&apos;_x,f&apos;_y)=(0,0)
$$&lt;/p&gt;
&lt;h3&gt;第二步：求二阶偏导，构造 Hessian 矩阵&lt;/h3&gt;
&lt;p&gt;二元函数的 Hessian 矩阵为：&lt;/p&gt;
&lt;p&gt;$$
H=
\begin{pmatrix}
f&apos;&lt;em&gt;{xx} &amp;amp; f&apos;&lt;/em&gt;{xy}\
f&apos;&lt;em&gt;{yx} &amp;amp; f&apos;&lt;/em&gt;{yy}
\end{pmatrix}
$$&lt;/p&gt;
&lt;h3&gt;第三步：用二阶判别法判断极值&lt;/h3&gt;
&lt;p&gt;记&lt;/p&gt;
&lt;p&gt;$$
A=f&apos;&lt;em&gt;{xx},\quad B=f&apos;&lt;/em&gt;{xy},\quad C=f&apos;_{yy}
$$&lt;/p&gt;
&lt;p&gt;计算&lt;/p&gt;
&lt;p&gt;$$
D=AC-B^2
$$&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;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;$D&amp;gt;0,\ A&amp;gt;0$&lt;/td&gt;
&lt;td&gt;极小值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$D&amp;gt;0,\ A&amp;lt;0$&lt;/td&gt;
&lt;td&gt;极大值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$D&amp;lt;0$&lt;/td&gt;
&lt;td&gt;鞍点&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$D=0$&lt;/td&gt;
&lt;td&gt;二阶判别法失效，需要其他方法&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;hr /&gt;
&lt;h2&gt;三、记忆口诀&lt;/h2&gt;
&lt;p&gt;二元函数极值判断可以记成：&lt;/p&gt;
&lt;p&gt;$$
D=f_{xx}f_{yy}-f_{xy}^2
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$D&amp;gt;0,\ f_{xx}&amp;gt;0$：极小值；&lt;/li&gt;
&lt;li&gt;$D&amp;gt;0,\ f_{xx}&amp;lt;0$：极大值；&lt;/li&gt;
&lt;li&gt;$D&amp;lt;0$：鞍点；&lt;/li&gt;
&lt;li&gt;$D=0$：无法判断。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>openipc固件编译和烧录</title><link>https://haoyn231.github.io/posts/linux/openipc/openipc%E5%9B%BA%E4%BB%B6%E7%BC%96%E8%AF%91%E5%92%8C%E7%83%A7%E5%BD%95/</link><guid isPermaLink="true">https://haoyn231.github.io/posts/linux/openipc/openipc%E5%9B%BA%E4%BB%B6%E7%BC%96%E8%AF%91%E5%92%8C%E7%83%A7%E5%BD%95/</guid><pubDate>Thu, 28 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;编译环境需要Ubuntu20.04、Ubuntu22.04或者Debian12，如果不是对应系统的话，可以用docker，参考这个项目：&lt;/p&gt;
&lt;p&gt;::gitHub{repo=&quot;haoyn231/rk35xx_build_env&quot;}&lt;/p&gt;
&lt;h3&gt;1. 获取官方SDK&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;https://github.com/OpenIPC/firmware
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 编译SSC338Q的openipc固件&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 安装依赖
make deps

# 生成配置文件
make BOARD=ssc338q_lite defconfig

# 编译buildroot linux
make BOARD=ssc338q_lite br-linux

# 全编译，生成固件
make BOARD=ssc338q_lite all
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最终生成的固件在 output/images&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;builder@archlinux:/workspace/firmware$ ls output/images/
openipc.ssc338q-nor-lite.tgz  rootfs.cpio  rootfs.squashfs.ssc338q  rootfs.ssc338q.tar  uImage.ssc338q
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 烧录固件&lt;/h3&gt;
&lt;p&gt;对于openipc设备，如果设备能正常启动，那么烧录固件最方便的方式是把固件传输到设备文件系统里，然后用系统自带的工具进行升级。
当前设备虽然有以太网卡，但是只有接口，需要外接。所以这里采取用SD卡来传输固件到设备上。
查看flash分区表：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;root@openipc-ssc338q:~# cat /proc/mtd
dev:    size   erasesize  name
mtd0: 00040000 00010000 &quot;boot&quot;
mtd1: 00010000 00010000 &quot;env&quot;
mtd2: 00200000 00010000 &quot;kernel&quot;
mtd3: 00800000 00010000 &quot;rootfs&quot;
mtd4: 005b0000 00010000 &quot;rootfs_data&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;需要准备一个FAT格式的SD卡
&lt;img src=&quot;./images/openipc%E5%9B%BA%E4%BB%B6%E7%BC%96%E8%AF%91%E4%B8%8E%E7%83%A7%E5%BD%95.png&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;备份旧镜像&lt;/h4&gt;
&lt;p&gt;备份到SD卡中&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 先创建一个文件夹
mkdir -p /mnt/mmcblk0p1/bak/
dd if=/dev/mtd0 of=/mnt/mmcblk0p1/bak/mtd0_boot.bin
dd if=/dev/mtd1 of=/mnt/mmcblk0p1/bak/mtd1_env.bin
dd if=/dev/mtd2 of=/mnt/mmcblk0p1/bak/mtd2_kernel.bin
dd if=/dev/mtd3 of=/mnt/mmcblk0p1/bak/mtd3_rootfs.bin
dd if=/dev/mtd4 of=/mnt/mmcblk0p1/bak/mtd4_rootfs_data.bin
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;使用系统自带的 sysupgrade 来烧录固件&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;cp /mnt/mmcblk0p1/images/openipc.ssc338q-nor-lite.tgz /tmp/
sync

sysupgrade \
  --archive=/tmp/openipc.ssc338q-nor-lite.tgz
  
# 全量更新，包括kernel和rootfs  
sysupgrade --force_ver --wipe_overlay --archive=/tmp/openipc.ssc338q-nor-lite.tgz
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>使用Vivado进行ZYNQ的PL端开发基础流程</title><link>https://haoyn231.github.io/posts/fpga/%E4%BD%BF%E7%94%A8vivado%E8%BF%9B%E8%A1%8Czynq%E7%9A%84pl%E7%AB%AF%E5%BC%80%E5%8F%91%E5%9F%BA%E7%A1%80%E6%B5%81%E7%A8%8B/%E4%BD%BF%E7%94%A8vivado%E8%BF%9B%E8%A1%8Czynq%E7%9A%84pl%E7%AB%AF%E5%BC%80%E5%8F%91%E5%9F%BA%E7%A1%80%E6%B5%81%E7%A8%8B/</link><guid isPermaLink="true">https://haoyn231.github.io/posts/fpga/%E4%BD%BF%E7%94%A8vivado%E8%BF%9B%E8%A1%8Czynq%E7%9A%84pl%E7%AB%AF%E5%BC%80%E5%8F%91%E5%9F%BA%E7%A1%80%E6%B5%81%E7%A8%8B/%E4%BD%BF%E7%94%A8vivado%E8%BF%9B%E8%A1%8Czynq%E7%9A%84pl%E7%AB%AF%E5%BC%80%E5%8F%91%E5%9F%BA%E7%A1%80%E6%B5%81%E7%A8%8B/</guid><pubDate>Mon, 18 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;从创建工程到烧录比特流文件到芯片里，最终固化到非易失性存储器里。这里的比特流文件可以理解为最终生成的结果，类似于编译出的二进制文件。&lt;/p&gt;
&lt;p&gt;在写好verilog文件，到生成比特流文件，中间还有很多步骤，包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;RTL描述与分析&lt;/li&gt;
&lt;li&gt;创建激励与仿真测试&lt;/li&gt;
&lt;li&gt;设计综合&lt;/li&gt;
&lt;li&gt;添加设计约束&lt;/li&gt;
&lt;li&gt;设计实现（布局布线）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;完成后才能生成比特流文件。整个流程对应Vivado界面左侧的Flow Navigator。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/image%20(0).png&quot; alt=&quot;0&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;一、示例程序：LED灯闪烁&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;module led_flash(
    input clk,
    input rst_n,
    output reg led
    );
    reg [25:0] cnt;
    always @ (posedge clk or negedge rst_n)
    begin
        if (!rst_n)
            cnt &amp;lt;=26&apos;d0;
        else if (cnt &amp;lt; 26&apos;d49_999_999)
            cnt &amp;lt;= cnt + 1&apos;b1;
        else
            cnt &amp;lt;= 26&apos;d0;
    end

    always @ (posedge clk or negedge rst_n)
    begin
        if (!rst_n)
            led &amp;lt;= 1&apos;b0;
        else if (cnt == 26&apos;d49_999_999)
            led &amp;lt;= ~led;
        else
            led &amp;lt;= led;
    end
    
endmodule 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建了一个LED灯闪烁的模块，包括时钟线（高电平触发 posedge）、复位信号（低电平触发 negedge）、输出的LED信号和一个26位的计数寄存器。&lt;/p&gt;
&lt;p&gt;两个always控制块，一个用作计数器，另一个用于控制LED。&lt;/p&gt;
&lt;h3&gt;二、RTL描述与分析&lt;/h3&gt;
&lt;p&gt;对当前HDL工程进行一次检查，检查是否有语法错误，但是无法检查是否有逻辑错误。同时把HDL展开为真正的逻辑结构。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/image%20(1).png&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;RTL Analysis就是指对理解当前的HDL工程到底是在干什么，它可以生成一个概括性的原理图，但不是真正的电路，只是一个示意图（RTL级抽象电路），我们可以通过查看这个示意图来确定自己编写的Verilog是否符合要求。&lt;/p&gt;
&lt;p&gt;如下是示例工程中的led_flash对应的原理图：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/image%20(2).png&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可以看到其中的时钟信号、复位信号、LED信号、逻辑门和触发器等等。&lt;/p&gt;
&lt;h3&gt;三、激励创建与仿真&lt;/h3&gt;
&lt;p&gt;对于大型的HDL工程，在实际综合前需要创建一个激励，对它进行仿真测试，来查看是否符合预期。它虽然是HDL开发的必要流程，但不是强制性的，对于简单的工程也可以不进行仿真测试，本文暂时略过。&lt;/p&gt;
&lt;h3&gt;四、设计综合&lt;/h3&gt;
&lt;p&gt;设计综合 Synthesis 就是将我们的HDL工程转化为门级网表&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/image%20(3).png&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可以得到工程对应的利用FPGA器件所具有的基本元件LUT搭建的电路图&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/image%20(4).png&quot; alt=&quot;4&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可以查看不同的LUT对应的逻辑表达式和真值表映射&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/image%20(5).png&quot; alt=&quot;5&quot; /&gt;&lt;/p&gt;
&lt;p&gt;通过Report Utilization，可以查看逻辑资源的使用情况
&lt;img src=&quot;./images/image%20(6).png&quot; alt=&quot;6&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;五、添加设计约束&lt;/h3&gt;
&lt;p&gt;在之前的操作里，完成了HDL电路设计、RTL分析和综合，最终生成了FPGA里真实的电路结构，但是它们还只是在FPGA内部，无法与外部交互，不知道时钟信号和复位信号从哪儿来，不知道LED信号对应哪个真实引脚。因此需要进行约束，其中引脚约束就是给信号绑定真实的IO，让它操作真实的引脚的电平，进行输入输出。&lt;/p&gt;
&lt;p&gt;在综合后的界面里，点击layout里的IO Planning，打开引脚分配界面，给信号分配真实的引脚&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/image%20(7).png&quot; alt=&quot;7&quot; /&gt;&lt;/p&gt;
&lt;p&gt;保存后就可以生成约束文件XDC&lt;/p&gt;
&lt;h3&gt;六、设计实现（布局布线）&lt;/h3&gt;
&lt;p&gt;在综合得到真实电路图和添加约束后，就可以通过电路图和约束文件在FPGA里实现这个设计，即把之前的设计实现在FPGA器件里。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/image%20(8).png&quot; alt=&quot;8&quot; /&gt;&lt;/p&gt;
&lt;p&gt;完成设计后，可以在Device中看到FPGA中生成的电路，也可以看到连接关系&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/image%20(9).png&quot; alt=&quot;9&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;七、生成比特流文件&lt;/h3&gt;
&lt;p&gt;实现设计后就可以生成比特流文件&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/image%20(10).png&quot; alt=&quot;10&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Generate Bitstream即可&lt;/p&gt;
&lt;h3&gt;八、烧录与固化&lt;/h3&gt;
&lt;p&gt;通过Flow打开Hardware Manager，连接设备，烧录比特流即可&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/image%20(11).png&quot; alt=&quot;11&quot; /&gt;&lt;/p&gt;
&lt;p&gt;对于程序固化来说，如果是纯FPGA芯片，可以通过Vivado之间把bin烧录到开发板的Flash中，但是对于ZYNQ，PL端没有引出非易失性存储器的控制引脚，需要使用SDK通过PS端来完成程序固化，暂时不做展开。&lt;/p&gt;
</content:encoded></item><item><title>QEMU学习（一）启动buildroot的virt板</title><link>https://haoyn231.github.io/posts/qemu%E5%AD%A6%E4%B9%A0/qemu%E5%AD%A6%E4%B9%A0%E4%B8%80%E5%90%AF%E5%8A%A8buildroot%E7%9A%84virt%E6%9D%BF/</link><guid isPermaLink="true">https://haoyn231.github.io/posts/qemu%E5%AD%A6%E4%B9%A0/qemu%E5%AD%A6%E4%B9%A0%E4%B8%80%E5%90%AF%E5%8A%A8buildroot%E7%9A%84virt%E6%9D%BF/</guid><description>用QEMU启动buildroot的virt板，走一遍流程，编译并启动一个简单的Linux系统</description><pubDate>Thu, 30 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Virt板是QEMU官方提供的一个专门模拟ARM/RISC-V指令集的通用设备，Buildroot里维护了很多QEMU Virt的配置，可以直接编译并启动：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/buildroot/buildroot/tree/master/configs&quot;&gt;https://github.com/buildroot/buildroot/tree/master/configs&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/QEMU%E5%AD%A6%E4%B9%A0%EF%BC%88%E4%B8%80%EF%BC%891.png&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;走一遍流程，用QEMU启动qemu_aarch64_virt_defconfig&lt;/p&gt;
&lt;h2&gt;一、获取源码并编译&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;git clone https://github.com/buildroot/buildroot.git

cd buildroot

# 生成对应的.config文件
make qemu_aarch64_virt_defconfig

# 编译生成kernel和rootfs
make -j$(nproc)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用buildroot的默认配置，它会自动下载或构建交叉编译工具链，下载linux kernel源码并编译&lt;/p&gt;
&lt;p&gt;编译后的结果在output/images中&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;❯ ls
Image  rootfs.ext2  rootfs.ext4  start-qemu.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;二、用QEMU启动Linux系统&lt;/h2&gt;
&lt;p&gt;buildroot在编译后会提供一个直接启动QEMU的脚本，其中具体操作QEMU的部分是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;qemu-system-aarch64 \
  -M virt \
  -cpu cortex-a53 \
  -nographic \
  -smp 1 \
  -kernel Image \
  -append &quot;rootwait root=/dev/vda console=ttyAMA0&quot; \
  -netdev user,id=eth0 \
  -device virtio-net-device,netdev=eth0 \
  -drive file=rootfs.ext4,if=none,format=raw,id=hd0 \
  -device virtio-blk-device,drive=hd0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;模拟cortex-a53的QEMU官方提供的ARM通用虚拟开发板，单核CPU, 无图形化界面，kernel的Image就在脚本所在目录。&lt;/p&gt;
&lt;p&gt;其中值得注意的是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;传递内核参数&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-append &quot;rootwait root=/dev/vda console=ttyAMA0&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;创建网络与添加虚拟网卡&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;利用了宿主机的虚拟磁盘来承载rootfs.ext4，然后挂载这个虚拟磁盘/dev/vda来作为rootfs，rootwait表示等待虚拟磁盘出现后再启动&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rootfs.ext4 (宿主机文件)
↓
QEMU 当成虚拟硬盘
↓
虚拟硬盘通过 VirtIO 暴露给 guest Linux
↓
Linux 识别成 /dev/vda
↓
内核根据 root=/dev/vda 挂载为 /
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;三、启动过程&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;❯ ./start-qemu.sh
Booting Linux on physical CPU 0x0000000000 [0x410fd034]
Linux version 6.18.7 (hao@archlinux) (aarch64-buildroot-linux-gnu-gcc.br_real (Buildroot 2026.02-701-ga9942e8793) 14.3.0, GNU ld (GNU Binutils) 2.45.1) #1 SMP Thu Apr 30 16:33:49 CST 2026
random: crng init done
Machine model: linux,dummy-virt
efi: UEFI not found.
OF: reserved mem: Reserved memory: No reserved-memory node in the DT
Zone ranges:
  DMA      [mem 0x0000000040000000-0x0000000047ffffff]
  DMA32    empty
  Normal   empty
Movable zone start for each node
Early memory node ranges
  node   0: [mem 0x0000000040000000-0x0000000047ffffff]
Initmem setup node 0 [mem 0x0000000040000000-0x0000000047ffffff]
psci: probing for conduit method from DT.
psci: PSCIv1.1 detected in firmware.
psci: Using standard PSCI v0.2 function IDs
psci: Trusted OS migration not required
psci: SMC Calling Convention v1.0
percpu: Embedded 20 pages/cpu s43864 r8192 d29864 u81920
Detected VIPT I-cache on CPU0
CPU features: detected: ARM erratum 845719
alternatives: applying boot alternatives
Kernel command line: rootwait root=/dev/vda console=ttyAMA0
printk: log buffer data + meta data: 131072 + 458752 = 589824 bytes
Dentry cache hash table entries: 16384 (order: 5, 131072 bytes, linear)
Inode-cache hash table entries: 8192 (order: 4, 65536 bytes, linear)
software IO TLB: SWIOTLB bounce buffer size adjusted to 0MB
software IO TLB: area num 1.
software IO TLB: mapped [mem 0x0000000047f1e000-0x0000000047f5e000] (0MB)
Built 1 zonelists, mobility grouping on.  Total pages: 32768
mem auto-init: stack:all(zero), heap alloc:off, heap free:off
SLUB: HWalign=64, Order=0-3, MinObjects=0, CPUs=1, Nodes=1
rcu: Hierarchical RCU implementation.
rcu:    RCU restricting CPUs from NR_CPUS=512 to nr_cpu_ids=1.
rcu: RCU calculated value of scheduler-enlistment delay is 25 jiffies.
rcu: Adjusting geometry for rcu_fanout_leaf=16, nr_cpu_ids=1
NR_IRQS: 64, nr_irqs: 64, preallocated irqs: 0
Root IRQ handler: gic_handle_irq
GICv2m: range[mem 0x08020000-0x08020fff], SPI[80:143]
rcu: srcu_init: Setting srcu_struct sizes based on contention.
arch_timer: cp15 timer running at 62.50MHz (virt).
clocksource: arch_sys_counter: mask: 0x1ffffffffffffff max_cycles: 0x1cd42e208c, max_idle_ns: 881590405314 ns
sched_clock: 57 bits at 63MHz, resolution 16ns, wraps every 4398046511096ns
Console: colour dummy device 80x25
Calibrating delay loop (skipped), value calculated using timer frequency.. 125.00 BogoMIPS (lpj=250000)
pid_max: default: 32768 minimum: 301
Mount-cache hash table entries: 512 (order: 0, 4096 bytes, linear)
Mountpoint-cache hash table entries: 512 (order: 0, 4096 bytes, linear)
cacheinfo: Unable to detect cache hierarchy for CPU 0
rcu: Hierarchical SRCU implementation.
rcu:    Max phase no-delay instances is 1000.
EFI services will not be available.
smp: Bringing up secondary CPUs ...
smp: Brought up 1 node, 1 CPU
SMP: Total of 1 processors activated.
CPU: All CPU(s) started at EL1
CPU features: detected: 32-bit EL0 Support
CPU features: detected: CRC32 instructions
CPU features: detected: PMUv3
alternatives: applying system-wide alternatives
Memory: 112176K/131072K available (8640K kernel code, 894K rwdata, 2100K rodata, 1152K init, 510K bss, 17476K reserved, 0K cma-reserved)
devtmpfs: initialized
clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 7645041785100000 ns
posixtimers hash table entries: 512 (order: 1, 8192 bytes, linear)
futex hash table entries: 256 (16384 bytes on 1 NUMA nodes, total 16 KiB, linear).
29392 pages in range for non-PLT usage
520912 pages in range for PLT usage
DMI not present or invalid.
NET: Registered PF_NETLINK/PF_ROUTE protocol family
DMA: preallocated 128 KiB GFP_KERNEL pool for atomic allocations
DMA: preallocated 128 KiB GFP_KERNEL|GFP_DMA pool for atomic allocations
DMA: preallocated 128 KiB GFP_KERNEL|GFP_DMA32 pool for atomic allocations
thermal_sys: Registered thermal governor &apos;step_wise&apos;
cpuidle: using governor menu
hw-breakpoint: found 6 breakpoint and 4 watchpoint registers.
ASID allocator initialised with 65536 entries
Serial: AMBA PL011 UART driver
9000000.pl011: ttyAMA0 at MMIO 0x9000000 (irq = 13, base_baud = 0) is a PL011 rev1
printk: console [ttyAMA0] enabled
ACPI: Interpreter disabled.
iommu: Default domain type: Translated
iommu: DMA domain TLB invalidation policy: strict mode
SCSI subsystem initialized
pps_core: LinuxPPS API ver. 1 registered
pps_core: Software ver. 5.3.6 - Copyright 2005-2007 Rodolfo Giometti &amp;lt;giometti@linux.it&amp;gt;
PTP clock support registered
vgaarb: loaded
clocksource: Switched to clocksource arch_sys_counter
pnp: PnP ACPI: disabled
NET: Registered PF_INET protocol family
IP idents hash table entries: 2048 (order: 2, 16384 bytes, linear)
tcp_listen_portaddr_hash hash table entries: 256 (order: 0, 4096 bytes, linear)
Table-perturb hash table entries: 65536 (order: 6, 262144 bytes, linear)
TCP established hash table entries: 1024 (order: 1, 8192 bytes, linear)
TCP bind hash table entries: 1024 (order: 3, 32768 bytes, linear)
TCP: Hash tables configured (established 1024 bind 1024)
UDP hash table entries: 256 (order: 2, 16384 bytes, linear)
UDP-Lite hash table entries: 256 (order: 2, 16384 bytes, linear)
NET: Registered PF_UNIX/PF_LOCAL protocol family
PCI: CLS 0 bytes, default 64
workingset: timestamp_bits=46 max_order=15 bucket_order=0
fuse: init (API version 7.45)
Block layer SCSI generic (bsg) driver version 0.4 loaded (major 249)
io scheduler mq-deadline registered
io scheduler kyber registered
pci-host-generic 4010000000.pcie: host bridge /pcie@10000000 ranges:
pci-host-generic 4010000000.pcie:       IO 0x003eff0000..0x003effffff -&amp;gt; 0x0000000000
pci-host-generic 4010000000.pcie:      MEM 0x0010000000..0x003efeffff -&amp;gt; 0x0010000000
pci-host-generic 4010000000.pcie:      MEM 0x8000000000..0xffffffffff -&amp;gt; 0x8000000000
pci-host-generic 4010000000.pcie: Memory resource size exceeds max for 32 bits
pci-host-generic 4010000000.pcie: ECAM at [mem 0x4010000000-0x401fffffff] for [bus 00-ff]
pci-host-generic 4010000000.pcie: PCI host bridge to bus 0000:00
pci_bus 0000:00: root bus resource [bus 00-ff]
pci_bus 0000:00: root bus resource [io  0x0000-0xffff]
pci_bus 0000:00: root bus resource [mem 0x10000000-0x3efeffff]
pci_bus 0000:00: root bus resource [mem 0x8000000000-0xffffffffff]
pci 0000:00:00.0: [1b36:0008] type 00 class 0x060000 conventional PCI endpoint
pci_bus 0000:00: resource 4 [io  0x0000-0xffff]
pci_bus 0000:00: resource 5 [mem 0x10000000-0x3efeffff]
pci_bus 0000:00: resource 6 [mem 0x8000000000-0xffffffffff]
virtio_blk virtio0: 1/0/0 default/read/poll queues
virtio_blk virtio0: [vda] 122880 512-byte logical blocks (62.9 MB/60.0 MiB)
rtc-pl031 9010000.pl031: registered as rtc0
rtc-pl031 9010000.pl031: setting system clock to 2026-04-30T08:36:05 UTC (1777538165)
hw perfevents: enabled with armv8_pmuv3 PMU driver, 7 (0,8000003f) counters available
NET: Registered PF_INET6 protocol family
Segment Routing with IPv6
In-situ OAM (IOAM) with IPv6
sit: IPv6, IPv4 and MPLS over IPv4 tunneling driver
NET: Registered PF_PACKET protocol family
NET: Registered PF_KEY protocol family
NET: Registered PF_VSOCK protocol family
registered taskstats version 1
clk: Disabling unused clocks
PM: genpd: Disabling unused power domains
check access for rdinit=/init failed: -2, ignoring
EXT4-fs (vda): orphan cleanup on readonly fs
EXT4-fs (vda): mounted filesystem c394bba6-5965-4a70-a0a4-17e04562b2fb ro with ordered data mode. Quota mode: disabled.
VFS: Mounted root (ext4 filesystem) readonly on device 254:0.
devtmpfs: mounted
Freeing unused kernel memory: 1152K
Run /sbin/init as init process
EXT4-fs (vda): re-mounted c394bba6-5965-4a70-a0a4-17e04562b2fb r/w.
Saving 256 bits of creditable seed for next boot
Starting syslogd: OK
Starting klogd: OK
Running sysctl: OK
Starting network: udhcpc: started, v1.37.0
udhcpc: broadcasting discover
udhcpc: broadcasting select for 10.0.2.15, server 10.0.2.2
udhcpc: lease of 10.0.2.15 obtained from 10.0.2.2, lease time 86400
deleting routers
adding dns 10.0.2.3
OK
Starting crond: OK

Welcome to Buildroot
buildroot login: root
# ls
# pwd
/root
# ls /
bin           lib           media         root          tmp
crond.reboot  lib64         mnt           run           usr
dev           linuxrc       opt           sbin          var
etc           lost+found    proc          sys

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./images/QEMU%E5%AD%A6%E4%B9%A0%EF%BC%88%E4%B8%80%EF%BC%892.png&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;退出QEMU：先按ctrl + a,再按x&lt;/p&gt;
</content:encoded></item><item><title>RKNN 部署与不同 SOC 性能分析</title><link>https://haoyn231.github.io/posts/linux/rknn%E9%83%A8%E7%BD%B2%E4%B8%8E%E4%B8%8D%E5%90%8Csoc%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90/</link><guid isPermaLink="true">https://haoyn231.github.io/posts/linux/rknn%E9%83%A8%E7%BD%B2%E4%B8%8E%E4%B8%8D%E5%90%8Csoc%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90/</guid><description>记录 RKNN 的部署流程，测试 YOLOv5 模型在不同硬件平台上的性能表现，分析适用场景。</description><pubDate>Thu, 26 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、概述&lt;/h2&gt;
&lt;p&gt;记录 RKNN 的部署流程，测试 YOLOv5 模型在不同硬件平台上的性能表现，分析适用场景。&lt;/p&gt;
&lt;p&gt;需要三个仓库：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/airockchip/yolov5&quot;&gt;yolov5&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;一个极其流行的单阶段目标检测算法实现库。它是&lt;strong&gt;模型的来源&lt;/strong&gt;。在这里获取原始的权重文件（&lt;code&gt;.pt&lt;/code&gt;），并利用其中的 &lt;code&gt;export.py&lt;/code&gt; 脚本，配合 &lt;code&gt;--rknpu&lt;/code&gt; 参数，导出一个专门为瑞芯微 NPU 结构优化过的中间格式（&lt;code&gt;.onnx&lt;/code&gt;）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/rockchip-linux/rknn-toolkit2&quot;&gt;rknn-toolkit2&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;瑞芯微为旗下 NPU 芯片提供的软件开发套件（SDK）。它是&lt;strong&gt;转换的核心工具&lt;/strong&gt;。它提供 C 或 Python 接口，负责把 &lt;code&gt;.onnx&lt;/code&gt; 模型转换成板子能跑的 &lt;code&gt;.rknn&lt;/code&gt; 格式。此外，它还承担了&lt;strong&gt;模型量化&lt;/strong&gt;（将浮点数转为 &lt;code&gt;int8&lt;/code&gt;）和&lt;strong&gt;精度评估&lt;/strong&gt;的重要任务。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/airockchip/rknn_model_zoo&quot;&gt;rknn_model_zoo&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;瑞芯微官方提供的各种主流算法（如 YOLO、MobileNet 等）的部署示例集合。&lt;strong&gt;测试工程模板&lt;/strong&gt;。它提供了经过优化的 C++ 源码（如 &lt;code&gt;rknn_yolov5_demo&lt;/code&gt;）和交叉编译脚本。最终在板子上运行的那个“测速程序”，就是从这个仓库里编译出来的。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;两个python虚拟环境&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;yolov5 环境&lt;/p&gt;
&lt;p&gt;安装了原生 PyTorch 和 YOLOv5 所需的高版本依赖。&lt;strong&gt;负责模型导出&lt;/strong&gt;。在这个环境下，运行 &lt;code&gt;python export.py&lt;/code&gt;，把 &lt;code&gt;.pt&lt;/code&gt; 变成 &lt;code&gt;.onnx&lt;/code&gt;。一旦拿到 &lt;code&gt;.onnx&lt;/code&gt;，这个环境的任务就完成了。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;RKNN-toolkit2 环境&lt;/p&gt;
&lt;p&gt;安装了特定版本的 &lt;code&gt;onnx&lt;/code&gt;和瑞芯微私有的 &lt;code&gt;rknn-toolkit2&lt;/code&gt; 软件包。&lt;strong&gt;负责模型量化与转换&lt;/strong&gt;。在这个环境下，运行转换脚本，把上一步得到的 &lt;code&gt;.onnx&lt;/code&gt; 喂进去，经过 &lt;code&gt;int8&lt;/code&gt; 量化，最后得到出 &lt;code&gt;.rknn&lt;/code&gt; 文件。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://markdownforyuanhao.oss-cn-hangzhou.aliyuncs.com/img1/20260226230208030.png&quot; alt=&quot;image-20260226230207856&quot; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;二、环境搭建&lt;/h2&gt;
&lt;p&gt;克隆上述三个仓库到&lt;code&gt;~/projects/rknn/&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;RKNN-toolkit2 环境搭建&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;conda create -n RKNN-Toolkit2 python=3.9
conda activate RKNN-Toolkit2

# 克隆工具链
git clone https://github.com/airockchip/rknn-toolkit2.git
cd rknn-toolkit2

# 安装依赖（注意根据 Python 版本选择对应的 requirements 文件）
pip install -r rknn-toolkit2/packages/x86_64/requirements_cp39-2.3.2.txt -i https://pypi.tuna.tsinghua.edu.cn/simple/

# 安装 RKNN-Toolkit2 软件包
pip install rknn-toolkit2/packages/x86_64/rknn_toolkit2-2.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Yolov5 环境搭建&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 克隆仓库
git clone https://github.com/airockchip/yolov5.git

cd yolov5

# 创建虚拟环境
conda create -n yolov5 python=3.9
# 进入虚拟环境并安装依赖库
conda activate yolov5
pip install -r requirements.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;三、转换模型：ONNX to RKNN&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;获取导出 ONNX 文件&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;（yolov5 conda 环境下）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd yolov5
python export.py --rknpu --weight yolov5s.pt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;得到 &lt;code&gt;yolov5s.pt&lt;/code&gt;、&lt;code&gt;yolov5s.onnx&lt;/code&gt;和&lt;code&gt;RK_anchors.txt&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;获取 RKNN 文件&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;（在RKNN-toolkit2 conda 环境下）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd rknn_model_zoo/examples/yolov5/python

# 指定 onnx 模型的位置和 target（rv1106）
python3 convert.py ~/projects/rknn/yolov5/yolov5s.onnx rv1106
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;转换为RKNN文件&lt;code&gt;examples/yolov5/model/yolov5.rknn&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;四、编译测试程序&lt;/h2&gt;
&lt;p&gt;在 &lt;code&gt;~/projects/rknn/rknn_model_zoo/examples/yolov5/cpp&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 导入编译工具链路径
export GCC_COMPILER=/home/hao/projects/luckfox-pico/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin/arm-rockchip830-linux-uclibcgnueabihf

./build-linux.sh -t rv1106 -a armv7l -d yolov5 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;编译并完成安装，将&lt;code&gt;rknn_model_zoo/install/rv1106_linux_armv7l/rknn_yolov5_demo&lt;/code&gt;通过 scp 协议传输到开发板上，执行程序&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./rknn_yolov5_demo model/yolov5.rknn model/bus.jpg
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;（原版代码只会执行一次，修改了demo的源码，让它连续执行10次并获取精确的 NPU 推理时常，计算结果后打印出来，修改后的源码在 https://github.com/haoyn231/rknn_model_zoo/tree/yolov5_benchmark/examples/yolov5/cpp）&lt;/p&gt;
&lt;p&gt;运行结果：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[root@luckfox rknn_yolov5_demo]# ./rknn_yolov5_demo model/yolov5.rknn model/bus.jpg
load lable ./model/coco_80_labels_list.txt
model input num: 1, output num: 3
input tensors:
  index=0, name=images, n_dims=4, dims=[1, 640, 640, 3], n_elems=1228800, size=1228800, fmt=NHWC, type=INT8, qnt_type=AFFINE, zp=-128, scale=0.003922
output tensors:
  index=0, name=output0, n_dims=4, dims=[1, 255, 80, 80], n_elems=1632000, size=1632000, fmt=NCHW, type=INT8, qnt_type=AFFINE, zp=-128, scale=0.003922
  index=1, name=367, n_dims=4, dims=[1, 255, 40, 40], n_elems=408000, size=408000, fmt=NCHW, type=INT8, qnt_type=AFFINE, zp=-128, scale=0.003922
  index=2, name=369, n_dims=4, dims=[1, 255, 20, 20], n_elems=102000, size=102000, fmt=NCHW, type=INT8, qnt_type=AFFINE, zp=-128, scale=0.003922
input_attrs[0].size_with_stride=1228800
model is NHWC input fmt
model input height=640, width=640, channel=3
origin size=640x640 crop size=640x640
input image: 640 x 640, subsampling: 4:2:0, colorspace: YCbCr, orientation: 1
scale=1.000000 dst_box=(0 0 639 639) allow_slight_change=1 _left_offset=0 _top_offset=0 padding_w=0 padding_h=0
rga_api version 1.10.1_[0]
rknn_run
========================================
NPU Inference time:
First run time: 85.209 ms
Average time of 10 runs: 86.624 ms
Estimated FPS: 11.54
========================================
person @ (209 242 283 516) 0.828
person @ (474 230 561 522) 0.798
person @ (114 235 207 543) 0.796
bus @ (93 133 549 462) 0.787
person @ (78 330 122 519) 0.408
write_image path: out.png width=640 height=640 channel=3 data=0xa5a31000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;推理结果：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://markdownforyuanhao.oss-cn-hangzhou.aliyuncs.com/img1/20260226211359529.png&quot; alt=&quot;out&quot; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;五、结果分析&lt;/h2&gt;
&lt;h3&gt;瑞芯微主流 SoC NPU 性能实测数据汇总表&lt;/h3&gt;
&lt;p&gt;除了 RV1106G3 的数据为本文章实测外，其他数据由触觉智能的文章提供。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;测试条件&lt;/strong&gt;：YOLOv5s-640-640 模型，INT8 量化，640*640 分辨率测试图片。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;芯片型号&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;核心硬件参数&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;首次运行耗时 (ms)&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;连续10次平均耗时 (ms)&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;帧率估算 (FPS)&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RV1106G3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;单核 A7 + 1.0 TOPS NPU&lt;/td&gt;
&lt;td&gt;85.209&lt;/td&gt;
&lt;td&gt;86.624&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;11.54&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RK3562&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;四核 A53 + 1.0 TOPS NPU&lt;/td&gt;
&lt;td&gt;66.165&lt;/td&gt;
&lt;td&gt;51.885&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;19.3&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RV1126B&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;四核 A53 + 3.0 TOPS NPU&lt;/td&gt;
&lt;td&gt;26.897&lt;/td&gt;
&lt;td&gt;26.0425&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;38.4&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RK3576&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4核A72 + 4核A53 + 6.0 TOPS NPU&lt;/td&gt;
&lt;td&gt;55.395&lt;/td&gt;
&lt;td&gt;23.7342&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;42.1&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RK3588&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4核A76 + 4核A55 + 6.0 TOPS NPU&lt;/td&gt;
&lt;td&gt;30.887&lt;/td&gt;
&lt;td&gt;21.2581&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;47.0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;img src=&quot;https://markdownforyuanhao.oss-cn-hangzhou.aliyuncs.com/img1/20260226223055099.png&quot; alt=&quot;2a1856faff4937fdaca5ed6c01602077&quot; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;实验结果分析与硬件适配性评估&lt;/h3&gt;
&lt;p&gt;通过对瑞芯微（Rockchip）系列 SoC 的推理性能实测发现，其 NPU 推理效率遵循 RK3588 &amp;gt; RK3576 &amp;gt; RV1126B &amp;gt; RK3562 &amp;gt; RV1106G3 的梯度分布，其中具备 6 TOPS 算力的 RK3588 与 RK3576 以超过 40 FPS 的表现领跑高实时性场景；作为对比，主打超低功耗与成本优势的 &lt;strong&gt;RV1106G3&lt;/strong&gt;（单核 A7 + 1.0 TOPS NPU）在处理 640*640 大分辨率模型时实现了 &lt;strong&gt;11.54 FPS&lt;/strong&gt; 的实测帧率，且展现出极佳的时延确定性，其首次运行与平均耗时波动显著低于多核架构芯片，使其在智能门铃、低功耗 IPC 等对响应稳定性要求较高、且成本敏感的垂直场景中，展现出比四核 A53 架构的 RK3562 更为突出的性价比优势。&lt;/p&gt;
&lt;h2&gt;参考链接&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;https://mp.weixin.qq.com/s/8cPELPb-4YGDo0fZ_o--Aw&lt;/li&gt;
&lt;li&gt;https://wiki.luckfox.com/zh/Luckfox-Pico-Ultra/RKNN&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>视频文案：一个视频讲清楚嵌入式Linux交叉编译</title><link>https://haoyn231.github.io/posts/linux/%E4%B8%80%E4%B8%AA%E8%A7%86%E9%A2%91%E8%AE%B2%E6%B8%85%E6%A5%9A%E5%B5%8C%E5%85%A5%E5%BC%8Flinux%E4%BA%A4%E5%8F%89%E7%BC%96%E8%AF%91/</link><guid isPermaLink="true">https://haoyn231.github.io/posts/linux/%E4%B8%80%E4%B8%AA%E8%A7%86%E9%A2%91%E8%AE%B2%E6%B8%85%E6%A5%9A%E5%B5%8C%E5%85%A5%E5%BC%8Flinux%E4%BA%A4%E5%8F%89%E7%BC%96%E8%AF%91/</guid><description>嵌入式Linux交叉编译的基础概念和技巧</description><pubDate>Thu, 19 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;iframe width=&quot;100%&quot; height=&quot;468&quot; src=&quot;//player.bilibili.com/player.html?bvid=BV17XZ9BfEaH&amp;amp;p=1&quot; scrolling=&quot;no&quot; border=&quot;0&quot; frameborder=&quot;no&quot; framespacing=&quot;0&quot; allowfullscreen=&quot;true&quot;&amp;gt; &amp;lt;/iframe&amp;gt;&lt;/p&gt;
&lt;p&gt;（上面视频的文案）&lt;/p&gt;
&lt;p&gt;大家新年好！最近突发奇想，决定写一篇关于&lt;strong&gt;嵌入式 Linux 交叉编译&lt;/strong&gt;的内容 。&lt;/p&gt;
&lt;p&gt;之所以想聊这个，是因为我发现很多刚入门的同学经常在群里问：编译出来的文件怎么烧录到开发板？动态库链接怎么老是报错？ 今天就把我这些年的经验总结分享给大家，希望能帮大家避坑。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;1. 搞清楚谁是 Host，谁是 Target&lt;/h2&gt;
&lt;p&gt;在聊编译之前，需要先弄明白两个最基本的概念：&lt;strong&gt;Host（主机）&lt;/strong&gt; 和 &lt;strong&gt;Target（目标机）&lt;/strong&gt; 。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Host（编译平台）&lt;/strong&gt;：指你干活的机器。比如学单片机时，Host 通常是 &lt;strong&gt;X86 架构的 Windows&lt;/strong&gt;；而在嵌入式 Linux 开发中，我们的 Host 一般是 &lt;strong&gt;X86 架构的 Linux 系统&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Target（运行平台）&lt;/strong&gt;：指程序最终运行的地方 。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;STM32&lt;/strong&gt;：属于 ARM Cortex-M 架构（32 位 ARM）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;泰山派 (RK3566)&lt;/strong&gt;：四核 Cortex-A55，属于 &lt;strong&gt;ARM64&lt;/strong&gt; 或 &lt;strong&gt;aarch64&lt;/strong&gt; 架构 。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ESP32&lt;/strong&gt;：有 Xtensa 架构，也有 RISC-V 架构 。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以，找编译链的时候，不能只看 Target，还得看 Host 能不能运行这个工具链 。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. Target 还要区分 ABI：Bare Metal 和 Linux&lt;/h2&gt;
&lt;p&gt;打开 ARM 官网的工具链下载页面，你会发现种类繁多 。这里有两个关键点：&lt;/p&gt;
&lt;h3&gt;操作系统区别&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;裸机 (Bare Metal)&lt;/strong&gt;：比如编译 STM32 或运行 RTOS 的程序，要选 &lt;code&gt;arm-none-eabi&lt;/code&gt; 。&lt;code&gt;EABI&lt;/code&gt; 指的是嵌入式应用二进制接口 。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Linux 目标机&lt;/strong&gt;：编译像 RK3566 这种跑 Linux 的设备，要选 &lt;code&gt;aarch64-linux-gnu&lt;/code&gt; 。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;硬件浮点 (Hard Float)&lt;/h3&gt;
&lt;p&gt;对于 ARMv7 架构（比如三核 A7 的 RK3506），还要注意有没有&lt;strong&gt;硬件浮点支持&lt;/strong&gt; 。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;gnueabihf&lt;/code&gt;：表示支持硬件浮点（Hard Float）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;gnueabi&lt;/code&gt;：则没有这个后缀 （软件浮点）。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;（注：ARMv8/AArch64 之后默认都实现了硬件浮点，所以通常不再专门区分）。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3. Linux的核心动态库：Libc 库&lt;/h2&gt;
&lt;p&gt;在 Linux 上运行的几乎所有用户态程序，都会动态链接 &lt;strong&gt;Libc（C 库）&lt;/strong&gt; 。它是对系统调用的封装，是你程序和内核沟通的桥梁 。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Glibc&lt;/strong&gt;：GNU Linux 上的标准实现 。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Musl&lt;/strong&gt;：一种更轻量、非常适合&lt;strong&gt;静态链接&lt;/strong&gt;的实现 。如果你追求程序的兼容性，不想管目标设备上的 Libc 版本是多少，用 Musl 把 C 库直接静态编译进程序里是个好选择 。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：做单片机开发的同学可能没这个概念，因为单片机程序是全静态编译后直接烧录闪存的，没有操作系统的动态链接概念 。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;4. 交叉编译动态链接：Sysroot&lt;/h2&gt;
&lt;p&gt;这是最让新人头疼的地方。你的程序不只链接 C 库，还可能链接音频库（如 ALSA）、视频编解码库等 。 这些库都在开发板上（&lt;code&gt;/usr/lib&lt;/code&gt; 目录下），你的主机（Host）里没有，编译时去哪里找？&lt;/p&gt;
&lt;p&gt;这时候就需要 &lt;strong&gt;Sysroot&lt;/strong&gt; 。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sysroot 的作用&lt;/strong&gt;：它相当于在你的主机上“伪造”了一个目标机的根目录 。它同步了目标机的 &lt;code&gt;/lib&lt;/code&gt;、&lt;code&gt;/usr/include&lt;/code&gt;、&lt;code&gt;/usr/lib&lt;/code&gt; 等目录 。&lt;/p&gt;
&lt;h3&gt;如何获取 Sysroot？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Buildroot 方案&lt;/strong&gt;：如果你用 Buildroot SDK，它编译完后会在 &lt;code&gt;output/target&lt;/code&gt; 下生成一个备份，这就是现成的 Sysroot 。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;任何由自己编译的 rootfs，都是相同的原理&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;主流发行版（Ubuntu/Debian）&lt;/strong&gt;：它们通过 &lt;code&gt;apt&lt;/code&gt; 管理软件，主机里没有现成的备份 。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;技巧&lt;/strong&gt;：你可以用 &lt;code&gt;rsync&lt;/code&gt; 工具，把开发板上的 &lt;code&gt;/usr/lib&lt;/code&gt; 和 &lt;code&gt;/usr/include&lt;/code&gt; 增量同步到主机的一个目录里，然后编译时指定这个目录为 Sysroot 。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;5. 总结&lt;/h2&gt;
&lt;p&gt;无论是 C/C++ 还是 Rust，交叉编译的核心逻辑是一样的 ：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;设置好交叉工具链（GCC/LLVM）的路径 。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;指定正确的 Sysroot 路径&lt;/strong&gt;，让编译器能找到目标平台的头文件和动态库 。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>异步编程学习（03）同步与异步，Promise与Future</title><link>https://haoyn231.github.io/posts/%E5%BC%82%E6%AD%A5%E7%BC%96%E7%A8%8B%E5%AD%A6%E4%B9%A0/%E5%90%8C%E6%AD%A5%E4%B8%8E%E5%BC%82%E6%AD%A5promise%E4%B8%8Efuture/</link><guid isPermaLink="true">https://haoyn231.github.io/posts/%E5%BC%82%E6%AD%A5%E7%BC%96%E7%A8%8B%E5%AD%A6%E4%B9%A0/%E5%90%8C%E6%AD%A5%E4%B8%8E%E5%BC%82%E6%AD%A5promise%E4%B8%8Efuture/</guid><description>同步与异步，从回调函数到 Promise/Future</description><pubDate>Sun, 15 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;上一个文章通过线程、IO 模型、轮询三个角度解释了阻塞与非阻塞，但是：&lt;strong&gt;阻塞/非阻塞关注的是“过程”（线程状态），而同步/异步关注的是“消息通知机制”（结果交付）。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;很多时候我们实现了“非阻塞”，却依然在“同步”地轮询。为了实现真正高效的系统，我们需要引入 Promise/Future 模型，让异步任务拥有同步的语义。&lt;/p&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;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;一、何为同步与异步&lt;/h2&gt;
&lt;h3&gt;同步&lt;/h3&gt;
&lt;p&gt;调用者发起请求后，在没有得到结果之前，该调用就不返回。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;调用必须等待结果&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;thread&amp;gt;
#include &amp;lt;chrono&amp;gt;

int slow_square(int x) {
    std::this_thread::sleep_for(std::chrono::seconds(2));
    return x * x;
}

int main() {
    std::cout &amp;lt;&amp;lt; &quot;开始计算\n&quot;;

    // 同步调用
    int result = slow_square(5);

    std::cout &amp;lt;&amp;lt; &quot;结果: &quot; &amp;lt;&amp;lt; result &amp;lt;&amp;lt; &quot;\n&quot;;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;异步&lt;/h3&gt;
&lt;p&gt;调用者发起请求后，调用直接返回，不需要立刻得到结果。结果通常通过&lt;strong&gt;回调（Callback）&lt;/strong&gt;、&lt;strong&gt;事件&lt;/strong&gt;或&lt;strong&gt;信号&lt;/strong&gt;来通知调用者。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;调用后立即返回，主线程不等待，结果通过回调通知&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;thread&amp;gt;

void slow_square_async(int x, void(*callback)(int)) {
    std::thread([x, callback]() {
        std::this_thread::sleep_for(std::chrono::seconds(2));
        callback(x * x);
    }).detach();
}

void on_done(int result) {
    std::cout &amp;lt;&amp;lt; &quot;结果: &quot; &amp;lt;&amp;lt; result &amp;lt;&amp;lt; &quot;\n&quot;;
}

int main() {
    std::cout &amp;lt;&amp;lt; &quot;开始计算\n&quot;;

    slow_square_async(5, on_done);

    std::cout &amp;lt;&amp;lt; &quot;主线程继续运行\n&quot;;

    std::this_thread::sleep_for(std::chrono::seconds(3));
}

&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;二、由回调函数到 Promise / Future&lt;/h2&gt;
&lt;h3&gt;1. 基于回调函数的异步代码&lt;/h3&gt;
&lt;p&gt;上面的异步实现是通过回调完成的，但回调有几个问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;控制流分散&lt;/li&gt;
&lt;li&gt;错误处理麻烦&lt;/li&gt;
&lt;li&gt;多个异步组合困难&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;例如，在一个后端服务中，我们需要：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;异步读取用户信息&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;再根据用户信息读取订单&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;最后打印结果&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;thread&amp;gt;
#include &amp;lt;chrono&amp;gt;

void get_user_async(void(*callback)(int)) {
    std::thread([callback]() {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        callback(42);  // user_id
    }).detach();
}

void get_order_async(int user_id, void(*callback)(int)) {
    std::thread([user_id, callback]() {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        callback(user_id * 10);  // order_id
    }).detach();
}

void on_order(int order_id) {
    std::cout &amp;lt;&amp;lt; &quot;订单ID: &quot; &amp;lt;&amp;lt; order_id &amp;lt;&amp;lt; &quot;\n&quot;;
}

void on_user(int user_id) {
    get_order_async(user_id, on_order);
}

int main() {
    get_user_async(on_user);
    std::this_thread::sleep_for(std::chrono::seconds(3));
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在真实服务中，“读取用户信息”和“读取订单”通常意味着：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;网络请求&lt;/li&gt;
&lt;li&gt;数据库查询&lt;/li&gt;
&lt;li&gt;RPC 调用&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些操作大部分时间都在等待 IO。如果采用同步阻塞方式处理，高频请求场景下，线程将大量时间浪费在等待上，系统吞吐量会严重下降。因此，异步执行往往是必须的。&lt;/p&gt;
&lt;p&gt;然而，上述回调写法带来了明显问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;业务逻辑被拆散到多个函数中&lt;/li&gt;
&lt;li&gt;控制流不再是线性结构&lt;/li&gt;
&lt;li&gt;错误必须逐层传递&lt;/li&gt;
&lt;li&gt;一旦再增加一个步骤，就会继续嵌套&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;更严重的是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;代码的执行顺序已经不等于代码的书写顺序。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;开发者阅读代码时，必须在多个回调之间来回跳转，才能还原真实的执行路径。这种现象被称为“回调地狱（Callback Hell）”。&lt;/p&gt;
&lt;hr /&gt;
&lt;h4&gt;核心问题&lt;/h4&gt;
&lt;p&gt;回调的本质问题不在于“异步”，而在于：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;异步逻辑无法被表达为一个“值”。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我们无法写出类似这样的代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;auto user = get_user_async();
auto order = get_order_async(user);
std::cout &amp;lt;&amp;lt; order;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因为异步调用不会返回结果，而是通过回调“推送”结果。&lt;/p&gt;
&lt;p&gt;那么，是否存在一种方式：&lt;strong&gt;既保持异步执行，又能像同步代码一样获取结果，同时避免把逻辑写进回调函数中？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;为了解决这个问题，C++11 引入了 Promise/Future 模型。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;2. Promise/Future 模型&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Future 是什么？&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Future = “未来会得到的结果”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;它本质上是一个：现在没有值，但将来一定会有值的对象&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;Promise 是什么？&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Promise = “承诺未来会给你一个值”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Promise 负责“生产结果”&lt;/li&gt;
&lt;li&gt;Future 负责“消费结果”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;它们是配对的。&lt;/p&gt;
&lt;p&gt;Promise / Future 基本用法&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;future&amp;gt;
#include &amp;lt;thread&amp;gt;

int main() {
    std::promise&amp;lt;int&amp;gt; prom;
    std::future&amp;lt;int&amp;gt; fut = prom.get_future(); // 配对

  	// 在另一个线程中设置 Promise
    std::thread t([&amp;amp;prom]() {
        std::this_thread::sleep_for(std::chrono::seconds(2));
        prom.set_value(25);
    });

    std::cout &amp;lt;&amp;lt; &quot;等待结果...\n&quot;;

  	// 在主线程中，从 Promise 获取 Future
    int result = fut.get();  // 会阻塞

    std::cout &amp;lt;&amp;lt; &quot;结果: &quot; &amp;lt;&amp;lt; result &amp;lt;&amp;lt; &quot;\n&quot;;

    t.join();
}

&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;线程是异步执行的&lt;/li&gt;
&lt;li&gt;但 &lt;code&gt;future.get()&lt;/code&gt; 是同步等待&lt;/li&gt;
&lt;li&gt;Future 让“异步执行”看起来像“同步取值”&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;Future 让异步任务拥有了“同步语义”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;上述用户和订单的例子，可以改成这样：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;future&amp;gt;
#include &amp;lt;thread&amp;gt;
#include &amp;lt;chrono&amp;gt;

int main() {
    using namespace std::chrono_literals;

    std::cout &amp;lt;&amp;lt; &quot;开始\n&quot;;

    // 异步获取用户
  	// 这里的auto，返回的实际上就是 std::future&amp;lt;int&amp;gt; 类型
    auto user_future = std::async(std::launch::async, [] {
        std::this_thread::sleep_for(1s);
        std::cout &amp;lt;&amp;lt; &quot;获取用户完成\n&quot;;
        return 42;
    });

    // 像同步一样“取值”
    int user_id = user_future.get();

    // 异步获取订单
    auto order_future = std::async(std::launch::async, [user_id] {
        std::this_thread::sleep_for(1s);
        std::cout &amp;lt;&amp;lt; &quot;获取订单完成\n&quot;;
        return user_id * 10;
    });

    int order_id = order_future.get();

    std::cout &amp;lt;&amp;lt; &quot;订单ID: &quot; &amp;lt;&amp;lt; order_id &amp;lt;&amp;lt; &quot;\n&quot;;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;虽然获取用户和获取订单都是异步操作，但是语义上是同步的。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;code&gt;std::async&lt;/code&gt;是 C++ 标准库引入的异步处理函数，在 GCC 和 Clang 的实现中，&lt;code&gt;std::async&lt;/code&gt; 会明确创建一个新的线程用于执行异步操作，在 MSVC 的实现中，它则会创建一个新的线程，或者利用 Windows 系统维护的线程池。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;std::async&lt;/code&gt;实际上是对&lt;code&gt;std::promise&lt;/code&gt;的一种包装，底层上，它们都共享一个&lt;strong&gt;共享状态（Shared State）&lt;/strong&gt;。&lt;code&gt;std::promise&lt;/code&gt; 需要你手动去 &lt;code&gt;set_value&lt;/code&gt;，如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;std::thread t([&amp;amp;prom]() {
    std::this_thread::sleep_for(std::chrono::seconds(2));
    prom.set_value(25);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;而 &lt;code&gt;std::async&lt;/code&gt; 则自动帮完成了“启动线程 -&amp;gt; 执行函数 -&amp;gt; 将返回值填入共享状态”的整个流水线。&lt;/p&gt;
&lt;p&gt;很明显，当前的&lt;code&gt;std::async&lt;/code&gt;并不适合高性能、高并发的操作，更适合作为简单的异步工具。在高并发场景中，开发者通常更倾向于使用自定义线程池，结合 &lt;code&gt;std::promise&lt;/code&gt; 或 &lt;code&gt;std::packaged_task&lt;/code&gt; 来实现可控的并发管理。&lt;/p&gt;
&lt;h3&gt;3. 异步不等于非阻塞&lt;/h3&gt;
&lt;p&gt;在上面的例子中&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int user_id = user_future.get();
int order_id = order_future.get();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这两个操作是同步地获取结果，如果异步任务尚未完成，&lt;code&gt;.get()&lt;/code&gt; 会阻塞当前线程直到结果就绪。这其实就是异步阻塞的一种（异步执行 + 同步等待）。&lt;/p&gt;
&lt;p&gt;如果刚创建 future 就执行&lt;code&gt;.get()&lt;/code&gt;，则会让异步代码退化为同步代码。&lt;/p&gt;
&lt;p&gt;当然，对于 C++ 的 Promise/Future 模型，还有很多细节，比如 &lt;code&gt;std::async&lt;/code&gt; 的执行策略、线程的生命周期管理、获取 future 的时机等等，但是这里不再深入，仅仅是引入这个模型来讨论同步异步。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;真正理想的方案是&lt;strong&gt;既能用“同步风格”表达异步逻辑，又不会阻塞线程资源&lt;/strong&gt;，很遗憾 C++ 标准库的 Promise/Future 模型不提供这个功能，真正实现异步非阻塞，还需要引入更高层级的抽象，使用第三方库，例如 boost::future，以及，最重要的协程。&lt;/p&gt;
&lt;p&gt;举这个例子是为了表明：Future 并没有消灭阻塞，它只是把“等待”从回调结构，变成了“显式取值”。最大的意义在于：让异步任务有了同步语义。至于是否阻塞，则取决于调用方是否选择等待。&lt;/p&gt;
</content:encoded></item><item><title>异步编程学习（02）阻塞非阻塞——线程、IO 与轮询</title><link>https://haoyn231.github.io/posts/%E5%BC%82%E6%AD%A5%E7%BC%96%E7%A8%8B%E5%AD%A6%E4%B9%A0/%E9%98%BB%E5%A1%9E%E4%B8%8E%E9%9D%9E%E9%98%BB%E5%A1%9E/</link><guid isPermaLink="true">https://haoyn231.github.io/posts/%E5%BC%82%E6%AD%A5%E7%BC%96%E7%A8%8B%E5%AD%A6%E4%B9%A0/%E9%98%BB%E5%A1%9E%E4%B8%8E%E9%9D%9E%E9%98%BB%E5%A1%9E/</guid><description>从线程、IO 模型与轮询三个角度来看阻塞与非阻塞</description><pubDate>Sun, 15 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;同步与异步，阻塞与非阻塞，真是异步编程中最难理解、最容易混淆的两对概念。&lt;/p&gt;
&lt;p&gt;这个文章算是对该知乎回答的一个读书笔记&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;怎样理解阻塞非阻塞与同步异步的区别？ - 学刑法的程序员的回答 - 知乎
https://www.zhihu.com/question/19732473/answer/241673170&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这里有一个很容易误解的点：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;同步 = 阻塞&lt;/p&gt;
&lt;p&gt;异步 = 非阻塞&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;chatGPT 这样告诉我：&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;同步 / 异步&lt;/td&gt;
&lt;td&gt;结果的交付方式&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;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;涉及的东西实在太多，就分开来写好了。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;我把对阻塞与非阻塞的讨论分为线程状态语义与IO模型语义，因为我发现在讨论中经常会把它们搞混。&lt;/p&gt;
&lt;h2&gt;一、 线程状态语义下的阻塞与非阻塞&lt;/h2&gt;
&lt;p&gt;线程状态语义就是指操作系统调度方面的语义，在 Linux 这种分时抢占式操作系统中，除了时间片轮转导致的线程调度，还包括线程被调度器挂起，进入&lt;strong&gt;阻塞&lt;/strong&gt;状态，不再参与时间片调度，直到该线程被唤醒。&lt;/p&gt;
&lt;p&gt;典型线程状态流转&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/%E9%98%BB%E5%A1%9E%E4%B8%8E%E9%9D%9E%E9%98%BB%E5%A1%9E_01.png&quot; alt=&quot;线程状态转换图&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;核心问题只有：线程有没有进入waiting状态&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;1. 线程状态语义下的阻塞&lt;/h3&gt;
&lt;p&gt;阻塞 = 线程被内核挂起（sleep）&lt;/p&gt;
&lt;p&gt;例子：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;std::this_thread::sleep_for(3s);		 // 主动进入sleep
read(fd, buf, size);   							// 默认阻塞的读取操作，阻塞 IO
pthread_mutex_lock();								// 互斥锁
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;共同点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;线程被调度器移出 run queue&lt;/li&gt;
&lt;li&gt;CPU 去执行别的线程&lt;/li&gt;
&lt;li&gt;当前线程不占 CPU&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一般阻塞操作都涉及系统调用，涉及用户态向内核态的切换，例如&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;read()&lt;/code&gt;读取文件，&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sleep()&lt;/code&gt; 主动让出 CPU 使用权&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 线程状态语义下的非阻塞&lt;/h3&gt;
&lt;p&gt;非阻塞 = 线程始终 runnable&lt;/p&gt;
&lt;p&gt;例子：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;while(1);      											// 死循环
heavy_compute(); 										// 耗时的计算任务
read(fd, ...)  											// O_NONBLOCK 且立即返回，非阻塞 IO
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;即使卡住：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;线程仍在 running&lt;/li&gt;
&lt;li&gt;没有进入 waiting&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;值得注意的是，当线程处于非阻塞状态时，只意味着线程会持续参与调度器的时间片轮转，但不意味着执行流不会被打断。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;二、 IO 模型语义下的阻塞与非阻塞&lt;/h2&gt;
&lt;p&gt;IO 模型有很多，但是这里只讨论阻塞与非阻塞&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;作者：学刑法的程序员
链接：https://www.zhihu.com/question/19732473/answer/241673170
来源：知乎
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I/O System Call 的阻塞/非阻塞， 同步/异步&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这里再重新审视 &lt;strong&gt;阻塞/非阻塞 IO&lt;/strong&gt; 这个概念， 其实&lt;strong&gt;阻塞和非阻塞&lt;/strong&gt;描述的是进程的一个操作是否会使得进程转变为“等待”的状态， 但是为什么我们总是把它和 IO 连在一起讨论呢？&lt;/p&gt;
&lt;p&gt;原因是， &lt;strong&gt;阻塞&lt;/strong&gt;这个词是与系统调用 System Call 紧紧联系在一起的， 因为要让一个进程进入 等待（waiting） 的状态, 要么是它主动调用 wait() 或 sleep() 等挂起自己的操作， 另一种就是它调用 System Call, 而 System Call 因为涉及到了 I/O 操作， 不能立即完成， 于是内核就会先将该进程置为等待状态， 调度其他进程的运行， 等到 它所请求的 I/O 操作完成了以后， 再将其状态更改回 ready 。&lt;/p&gt;
&lt;p&gt;操作系统内核在执行 System Call 时， CPU 需要与 IO 设备完成一系列物理通信上的交互， 其实再一次会涉及到阻塞和非阻塞的问题， 例如， 操作系统发起了一个读硬盘的请求后， 其实是向硬盘设备通过总线发出了一个请求，它即可以阻塞式地等待IO 设备的返回结果，也可以非阻塞式的继续其他的操作。 在现代计算机中，这些物理通信操作基本都是异步完成的， 即发出请求后， 等待 I/O 设备的中断信号后， 再来读取相应的设备缓冲区。 但是，大部分操作系统默认为用户级应用程序提供的都是阻塞式的系统调用 （blocking systemcall）接口， 因为阻塞式的调用，使得应用级代码的编写更容易（代码的执行顺序和编写顺序是一致的）。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;1. 阻塞 IO&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;线程调用 System Call，涉及 IO 操作，不能立即返回，于是内核就会先将该线程置为等待状态，等到它所请求的 IO 操作完成了以后， 再将其状态更改回 ready&lt;/strong&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;内核帮你等&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这里的 IO 操作就是&lt;strong&gt;阻塞 IO&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;例如&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;unistd.h&amp;gt;
#include &amp;lt;sys/socket.h&amp;gt;
#include &amp;lt;netinet/in.h&amp;gt;
#include &amp;lt;cstring&amp;gt;

int main() {
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);

    sockaddr_in addr{};
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8888);
    addr.sin_addr.s_addr = INADDR_ANY;

    bind(server_fd, (sockaddr*)&amp;amp;addr, sizeof(addr));
    listen(server_fd, 1);

    int client_fd = accept(server_fd, nullptr, nullptr);

    char buf[1024];

    std::cout &amp;lt;&amp;lt; &quot;等待数据...\n&quot;;

    // ⭐ 阻塞 IO
    int n = read(client_fd, buf, sizeof(buf));

    std::cout &amp;lt;&amp;lt; &quot;收到: &quot; &amp;lt;&amp;lt; std::string(buf, n) &amp;lt;&amp;lt; &quot;\n&quot;;

    close(client_fd);
    close(server_fd);
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 非阻塞 IO&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;进程调用 System Call，涉及 IO 操作，如果当前数据尚未就绪，内核不会将该进程置为等待状态，而是立即返回一个错误码（如 EAGAIN / EWOULDBLOCK），进程仍然保持 ready 状态，由用户决定稍后再次发起请求。&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;内核不帮你等，自己决定怎么等&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这里的 IO 操作就是&lt;strong&gt;非阻塞 IO&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;unistd.h&amp;gt;
#include &amp;lt;sys/socket.h&amp;gt;
#include &amp;lt;netinet/in.h&amp;gt;
#include &amp;lt;fcntl.h&amp;gt;
#include &amp;lt;cstring&amp;gt;

int main() {
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);

    sockaddr_in addr{};
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8888);
    addr.sin_addr.s_addr = INADDR_ANY;

    bind(server_fd, (sockaddr*)&amp;amp;addr, sizeof(addr));
    listen(server_fd, 1);

    int client_fd = accept(server_fd, nullptr, nullptr);

    // ⭐ 设置为非阻塞
    fcntl(client_fd, F_SETFL, O_NONBLOCK);

    char buf[1024];

    std::cout &amp;lt;&amp;lt; &quot;尝试读取数据...\n&quot;;

    int n = read(client_fd, buf, sizeof(buf));

    if (n &amp;lt; 0) {
        std::cout &amp;lt;&amp;lt; &quot;数据未就绪，立即返回\n&quot;;
    } else {
        std::cout &amp;lt;&amp;lt; &quot;收到: &quot; &amp;lt;&amp;lt; std::string(buf, n) &amp;lt;&amp;lt; &quot;\n&quot;;
    }

    close(client_fd);
    close(server_fd);
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于非阻塞 IO，获取数据结果有很多方式，暂时按下不表，只需知道：&lt;strong&gt;非阻塞 IO 本身并不负责“自动交付”结果，它只负责在资源未就绪时“拒绝挂起线程”。&lt;/strong&gt;&lt;/p&gt;
&lt;hr /&gt;
&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;code&gt;while(1);&lt;/code&gt;，程序会彻底卡死，但这在内核看来，它是最“活跃”的非阻塞任务。&lt;/p&gt;
&lt;p&gt;我们可以简单的把操作分为阻塞操作与非阻塞操作：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;阻塞操作&lt;/strong&gt;：使当前线程被挂起的操作&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;阻塞的 System Call，发生用户态向内核态的转换&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sleep()&lt;/code&gt; 主动进入 waiting，时间到后自动唤醒&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;非阻塞操作&lt;/strong&gt;：持续占用 CPU 的操作&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;高负载计算任务&lt;/li&gt;
&lt;li&gt;&lt;code&gt;while(1)&lt;/code&gt; 死循环&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以上非阻塞操作，尽管它们在口语里使得当前线程&lt;strong&gt;不动了&lt;/strong&gt;，但是它们是非阻塞的。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;四、 轮询（Polling）会阻塞操作线程吗？&lt;/h2&gt;
&lt;h3&gt;1. 用户态忙轮询（busy polling）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;while (true) {
    if (data_ready()) {
        break;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;特点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;线程始终 running&lt;/li&gt;
&lt;li&gt;CPU 被吃满&lt;/li&gt;
&lt;li&gt;没有进入 waiting&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;线程语义：非阻塞&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;IO 模型语义：没有模型，只是暴力检查&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;一般称之为：忙等（busy wait）。&lt;/p&gt;
&lt;h3&gt;2. Linux 的 &lt;code&gt;poll()&lt;/code&gt; 系统调用&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;poll()&lt;/code&gt; 系统调用既可以是阻塞的，也可以是非阻塞的。对于 &lt;code&gt;poll()&lt;/code&gt; 系统调用，涉及多路 IO 复用，这里只是简单介绍一下，暂不深入。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;阻塞的 &lt;code&gt;poll()&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;poll(fds, nfds, 5000);   // 等 5 秒
poll(fds, nfds, -1);     // 无限等
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这种情况：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当前线程进入内核&lt;/li&gt;
&lt;li&gt;若没有事件，线程被挂起（waiting）&lt;/li&gt;
&lt;li&gt;等待事件或超时唤醒&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;线程状态语义：阻塞&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;IO 模型语义：IO 多路复用（非阻塞 IO 模型的一种）&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;非阻塞的 &lt;code&gt;poll()&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;立即返回轮循&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;poll(fds, nfds, 0);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这种情况：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;内核：检查是否有事件，没有就直接返回&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;线程始终 runnable。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;线程语义：非阻塞&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;IO模型语义：仍然是 IO 多路复用&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>异步编程学习（01）裸机中的同步异步</title><link>https://haoyn231.github.io/posts/%E5%BC%82%E6%AD%A5%E7%BC%96%E7%A8%8B%E5%AD%A6%E4%B9%A0/%E8%A3%B8%E6%9C%BA%E4%B8%AD%E7%9A%84%E5%90%8C%E6%AD%A5%E5%BC%82%E6%AD%A5/</link><guid isPermaLink="true">https://haoyn231.github.io/posts/%E5%BC%82%E6%AD%A5%E7%BC%96%E7%A8%8B%E5%AD%A6%E4%B9%A0/%E8%A3%B8%E6%9C%BA%E4%B8%AD%E7%9A%84%E5%90%8C%E6%AD%A5%E5%BC%82%E6%AD%A5/</guid><description>分析单片机裸机框架中的同步异步逻辑</description><pubDate>Sat, 14 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;最早让我对这些异步编程产生兴趣的是 Rust 的 Tokio 异步运行时和 Embassy 框架，其中 Embassy 框架在单片机里引入了无栈协程，这对于传统 RTOS 和基于回调的事件驱动来说无疑是革命性的，让我对其产生了浓厚兴趣。&lt;/p&gt;
&lt;p&gt;异步编程需要的前置概念实在太多，牵扯到&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;阻塞IO/非阻塞IO&lt;/li&gt;
&lt;li&gt;同步/异步&lt;/li&gt;
&lt;li&gt;select/poll&lt;/li&gt;
&lt;li&gt;epoll&lt;/li&gt;
&lt;li&gt;io_uring&lt;/li&gt;
&lt;li&gt;协程&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以及很多Linux系统编程和操作系统的概念，作为一个电子工程的学生，我还是决定先从最早熟悉的单片机裸机开始，一步步梳理一下我对异步编程的理解。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;一、裸机框架中的同步&lt;/h2&gt;
&lt;p&gt;单片机裸机，可以视为一个单线程的应用，在 main 函数开始执行后，总体是一个同步逻辑：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 伪代码
void main(void) {
    // 1. 系统初始化（时钟、GPIO、外设等）
    clock_init();           // 初始化系统时钟
    gpio_init();            // 初始化 GPIO 管脚
    uart_init(115200);      // 初始化串口通信
    timer_init();           // 初始化定时器
    dma_init();             // 初始化 DMA 控制器

    // 2. 启用全局中断
    enable_global_interrupt();

    // 3. 主循环：不断执行业务逻辑
    while (1) {
        // 执行具体的业务逻辑
        // 处理传感器数据、通信、计算等
        do_business_logic();

    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在最基础的裸机代码中，一切都是顺序执行的（Synchronous）。 当我们需要处理一个 IO 密集型任务（如等待传感器数据、DMA 搬运、串口发送）时，最简单的逻辑就是&lt;strong&gt;轮询（Polling）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;以 DMA 数据传输为例，裸机中确认 DMA 传输是否完成，可以通过轮循的方式：&lt;/p&gt;
&lt;p&gt;CPU 发起 DMA 传输请求后，进入一个死循环，不断查询状态寄存器。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 伪代码
void dma_transfer_polling(uint32_t src, uint32_t dst, uint32_t len) {
    // 1. 配置硬件：源地址、目的地址、长度
    DMA_REG-&amp;gt;SAR = src;
    DMA_REG-&amp;gt;DAR = dst;
    DMA_REG-&amp;gt;CNT = len;

    // 2. 启动 DMA
    DMA_REG-&amp;gt;CR |= DMA_CR_EN;

    // 3. [阻塞点]：死循环检查标志位
    // CPU 100% 耗在此处，无法响应其他任务（除非是高优先级中断）
    while (!(DMA_REG-&amp;gt;SR &amp;amp; DMA_SR_TCIF)) {
        // CPU 只是在不断读取总线上的寄存器状态
        __NOP(); 
    }

    // 4. 清除标志位，关闭 DMA
    DMA_REG-&amp;gt;SR &amp;amp;= ~DMA_SR_TCIF;
    DMA_REG-&amp;gt;CR &amp;amp;= ~DMA_CR_EN;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里的操作就是同步的，由于裸机框架中，没有操作系统的任务调度，CPU 只能卡在这里，直到 DMA 传输完成，函数才会返回，才能继续执行后续代码。&lt;/p&gt;
&lt;p&gt;这里的 CPU 实际上是一个&lt;strong&gt;忙等&lt;/strong&gt;状态，不断对 DMA 状态寄存器进行&lt;strong&gt;轮询（Poll）&lt;/strong&gt;。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;二、裸机框架中的异步&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;中断（Interrupt）是裸机框架中唯一的、真正的异步机制。&lt;/strong&gt; 它允许 CPU 在发起硬件请求后，立即返回去执行 &lt;code&gt;while(1)&lt;/code&gt; 循环中的其他逻辑，直到硬件完成任务，并通过&lt;strong&gt;中断服务函数（ISR）&lt;/strong&gt;，来让 CPU 执行后续逻辑。&lt;/p&gt;
&lt;p&gt;CPU 完成 DMA 配置后，就去负责其他事情，当 DMA 传输完成后，通过硬件中断，CPU 进入中断服务函数，来完成相关操作：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 伪代码
// 全局状态标志（需加 volatile 防止编译器过度优化）
volatile bool g_dma_done = false;

void dma_transfer_irq(uint32_t src, uint32_t dst, uint32_t len) {
    // 1. 配置并开启 DMA 中断使能 (TCIE)
    DMA_REG-&amp;gt;CR |= DMA_CR_TCIE; 
    
    // 2. 设置地址与长度并启动
    DMA_REG-&amp;gt;SAR = src;
    DMA_REG-&amp;gt;DAR = dst;
    DMA_REG-&amp;gt;CNT = len;
    DMA_REG-&amp;gt;CR |= DMA_CR_EN;

    // 3. [非阻塞]：直接返回，CPU 可以去跑 main 循环里的其他逻辑
    return;
}

// 硬件自动调用的中断服务程序 (ISR)
void DMA_IRQHandler(void) {
    if (DMA_REG-&amp;gt;SR &amp;amp; DMA_SR_TCIF) {
        // 处理业务逻辑（例如设置标志位或调用回调函数）
        g_dma_done = true;
        
        // 关键：清除中断标志，否则会死循环进入中断
        DMA_REG-&amp;gt;SR &amp;amp;= ~DMA_SR_TCIF;
        DMA_REG-&amp;gt;CR &amp;amp;= ~DMA_CR_EN;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在调用&lt;code&gt;dma_transfer_irq&lt;/code&gt;后，函数立即返回，去执行&lt;code&gt;while(1)&lt;/code&gt;循环后面的任务，当 DMA 传输完成后，通过中断强制切换 CPU 来运行 ISR，在里面执行业务逻辑。&lt;/p&gt;
&lt;p&gt;但其实一般不会在 ISR 中执行耗时的业务逻辑，一般是设置标志位，在&lt;code&gt;while(1)&lt;/code&gt;中读取改标志位，如果已经被设置，则执行耗时的业务逻辑。ISR 应该是快速退出的。&lt;/p&gt;
&lt;hr /&gt;
</content:encoded></item><item><title>编程日记（01）过早抽象也是万恶之源！</title><link>https://haoyn231.github.io/posts/%E7%BC%96%E7%A8%8B%E6%97%A5%E8%AE%B0/01%E8%BF%87%E6%97%A9%E6%8A%BD%E8%B1%A1%E4%B9%9F%E6%98%AF%E4%B8%87%E6%81%B6%E4%B9%8B%E6%BA%90/</link><guid isPermaLink="true">https://haoyn231.github.io/posts/%E7%BC%96%E7%A8%8B%E6%97%A5%E8%AE%B0/01%E8%BF%87%E6%97%A9%E6%8A%BD%E8%B1%A1%E4%B9%9F%E6%98%AF%E4%B8%87%E6%81%B6%E4%B9%8B%E6%BA%90/</guid><description>半吊子 C++ 开发者的反思</description><pubDate>Fri, 13 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;程序员圈子最著名的观点，莫过于“过早优化是万恶之源”，在踩了无数坑之后，我认为：过早抽象也是万恶之源！&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;一、C++ 领域的过度设计 / 过早抽象&lt;/h2&gt;
&lt;p&gt;所谓“过早抽象”，可以认为是过度设计的另一个称呼，但是最早我对过度设计有误解。作为一个C/C++开发者，我曾以为过度设计，就是 Java 那种过度依赖设计模式，无论干什么都先来一套工厂模式、观察者模式，导致业务逻辑冗余。但是，我发现过度设计在 C++ 领域，一个更形象的说法是过度抽象。&lt;/p&gt;
&lt;p&gt;C++ 和 Rust 这种高级语言的精髓是&lt;strong&gt;零成本抽象&lt;/strong&gt;，尤其在嵌入式 C++ 这种高度 C 和 C++ 混写的领域，开发者需要用 C++ 提供的功能封装原始的 C 接口，不仅要做的对 C 接口的兼容，还用利用 C++ 的高级特性来引入 RAII、生命周期、所有权等内存安全的理念，来使得基于 C SDK 开发的项目更具有可读性和可维护性。&lt;/p&gt;
&lt;p&gt;但是，这种 C 和 C++ 混写的领域恰恰是改革深水区：一个芯片的某个领域的 C SDK（例如 RK 的 Rockit），是&lt;strong&gt;原厂工程师不断调试和封装的结果&lt;/strong&gt;，内部封装了复杂的内存管理、硬件管理，抑或是隐式逻辑，它会有自己的一套逻辑，例如某个函数封装了对寄存器的配置，或者利用回调函数来实现数据的转移。而作为这个 C SDK 的二次开发者，我们很难说能完全理解这个 SDK，更不用说比原厂工程师还懂，这种情况下，使用 C API 反而是更明智的选择。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;二、试图用语言特性去掩盖业务知识的匮乏&lt;/h2&gt;
&lt;p&gt;我这个半吊子 C++ 开发者，总是妄图用所谓“现代 C++ 的高级特性”来封装原始 C API，例如：&lt;/p&gt;
&lt;p&gt;用 C++17 的 &lt;code&gt;std::variant&lt;/code&gt; 来封装 Rockit 的媒体帧，以下是当时我和Gemimi的对话&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://markdownforyuanhao.oss-cn-hangzhou.aliyuncs.com/img1/20260213121016898.png&quot; alt=&quot;FireShot Capture 025 - Google Gemini - [gemini.google.com]&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;事实上：当时我根本没有多少对这个媒体帧的理解！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;最终的结果是：这个模型在接触到 VI、VPSS、VENC 之间的硬件绑定或者软件处理模型时失效了，复杂度上升，而我也无力维护，最终不了了之。&lt;/p&gt;
&lt;p&gt;简言之，就是我&lt;strong&gt;试图用语言的高度，去掩盖对业务深度理解的匮乏&lt;/strong&gt;。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;三、AI 时代的认知负债&lt;/h2&gt;
&lt;p&gt;“过早抽象”本质上是一种&lt;strong&gt;认知负债&lt;/strong&gt;——在还没赚到足够的“领域知识”时，就提前支取了“架构设计”。&lt;/p&gt;
&lt;p&gt;我曾经产生过一种幻觉，在现在的AI时代，我只要问问 AI，就能掌握 C++ 的种种高级特性，并且运用于工程中，但是问题就出在这儿：我用的 Agent，默认我和它都是 C++/Rust 的高级开发者，它的语料库里充斥着&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;设计模式&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;零成本抽象&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ownership&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;trait system&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;variant / enum&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;AI 可以对各种特性可以有鞭辟入里的讲解，但唯独缺少了贴合实际的实战，最终的结果就是诱导我进行过度抽象，可以说是双向奔赴。&lt;/p&gt;
&lt;p&gt;在 AI 时代，无论是代码还是写作，高级特性、华丽的语言开始变得廉价，但是对某个领域的深入理解仍然昂贵，我想，这也是人工智能与作家、艺术家、工程师的本质区别。&lt;/p&gt;
&lt;p&gt;C++、Rust那种零成本抽象，某种程度上是高级开发者的工具，它有巨大的认知开销，需要开发者对业务逻辑非常熟悉，才能用语言特性来写出高性能的代码。&lt;/p&gt;
&lt;p&gt;而对于我这种半吊子初学者，更应该遵守的是&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;20/80 原则：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Make it work&lt;/strong&gt;（先跑通功能）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Make it right&lt;/strong&gt;（对代码进行抽象）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Make it fast&lt;/strong&gt;（对代码进行性能优化）。&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&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;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;四、什么时候才应该进行抽象&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;抽象的本质是压缩重复信息&lt;/strong&gt;，在 Make it work 的过程中，注意那些需要多次重复的操作，它们才是需要进行抽象的地方，如果没有重复，或者用的很少，就不应该去进行抽象。&lt;/p&gt;
&lt;p&gt;还是回到那个观点：&lt;strong&gt;想要把语言的高级特性、设计模式运用熟练，需要开发者对业务逻辑有深刻的理解&lt;/strong&gt;，我作为一个成长在 AI 时代的开发者，最不缺的就是高级特性和设计模式，缺的是对业务逻辑的理解。&lt;/p&gt;
</content:encoded></item><item><title>毕设日志（四）NPU 推理与硬件资源分配</title><link>https://haoyn231.github.io/posts/linux/%E6%AF%95%E4%B8%9A%E8%AE%BE%E8%AE%A1%E6%99%BA%E8%83%BD%E7%BD%91%E7%BB%9C%E6%91%84%E5%83%8F%E5%A4%B4/npu%E6%8E%A8%E7%90%86%E4%B8%8E%E7%A1%AC%E4%BB%B6%E8%B5%84%E6%BA%90%E5%88%86%E9%85%8D/</link><guid isPermaLink="true">https://haoyn231.github.io/posts/linux/%E6%AF%95%E4%B8%9A%E8%AE%BE%E8%AE%A1%E6%99%BA%E8%83%BD%E7%BD%91%E7%BB%9C%E6%91%84%E5%83%8F%E5%A4%B4/npu%E6%8E%A8%E7%90%86%E4%B8%8E%E7%A1%AC%E4%BB%B6%E8%B5%84%E6%BA%90%E5%88%86%E9%85%8D/</guid><description>添加 NPU 推理模块时触到了硬件性能瓶颈</description><pubDate>Thu, 12 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;经过漫长的摸索和 Debug，我意识到在 RV1106G3 这个单核 A7 + 256MB DDR3 的嵌入式设备上，再精妙的设计也要向现实妥协：之前设想的异构并行逻辑，受限于 DDR3 的带宽，导致 NPU 获取图像超时，产生运行时报错。只能退而求其次，先用最原始的串行逻辑，就算显示帧率感人，CPU 占用高，但先把基础功能实现了，再谈性能优化。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.luckfox.com/zh/Luckfox-Pico-Zero/MPI&quot;&gt;Luckfox 的 RKMPI 例程&lt;/a&gt;中提供了 YOLOv5 和 Retainface 两种示例，虽然都是 NPU 推理，但是硬件资源分配和视频流处理的逻辑大不相同，其中，YOLOv5 推理对性能要求是比 Retainface 高很多的。&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;LuckfoxTECH/luckfox_pico_rkmpi_example&quot;}&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;一、三种硬件资源（VI、VPSS、VENC）分配方式&lt;/h2&gt;
&lt;p&gt;为了完成&lt;strong&gt;采集图像给 NPU 推理，把推理结果标注到视频流上，编码后进行流媒体分发&lt;/strong&gt;这个基本流程，在硬件资源分配上，一共有三种方式：&lt;/p&gt;
&lt;h3&gt;1. VI 通道分流&lt;/h3&gt;
&lt;p&gt;VI 通道分为 ch0 和 ch1，一路输出 NV12 给 VENC 编码，一路输出 NV12 经由格式转换和缩放后给NPU推理使用。硬件流水线，效率最高，CPU 压力小，但是多硬件模块并行访问内存，内存带宽压力最大。运行 Retainface 这个轻量级模型还行。&lt;/p&gt;
&lt;p&gt;在使用 VI 通道分流时，就算没有加入 NPU 推理，也会有 VI 通道采集报错，在使用 VPSS 通道分流的基础上，完成了 IPC 的基础功能，后续就没有再考虑过 VI 通道分流。&lt;/p&gt;
&lt;p&gt;Retainface 例程使用的是 VI 通道分流&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://wiki.luckfox.com/img/RV1106/RKNN/020.jpg&quot; alt=&quot;retainface&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;2. VPSS 通道分流&lt;/h3&gt;
&lt;p&gt;VI 通道只保留一个，VPSS 通道分为两个，一个给 VENC 编码，一个缩放到模型需要的大小（例如 YOLOv5 模型需要的是 640x640 ），再经由格式转换后给 NPU 推理。&lt;/p&gt;
&lt;p&gt;使用 VPSS 通道分流时，VPSS 通道和图像处理（RGA 或者 opencv-mobile）能输出正确的图像，但仍有NPU 获取图像超时。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://wiki.luckfox.com/img/RV1106/RKNN/008.jpg&quot; alt=&quot;VPSS 通道分流&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;3. 不分流，串行逻辑&lt;/h3&gt;
&lt;p&gt;VI 通道和 VPSS 通道只保留一个，不使用硬件绑定（自动流水线），而是软件手动进行流转。先格式转换 + 缩放，NPU 推理后进行标注，再进行编码和流媒体分发。CPU 压力最大，效率（帧率）最低，但是内存带宽压力小。&lt;/p&gt;
&lt;p&gt;YOLOv5 使用的是串行逻辑&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://wiki.luckfox.com/img/RV1106/RKNN/017.jpg&quot; alt=&quot;yolov5&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;二、AIPC 软件设计&lt;/h2&gt;
&lt;p&gt;::gitHub{repo=&quot;haoyn231/aipc&quot;}&lt;/p&gt;
&lt;p&gt;理想情况下，是使用 VI 通道分流，或者 VPSS 通道分流，尽可能利用 RV1106 的硬件处理单元，降低 CPU 压力，但是这条路让我踩了无数坑，不是 VI 采集失败就是 NPU 推理失败，基本都是硬件处理单元压力过大、内存带宽压力过大导致的，只能退而求其次。&lt;/p&gt;
&lt;p&gt;对于我毕设准备实现的 YOLOv5 和 Retainface 两个模型来说，它们对硬件资源和内存的需求各不相同，当前我通过一刀切的方式实现了“跑起来”这个基础需求：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;纯 IPC 模式：不进行硬件分流，最大化利用硬件资源进行采集和编解码，实现 1080p 推流&lt;/li&gt;
&lt;li&gt;AI 推理模式：不进行硬件分流，串行逻辑，在个位数帧率下实现了 YOLOv5 推理 + 标注 + 推流&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;应该是设计一个冷切换的硬件资源分配策略（初始化系统时调用，切换模型时，对硬件资源进行重新配置），以满足不同模型对硬件资源的需求，也方便后续拓展和性能调优。&lt;/p&gt;
</content:encoded></item><item><title>毕设日志（三）编码视频流的多路分发处理与优化分析</title><link>https://haoyn231.github.io/posts/linux/%E6%AF%95%E4%B8%9A%E8%AE%BE%E8%AE%A1%E6%99%BA%E8%83%BD%E7%BD%91%E7%BB%9C%E6%91%84%E5%83%8F%E5%A4%B4/%E7%BC%96%E7%A0%81%E8%A7%86%E9%A2%91%E6%B5%81%E7%9A%84%E5%A4%9A%E8%B7%AF%E5%88%86%E5%8F%91%E5%A4%84%E7%90%86%E4%B8%8E%E4%BC%98%E5%8C%96%E5%88%86%E6%9E%90/</link><guid isPermaLink="true">https://haoyn231.github.io/posts/linux/%E6%AF%95%E4%B8%9A%E8%AE%BE%E8%AE%A1%E6%99%BA%E8%83%BD%E7%BD%91%E7%BB%9C%E6%91%84%E5%83%8F%E5%A4%B4/%E7%BC%96%E7%A0%81%E8%A7%86%E9%A2%91%E6%B5%81%E7%9A%84%E5%A4%9A%E8%B7%AF%E5%88%86%E5%8F%91%E5%A4%84%E7%90%86%E4%B8%8E%E4%BC%98%E5%8C%96%E5%88%86%E6%9E%90/</guid><description>由多线程模型向 ASIO 事件驱动模型的转变</description><pubDate>Thu, 05 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;截止&lt;strong&gt;tag: v0.4.0&lt;/strong&gt;，实现了：&lt;/p&gt;
&lt;p&gt;1080p@15hz 下多路并发&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;RTSP 推流&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;录制 mp4&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;WebSocket 实时预览&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;个位数内存占用，无内存泄露，但是问题是：开启了过多的线程。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;1080p@15hz 是实际测试，设计参数是1080p@30hz，后面再优化性能&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;::github{repo=&quot;haoyn231/aipc&quot;}&lt;/p&gt;
&lt;p&gt;我的设计原则是：&lt;strong&gt;优先 Make it work，再说 Make it fast 和 better 的事&lt;/strong&gt;。在上述三个路径同时运行时，通过 Top 查看性能占用，RV1106 的单核 A7 占用会飙到 40%，内存占用倒不是很多，总占用才 35MB 左右（系统占用 29MB ），总内存有 256MB 呢（实际可用内存 144MB ）。&lt;/p&gt;
&lt;p&gt;但问题是，之前使用 http 信令的 WebRTC 用于 Web 界面预览，在同时执行 RTSP 推流和 mp4 录制时，SOC 芯片明显发热。于是我决定把 Web 界面预览的操作用WebSocket 来简单实现，后期再说 WebRTC 的事。&lt;/p&gt;
&lt;p&gt;在使用 WebSocket 进行 Web 界面预览时，运行效果良好，延时也低，但是运行几十秒后系统就会卡死，通过 Top 监控，不是 CPU 占用过高或者内存泄露的问题，暂不清楚。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;后面发现是忘了插天线，导致网络压力大时ssh连接断开，我误以为系统卡死，难绷&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;于是决定先对当前并发逻辑进行优化，&lt;strong&gt;从优先实现功能的多线程架构改为用 ASIO（epoll）来优化网络 IO 任务&lt;/strong&gt;，降低 CPU 占用，为后面引入 RKNN 推理打基础（虽然NPU推理大概率吃内存而不是CPU，但是先搞了再说）。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;一、多线程架构：分发器+多消费者&lt;/h2&gt;
&lt;p&gt;目前实现的多线程架构，算是一步步 Vibe Coding 出来的，没有及时 Code Review，&lt;strong&gt;复杂度已经上升到一种程度了：再不进行 Code Review 和重构，作为提示词咏唱魔法师的我已经完全看不懂了，更别说维护。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;因此在日志的这部分，将对这个架构进行一次解析，之后就用 ASIO 进行重构，力求提升可读性，降低复杂度。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;对于 VENC 编码流的处理（RTSP、WebRTC、文件保存、WebSocket网页预览），采用分发器 + 多消费者模式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;一个获取线程：从 VENC 获取编码流&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;多个消费者线程：RTSP、WebRTC、文件保存、WebSocket网页预览各自独立的消费者&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;设计理念：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;零拷贝：所有消费者共享同一内存块（通过 shared_ptr 引用计数）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;解耦：任一消费者处理慢不影响其他消费者&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;扩展性好：新增功能只需注册新消费者&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h3&gt;1. 视频流的获取路径&lt;/h3&gt;
&lt;p&gt;在&lt;code&gt;main&lt;/code&gt;函数中，开启流分发&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# src/main.cpp 
// 启动流分发
stream_manager_start();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;启动分发器&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# src/thread_stream.cpp
// class StreamDispatcher  
void Start() {
    if (running_) return;
    running_ = true;

    // 先启动消费者分发线程（确保它们准备好接收数据）
    for (auto&amp;amp; c : consumers_) {
        c.thread = std::thread(&amp;amp;StreamDispatcher::DispatchLoop, this, &amp;amp;c);
        LOG_DEBUG(&quot;Started dispatch thread for consumer: {}&quot;, c.name);
    }

    // 短暂延迟，确保消费者线程就绪
    std::this_thread::sleep_for(std::chrono::milliseconds(50));

    // 最后启动获取线程
    fetch_thread_ = std::thread(&amp;amp;StreamDispatcher::FetchLoop, this);

    LOG_INFO(&quot;StreamDispatcher started with {} consumers&quot;, consumers_.size());
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;整个编码视频流的生产者线程&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# src/thread_stream.cpp
// class StreamDispatcher 
/**
 * @brief 获取循环 - 从 VENC 获取编码流并分发到各队列
 * 
 * 使用零拷贝共享：多个消费者共享同一个 VENC buffer
 * shared_ptr 引用计数管理，最后一个使用者释放时调用 ReleaseStream
 */
void FetchLoop() {
    LOG_DEBUG(&quot;Fetch loop started for VENC channel {}&quot;, venc_chn_id_);

    uint64_t frameCount = 0;
    uint64_t errorCount = 0;

    while (running_) {
        // 从 VENC 获取编码流（零拷贝，shared_ptr 管理生命周期）
        auto stream = acquire_encoded_stream(venc_chn_id_, 1000);  // 1秒超时

        if (!stream) {
            errorCount++;
            if (errorCount % 5 == 1) {  // 每5次错误打印一次
                LOG_WARN(&quot;Failed to get stream from VENC channel {} (error #{})&quot;, 
                         venc_chn_id_, errorCount);
            }
            continue;
        }

        frameCount++;
        errorCount = 0;  // 重置错误计数

        // 打印帧信息
        if (frameCount &amp;lt;= 5 || frameCount % 30 == 0) {
            LOG_DEBUG(&quot;Got frame #{}, size={} bytes&quot;, 
                     frameCount, stream-&amp;gt;pstPack ? stream-&amp;gt;pstPack-&amp;gt;u32Len : 0);
        }

        // 分发给所有消费者（零拷贝，只是增加引用计数）
        for (auto&amp;amp; c : consumers_) {
            c.queue-&amp;gt;push(stream);  // shared_ptr 拷贝，引用计数 +1
        }

        // stream 在这里离开作用域后，引用计数 -1
        // 当所有消费者都处理完毕后，最后一个释放时会调用 ReleaseStream
    }

    LOG_DEBUG(&quot;Fetch loop exited, total frames: {}&quot;, frameCount);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中，&lt;code&gt;auto stream = acquire_encoded_stream(venc_chn_id_, 1000);&lt;/code&gt;获取的是&lt;code&gt;std::shared_ptr&lt;/code&gt;封装的编码流&lt;code&gt;EncodedStreamPtr&lt;/code&gt;，确保多消费者场景下的正确生命周期管理，具体参考：&lt;a href=&quot;https://haoyn231.github.io/posts/linux/%E6%AF%95%E4%B8%9A%E8%AE%BE%E8%AE%A1%E6%99%BA%E8%83%BD%E7%BD%91%E7%BB%9C%E6%91%84%E5%83%8F%E5%A4%B4/luckfox-rkmpi%E7%A4%BA%E4%BE%8B%E7%9A%84%E8%B5%84%E6%BA%90%E7%AE%A1%E7%90%86/&quot;&gt;毕设日志（二）Luckfox-RKMPI示例的资源管理分析&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;2. 生产者向消费者的数据传输：队列&lt;/h3&gt;
&lt;p&gt;生产者线程向消费者线程传递编码数据流&lt;code&gt;EncodedStreamPtr&lt;/code&gt;，通过一个特殊的队列，每个消费者都维护一个队列&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# src/common/media_buffer.h
// ============================================================================
// 线程安全的媒体队列 - 用于模块间数据分发
// ============================================================================

/**
 * @brief 线程安全的媒体缓冲队列
 * 
 * 用于 VENC -&amp;gt; RTSP/WebRTC 的数据分发
 * 支持有界队列，超出容量时自动丢弃最旧的帧（背压处理）
 * 
 * @tparam T 媒体数据类型（VideoFramePtr 或 EncodedStreamPtr）
 */
template &amp;lt;typename T&amp;gt;
class MediaQueue {
public:
  		// 具体实现见：https://github.com/haoyn231/aipc/blob/main/src/common/media_buffer.h
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;消费者线程从它们单独维护的队列中获取编码数据帧&lt;code&gt;EncodedStreamPtr&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;3. 消费者的注册、线程启动和数据流转&lt;/h3&gt;
&lt;h4&gt;注册&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;消费者线程将自己的流消费者回调函数注册到 StreamDispatcher （生产者）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;StreamManager&lt;/code&gt;（在 &lt;code&gt;src/thread_stream.cpp&lt;/code&gt;） 它负责初始化各个业务模块（RTSP、File等）。&lt;/p&gt;
&lt;p&gt;以消费者 RTSP 为例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# src/thread_stream.cpp
StreamManager::StreamManager(const StreamConfig&amp;amp; config)
    : config_(config) {
    
    LOG_INFO(&quot;Creating StreamManager...&quot;);
    
    // 创建 RTSP 线程（如果启用）
    if (config_.enable_rtsp) {
        rtsp_thread_ = std::make_unique&amp;lt;RtspThread&amp;gt;(config_.rtsp_config);
        
        if (rtsp_thread_-&amp;gt;IsValid()) {
            // 注册 RTSP 消费者到流分发器
            rkvideo_register_stream_consumer(
                &quot;rtsp&quot;,
                &amp;amp;RtspThread::StreamConsumer,
                rtsp_thread_.get(),
                3  // 队列大小
            );
            LOG_INFO(&quot;RTSP consumer registered&quot;);
        } else {
            LOG_ERROR(&quot;Failed to create RTSP thread&quot;);
            rtsp_thread_.reset();
        }
    }
    
    // 创建文件保存线程（如果启用）
		// ......
    
    // 创建 WebRTC 线程（如果启用）
		// ......
    
    // 创建 WebSocket 预览服务器（如果启用）
		// ......
    
    LOG_INFO(&quot;StreamManager created&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里传递了一个关键的东西：&lt;strong&gt;回调函数指针&lt;/strong&gt; (&lt;code&gt;&amp;amp;RtspThread::StreamConsumer&lt;/code&gt;)。这相当于 RTSP 模块说：“以后有视频帧了，请调用这个函数给我。”&lt;/p&gt;
&lt;p&gt;&lt;code&gt;StreamDispatcher&lt;/code&gt;（实例为&lt;code&gt;g_dispatcher&lt;/code&gt;） 收到注册请求后，在其内部列表 &lt;code&gt;consumers_&lt;/code&gt; 中增加一项。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# src/rkvideo/rkvideo.cpp
int rkvideo_init() {
  
  	// ......
		// 创建流分发器
    g_dispatcher = std::make_unique&amp;lt;StreamDispatcher&amp;gt;(0);
    
    // 注册已登记的消费者
    {
        std::lock_guard&amp;lt;std::mutex&amp;gt; lock(g_consumerMutex);
        for (const auto&amp;amp; reg : g_pendingConsumers) {
            g_dispatcher-&amp;gt;RegisterConsumer(reg.name, 
                [reg](EncodedStreamPtr stream) {
                    if (reg.callback) {
                        reg.callback(stream, reg.userData);
                    }
                }, 
                reg.queueSize);
        }
        g_pendingConsumers.clear();
    }
  	return 0;
  
  	// ......
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;它同时为这个消费者创建了一个&lt;strong&gt;专属队列 (&lt;code&gt;EncodedStreamQueue&lt;/code&gt;)&lt;/strong&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# src/rkvideo/stream_dispatcher.h
/**
 * @brief 注册流消费者
 * 
 * @param name 消费者名称（用于日志）
 * @param consumer 消费者回调函数
 * @param queue_size 消费者队列大小（背压控制）
 */
void RegisterConsumer(const std::string&amp;amp; name, StreamConsumer consumer, size_t queue_size = 3) {
  	
  	// 在增加消费者的同时，创建了一个专属队列
    consumers_.push_back({name, consumer, std::make_unique&amp;lt;EncodedStreamQueue&amp;gt;(queue_size)});
    LOG_INFO(&quot;Registered stream consumer: {}&quot;, name);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;启动线程&lt;/h4&gt;
&lt;p&gt;为每个消费者创建线程，并创建获取数据流的线程（生产者）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# src/rkvideo/stream_dispatcher.h

// Class StreamDispatcher
/**
 * @brief 启动分发器
 * 
 * 启动后会创建：
 * 1. 多个分发线程：为每个消费者分发数据（先启动，确保准备就绪）
 * 2. 一个获取线程：从 VENC 获取编码流
 */
void Start() {
    if (running_) return;
    running_ = true;
		
  	// 依次启动所有消费者，每个都创建一个线程
    for (auto&amp;amp; c : consumers_) {
        c.thread = std::thread(&amp;amp;StreamDispatcher::DispatchLoop, this, &amp;amp;c);
        LOG_DEBUG(&quot;Started dispatch thread for consumer: {}&quot;, c.name);
    }

    // 短暂延迟，确保消费者线程就绪
    std::this_thread::sleep_for(std::chrono::milliseconds(50));

    // 最后启动获取线程
    fetch_thread_ = std::thread(&amp;amp;StreamDispatcher::FetchLoop, this);

    LOG_INFO(&quot;StreamDispatcher started with {} consumers&quot;, consumers_.size());
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;运行（数据流转）&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;生产者 (FetchLoop 线程)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;从 VENC 拿到 &lt;code&gt;EncodedStreamPtr&lt;/code&gt; (shared_ptr)。&lt;/li&gt;
&lt;li&gt;它遍历所有消费者，把同一份指针 &lt;code&gt;push&lt;/code&gt; 到它们各自的队列中。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;缓冲 (MediaQueue)&lt;/p&gt;
&lt;p&gt;每个消费者都有自己独立的队列。RTSP 处理慢了，只会堆积 RTSP 的队列，不会影响文件录制的队列。所谓解耦。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;消费者 (DispatchLoop 线程)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意：RTSP 的回调函数不是在 RTSP 自己的网络线程里执行的，而是在这个专门的“分发线程”里执行的。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;唤醒&lt;/strong&gt;：当队列有数据，&lt;code&gt;DispatchLoop&lt;/code&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;pre&gt;&lt;code&gt;# src/rkvideo/stream_dispatcher.h 
/**
 * @brief 分发循环 - 从队列取出数据并调用消费者回调
 */
void DispatchLoop(ConsumerInfo* consumer) {
    LOG_DEBUG(&quot;Dispatch loop started for consumer: {}&quot;, consumer-&amp;gt;name);

    uint64_t processedCount = 0;
    while (running_) {
        EncodedStreamPtr stream;
        // 尝试从队列中pop出数据
        if (consumer-&amp;gt;queue-&amp;gt;pop(stream, 1000)) {  // 1秒超时
            if (stream &amp;amp;&amp;amp; consumer-&amp;gt;callback) {
                processedCount++;
                if (processedCount &amp;lt;= 5 || processedCount % 30 == 0) {
                    LOG_DEBUG(&quot;Consumer {} processing frame #{}, ref_count={}&quot;, 
                             consumer-&amp;gt;name, processedCount, stream.use_count());
                }
                consumer-&amp;gt;callback(stream); // &amp;lt;--- 这里调用了 RtspThread::StreamConsumer
            }
        }
    }

    LOG_DEBUG(&quot;Dispatch loop exited for consumer: {}, processed {} frames&quot;, 
             consumer-&amp;gt;name, processedCount);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;最终发送&lt;/strong&gt;： 代码跳转到 &lt;code&gt;src/rtsp/thread_rtsp.cpp&lt;/code&gt; 的 &lt;code&gt;StreamConsumer&lt;/code&gt;，最终调用 &lt;code&gt;SendVideoFrame&lt;/code&gt; 把数据塞给网络库（Live555或其他）。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;# src/rtsp/rk_rtsp.cpp
bool RtspServer::SendVideoFrame(const EncodedStreamPtr&amp;amp; stream) {
  if (!initialized_ || !stream || !stream-&amp;gt;pstPack) {
      LOG_WARN(&quot;SendVideoFrame: invalid state or stream&quot;);
      return false;
  }

  // 从 MPI buffer 获取虚拟地址
  void* data = RK_MPI_MB_Handle2VirAddr(stream-&amp;gt;pstPack-&amp;gt;pMbBlk);
  if (!data) {
      LOG_ERROR(&quot;Failed to get virtual address from MB handle&quot;);
      stats_.errors++;
      return false;
  }

  LOG_TRACE(&quot;SendVideoFrame: len={}, pts={}&quot;, stream-&amp;gt;pstPack-&amp;gt;u32Len, stream-&amp;gt;pstPack-&amp;gt;u64PTS);

  return SendVideoData(
      static_cast&amp;lt;const uint8_t*&amp;gt;(data),
      stream-&amp;gt;pstPack-&amp;gt;u32Len,
      stream-&amp;gt;pstPack-&amp;gt;u64PTS
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;4. 总结&lt;/h3&gt;
&lt;p&gt;当前的架构可以理解为：&lt;strong&gt;“1个生产者 + N个搬运工线程 + N个队列”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这是疏于Code Review的Vibe Coding产物&lt;/strong&gt;，力求快速实现功能，它目前的问题是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;消费者注册与对应回调函数注册繁琐&lt;/li&gt;
&lt;li&gt;消费者线程生命周期管理复杂：需要进行&lt;code&gt;running_&lt;/code&gt;判断和手动&lt;code&gt;join()&lt;/code&gt;，容易造成死锁，难以维护&lt;/li&gt;
&lt;li&gt;单核CPU上多线程上下文切换代价大&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;./images/mermaid-%E5%A4%9A%E7%BA%BF%E7%A8%8B%E6%A8%A1%E5%9E%8B.png&quot; alt=&quot;多线程模型&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;单看流程图就头大了&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;二、引入 ASIO 后的事件驱动模型&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;系统现在采用“1个获取线程 + 1个主 IO 线程 + 1个文件线程”的精简模式，详情见tag: v0.5.0&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;获取线程与多线程模型相同&lt;/li&gt;
&lt;li&gt;主 IO 线程通过 ASIO 事件驱动模型调度 RTSP、WebSocket、WebRTC 等网络 IO 任务&lt;/li&gt;
&lt;li&gt;文件线程负责 mp4 视频录制&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;原模型中“N个队列”的数据传输模式也修改了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;主IO线程的多个任务直接通过 Lambda 闭包封装一份编码数据帧的引用，不用通过队列传递&lt;/li&gt;
&lt;li&gt;文件线程通过队列获取&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1. 视频流的获取路径&lt;/h3&gt;
&lt;p&gt;该模型下视频流的获取路径和多线程模型基本相同&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# src/rkvideo/stream_dispatcher.h
// class StreamDispatcher
/**
 * @brief 启动分发器
 * 
 * 启动后会创建：
 * 1. 一个 Fetch 线程：从 VENC 获取编码流并直接分发
 * 2. Queued 类型消费者的独立处理线程
 */
void Start() {
    if (running_) return;
    running_ = true;

    // 启动 Queued 类型消费者的处理线程
    for (auto&amp;amp; c : consumers_) {
        if (c.type == ConsumerType::Queued &amp;amp;&amp;amp; c.queue) {
            c.thread = std::thread(&amp;amp;StreamDispatcher::QueuedConsumerLoop, this, &amp;amp;c);
            LOG_DEBUG(&quot;Started queued consumer thread for: {}&quot;, c.name);
        }
    }

    // 短暂延迟，确保 Queued 消费者线程就绪
    if (HasQueuedConsumers()) {
        std::this_thread::sleep_for(std::chrono::milliseconds(50));
    }

    // 启动 Fetch 线程
    fetch_thread_ = std::thread(&amp;amp;StreamDispatcher::FetchLoop, this);

    LOG_INFO(&quot;StreamDispatcher started with {} consumers (optimized for single-core)&quot;, 
             consumers_.size());
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 消费者的注册：AsyncIO 类型和 Queued 类型&lt;/h3&gt;
&lt;p&gt;消费者类型与结构体&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# src/rkvideo/stream_dispatcher.h
/**
 * @brief 消费者类型
 */
enum class ConsumerType {
    Direct,     ///&amp;lt; 直接在 Fetch 线程中执行（仅用于极快的操作）
    AsyncIO,    ///&amp;lt; 通过 asio::post 投递到 IO 线程执行（网络发送）
    Queued      ///&amp;lt; 通过队列投递到独立线程（文件写入等阻塞操作）
};

// class StreamDispatcher
struct ConsumerInfo {
    std::string name;
    StreamConsumer callback; 											// 回调函数
    ConsumerType type = ConsumerType::AsyncIO;		// 默认为 AsyncIO 类型
    std::unique_ptr&amp;lt;EncodedStreamQueue&amp;gt; queue;  	// 仅 Queued 类型使用
    std::thread thread;                          	// 仅 Queued 类型使用
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;消费者注册仍由流分发器负责&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# src/rkvideo/stream_dispatcher.h
// class StreamDispatcher
/**
 * @brief 注册流消费者
 * 
 * @param name 消费者名称（用于日志）
 * @param consumer 消费者回调函数
 * @param type 消费者类型，决定回调的执行方式
 * @param queue_size 队列大小（仅对 Queued 类型有效）
 */
void RegisterConsumer(const std::string&amp;amp; name, StreamConsumer consumer, 
                      ConsumerType type = ConsumerType::AsyncIO,
                      size_t queue_size = 3) {
    ConsumerInfo info;
    info.name = name;
    info.callback = consumer;
    info.type = type;

    // 只有 Queued 类型需要队列和线程
    if (type == ConsumerType::Queued) {
        info.queue = std::make_unique&amp;lt;EncodedStreamQueue&amp;gt;(queue_size);
    }

    consumers_.push_back(std::move(info));
    LOG_INFO(&quot;Registered stream consumer: {} (type={})&quot;, name, 
             type == ConsumerType::Direct ? &quot;Direct&quot; :
             type == ConsumerType::AsyncIO ? &quot;AsyncIO&quot; : &quot;Queued&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 数据流转&lt;/h3&gt;
&lt;p&gt;该函数由生产者线程调用&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对于 Direct 类型：直接在生产者线程中调用消费者回调&lt;/li&gt;
&lt;li&gt;对于 AsyncIO 类型：将消费者回调通过 lambda 投递到IO线程到主IO循环中执行&lt;/li&gt;
&lt;li&gt;对于 Queued 类型：放入队列，由专属线程处理&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;# src/rkvideo/stream_dispatcher.h
/**
 * @brief 分发帧给所有消费者
 * 
 * 根据消费者类型选择不同的分发策略：
 * - Direct: 直接在当前线程调用
 * - AsyncIO: 通过 asio::post 投递到 IO 线程
 * - Queued: 放入消费者专属队列
 */
void DispatchToConsumers(EncodedStreamPtr stream) {
    for (auto&amp;amp; c : consumers_) {
        if (!c.callback) continue;

        switch (c.type) {
            case ConsumerType::Direct:
                // 直接在 Fetch 线程中执行（仅用于极快的操作）
                c.callback(stream);
                break;

            case ConsumerType::AsyncIO:
                // 投递到 IO 线程执行（网络发送）
                // 捕获 stream 的拷贝，增加引用计数
                PostToIo([callback = c.callback, stream]() {
                    callback(stream);
                });
                break;

            case ConsumerType::Queued:
                // 放入队列，由专属线程处理（文件写入）
                if (c.queue) {
                    c.queue-&amp;gt;push(stream);
                }
                break;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;PostToIo&lt;/code&gt;最终调用&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# src/common/asio_context.h
// class IoContext
/**
 * @brief 投递任务到 IO 线程执行
 * 
 * 线程安全，可从任意线程调用
 * 
 * @param handler 要执行的任务
 */
template&amp;lt;typename Handler&amp;gt;
void Post(Handler&amp;amp;&amp;amp; handler) {
    asio::post(io_context_, std::forward&amp;lt;Handler&amp;gt;(handler));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. 任务闭包化：使用 Lambda 捕获与 ASIO 异步调度&lt;/h3&gt;
&lt;p&gt;对于 AsyncIO 类型消费者任务的处理，是使用 Lambda 表达式将“数据”与“行为”打包成一个&lt;strong&gt;异步任务（Closure）&lt;/strong&gt;，直接投递至主 IO 线程的事件循环中&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# src/rkvideo/stream_dispatcher.h 
case ConsumerType::AsyncIO:
    // 将 stream 拷贝一份捕获到 lambda 中
		// 并非数据深拷贝，而是 shared_ptr 引用计数自动 +1
    PostToIo([callback = c.callback, stream]() {
        callback(stream); // 任务执行时，引用计数保证了 buffer 依然有效
    });
    break;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这种设计解决了原多线程编程中的生命周期问题和竞争问题：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;生命周期问题&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;生产者视角&lt;/strong&gt;：&lt;code&gt;FetchLoop&lt;/code&gt; 只需要完成任务投递，随后 &lt;code&gt;stream&lt;/code&gt; 离开局部作用域，引用计数减 1。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;消费者视角&lt;/strong&gt;：只要主 IO 线程中的任务尚未执行完毕，Lambda 闭包就一直持有该 &lt;code&gt;stream&lt;/code&gt; 的副本，引用计数不为 0，底层硬件 Buffer 就绝不会被 VENC 驱动释放。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;无需手动维护&lt;/strong&gt;：一旦 &lt;code&gt;callback&lt;/code&gt; 执行结束，Lambda 闭包销毁，引用计数降至 0，此时 &lt;code&gt;ReleaseStream&lt;/code&gt; 会被自动触发，将 Buffer 归还硬件。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;消除上下文切换与竞争&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;在原有的“搬运工”模型中，RTSP 或 WebSocket 模块在发送数据前，往往需要先获取锁来从自己的队列中提取帧。而在 ASIO 模型下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;线程一致性&lt;/strong&gt;：所有的网络发送逻辑（RTSP 发送、WS 推送）全部运行在 &lt;code&gt;Main IO Thread&lt;/code&gt; 这一同一个线程中。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;无锁设计&lt;/strong&gt;：由于任务是顺序执行的，网络模块内部的发送缓冲区不再需要互斥锁来保护，消除了 CPU 在多个 Dispatch 线程间进行内核级切换的开销。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5. 总结&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./images/mermaid-ASIO%E4%BA%8B%E4%BB%B6%E9%A9%B1%E5%8A%A8%E6%A8%A1%E5%9E%8B.png&quot; alt=&quot;ASIO事件驱动模型&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;相比原多线程模型，流程图精简了不少，也提升了性能（砍了好几个不必要的线程）。&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>毕设日志（二）Luckfox RKMPI示例的资源管理分析</title><link>https://haoyn231.github.io/posts/linux/%E6%AF%95%E4%B8%9A%E8%AE%BE%E8%AE%A1%E6%99%BA%E8%83%BD%E7%BD%91%E7%BB%9C%E6%91%84%E5%83%8F%E5%A4%B4/luckfox-rkmpi%E7%A4%BA%E4%BE%8B%E7%9A%84%E8%B5%84%E6%BA%90%E7%AE%A1%E7%90%86/</link><guid isPermaLink="true">https://haoyn231.github.io/posts/linux/%E6%AF%95%E4%B8%9A%E8%AE%BE%E8%AE%A1%E6%99%BA%E8%83%BD%E7%BD%91%E7%BB%9C%E6%91%84%E5%83%8F%E5%A4%B4/luckfox-rkmpi%E7%A4%BA%E4%BE%8B%E7%9A%84%E8%B5%84%E6%BA%90%E7%AE%A1%E7%90%86/</guid><description>分析 luckfox_pico_rtsp_retinaface_osd 例程中对媒体资源的处理，并阐述毕设思路</description><pubDate>Sat, 31 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;分析 &lt;a href=&quot;https://github.com/LuckfoxTECH/luckfox_pico_rkmpi_example/blob/kernel-5.10.160/example/luckfox_pico_rtsp_retinaface_osd/src/main.cc&quot;&gt;&lt;code&gt;luckfox_pico_rtsp_retinaface_osd&lt;/code&gt;&lt;/a&gt;例程&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;LuckfoxTECH/luckfox_pico_rkmpi_example&quot;}&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;一、VENC编码和RTSP推流线程&lt;/h2&gt;
&lt;h3&gt;1. 编码和推流线程分析&lt;/h3&gt;
&lt;p&gt;在 RKMPI 架构中，VI（视频输入）到 VENC（视频编码）的数据流转通常是&lt;strong&gt;硬件自动完成&lt;/strong&gt;的，实现零拷贝。我们的任务是从 VENC 获取编码后的 H.264/H.265 数据包并交付给推流协议（如 RTSP）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static void *GetMediaBuffer(void *arg) {
	(void)arg;
	printf(&quot;========%s========\n&quot;, __func__);
	void *pData = RK_NULL;
	int s32Ret;

	VENC_STREAM_S stFrame;
	stFrame.pstPack = (VENC_PACK_S *)malloc(sizeof(VENC_PACK_S));

	while (1) {
    
    // 获取编码后的H.264包
		s32Ret = RK_MPI_VENC_GetStream(0, &amp;amp;stFrame, -1);
		if (s32Ret == RK_SUCCESS) {
			if (g_rtsplive &amp;amp;&amp;amp; g_rtsp_session) {
				pData = RK_MPI_MB_Handle2VirAddr(stFrame.pstPack-&amp;gt;pMbBlk);
				rtsp_tx_video(g_rtsp_session, (uint8_t *)pData, stFrame.pstPack-&amp;gt;u32Len,
				              stFrame.pstPack-&amp;gt;u64PTS);
				rtsp_do_event(g_rtsplive);
			}

      // 资源释放，释放编码后的包
			s32Ret = RK_MPI_VENC_ReleaseStream(0, &amp;amp;stFrame);
			if (s32Ret != RK_SUCCESS) {
				RK_LOGE(&quot;RK_MPI_VENC_ReleaseStream fail %x&quot;, s32Ret);
			}
		}
    
    // 暂停10ms
		usleep(10 * 1000);
	}
	printf(&quot;\n======exit %s=======\n&quot;, __func__);
  
  // 线程结束时释放stFrame
	free(stFrame.pstPack);
	return NULL;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. &lt;code&gt;VENC_STREAM_S&lt;/code&gt;编码数据帧分析&lt;/h3&gt;
&lt;p&gt;其中，主要的帧格式是&lt;code&gt;VENC_STREAM_S&lt;/code&gt;，可以看到它封装了 VENC 支持的编码格式&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* Defines the features of an stream */
typedef struct rkVENC_STREAM_S {
    VENC_PACK_S ATTRIBUTE* pstPack;            /* R; stream pack attribute*/
    RK_U32      ATTRIBUTE u32PackCount;        /* R; the pack number of one frame stream*/
    RK_U32      u32Seq;                        /* R; the list number of stream*/

    union {
        VENC_STREAM_INFO_H264_S   stH264Info;                        /* R; the stream info of h264*/
        VENC_STREAM_INFO_JPEG_S   stJpegInfo;                        /* R; the stream info of jpeg*/
        VENC_STREAM_INFO_H265_S   stH265Info;                        /* R; the stream info of h265*/
        VENC_STREAM_INFO_PRORES_S stProresInfo;                      /* R; the stream info of prores*/
    };

    union {
        VENC_STREAM_ADVANCE_INFO_H264_S   stAdvanceH264Info;         /* R; the stream info of h264*/
        VENC_STREAM_ADVANCE_INFO_JPEG_S   stAdvanceJpegInfo;         /* R; the stream info of jpeg*/
        VENC_STREAM_ADVANCE_INFO_H265_S   stAdvanceH265Info;         /* R; the stream info of h265*/
        VENC_STREAM_ADVANCE_INFO_PRORES_S stAdvanceProresInfo;       /* R; the stream info of prores*/
    };
} VENC_STREAM_S;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;二、NPU推理线程&lt;/h2&gt;
&lt;h3&gt;1. NPU推理线程分析&lt;/h3&gt;
&lt;p&gt;利用&lt;code&gt;RK_MPI_VI_GetChnFrame&lt;/code&gt;从 VI 通道获取原始数据帧，再利用 &lt;strong&gt;opencv-mobile&lt;/strong&gt; 进行格式转换和缩放，最后用 RGN，在 VENC 编码前将 OSD 合入视频帧&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;格式转换&lt;/strong&gt;：将 &lt;strong&gt;NV12&lt;/strong&gt; 转换为 &lt;strong&gt;BGR&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;缩放&lt;/strong&gt;：将图像适配模型输入的 &lt;strong&gt;640×640&lt;/strong&gt; 尺寸&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;static void *RetinaProcessBuffer(void *arg) {
  
  // ... 变量初始化 ...
  
  // 这是rkmpi中最核心最原始的数据帧，用它来承接从VI通道中获取的原始图像
	VIDEO_FRAME_INFO_S stViFrame;

	while(1)
	{
    // 手动抓取，主动向VI的通道1（专门用于推理的）请求一帧数据
		s32Ret = RK_MPI_VI_GetChnFrame(0, 1, &amp;amp;stViFrame, -1);
    
    // 之后利用opencv-mobile，对数据帧进行格式转换和缩放
		if(s32Ret == RK_SUCCESS)
		{
      // 获取虚拟地址
			void *vi_data = RK_MPI_MB_Handle2VirAddr(stViFrame.stVFrame.pMbBlk);
			if(vi_data != RK_NULL)
			{
        // 使用cv::Mat对原始数据帧进行封装，并进行格式转换和缩放处理
				cv::Mat yuv420sp(disp_height + disp_height / 2, disp_width, CV_8UC1, vi_data);
				cv::Mat bgr(disp_height, disp_width, CV_8UC3);			
				cv::Mat model_bgr(model_height, model_width, CV_8UC3);			

				cv::cvtColor(yuv420sp, bgr, cv::COLOR_YUV420sp2BGR);

				cv::resize(bgr, model_bgr, cv::Size(model_width ,model_height), 0, 0, cv::INTER_LINEAR);	
        
        // memcpy到RKNN输入
				memcpy(rknn_app_ctx.input_mems[0]-&amp;gt;virt_addr, model_bgr.data, model_width * model_height * 3);
        
        // 阻塞，该函数计算结束才会返回
        // od_results为推理结果
				inference_retinaface_model(&amp;amp;rknn_app_ctx, &amp;amp;od_results);

        // 利用rgn，将osd覆盖到venc的输入数据上，实现标注
				// ... RGN 处理逻辑 ...
				}		
			}
      // 释放帧资源
			s32Ret = RK_MPI_VI_ReleaseChnFrame(0, 1, &amp;amp;stViFrame);
		}
    // 暂停500ms
		usleep(500000);		
		// ... 清理 RGN 资源 ...
	}			
	return NULL;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. &lt;code&gt;VIDEO_FRAME_INFO_S&lt;/code&gt;原始数据帧分析&lt;/h3&gt;
&lt;p&gt;该线程操作的数据帧：&lt;code&gt;VIDEO_FRAME_INFO_S&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;typedef struct rkVIDEO_FRAME_S {
    MB_BLK              pMbBlk; // 底层内存块句柄，该结构体本质上是对它的一层封装
    RK_U32              u32Width;
    RK_U32              u32Height;
    RK_U32              u32VirWidth;
    RK_U32              u32VirHeight;
    VIDEO_FIELD_E       enField;
    PIXEL_FORMAT_E      enPixelFormat;
    VIDEO_FORMAT_E      enVideoFormat;
    COMPRESS_MODE_E     enCompressMode;
    DYNAMIC_RANGE_E     enDynamicRange;
    COLOR_GAMUT_E       enColorGamut;

    RK_VOID            *pVirAddr[RK_MAX_COLOR_COMPONENT]; //虚拟地址

    RK_U32              u32TimeRef;
    RK_U64              u64PTS;

    RK_U64              u64PrivateData;
    RK_U32              u32FrameFlag;     /* FRAME_FLAG_E, can be OR operation. */
} VIDEO_FRAME_S;

typedef struct rkVIDEO_FRAME_INFO_S {
    VIDEO_FRAME_S stVFrame;
} VIDEO_FRAME_INFO_S;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;三、毕设软件设计思路&lt;/h2&gt;
&lt;p&gt;也就是说，对于我毕业设计这个需求，我需要处理两种格式，一是 VENC 编码后的 H.264 视频流，二是从 VI 通道直接获取的原始数据帧。rkmpi 实际上已经做了一定程度的封装了，我应该在它封装的基础上进行管理，而不是拆开它们再自己封装。&lt;/p&gt;
&lt;h3&gt;1.  使用&lt;code&gt;std::share_ptr&lt;/code&gt;和自定义删除器来封装数据帧&lt;/h3&gt;
&lt;p&gt;原始帧和编码流虽然接口不同，但是它们的使用都符合这么一个逻辑：每一次循环开始时，先获取，进行处理，再释放。对于线性场景，比如 VENC 编码后，交由 RTSP 进行推流，那么可以在循环中执行上述逻辑。&lt;/p&gt;
&lt;p&gt;但是对于我毕设的需求来说，编码流会可能会同时用于 RTSP、WebRTC 推流和本地保存，那么上述逻辑就会变得复杂且难以处理。此时适合使用&lt;code&gt;std::shared_ptr&lt;/code&gt;结合&lt;strong&gt;自定义删除器&lt;/strong&gt;来进行处理，不同场景共享引用计数，只有当所有引用都结束后，再结束它的生命周期，进行析构（释放资源）。&lt;/p&gt;
&lt;p&gt;以下是对编码流的处理，原始帧类似&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/**
 * @brief 编码后的视频流智能指针 (VENC 输出)
 * 
 * 用于推流路径，H.264/H.265 编码后的数据包
 * 释放时自动调用 RK_MPI_VENC_ReleaseStream
 */
using EncodedStreamPtr = std::shared_ptr&amp;lt;VENC_STREAM_S&amp;gt;;

/**
 * @brief 从 VENC 通道获取编码流并包装为智能指针
 * 
 * @param chn_id VENC 通道 ID
 * @param timeout_ms 超时时间（毫秒），-1 表示阻塞等待
 * @return EncodedStreamPtr 成功返回流指针，失败返回 nullptr
 * 
 * @note 返回的智能指针在所有引用释放后会自动调用 RK_MPI_VENC_ReleaseStream
 * @note 内部会分配 VENC_PACK_S，也会在释放时一并清理
 */
inline EncodedStreamPtr acquire_encoded_stream(RK_S32 chn_id, RK_S32 timeout_ms = -1) {
    auto stream = new VENC_STREAM_S();
    stream-&amp;gt;pstPack = new VENC_PACK_S();
    
    RK_S32 ret = RK_MPI_VENC_GetStream(chn_id, stream, timeout_ms);
    if (ret != RK_SUCCESS) {
        delete stream-&amp;gt;pstPack;
        delete stream;
        return nullptr;
    }
    
    // 创建带自定义删除器的 shared_ptr
    return EncodedStreamPtr(stream, [chn_id](VENC_STREAM_S* p) {
        if (p) {
            RK_MPI_VENC_ReleaseStream(chn_id, p);
            delete p-&amp;gt;pstPack;
            delete p;
        }
    });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;零拷贝传递&lt;/strong&gt;：不同场景实际上拿到的是同一个内存块地址，不会有内存拷贝。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;多路共享&lt;/strong&gt;：即使 RTSP 线程因为网络抖动卡住了，WebRTC 处理完后引用计数减 1，等到 RTSP 也处理完，引用计数归零，MPI 资源自动释放。&lt;/p&gt;
&lt;p&gt;缺点就是：VENC的缓冲池是有限的，如果 RTSP 和 WebRTC 任何一方长时间持有指针不释放，会导致 VENC 模块因为拿不到空闲 Buffer 而阻塞或丢帧。后续应该引入&lt;strong&gt;丢帧机制&lt;/strong&gt;或&lt;strong&gt;超时强制释放&lt;/strong&gt;。如果某个消费者（如本地保存）处理太慢，应该主动&lt;code&gt;reset()&lt;/code&gt;掉该路指针，宁可丢帧也不要阻塞采集前端。&lt;/p&gt;
&lt;h3&gt;2. 针对 AI 推理的原始帧处理 (VI → RKNN)&lt;/h3&gt;
&lt;p&gt;对于推理线程使用的原始数据帧，它的路径和所有权比较明确，毕竟同时只会有一个 AI 推理的线程存在。&lt;/p&gt;
</content:encoded></item><item><title>使用GDB调试嵌入式设备</title><link>https://haoyn231.github.io/posts/tools/%E4%BD%BF%E7%94%A8gdb%E8%B0%83%E8%AF%95%E5%B5%8C%E5%85%A5%E5%BC%8F%E8%AE%BE%E5%A4%87/</link><guid isPermaLink="true">https://haoyn231.github.io/posts/tools/%E4%BD%BF%E7%94%A8gdb%E8%B0%83%E8%AF%95%E5%B5%8C%E5%85%A5%E5%BC%8F%E8%AE%BE%E5%A4%87/</guid><description>介绍在嵌入式 Linux 设备中使用 GDB 进行程序调试的方法</description><pubDate>Fri, 30 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;本文主要介绍在嵌入式 Linux 设备中使用 GDB 进行程序调试的方法。对于单片机开发，虽然可以使用 IDE 集成的调试功能，或通过 &lt;code&gt;pyocd&lt;/code&gt;、&lt;code&gt;openocd&lt;/code&gt; 配合 &lt;code&gt;DAPLink&lt;/code&gt; 等硬件调试探针，但其核心原理与 GDB 调试是一致的。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;环境准备&lt;/h2&gt;
&lt;p&gt;在进行调试前，需确保开发环境（如 Buildroot）已安装必要的调试组件。&lt;/p&gt;
&lt;h3&gt;1. 安装组件&lt;/h3&gt;
&lt;p&gt;通过 &lt;code&gt;menuconfig&lt;/code&gt; 在根文件系统中启用以下工具：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;gdb&lt;/strong&gt;: 运行在开发板上的完整调试器。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;gdbserver&lt;/strong&gt;: 运行在开发板上的轻量化调试代理（推荐用于交叉调试）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;验证安装：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[root@luckfox root]# gdb --version
GNU gdb (GDB) 10.2
[root@luckfox root]# gdbserver --version
GNU gdbserver (GDB) 10.2
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 编译配置&lt;/h3&gt;
&lt;p&gt;为了生成调试符号表并确保代码执行顺序与源码一致，必须在编译时开启 &lt;code&gt;-g&lt;/code&gt; 参数并尽量禁用优化 &lt;code&gt;-O0&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 例如使用CMake进行构建时，设置参数
set(CMAKE_CXX_FLAGS_DEBUG &quot;${CMAKE_CXX_FLAGS_DEBUG} -O0 -g&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;常用调试途径对比&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;途径&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;实现方式&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;适用场景&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GDB 命令行&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;直接在板端通过 GDB 运行程序或分析 Core Dump&lt;/td&gt;
&lt;td&gt;快速定位崩溃点、打印堆栈、资源受限无法连接主机时&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;单步调试&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;通过 &lt;code&gt;gdbserver&lt;/code&gt; 连接主机（如 VSCode）远程调试&lt;/td&gt;
&lt;td&gt;逻辑分析、对照源码逐行排查、复杂业务流程调试&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;一、 GDB 命令行调试&lt;/h2&gt;
&lt;h3&gt;1. 场景一：实时捕获（Live Debug）&lt;/h3&gt;
&lt;p&gt;针对可以稳定复现的程序崩溃（Segmentation fault）。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;启动调试&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gdb ./your_app
&lt;/code&gt;&lt;/pre&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;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;命令&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;说明&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;set args --config ./test.conf&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;设置程序启动参数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;run&lt;/code&gt; (r)&lt;/td&gt;
&lt;td&gt;开始运行程序&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bt&lt;/code&gt; (backtrace)&lt;/td&gt;
&lt;td&gt;打印当前简略调用堆栈&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bt full&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;打印堆栈及每一层函数的局部变量值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;thread apply all bt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;查看所有线程的堆栈（用于分析多线程死锁或崩溃）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;2. 场景二：Core Dump 核心转储&lt;/h3&gt;
&lt;p&gt;用于处理偶发性崩溃，通过事后“尸检”定位问题。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;启用生成&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ulimit -c unlimited  # 设置 core 文件大小不受限制
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;设置路径&lt;/strong&gt;（可选）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 将 core 文件统一存放到 /tmp，并按进程名、PID 和时间命名
echo &quot;/tmp/core-%e-%p-%t&quot; &amp;gt; /proc/sys/kernel/core_pattern
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;加载分析&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 必须同时提供可执行文件和 core 文件
gdb ./your_app core
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. 场景三：深入崩溃点&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;操作&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;命令示例&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;功能描述&lt;/strong&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;&lt;code&gt;frame 2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;切换到堆栈中的指定层级（如切换回应用代码层）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;查看源码&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;list&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;显示当前断点或崩溃点附近的源码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;检查变量&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;print *my_ptr&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;打印变量值或解引用指针&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;检查状态&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;info locals&lt;/code&gt; / &lt;code&gt;info args&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;打印当前函数的所有局部变量或输入参数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;汇编调试&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;disassemble&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;在无源码时查看当前函数的汇编代码&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;二、 远程单步调试&lt;/h2&gt;
&lt;p&gt;在嵌入式开发中，通常在板端运行 &lt;code&gt;gdbserver&lt;/code&gt;（节省空间），在 Host 端（PC）使用带符号表的程序和源码进行可视化调试。&lt;/p&gt;
&lt;h3&gt;1. 板端操作&lt;/h3&gt;
&lt;p&gt;启动 &lt;code&gt;gdbserver&lt;/code&gt; 并监听调试端口（如 &lt;code&gt;1234&lt;/code&gt;）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gdbserver :1234 ./my_app
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. Host 端配置 (VSCode)&lt;/h3&gt;
&lt;p&gt;需安装 &lt;strong&gt;C/C++ (cpptools)&lt;/strong&gt; 扩展。创建 &lt;code&gt;.vscode/launch.json&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &quot;version&quot;: &quot;0.2.0&quot;,
    &quot;configurations&quot;: [
        {
            &quot;name&quot;: &quot;(gdb) Luckfox Remote Debug&quot;,
            &quot;type&quot;: &quot;cppdbg&quot;,
            &quot;request&quot;: &quot;launch&quot;,
            &quot;program&quot;: &quot;${workspaceFolder}/build/my_app&quot;, // 必须是带调试符号的可执行文件
            &quot;args&quot;: [],
            &quot;stopAtEntry&quot;: true,
            &quot;cwd&quot;: &quot;${workspaceFolder}&quot;,
            &quot;environment&quot;: [],
            &quot;externalConsole&quot;: false,
            &quot;MIMode&quot;: &quot;gdb&quot;,
            &quot;miDebuggerPath&quot;: &quot;/opt/luckfox-sdk/tools/linux/toolchain/.../arm-linux-gnueabihf-gdb&quot;, // 交叉编译器的 GDB 路径
            &quot;miDebuggerServerAddress&quot;: &quot;192.168.1.123:1234&quot;, // 板端 IP 和端口
            &quot;setupCommands&quot;: [
                {
                    &quot;description&quot;: &quot;Enable pretty-printing for gdb&quot;,
                    &quot;text&quot;: &quot;-enable-pretty-printing&quot;,
                    &quot;ignoreFailures&quot;: true
                },
                {
                    &quot;description&quot;: &quot;Set Sysroot for Cross-Debugging&quot;,
                    &quot;text&quot;: &quot;set sysroot ${workspaceFolder}/buildroot/output/staging&quot;, // 关键：指定交叉编译系统库路径
                    &quot;ignoreFailures&quot;: false
                }
            ]
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;配置完成后，在 VSCode 中按 &lt;code&gt;F5&lt;/code&gt; 即可进入图形化单步调试界面。&lt;/p&gt;
</content:encoded></item><item><title>毕设日志（一） Luckfox RK_MPI示例视频流与AI推理链路分析</title><link>https://haoyn231.github.io/posts/linux/%E6%AF%95%E4%B8%9A%E8%AE%BE%E8%AE%A1%E6%99%BA%E8%83%BD%E7%BD%91%E7%BB%9C%E6%91%84%E5%83%8F%E5%A4%B4/luckfox-rk_mpi%E7%A4%BA%E4%BE%8B%E8%A7%86%E9%A2%91%E6%B5%81%E4%B8%8Eai%E6%8E%A8%E7%90%86%E9%93%BE%E8%B7%AF%E5%88%86%E6%9E%90/</link><guid isPermaLink="true">https://haoyn231.github.io/posts/linux/%E6%AF%95%E4%B8%9A%E8%AE%BE%E8%AE%A1%E6%99%BA%E8%83%BD%E7%BD%91%E7%BB%9C%E6%91%84%E5%83%8F%E5%A4%B4/luckfox-rk_mpi%E7%A4%BA%E4%BE%8B%E8%A7%86%E9%A2%91%E6%B5%81%E4%B8%8Eai%E6%8E%A8%E7%90%86%E9%93%BE%E8%B7%AF%E5%88%86%E6%9E%90/</guid><description>主要分析 luckfox_pico_rtsp_retinaface_osd 例程，探讨其如何实现摄像头采集、NPU 推理、硬件编码及 RTSP 推流的完整链路</description><pubDate>Fri, 30 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;主要分析 &lt;a href=&quot;https://github.com/LuckfoxTECH/luckfox_pico_rkmpi_example/blob/kernel-5.10.160/example/luckfox_pico_rtsp_retinaface_osd/src/main.cc&quot;&gt;&lt;code&gt;luckfox_pico_rtsp_retinaface_osd&lt;/code&gt;&lt;/a&gt;例程，探讨其如何实现摄像头采集、NPU 推理、硬件编码及 RTSP 推流的完整链路。&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;LuckfoxTECH/luckfox_pico_rkmpi_example&quot;}&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;1. 视频流链路概览&lt;/h2&gt;
&lt;p&gt;该程序在硬件层面上构建了两条并行的核心路径，通过 &lt;code&gt;RK_MPI&lt;/code&gt;（Rockchip Media Process Interface）协调不同硬件模块（VI、VENC、NPU、RGN）的交互。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;路径名称&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;数据流向&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;核心功能&lt;/strong&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;VI Chn0 → VENC → RTSP&lt;/td&gt;
&lt;td&gt;负责视频采集、硬件 H.264 编码及网络传输&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;推理路径&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;VI Chn1 → CPU/NPU → RGN&lt;/td&gt;
&lt;td&gt;负责获取原始帧、图像预处理、AI 推理及结果绘制&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;2. VI 模块初始化与格式选择&lt;/h2&gt;
&lt;p&gt;程序初始化了两个 VI 通道，分别进行推流与AI推理。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;	// vi init
	vi_dev_init();
	vi_chn_init(0, width, height);
	vi_chn_init(1, width, height);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.1 通道分配&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;通道 0&lt;/strong&gt;：负责推流，直接绑定至编码器。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;通道 1&lt;/strong&gt;：负责 AI 推理，供用户态程序主动获取帧数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.2 像素格式与配置&lt;/h3&gt;
&lt;p&gt;两个通道均设置为 &lt;code&gt;RK_FMT_YUV420SP&lt;/code&gt;（即 &lt;strong&gt;NV12&lt;/strong&gt; 格式）。采用 &lt;code&gt;VI_V4L2_MEMORY_TYPE_DMABUF&lt;/code&gt; 以减少内存拷贝开销。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int vi_chn_init(int channelId, int width, int height) {
  int ret;
  int buf_cnt = 4;
  // VI init
  VI_CHN_ATTR_S vi_chn_attr;
  memset(&amp;amp;vi_chn_attr, 0, sizeof(vi_chn_attr));
  vi_chn_attr.stIspOpt.u32BufCount = buf_cnt;
  vi_chn_attr.stIspOpt.enMemoryType = VI_V4L2_MEMORY_TYPE_DMABUF; 
  vi_chn_attr.stSize.u32Width = width;
  vi_chn_attr.stSize.u32Height = height;
  vi_chn_attr.enPixelFormat = RK_FMT_YUV420SP;
  vi_chn_attr.enCompressMode = COMPRESS_MODE_NONE; 
  vi_chn_attr.u32Depth = 2; 
  ret = RK_MPI_VI_SetChnAttr(0, channelId, &amp;amp;vi_chn_attr);
  ret |= RK_MPI_VI_EnableChn(0, channelId);
  if (ret) {
    printf(&quot;ERROR: create VI error! ret=%d\n&quot;, ret);
    return ret;
  }
  return ret;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;3. 推流通路 (VI Chn0 → VENC → RTSP)&lt;/h2&gt;
&lt;p&gt;推流路径实现了&lt;strong&gt;纯硬件链路&lt;/strong&gt;，数据在底层驱动中通过 &lt;code&gt;SYS_Bind&lt;/code&gt; 自动流转。&lt;/p&gt;
&lt;h3&gt;3.1 硬件绑定逻辑&lt;/h3&gt;
&lt;p&gt;通过 &lt;code&gt;RK_MPI_SYS_Bind&lt;/code&gt; 将 VI 模块与 VENC 模块在内核层关联，避免了将原始 YUV 数据频繁拷贝到用户空间。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// bind vi to venc  
stSrcChn.enModId = RK_ID_VI;
stSrcChn.s32DevId = 0;
stSrcChn.s32ChnId = 0;
    
stvencChn.enModId = RK_ID_VENC;
stvencChn.s32DevId = 0;
stvencChn.s32ChnId = 0;

s32Ret = RK_MPI_SYS_Bind(&amp;amp;stSrcChn, &amp;amp;stvencChn);
&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;：VENC 接收到 NV12 数据，根据 &lt;code&gt;RK_VIDEO_ID_AVC&lt;/code&gt; 配置进行 H.264 硬件压缩。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;流获取&lt;/strong&gt;：&lt;code&gt;GetMediaBuffer&lt;/code&gt; 线程通过 &lt;code&gt;RK_MPI_VENC_GetStream(0, &amp;amp;stFrame, -1)&lt;/code&gt; &lt;strong&gt;阻塞式&lt;/strong&gt;获取码流。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;推流&lt;/strong&gt;：调用 &lt;code&gt;rtsp_tx_video&lt;/code&gt; 将 H.264 数据包发送至客户端。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;4. AI 推理路径 (VI Chn1 → NPU)&lt;/h2&gt;
&lt;p&gt;推理路径涉及用户态的图像处理与 NPU 调度。&lt;/p&gt;
&lt;h3&gt;4.1 图像获取与预处理&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;获取数据&lt;/strong&gt;：&lt;code&gt;RetinaProcessBuffer&lt;/code&gt; 线程通过 &lt;code&gt;RK_MPI_VI_GetChnFrame&lt;/code&gt; 从通道 1 获取 NV12 帧。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;格式转换&lt;/strong&gt;：利用 &lt;code&gt;opencv-mobile&lt;/code&gt; 将 &lt;strong&gt;NV12&lt;/strong&gt; 转换为 &lt;strong&gt;BGR&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;尺寸缩放&lt;/strong&gt;：调用 &lt;code&gt;cv::resize&lt;/code&gt; 将图像适配模型输入的 $640 \times 640$ 尺寸。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.2 NPU 任务调度&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;inference_retinaface_model&lt;/code&gt; 函数执行&lt;strong&gt;同步推理&lt;/strong&gt;：CPU 调用后进入等待状态，直到 NPU 硬件完成计算并返回 &lt;code&gt;od_results&lt;/code&gt; 结果。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;5. OSD 叠加机制&lt;/h2&gt;
&lt;p&gt;OSD（On-Screen Display）并未修改原始图像内存，而是通过 &lt;strong&gt;RGN（Region）模块&lt;/strong&gt; 实现。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;原理&lt;/strong&gt;：AI 推理得到的坐标结果被转化为 RGN 画布操作。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;硬件合成&lt;/strong&gt;：RGN 模块将线框作为叠加层，在进入 VENC 编码前由硬件自动合入视频帧。这保证了 RTSP 流中自带检测框。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;6. 模块同步机制&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;监控对象&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;实现机制&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;关键函数/参数&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;VENC 编码完成&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;阻塞等待 (Blocking)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;RK_MPI_VENC_GetStream&lt;/code&gt; 的 &lt;code&gt;s32MilliSec&lt;/code&gt; 参数设为 &lt;code&gt;-1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NPU 推理完成&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;同步调用 (Synchronous)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_retinaface_model&lt;/code&gt; 直至计算结束才返回&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;VI 数据就绪&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;循环轮询/阻塞&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;在推理线程中主动请求帧数据 &lt;code&gt;RK_MPI_VI_GetChnFrame&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这些阻塞其实可以接受，毕竟本来就是在对应单独线程中的操作，而不是同一个线程中的同步操作。这个线程阻塞了就是阻塞了，也没有优化的必要。&lt;/p&gt;
&lt;h2&gt;7. 关于毕业设计软件部分&lt;/h2&gt;
&lt;p&gt;在此基础上实现：&lt;strong&gt;WebRTC推流&lt;/strong&gt;，显示在一个经过设计的网页里，通过该网页，可以动态调整模型类型（例如从&lt;code&gt;yolov5&lt;/code&gt;切换为&lt;code&gt;retainface&lt;/code&gt;）。&lt;/p&gt;
&lt;h3&gt;7.1 WebRTC&lt;/h3&gt;
&lt;p&gt;复用 &lt;code&gt;venc&lt;/code&gt; 的码流即可，引入第三方&lt;strong&gt;WebRTC&lt;/strong&gt;库，准备选择&lt;code&gt;libdatachannel&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;paullouisageneau/libdatachannel&quot;}&lt;/p&gt;
&lt;h3&gt;7.2 AI模型动态切换&lt;/h3&gt;
&lt;p&gt;注意到luckfox提供的例程中，对于不同的AI模型，虽然推理、处理和绘制的逻辑有很大不同，但是都有几个实现这些功能的线程，也就是说，可以直接用&lt;strong&gt;线程隔离&lt;/strong&gt;，而不是去做复杂的接口设计和动态切换。&lt;/p&gt;
&lt;p&gt;这里牵扯到一个问题，就是&lt;strong&gt;RKNN模型&lt;/strong&gt;的加载问题，如果只是简单的线程切换的话，所有RKNN模型在启动阶段就要加载进内存中，对于RV1106来说，这些内存占用不能忽视。&lt;/p&gt;
&lt;p&gt;为此需要实现一个热加载逻辑，不仅仅要维护不同AI模型线程的切换，还有做到资源的管理，实现“&lt;strong&gt;先卸载、再加载&lt;/strong&gt;”。&lt;/p&gt;
</content:encoded></item><item><title>Arch Linux通过Distrobox运行Vivado</title><link>https://haoyn231.github.io/posts/fpga/archlinux%E9%80%9A%E8%BF%87disrobox%E8%BF%90%E8%A1%8Cvivado2020/archlinux%E9%80%9A%E8%BF%87distrobox%E8%BF%90%E8%A1%8Cvivado2020/</link><guid isPermaLink="true">https://haoyn231.github.io/posts/fpga/archlinux%E9%80%9A%E8%BF%87disrobox%E8%BF%90%E8%A1%8Cvivado2020/archlinux%E9%80%9A%E8%BF%87distrobox%E8%BF%90%E8%A1%8Cvivado2020/</guid><description>摆脱Ubuntu虚拟机难用的图形化界面和繁琐的配置</description><pubDate>Mon, 26 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;对于&lt;strong&gt;Arch Linux&lt;/strong&gt;运行Vivado开发Zynq来说，一般会考虑&lt;strong&gt;KVM虚拟机&lt;/strong&gt;安装对应版本的Ubuntu，但是抛开内存和性能损耗不谈，单单是USB设备挂载、NFS挂载等后续问题，就够麻烦了，而且老版本Ubuntu的GUI结合KVM难绷的图形性能（尤其是N卡，无法做到显卡直通和3D加速），对于使用Vivado这样的图形化界面来说非常难受。&lt;/p&gt;
&lt;p&gt;这里选择使用 &lt;strong&gt;Distrobox&lt;/strong&gt; 进行环境配置。它通过与宿主机共享内核和命名空间，让 Ubuntu 不再是隔离的虚拟系统，而是以“透明环境”的形式融入 Arch Linux。这种架构使 Vivado 可以直接调用宿主机的 NVIDIA 驱动进行硬件加速渲染，避免了 KVM 虚拟机中常见的界面卡顿和操作迟滞。同时，容器共享 &lt;code&gt;/dev&lt;/code&gt; 设备节点和网络栈，使 JTAG 下载、Zynq 调试及 NFS 等网络服务无需额外配置即可使用，整体体验接近原生 Ubuntu LTS，大幅降低了环境折腾成本。&lt;/p&gt;
&lt;p&gt;相比之下，&lt;strong&gt;Docker 更偏向应用级容器&lt;/strong&gt;，默认权限受限，对 GUI 程序和硬件访问并不友好。在 Vivado 这类重度依赖图形界面与外设的开发场景下，Docker 往往需要额外处理显示转发、设备映射和权限问题，配置复杂且稳定性不足，更适合作为自动化构建或批处理工具，而非日常交互式开发环境。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;使用环境&lt;/strong&gt;：Arch Linux、KED桌面、Wayland、Nvidia独立显卡&lt;/p&gt;
&lt;p&gt;既然Wayland和n卡都能跑通，其他环境只会更方便&lt;/p&gt;
&lt;h2&gt;一、通过Distrobox安装Ubuntu20.04&lt;/h2&gt;
&lt;h4&gt;安装和配置Distrobox&lt;/h4&gt;
&lt;p&gt;代理相关的不再赘述&lt;/p&gt;
&lt;p&gt;优先选择podman&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo pacman -S podman distrobox
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建和启动容器，并初始化&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;distrobox create -n fpga-dev --image ubuntu:20.04

# 进入容器
distrobox enter fpga-dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;distrobox容器会继承宿主机的shell设置（比如oh-my-zsh）和/dev，可谓相当方便&lt;/p&gt;
&lt;p&gt;进入ubuntu容器后，安装需要的软件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 更新源并安装 Vivado 必需的依赖和常用工具
sudo apt update
sudo apt install -y libtinfo5 libncurses5 libx11-6 libxrender1 libxtst6 libxi6 libglib2.0-0 build-essential bc locales git vim wget curl libxft2 libfontconfig1 libdbus-1-3

# 修正 Ubuntu 的 dash 问题（Petalinux 强依赖 bash）
sudo dpkg-reconfigure dash # 弹窗选择 No
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;二、下载和安装Vivado2020&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.xilinx.com/support/download/index.html/content/xilinx/en/downloadNav/vivado-design-tools/archive.html&quot;&gt;下载网址&lt;/a&gt;，下载时注意关闭代理，不然四五十GB挺肉疼的&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://markdownforyuanhao.oss-cn-hangzhou.aliyuncs.com/img1/20260125142210713.png&quot; alt=&quot;截屏2026-01-25 14.22.05&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;解压与安装&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;tar -xzf Xilinx_Unified_2020.2.tar.gz

cd Xilinx_Unified_2020.2_1118_1232 

# 注意是在宿主机桌面distrobox的ubuntu终端里执行，不能是ssh的终端里
sudo ./xsetup
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;根据引导一步步安装&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://markdownforyuanhao.oss-cn-hangzhou.aliyuncs.com/img1/20260125235105699.png&quot; alt=&quot;截屏2026-01-25 20.52.59&quot; /&gt;&lt;/p&gt;
&lt;p&gt;选择Vitis&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://markdownforyuanhao.oss-cn-hangzhou.aliyuncs.com/img1/20260125235116364.png&quot; alt=&quot;截屏2026-01-25 20.53.12&quot; /&gt;&lt;/p&gt;
&lt;p&gt;对于zynq7010/7020的话就是选Zynq-7000，我这里顺带安装一下Artix-7，未来可能会用&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://markdownforyuanhao.oss-cn-hangzhou.aliyuncs.com/img1/20260125235127997.png&quot; alt=&quot;截屏2026-01-25 20.54.13&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;三、启动Vivado&lt;/h2&gt;
&lt;p&gt;实测直接启动后会白屏，搜索得知需要添加一个环境变量&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.csdn.net/weixin_52027058/article/details/128279806&quot;&gt;Linux下安装Vivado打开后空白屏的解决方案&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 可以添加到宿主机的~/.bashrc或者~/.zshrc里
export _JAVA_AWT_WM_NONREPARENTING=1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://markdownforyuanhao.oss-cn-hangzhou.aliyuncs.com/img1/20260125235246943.png&quot; alt=&quot;截屏2026-01-25 23.46.05&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 根据安装时的具体位置
source /tools/Xilinx/Vivado/2020.2/settings64.sh

# 启动Vivado的图形化界面
vivado
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果如图：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://markdownforyuanhao.oss-cn-hangzhou.aliyuncs.com/img1/20260125235750134.png&quot; alt=&quot;48688AD08028BE650FBA02D648B15086&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>在 Wayland (KDE Plasma) 环境下配置 Sunshine</title><link>https://haoyn231.github.io/posts/tools/wayland%E9%85%8D%E7%BD%AEsunshine/</link><guid isPermaLink="true">https://haoyn231.github.io/posts/tools/wayland%E9%85%8D%E7%BD%AEsunshine/</guid><description>在KDE Plasma (Wayland) 以及 NVIDIA 独立显卡环境下配置 Sunshine 服务</description><pubDate>Sat, 24 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;在KDE Plasma (Wayland) 以及 NVIDIA 独立显卡环境下配置 Sunshine 服务。&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;LizardByte/Sunshine&quot;}&lt;/p&gt;
&lt;h2&gt;环境&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;操作系统&lt;/strong&gt;: Arch Linux&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;桌面环境&lt;/strong&gt;: KDE Plasma (Wayland)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;显卡&lt;/strong&gt;: NVIDIA 独立显卡（需确保已安装 &lt;code&gt;nvidia&lt;/code&gt; 驱动）&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;一、 安装 Sunshine&lt;/h2&gt;
&lt;p&gt;使用 AUR 助手安装 Sunshine 软件包（其他系统去GitHub仓库release下载对应安装包）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 使用 yay 安装
yay -S sunshine
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;二、 配置权限与输入环境&lt;/h2&gt;
&lt;p&gt;为了让 Sunshine 能够捕获输入设备并正常工作，需要配置 &lt;code&gt;uinput&lt;/code&gt; 权限。&lt;/p&gt;
&lt;h3&gt;1. 创建 udev 规则&lt;/h3&gt;
&lt;p&gt;创建规则文件以允许 &lt;code&gt;input&lt;/code&gt; 组访问 &lt;code&gt;uinput&lt;/code&gt; 设备：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 创建规则文件
echo &apos;KERNEL==&quot;uinput&quot;, GROUP=&quot;input&quot;, MODE=&quot;0660&quot;, OPTIONS+=&quot;static_node=uinput&quot;&apos; | sudo tee /etc/udev/rules.d/60-sunshine.rules

# 确保当前用户在 input 用户组中
sudo usermod -aG input $USER
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 设置捕获权限&lt;/h3&gt;
&lt;p&gt;Sunshine 需要 &lt;code&gt;cap_sys_admin&lt;/code&gt; 权限来执行特定的屏幕抓取操作：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo setcap cap_sys_admin+ep $(readlink -f $(which sunshine))
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;三、 检查环境依赖&lt;/h2&gt;
&lt;p&gt;在 Wayland 环境下，Sunshine 依赖 &lt;code&gt;xdg-desktop-portal&lt;/code&gt; 来实现屏幕捕获。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;检查项&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;命令&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;说明&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Portal 插件&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pacman -Qs xdg-desktop-portal-kde&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;确保安装了针对 KDE 的 Portal 接口实现&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;KMS 状态&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nvidia-smi&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;确保加载了Nvidia显卡驱动&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;四、 初始启动与配置&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;首次手动启动&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;在 KDE 桌面会话内的终端（如 Konsole）中输入 &lt;code&gt;sunshine&lt;/code&gt; 启动。&lt;strong&gt;注意&lt;/strong&gt;：请勿通过远程 SSH 终端执行此操作，否则可能无法正确关联到当前的图形会话。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;访问 Web UI&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;打开浏览器访问 https://localhost:47990。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;完成向导&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;根据页面提示设置用户名、密码并完成基础编码器配置（建议选择 NVENC）。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;五、 配置 Sunshine 为守护进程&lt;/h2&gt;
&lt;p&gt;使用 &lt;strong&gt;User 模式&lt;/strong&gt; 的 Systemd 服务可以确保 Sunshine 在用户登录后自动访问图形和音频服务。&lt;/p&gt;
&lt;h3&gt;1. 服务管理命令&lt;/h3&gt;
&lt;p&gt;AUR 提供的软件包通常已内置服务文件。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;操作类型&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;命令&lt;/strong&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;&lt;code&gt;systemctl --user start sunshine&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;设置开机自启&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;systemctl --user enable sunshine&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;查看实时日志&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;journalctl --user -u sunshine -f&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;停止服务&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;systemctl --user stop sunshine&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;2. 注意事项&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;code&gt;journalctl&lt;/code&gt; 命令检查是否有关于 &lt;code&gt;vaapi&lt;/code&gt; 或 &lt;code&gt;cuda&lt;/code&gt; 的报错。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>C++学习笔记：std::unique_ptr自定义删除器</title><link>https://haoyn231.github.io/posts/c/c%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E8%87%AA%E5%AE%9A%E4%B9%89%E5%88%A0%E9%99%A4%E5%99%A8/</link><guid isPermaLink="true">https://haoyn231.github.io/posts/c/c%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E8%87%AA%E5%AE%9A%E4%B9%89%E5%88%A0%E9%99%A4%E5%99%A8/</guid><description>使用std::unique_ptr，对c-style的资源进行RAII式的安全封装</description><pubDate>Mon, 15 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、直接在&lt;code&gt;std::unique_ptr&lt;/code&gt;中调用自定义删除器&lt;/h2&gt;
&lt;h3&gt;代码：&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;memory&amp;gt;

// 这里有一个c-style的资源，清理它需要手动
// 这个XPacket是外部提供的，不好进行重构（添加构造和析构函数）
struct XPacket {
    unsigned char* data = nullptr; //一个需要手动释放的堆内存指针
    int size = 0;
};

// 专门创建一个PacketDelete类来进行XPacket的清理
class PacketDelete {
public:
    // 重载了()
    void operator() (XPacket* p) const {
        std::cout &amp;lt;&amp;lt; &quot;call PacketDelete()&quot; &amp;lt;&amp;lt; std::endl;
        // 在这里释放XPacket的资源，或者调用资源清理的接口
        std::free(p-&amp;gt;data);
        delete p;
    }
};

int main() {
    // 自定义空间删除方法，希望智能指针离开作用域后调用指定的资源清理方法
  	// 而不是单纯的结束XPacket的生命周期（它是一个c-style结构体，没有专门的析构函数进行资源清理）
    std::unique_ptr&amp;lt;XPacket, PacketDelete&amp;gt; p1 (new XPacket);

   
    // std::unique_ptr&amp;lt;XPacket, PacketDelete&amp;gt; p3 (new XPacket);
    // p3.get_deleter()(p2.get());
  
  	// std::make_unique()不支持自定义删除器，只能用std::unique_ptr&amp;lt;&amp;gt;来创建
    // auto p2 = std::make_unique&amp;lt;XPacket, PacketDelete&amp;gt;();
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;解析&lt;/h3&gt;
&lt;h3&gt;1、&lt;code&gt;PacketDelete&lt;/code&gt;类的函数调用运算符&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;class PacketDelete {
public:
    // 重载了()
    void operator() (XPacket* p) const {
        std::cout &amp;lt;&amp;lt; &quot;call PacketDelete()&quot; &amp;lt;&amp;lt; std::endl;
        delete p-&amp;gt;data;
        delete p;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是一个&lt;strong&gt;函数调用运算符 (Function Call Operator)&lt;/strong&gt;，俗称 &lt;strong&gt;仿函数 (Functor)&lt;/strong&gt;。它使得 &lt;code&gt;PacketDelete&lt;/code&gt; 类的对象可以像函数一样被调用。&lt;/p&gt;
&lt;h3&gt;2、&lt;code&gt;std::unique_ptr&lt;/code&gt;调用自定义删除器&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;std::unique_ptr&amp;lt;XPacket, PacketDelete&amp;gt; p1 (new XPacket);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当 &lt;code&gt;p1&lt;/code&gt; 智能指针离开作用域时，它会执行以下步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;获取它持有的 &lt;code&gt;XPacket*&lt;/code&gt; 裸指针。&lt;/li&gt;
&lt;li&gt;获取它的删除器对象（类型为 &lt;code&gt;PacketDelete&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;调用删除器对象的 &lt;code&gt;operator()&lt;/code&gt;，并将裸指针作为参数传入：&lt;code&gt;p2.get_deleter()(p2.get())&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;二、引入 Lambda 表达式作为删除器&lt;/h2&gt;
&lt;h3&gt;代码：&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;memory&amp;gt;

struct XPacket {
    unsigned char* data = nullptr;
    int size = 0;
};

int main() {
		auto lambda_deleter = [](XPacket* p) {
        std::cout &amp;lt;&amp;lt; &quot;call Lambda Deleter()&quot; &amp;lt;&amp;lt; std::endl;
        std::free(p-&amp;gt;data);
        delete p;
    };

    // 使用decltype推断lambda的类型，作为模板参数
    std::unique_ptr&amp;lt;XPacket, decltype(lambda_deleter)&amp;gt; p2 (new XPacket, lambda_deleter);
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;解析&lt;/h3&gt;
&lt;h4&gt;一、与类类型删除器 (&lt;code&gt;PacketDelete&lt;/code&gt;)的区别&lt;/h4&gt;
&lt;p&gt;类类型删除器 (&lt;code&gt;PacketDelete&lt;/code&gt;)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;std::unique_ptr&amp;lt;XPacket, PacketDelete&amp;gt; p1 (new XPacket); // 只需要传入指针
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>X11转发</title><link>https://haoyn231.github.io/posts/tools/x11%E8%BD%AC%E5%8F%91/</link><guid isPermaLink="true">https://haoyn231.github.io/posts/tools/x11%E8%BD%AC%E5%8F%91/</guid><description>配置X11转发的server和client</description><pubDate>Sun, 30 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;X11 Forwarding 配置 (Ubuntu -&amp;gt; macOS)&lt;/h1&gt;
&lt;p&gt;本文档记录了如何通过 SSH 将 Ubuntu（X Client）上的图形界面应用转发至 macOS（X Server）上显示。主要适用于容器环境或远程 SSH 连接场景。&lt;/p&gt;
&lt;h2&gt;核心概念&lt;/h2&gt;
&lt;p&gt;在 X Window System 架构中，角色定义如下：&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;strong&gt;X Client&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Ubuntu&lt;/td&gt;
&lt;td&gt;运行应用程序（计算端）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;X Server&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;macOS&lt;/td&gt;
&lt;td&gt;提供显示服务和输入设备（显示端）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;1. Server 端配置 (macOS)&lt;/h2&gt;
&lt;p&gt;macOS 没有原生 X Server，需安装 &lt;strong&gt;XQuartz&lt;/strong&gt; 作为替代。&lt;/p&gt;
&lt;h3&gt;1.1 安装 XQuartz&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;brew install --cask xquartz
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：安装完成后，必须 &lt;strong&gt;注销并重新登录&lt;/strong&gt; 或 &lt;strong&gt;重启电脑&lt;/strong&gt;，以使 &lt;code&gt;$DISPLAY&lt;/code&gt; 环境变量生效。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;1.2 配置权限&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;启动 &lt;strong&gt;XQuartz&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;进入 &lt;strong&gt;偏好设置 (Preferences)&lt;/strong&gt; -&amp;gt; &lt;strong&gt;安全性 (Security)&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;勾选 &lt;strong&gt;&quot;Allow connections from network clients&quot;&lt;/strong&gt; (允许来自网络客户端的连接)。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;2. Client 端配置 (Ubuntu)&lt;/h2&gt;
&lt;h3&gt;2.1 安装必要组件&lt;/h3&gt;
&lt;p&gt;安装 X11 认证工具、SSH 服务及必要的渲染库：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo apt update
sudo apt install xauth openssh-server
sudo apt install libxrender1 libxtst6 libxi6
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.2 修改 SSH 配置&lt;/h3&gt;
&lt;p&gt;编辑 &lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt; 文件，确保以下配置项未被注释且设置为正确的值：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;X11Forwarding yes
X11DisplayOffset 10
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.3 重启 SSH 服务&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl restart ssh
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;3. 建立连接与验证&lt;/h2&gt;
&lt;h3&gt;3.1 发起 SSH 连接&lt;/h3&gt;
&lt;p&gt;使用带有 X11 转发参数的命令连接到 Ubuntu：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 格式: ssh -Y -C user@ip
ssh -Y -C user@192.168.137.121
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;参数说明：&lt;/strong&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;code&gt;-Y&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Trusted X11 Forwarding&lt;/td&gt;
&lt;td&gt;启用受信任的 X11 转发（解决部分权限问题）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-C&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Compression&lt;/td&gt;
&lt;td&gt;开启数据压缩，优化传输性能&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;3.2 验证测试&lt;/h3&gt;
&lt;p&gt;连接成功后，在 SSH 终端中启动图形界面应用（如 &lt;code&gt;xeyes&lt;/code&gt;）进行测试：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;xeyes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;预期结果&lt;/strong&gt;：
macOS 端的 XQuartz 会自动被唤起，并在桌面上显示一双跟随鼠标移动的眼睛，即表示 X11 转发配置成功。&lt;/p&gt;
</content:encoded></item><item><title>Frp内网穿透到云服务器</title><link>https://haoyn231.github.io/posts/tools/frp%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8F%E5%88%B0%E4%BA%91%E6%9C%8D%E5%8A%A1%E5%99%A8/</link><guid isPermaLink="true">https://haoyn231.github.io/posts/tools/frp%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8F%E5%88%B0%E4%BA%91%E6%9C%8D%E5%8A%A1%E5%99%A8/</guid><description>把本地服务器的端口映射到公网服务器，通过公网服务器ip地址+端口来访问内网</description><pubDate>Fri, 28 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;把本地服务器的端口映射到公网服务器，通过公网服务器ip地址+端口来访问内网&lt;/p&gt;
&lt;p&gt;具体为：&lt;img src=&quot;https://markdownforyuanhao.oss-cn-hangzhou.aliyuncs.com/img1/20251128141147350.png&quot; alt=&quot;截屏2025-11-28 14.11.44&quot; /&gt;&lt;/p&gt;
&lt;p&gt;其中&lt;code&gt;192.168.31.29&lt;/code&gt;为本地服务器内网ip&lt;/p&gt;
&lt;h2&gt;公网服务器（Frp服务端）&lt;/h2&gt;
&lt;p&gt;下载amd64/Linux的frp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wget https://github.com/fatedier/frp/releases/download/v0.65.0/frp_0.65.0_linux_amd64.tar.gz
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;解压&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tar -zxvf frp_0.65.0_linux_amd64.tar.gz
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;把frpc复制到&lt;code&gt;/usr/local/bin&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd frp_0.65.0_linux_amd64/

sudo cp frps /usr/local/bin/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;设置配置文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo mkdir -p /etc/frp

sudo vim /etc/frp/frps.toml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;填入以下内容&lt;/p&gt;
&lt;p&gt;&lt;code&gt;auth.token&lt;/code&gt;和&lt;code&gt;webServer.password&lt;/code&gt;要配置的足够安全，不然有安全隐患。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# /etc/frp/frps.toml

# frp 服务端监听的控制端口，用于和客户端建立连接
bindPort = 7000

# 鉴权机制 
auth.method = &quot;token&quot;
auth.token = &quot;password&quot; 

# FRP Dashboard 面板，方便监控连接数和流量
webServer.addr = &quot;0.0.0.0&quot;
webServer.port = 7500
webServer.user = &quot;admin&quot;
webServer.password = &quot;password&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建和配置&lt;code&gt;/etc/systemd/system/frps.service&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# /etc/systemd/system/frps.service

[Unit]
Description=Frp Server Service
After=network.target

[Service]
Type=simple
User=root
Restart=on-failure
RestartSec=5s
# 注意配置文件的路径
ExecStart=/usr/local/bin/frps -c /etc/frp/frps.toml

[Install]
WantedBy=multi-user.target
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;启动和检查状态&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl enable --now frps

systemctl status frps
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;云服务器放行端口&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;公网防火墙 (安全组)&lt;/strong&gt;： 在服务器的控制台的&lt;strong&gt;安全组&lt;/strong&gt; 中放行以下端口：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;7000&lt;/code&gt; (FRP 通信)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;8000&lt;/code&gt; (映射后的 WS)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;8002&lt;/code&gt; (映射后的 Web)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;8003&lt;/code&gt; (映射后的 Vision)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以及确保云服务器中的对应端口没有被占用&lt;/p&gt;
&lt;p&gt;可以通过服务器ip和7500端口进入控制面板&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://markdownforyuanhao.oss-cn-hangzhou.aliyuncs.com/img1/20251128135233845.png&quot; alt=&quot;截屏2025-11-28 13.52.28&quot; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;内网服务器（Frp客户端）&lt;/h2&gt;
&lt;p&gt;同样的下载和解压Frp，把frpc复制到&lt;code&gt;/usr/local/bin&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo cp frpc /usr/local/bin/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建配置文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo mkdir -p /etc/frp

sudo vim /etc/frp/frpc.toml
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# /etc/frp/frpc.toml

# 公网服务器 IP
serverAddr = &quot;x.x.x.x&quot;
serverPort = 7000

# 鉴权 Token (必须与服务端 frps.toml 一致)
auth.method = &quot;token&quot;
auth.token = &quot;password&quot;

# --- 隧道 1: WebSocket API ---
[[proxies]]
name = &quot;xiaozhi-ws-8000&quot;
type = &quot;tcp&quot;
localIP = &quot;127.0.0.1&quot;
localPort = 8000
remotePort = 8000
transport.useEncryption = true
transport.useCompression = true

# --- 隧道 2: Web 管理 &amp;amp; OTA ---
[[proxies]]
name = &quot;xiaozhi-web-8002&quot;
type = &quot;tcp&quot;
localIP = &quot;127.0.0.1&quot;
localPort = 8002
remotePort = 8002
transport.useEncryption = true
transport.useCompression = true

# --- 隧道 3: 视觉分析接口 ---
[[proxies]]
name = &quot;xiaozhi-vision-8003&quot;
type = &quot;tcp&quot;
localIP = &quot;127.0.0.1&quot;
localPort = 8003
remotePort = 8003
transport.useEncryption = true
transport.useCompression = true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对应内网服务器的8000、8002、8003端口&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://markdownforyuanhao.oss-cn-hangzhou.aliyuncs.com/img1/20251128141147350.png&quot; alt=&quot;截屏2025-11-28 14.11.44&quot; /&gt;&lt;/p&gt;
&lt;p&gt;临时测试&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;frpc -c frpc.toml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;参考云服务器配置，设置为systemd守护进程并启动运行&lt;/p&gt;
&lt;p&gt;创建和配置&lt;code&gt;/etc/systemd/system/frpc.service&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# /etc/systemd/system/frpc.service

[Unit]
Description=Frp Client Service
After=network.target

[Service]
Type=simple
User=root
Restart=on-failure
RestartSec=5s
# 注意配置文件的路径
ExecStart=/usr/local/bin/frpc -c /etc/frp/frpc.toml

[Install]
WantedBy=multi-user.target
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;启动和检查状态&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl enable --now frpc

systemctl status frpc
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;理论上实现的效果&lt;/h2&gt;
&lt;p&gt;假设公网服务器ip地址为&lt;code&gt;xxx.xxx.xxx.xxx&lt;/code&gt;，浏览器输入&lt;code&gt;http://xxx.xxx.xxx.xxx:8002&lt;/code&gt;即可进入AI对话服务端的web界面，其他以此类推。&lt;/p&gt;
&lt;p&gt;后面再在公网服务器上部署 Nginx ，把形如&lt;code&gt;http://xxx.com/xiaozhi/ota/&lt;/code&gt;到地址指向指定端口。&lt;/p&gt;
</content:encoded></item><item><title>rkdeveloptool</title><link>https://haoyn231.github.io/posts/tools/rkdeveloptool%E7%9A%84%E5%9F%BA%E7%A1%80%E4%BD%BF%E7%94%A8/</link><guid isPermaLink="true">https://haoyn231.github.io/posts/tools/rkdeveloptool%E7%9A%84%E5%9F%BA%E7%A1%80%E4%BD%BF%E7%94%A8/</guid><description>rkdeveloptool的基础使用</description><pubDate>Fri, 21 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;rk开源的烧录工具，可以用于烧录整个img镜像，例如armbian的镜像&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;rockchip-linux/rkdeveloptool&quot;}&lt;/p&gt;
&lt;p&gt;建议自行编译最新版本的，注意配置udev规则&lt;code&gt;99-rockchip.rules&lt;/code&gt;（GitHub仓库里下载）&lt;/p&gt;
&lt;p&gt;前提：设备进入maskrom模式&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo rkdeveloptool ld
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;列出进入maskrom模式的设备&lt;/p&gt;
&lt;h3&gt;一、初始化Loader&lt;/h3&gt;
&lt;p&gt;初始化DRAM，刷入类似miniloader.bin的文件&lt;/p&gt;
&lt;p&gt;包含了一段 SPL (Secondary Program Loader) 代码，它的主要职责就是&lt;strong&gt;初始化内存时序&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo ./rkdeveloptool db rk35xx_spl_loader.bin
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;二、刷入镜像&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;sudo ./rkdeveloptool wl 0 system_image.img
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;三、重启设备&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;sudo ./rkdeveloptool rd
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Fedora43使用KVM虚拟机安装Ubuntu</title><link>https://haoyn231.github.io/posts/linux/fedora43%E4%BD%BF%E7%94%A8kvm%E8%99%9A%E6%8B%9F%E6%9C%BA%E5%AE%89%E8%A3%85ubuntu/</link><guid isPermaLink="true">https://haoyn231.github.io/posts/linux/fedora43%E4%BD%BF%E7%94%A8kvm%E8%99%9A%E6%8B%9F%E6%9C%BA%E5%AE%89%E8%A3%85ubuntu/</guid><description>基于Fedora的KVM虚拟机安装Ubuntu作为开发环境</description><pubDate>Thu, 30 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Fedora 43 KVM 虚拟机安装 Ubuntu 记录&lt;/h1&gt;
&lt;p&gt;本文档记录在 Fedora 43 宿主机上使用 KVM 部署 Ubuntu (22.04/18.04) 虚拟机的完整流程，涵盖环境搭建、虚拟机创建、资源共享、网络桥接及快照管理。&lt;/p&gt;
&lt;h2&gt;1. 环境准备与虚拟化支持&lt;/h2&gt;
&lt;p&gt;首先需要在宿主机开启虚拟化支持并安装必要的管理工具。&lt;/p&gt;
&lt;h3&gt;1.1 安装 KVM 相关组件&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 安装虚拟化组包
sudo dnf groupinstall &quot;Virtualization&quot;

# 安装安装工具与查看器
sudo dnf install virt-install virt-viewer
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.2 启动服务&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl enable --now libvirtd
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;2. 构建虚拟机&lt;/h2&gt;
&lt;h3&gt;2.1 准备镜像与目录&lt;/h3&gt;
&lt;p&gt;建议将 ISO 镜像移动到 &lt;code&gt;libvirt&lt;/code&gt; 默认目录以避免权限问题。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 创建虚拟机镜像存放目录 (可选，默认在 /var/lib/libvirt/images/)
sudo mkdir -p /var/lib/libvirt/images

# 移动 ISO 镜像
sudo mv /home/hao/ubuntu-22.04.5-desktop-amd64.iso /var/lib/libvirt/images/
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.2 执行安装命令&lt;/h3&gt;
&lt;p&gt;使用 &lt;code&gt;virt-install&lt;/code&gt; 创建名为 &lt;code&gt;vivado-lab&lt;/code&gt; 的虚拟机，8GB内存，8个处理器逻辑核心，500GB存储空间。按需修改，核心数和内存都可以后期调整&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 预先分配足够的磁盘空间
sudo qemu-img create -f qcow2 /var/lib/libvirt/images/vivado-lab.qcow2 500G

# 确保所有者正确 (libvirt/qemu 进程通常运行在 qemu 用户下)
sudo chown qemu:qemu /var/lib/libvirt/images/vivado-lab.qcow2

# 恢复默认上下文
sudo restorecon -Rv /var/lib/libvirt/images/

# 再次确认关闭 SELinux (临时测试用，排除干扰)
sudo setenforce 0

sudo virt-install \
  --name vivado-lab \
  --memory 8192 \
  --vcpus 8 \
  --cpu host-model \
  --disk path=/var/lib/libvirt/images/vivado-lab.qcow2,bus=virtio,format=qcow2 \
  --cdrom /var/lib/libvirt/images/ubuntu-22.04.5-desktop-amd64.iso \
  --os-variant ubuntu22.04 \
  --network network=default,model=virtio \
  --graphics spice,listen=none,gl.enable=no \
  --video virtio \
  --channel spicevmc \
  --check disk_size=off \
  --noautoconsole
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;参数说明：&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;参数&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;说明&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--cpu host-passthrough&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;透传宿主机 CPU 特性，性能最佳&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--graphics spice&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;使用 SPICE 图形协议&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--disk ...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;指定磁盘路径、大小 、总线类型 (virtio)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--noautoconsole&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;自动后台安装，不直接弹出控制台&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;2.3 访问与管理&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 通过 virt-viewer 进入虚拟机界面
sudo virt-viewer --attach vivado-lab

# 打开图形化管理器
sudo virt-manager
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;常用管理命令：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 关闭虚拟机
sudo virsh shutdown vivado-lab

# 开启虚拟机
sudo virsh start vivado-lab

# 查看所有虚拟机状态
sudo virsh list --all

# 删除虚拟机
sudo virsh undefine vivado-lab --remove-all-storage --delete-snapshots --nvram
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;3. 配置宿主机与虚拟机共享目录 (VirtioFS)&lt;/h2&gt;
&lt;p&gt;将宿主机的 &lt;code&gt;/var/lib/libvirt/share&lt;/code&gt; 目录挂载到虚拟机的 &lt;code&gt;/mnt/share&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;3.1 宿主机目录权限配置&lt;/h3&gt;
&lt;p&gt;必须正确设置目录的所有者和 SELinux 上下文，否则虚拟机无法访问。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 1. 创建目录
sudo mkdir -p /var/lib/libvirt/share

# 2. 设置归属 (移交给 qemu 用户)
sudo chown qemu:qemu /var/lib/libvirt/share

# 3. 设置 SELinux 上下文 (放行 virt 访问)
sudo chcon -t virt_image_t /var/lib/libvirt/share
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.2 编辑虚拟机 XML 配置&lt;/h3&gt;
&lt;p&gt;使用 &lt;code&gt;sudo virsh edit vivado-lab&lt;/code&gt; 修改配置文件。&lt;/p&gt;
&lt;p&gt;步骤 1：添加 Filesystem 设备&lt;/p&gt;
&lt;p&gt;在 &amp;lt;devices&amp;gt; 段落下添加：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;filesystem type=&apos;mount&apos; accessmode=&apos;passthrough&apos;&amp;gt;
  &amp;lt;driver type=&apos;virtiofs&apos;/&amp;gt;
  &amp;lt;source dir=&apos;/var/lib/libvirt/share&apos;/&amp;gt;
  &amp;lt;target dir=&apos;host_share&apos;/&amp;gt;
&amp;lt;/filesystem&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;步骤 2：开启共享内存&lt;/p&gt;
&lt;p&gt;在 &amp;lt;domain&amp;gt; 段落下（通常在 CPU 配置附近）添加或修改：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;domain type=&apos;kvm&apos;&amp;gt;
  ...
  &amp;lt;memoryBacking&amp;gt;
    &amp;lt;source type=&apos;memfd&apos;/&amp;gt;
    &amp;lt;access mode=&apos;shared&apos;/&amp;gt;
  &amp;lt;/memoryBacking&amp;gt;
  ...
&amp;lt;/domain&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.3 虚拟机内部挂载&lt;/h3&gt;
&lt;p&gt;启动虚拟机后，在内部执行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo mkdir /mnt/host

# 挂载共享目录 (target tag 为 host_share)
sudo mount -t virtiofs host_share /mnt/host
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;4. 网络配置 (桥接模式)&lt;/h2&gt;
&lt;p&gt;将宿主机物理网卡桥接到 &lt;code&gt;br0&lt;/code&gt;，并让虚拟机连接该网桥，使其获得独立局域网 IP。&lt;/p&gt;
&lt;h3&gt;4.1 宿主机创建网桥&lt;/h3&gt;
&lt;p&gt;物理有线网卡名称为 &lt;code&gt;enp6s0f4u1u4&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 1. 创建网桥从属连接
sudo nmcli con add type bridge-slave ifname enp6s0f4u1u4 master br0

# 2. 删除旧的有线连接 (防止冲突)
sudo nmcli con delete &quot;有线连接 1&quot;

# 3. 激活网桥
sudo nmcli con up br0
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.2 虚拟机网络设置&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;virt-manager&lt;/code&gt; 中将虚拟机的网络接口修改为 &lt;strong&gt;桥接设备 -&amp;gt; br0&lt;/strong&gt;，或编辑 XML 将 interface type 改为 &lt;code&gt;bridge&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://markdownforyuanhao.oss-cn-hangzhou.aliyuncs.com/img1/20251121210214247.png&quot; alt=&quot;截屏2025-11-21 21.02.09&quot; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;5. 虚拟机基础服务配置&lt;/h2&gt;
&lt;h3&gt;5.1 配置 APT 镜像源 (清华源)&lt;/h3&gt;
&lt;p&gt;加速软件安装与更新。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo sed -i.bak &apos;s/cn.archive.ubuntu.com/mirrors.tuna.tsinghua.edu.cn/g&apos; /etc/apt/sources.list &amp;amp;&amp;amp; \
sudo sed -i &apos;s/security.ubuntu.com/mirrors.tuna.tsinghua.edu.cn/g&apos; /etc/apt/sources.list &amp;amp;&amp;amp; \
sudo apt update
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.2 安装 SSH 服务&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;sudo apt install openssh-server

# 启动并设置开机自启
sudo systemctl enable --now ssh

# 检查状态
sudo systemctl status ssh
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;6. Tailscale 子网路由配置 (宿主机)&lt;/h2&gt;
&lt;p&gt;配置宿主机作为 Tailscale 子网路由器，转发 &lt;code&gt;192.168.137.0/24&lt;/code&gt; 网段流量。&lt;/p&gt;
&lt;h3&gt;6.1 开启内核转发&lt;/h3&gt;
&lt;p&gt;编辑 &lt;code&gt;/etc/sysctl.d/99-tailscale.conf&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;应用配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo sysctl -p /etc/sysctl.d/99-tailscale.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6.2 广播路由&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 启动 Tailscale 并广播子网
sudo tailscale up --advertise-routes=192.168.137.0/24
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;验证：外部节点 Ping 虚拟机 IP (e.g., &lt;code&gt;192.168.137.226&lt;/code&gt;) 应正常连通。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://markdownforyuanhao.oss-cn-hangzhou.aliyuncs.com/img1/20251121212315323.png&quot; alt=&quot;截屏2025-11-21 21.23.12&quot; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;7. 虚拟机快照管理&lt;/h2&gt;
&lt;p&gt;使用 &lt;code&gt;virsh snapshot&lt;/code&gt; 系列命令管理系统状态。&lt;/p&gt;
&lt;h3&gt;7.1 创建快照&lt;/h3&gt;
&lt;p&gt;创建完整系统快照 (Live Snapshot)&lt;/p&gt;
&lt;p&gt;保留当前运行状态 (内存+磁盘)。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo virsh snapshot-create-as \
  --domain vivado-lab \
  --name &quot;snap-20251122-base&quot; \
  --description &quot;Base install with network and ssh configured&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建关机状态快照 (Disk-only)&lt;/p&gt;
&lt;p&gt;占用空间小，适合纯磁盘备份。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 先关闭虚拟机
sudo virsh shutdown vivado-lab

# 创建快照
sudo virsh snapshot-create-as \
  --domain vivado-lab \
  --name &quot;env-ready-2025&quot; \
  --description &quot;Setup with SSH, and Tsinghua Mirror&quot; \
  --disk-only \
  --atomic
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;7.2 查看与管理快照&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 列出所有快照
sudo virsh snapshot-list --domain vivado-lab

# 查看快照详细信息
sudo virsh snapshot-info --domain vivado-lab --snapshotname &quot;env-ready-2025&quot;

# 恢复到指定快照
sudo virsh snapshot-revert --domain vivado-lab --snapshotname &quot;env-ready-2025&quot;
# 恢复并暂停 (便于检查)
# sudo virsh snapshot-revert --domain vivado-lab --snapshotname &quot;env-ready-2025&quot; --paused

# 删除快照
sudo virsh snapshot-delete --domain vivado-lab --snapshotname &quot;snap-20251122-base&quot;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Conan+CMake基本编译流程</title><link>https://haoyn231.github.io/posts/c/conancmake%E5%9F%BA%E6%9C%AC%E7%BC%96%E8%AF%91%E6%B5%81%E7%A8%8B/</link><guid isPermaLink="true">https://haoyn231.github.io/posts/c/conancmake%E5%9F%BA%E6%9C%AC%E7%BC%96%E8%AF%91%E6%B5%81%E7%A8%8B/</guid><description>在CMake构建的项目中使用Conan2作为包管理器</description><pubDate>Tue, 21 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Conan+CMake基本编译流程&lt;/h1&gt;
&lt;h2&gt;本地编译&lt;/h2&gt;
&lt;h3&gt;1. Conan 安装依赖&lt;/h3&gt;
&lt;p&gt;首先，使用 Conan 安装项目所需的第三方包。Conan 会根据构建类型（Debug/Release）将生成的文件放置在不同的目录中。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;默认情况下，Conan 会创建 &lt;code&gt;build/Release/generators&lt;/code&gt; 目录来存放生成文件。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;conan install . --build=missing
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;可以明确指定构建类型，例如 &lt;code&gt;Debug&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;conan install . --build=missing -s build_type=Debug
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;--output-folder&lt;/code&gt; 参数可以指定输出目录，但通常不是必需的，因为 Conan 会根据配置自动管理路径。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. CMake 配置与编译&lt;/h3&gt;
&lt;p&gt;Conan 在安装依赖后，会自动生成一个 CMake 预设文件 (&lt;code&gt;CMakeUserPresets.json&lt;/code&gt;)，其中包含了配置和编译所需的全部信息。因此，推荐直接使用预设来简化操作。&lt;/p&gt;
&lt;h4&gt;使用 CMake 预设（推荐）&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;配置 CMake&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;使用 &lt;code&gt;conan-debug&lt;/code&gt; 或 &lt;code&gt;conan-release&lt;/code&gt; 预设来配置项目。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 配置 Debug 版本
cmake --preset conan-debug

# 配置 Release 版本
cmake --preset conan-release
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;编译&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;同样使用预设来执行编译。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 编译 Debug 版本
cmake --build --preset conan-debug

# 编译 Release 版本
cmake --build --preset conan-release
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;手动配置&lt;/h4&gt;
&lt;p&gt;如果不使用预设，则需要手动指定工具链文件和构建类型。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;配置 CMake&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 配置 Release 版本
cmake -S . -B build/Release -G Ninja \
      -DCMAKE_TOOLCHAIN_FILE=build/Release/generators/conan_toolchain.cmake \
      -DCMAKE_BUILD_TYPE=Release
      
# 配置 Debug 版本
cmake -S . -B build/Debug -G Ninja \
      -DCMAKE_TOOLCHAIN_FILE=build/Debug/generators/conan_toolchain.cmake \
      -DCMAKE_BUILD_TYPE=Debug
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;编译&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 编译 Release 版本
cmake --build build/Release

# 编译 Debug 版本
cmake --build build/Debug
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;交叉编译&lt;/h2&gt;
&lt;h3&gt;1. 创建 Profile 文件&lt;/h3&gt;
&lt;p&gt;首先，需要为目标平台创建一个 Conan Profile 文件。例如，为 aarch64 架构创建一个 &lt;code&gt;profiles/aarch64_profile&lt;/code&gt; 文件。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[settings]
os=Linux
arch=armv8
compiler=gcc
compiler.version=15.2
compiler.libcxx=libstdc++11
compiler.cppstd=20

[conf]
# 告诉 Conan 加载我们自定义的 CMake 工具链文件
# 使用 tools.cmake.cmaketoolchain:user_toolchain 来指定
# 注意: ${profile_dir} 是 Conan 变量，但实践中建议使用绝对路径以确保正确识别
tools.cmake.cmaketoolchain:user_toolchain=[&quot;/path/to/your/project/cmake/aarch64-toolchain.cmake&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在对应的 &lt;code&gt;.cmake&lt;/code&gt; 工具链文件中，你需要设置交叉编译所需的编译器、系统根目录等环境变量。&lt;/p&gt;
&lt;h3&gt;2. 执行编译流程&lt;/h3&gt;
&lt;p&gt;交叉编译的流程与本地编译非常相似，区别在于需要在 &lt;code&gt;conan install&lt;/code&gt; 命令中通过 &lt;code&gt;-pr:h&lt;/code&gt; 参数指定 Host 平台的 Profile 文件。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Conan 安装依赖&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;conan install . --build=missing -s build_type=Debug -pr:h profiles/aarch64_profile
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;配置 CMake&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Conan 同样会生成适用于交叉编译的 CMake 预设。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cmake --preset conan-debug
cmake --preset conan-release
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;编译&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cmake --build --preset conan-debug
cmake --build --preset conan-release
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>pyocd调用flm格式的下载算法</title><link>https://haoyn231.github.io/posts/tools/pyocd%E8%B0%83%E7%94%A8flm%E6%A0%BC%E5%BC%8F%E7%9A%84%E4%B8%8B%E8%BD%BD%E7%AE%97%E6%B3%95/</link><guid isPermaLink="true">https://haoyn231.github.io/posts/tools/pyocd%E8%B0%83%E7%94%A8flm%E6%A0%BC%E5%BC%8F%E7%9A%84%E4%B8%8B%E8%BD%BD%E7%AE%97%E6%B3%95/</guid><description>flm格式可由keil mdk生成，相比openocd的下载算法更好维护</description><pubDate>Sun, 03 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;pyocd 调用 FLM 格式的外部下载算法&lt;/h1&gt;
&lt;p&gt;记录如何通过 &lt;code&gt;pyocd&lt;/code&gt; 调用 Keil 使用的 &lt;code&gt;.FLM&lt;/code&gt; 格式外部下载算法，将程序烧录到 STM32H7 系列微控制器的 QSPI Flash 中。&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;haoyn231/STM32H750XBH6_CMake_Template&quot;}&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;开发板&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://item.taobao.com/item.htm?id=838568127129&amp;amp;pisk=gIi_MI2qJ8VFHG385oJFVo7BQuZfhp-yDtwxExINHlETliHZN-S4uF2fcWMJI58DSjgIMX4a6Sz4c-GztAS2IAJXcxH86oua_-hEGXbZ_mPqTnMoNPSZHmWi-bkRbc8g0iZgmodya3-rQAq0DXNEywfGvRy60GEYHlqda56wy3-rIv6aBLkp4mlDc0oAkihYWyCLIWeTH-F9dyezHoITkGQL9W2YDoFOWyBLn81ADSF9d9eYBNQADPUd9-yVBoExMyHLt-EYHjEvdvF3HDUHAJJ41A9WHUubK3YovWsADDwpi5M1Cg2tfR98aANBwdoCqPN-BWsv6ibG95e-2B7gFPGbJDkVUiPrB4c0efC9aPDxRXwsTBbQd4liVknJtiGjEDcQpbKf45HnU4ziXESzPlP_lcH61_NKfXigXf8AVjls7XaQ0EjYXl2Ssln9T1Zjpczozyf6TSurLcUIwBSIirgo3-G6fGEEzf08r2AfP73rEkszcgP5F8QX2kSbd7JBdZbcEQOznV0AZ0zTKJ5ydp1_vPe3d7JBdZb0WJ2e3p9C1kC..&amp;amp;spm=tbpc.boughtlist.suborder_itemtitle.1.3c6a2e8dVW0dPQ&amp;amp;sku_properties=-1%3A-1&quot;&gt;反客STM32H750XHB6&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;参考工程&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/haoyn231/STM32H750XBH6_CMake_Template&quot;&gt;Peakors/STM32H750XBH6_Template&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;下载算法&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;FK750M6_XBH6_V0.FLM&lt;/code&gt; (由开发板资料提供)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;测试环境&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fedora 42 (经验可跨平台通用)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;img src=&quot;https://markdownforyuanhao.oss-cn-hangzhou.aliyuncs.com/img1/20250803220535715.png&quot; alt=&quot;Snipaste_2025-08-03_22-05-27&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;前置依赖&lt;/h3&gt;
&lt;p&gt;确保已安装以下工具链：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pyocd&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cmake&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ninja&lt;/code&gt; 或 &lt;code&gt;make&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;arm-none-eabi-gcc&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;编译流程&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 生成 Ninja/Make 构建文件
cmake -S . -B build -G Ninja

# 执行编译
cmake --build build/
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;原理说明&lt;/h3&gt;
&lt;p&gt;通过在项目根目录创建 &lt;code&gt;pyocd_user.py&lt;/code&gt; 脚本，可以自定义 pyOCD 的连接行为。pyOCD 在启动时会自动检测并加载该文件。&lt;/p&gt;
&lt;p&gt;我们在脚本中定义一个 &lt;code&gt;will_connect&lt;/code&gt; 函数，该函数会在 pyOCD 连接目标板之前被调用。通过这个函数，我们可以向 pyOCD 的内存地图中添加外部 Flash 的信息（如 QSPI Flash），并指定其使用的 &lt;code&gt;.FLM&lt;/code&gt; 下载算法。&lt;/p&gt;
&lt;p&gt;这种机制使得 pyOCD 能够在不修改内置目标支持的情况下，扩展对外部存储器的烧录能力。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 目录结构&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;将下载算法文件 &lt;code&gt;FK750M6_XBH6_V0.FLM&lt;/code&gt; 存放到项目下的 &lt;code&gt;.config&lt;/code&gt; 文件夹中。&lt;/li&gt;
&lt;li&gt;在项目根目录创建 &lt;code&gt;pyocd_user.py&lt;/code&gt; 文件。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. &lt;code&gt;pyocd_user.py&lt;/code&gt; 脚本&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# pyocd_user.py

from pyocd.core.memory_map import FlashRegion
import logging

# --- 定义外部 QSPI Flash 的参数 ---
FLM_FILE = &quot;.config/FK750M6_XBH6_V0.FLM&quot; # 存放下载算法的相对路径
QSPI_FLASH_START = 0x90000000
QSPI_FLASH_SIZE = 32 * 1024 * 1024
QSPI_FLASH_BLOCKSIZE = 0x1000

def will_connect(board):
    &quot;&quot;&quot;
    这是一个 pyOCD 代理函数，会在连接前被自动调用。
    
    &quot;&quot;&quot;
    target = board.target
    
    # 定义外部 Flash 区域
    external_flash = FlashRegion(
        name=&quot;qspi_flash&quot;,
        start=QSPI_FLASH_START,
        length=QSPI_FLASH_SIZE,
        blocksize=QSPI_FLASH_BLOCKSIZE,
        is_boot_memory=False,
        flm=FLM_FILE
    )
    
    # 将其添加到内存地图
    target.memory_map.add_region(external_flash)
    
    logging.info(f&quot;用户脚本：成功添加外部 QSPI Flash 区域 &apos;{external_flash.name}&apos;&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;具体定义参考&lt;code&gt;/home/hao/.local/lib/python3.13/site-packages/pyocd/core/memory_map.py&lt;/code&gt;（pyocd安装路径）&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://markdownforyuanhao.oss-cn-hangzhou.aliyuncs.com/img1/20250803215751929.png&quot; alt=&quot;Snipaste_2025-08-03_21-27-37&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;烧录指令&lt;/h3&gt;
&lt;p&gt;使用以下指令将编译生成的 &lt;code&gt;.bin&lt;/code&gt; 文件烧录到 QSPI Flash 的起始地址 &lt;code&gt;0x90000000&lt;/code&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--target stm32h750xx&lt;/code&gt;：指定目标芯片型号。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-O connect_mode=under-reset&lt;/code&gt;：设置连接模式为复位下连接。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;./build/STM32H750XBH6_Template.bin@0x90000000&lt;/code&gt;：指定固件路径和烧录地址。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;pyocd flash --target stm32h750xx -O connect_mode=under-reset ./build/STM32H750XBH6_Template.bin@0x90000000
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;日志参考&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. 简洁版日志&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;❯ pyocd flash --target stm32h750xx -O connect_mode=under-reset ./build/STM32H750XBH6_Template.bin@0x90000000
0000447 I Loading /home/hao/projects/stm32_projects/STM32H750XBH6/CMake/STM32H750XBH6_Template/build/STM32H750XBH6_Template.bin at 0x90000000 [load_cmd]
[==================================================] 100%
0002722 I Erased 65536 bytes (1 sector), programmed 65536 bytes (8 pages), skipped 0 bytes (0 pages) at 28.14 kB/s [loader]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2. 详细版日志 (&lt;code&gt;-v&lt;/code&gt; 参数)&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;❯ pyocd flash --target stm32h750xx -v -O connect_mode=under-reset ./build/STM32H750XBH6_Template.bin@0x90000000
0000346 I Target type is stm32h750xx [board]
0000349 I 用户脚本：成功添加外部 QSPI Flash 区域 &apos;qspi_flash&apos; [pyocd_user]
0000349 I Asserting reset prior to connect [coresight_target]
0000352 I DP IDR = 0x6ba02477 (v2 rev6) [dap]
0000366 I CR: 0x0070003f [target_STM32H750xx]
0000370 I AHB-AP#0 IDR = 0x84770001 (AHB-AP var0 rev8) [discovery]
0000373 I AHB-AP#1 IDR = 0x84770001 (AHB-AP var0 rev8) [discovery]
0000376 I APB-AP#2 IDR = 0x54770002 (APB-AP var0 rev5) [discovery]
0000379 I AHB-AP#0 Class 0x1 ROM table #0 @ 0xe00fe000 (designer=020:ST part=450) [rom_table]
0000381 I [0]&amp;lt;e00ff000:ROM class=1 designer=43b:Arm part=4c7&amp;gt; [rom_table]
0000381 I   AHB-AP#0 Class 0x1 ROM table #1 @ 0xe00ff000 (designer=43b:Arm part=4c7) [rom_table]
0000383 I   [0]&amp;lt;e000e000:SCS v7-M class=14 designer=43b:Arm part=00c&amp;gt; [rom_table]
0000385 I   [1]&amp;lt;e0001000:DWT v7-M class=14 designer=43b:Arm part=002&amp;gt; [rom_table]
0000386 I   [2]&amp;lt;e0002000:FPB v7-M class=14 designer=43b:Arm part=00e&amp;gt; [rom_table]
0000387 I   [3]&amp;lt;e0000000:ITM v7-M class=14 designer=43b:Arm part=001&amp;gt; [rom_table]
0000389 I [1]&amp;lt;e0041000:ETM M7 class=9 designer=43b:Arm part=975 devtype=13 archid=4a13 devid=0:0:0&amp;gt; [rom_table]
0000391 I [2]&amp;lt;e0043000:CTI CS-400 class=9 designer=43b:Arm part=906 devtype=14 archid=0000 devid=40800:0:0&amp;gt; [rom_table]
0000393 I APB-AP#2 Class 0x1 ROM table #0 @ 0xe00e0000 (designer=020:ST part=450) [rom_table]
0000396 I [2]&amp;lt;e00e3000:SWO CS-400 class=9 designer=43b:Arm part=914 devtype=11 archid=0000 devid=ea0:0:0&amp;gt; [rom_table]
0000398 I [3]&amp;lt;e00e4000:Trace Funnel CS-400 class=9 designer=43b:Arm part=908 devtype=12 archid=0000 devid=32:0:0&amp;gt; [rom_table]
0000399 I [4]&amp;lt;e00e5000:TSGEN class=15 designer=43b:Arm part=101&amp;gt; [rom_table]
0000400 I [5]&amp;lt;e00f0000:ROM class=1 designer=020:ST part=001&amp;gt; [rom_table]
0000401 I   APB-AP#2 Class 0x1 ROM table #1 @ 0xe00f0000 (designer=020:ST part=001) [rom_table]
0000404 I   [0]&amp;lt;e00f1000:CTI CS-400 class=9 designer=43b:Arm part=906 devtype=14 archid=0000 devid=40800:0:0&amp;gt; [rom_table]
0000406 I   [2]&amp;lt;e00f3000:Trace Funnel CS-400 class=9 designer=43b:Arm part=908 devtype=12 archid=0000 devid=34:0:0&amp;gt; [rom_table]
0000408 I   [3]&amp;lt;e00f4000:ETF class=9 designer=43b:Arm part=961 devtype=32 archid=0000 devid=380:0:0&amp;gt; [rom_table]
0000410 I   [4]&amp;lt;e00f5000:TPIU CS-400 class=9 designer=43b:Arm part=912 devtype=11 archid=0000 devid=a0:0:0&amp;gt; [rom_table]
0000415 I CPU core #0: Cortex-M7 r1p1, v7.0-M architecture [cortex_m]
0000415 I   Extensions: [DSP, FPU, FPU_DP, FPU_V5, MPU] [cortex_m]
0000415 I   FPU present: FPv5-D16-M [cortex_m]
0000416 I 4 hardware watchpoints [dwt]
0000419 I 8 hardware breakpoints, 1 literal comparators [fpb]
0000431 I Deasserting reset post connect [coresight_target]
0000433 I Creating flash algo for region qspi_flash from: /home/hao/projects/stm32_projects/STM32H750XBH6/CMake/STM32H750XBH6_Template/.config/FK750M6_XBH6_V0.FLM [flm_region_builder]
0000448 I Loading /home/hao/projects/stm32_projects/STM32H750XBH6/CMake/STM32H750XBH6_Template/build/STM32H750XBH6_Template.bin at 0x90000000 [load_cmd]
[==================================================] 100%
0002723 I Erased 65536 bytes (1 sector), programmed 65536 bytes (8 pages), skipped 0 bytes (0 pages) at 28.14 kB/s [loader]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;实现效果&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Bootloader 日志 (运行于片内 Flash)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;程序启动后，首先运行片内 Flash 中的 Bootloader，通过串口打印启动日志。&lt;/li&gt;
&lt;li&gt;Bootloader 日志截图 （乱码系中文编码问题）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://markdownforyuanhao.oss-cn-hangzhou.aliyuncs.com/img1/20250803215422276.png&quot; alt=&quot;Snipaste_2025-08-03_21-16-45&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;主程序日志 (运行于外部 QSPI Flash)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bootloader 跳转到 QSPI Flash 中执行主程序，主程序通过 USB 虚拟串口打印运行日志。&lt;/li&gt;
&lt;li&gt;USB 虚拟串口日志截图&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://markdownforyuanhao.oss-cn-hangzhou.aliyuncs.com/img1/20250803215435181.png&quot; alt=&quot;Snipaste_2025-08-03_21-17-15&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;串口波特率115200&lt;/p&gt;
</content:encoded></item><item><title>使用docker编译openwrt</title><link>https://haoyn231.github.io/posts/linux/%E4%BD%BF%E7%94%A8docker%E7%BC%96%E8%AF%91openwrt/</link><guid isPermaLink="true">https://haoyn231.github.io/posts/linux/%E4%BD%BF%E7%94%A8docker%E7%BC%96%E8%AF%91openwrt/</guid><description>利用 Docker 环境编译 OpenWrt 固件，以实现编译环境的隔离和一致性</description><pubDate>Sat, 02 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;介绍如何利用 Docker 环境编译 OpenWrt 固件，以实现编译环境的隔离和一致性。&lt;/p&gt;
&lt;h2&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;strong&gt;网络环境&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;良好的科学上网环境，确保可以顺畅访问 GitHub、OpenWrt 源码等国外资源。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;软件工具&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;在宿主机上预先安装好 &lt;code&gt;Git&lt;/code&gt; 和 &lt;code&gt;Docker&lt;/code&gt; (包括 &lt;code&gt;docker-compose&lt;/code&gt;)。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;个人能力&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;具备基本的 Linux 命令行操作和解决问题的能力。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;环境搭建&lt;/h2&gt;
&lt;h3&gt;1. Dockerfile&lt;/h3&gt;
&lt;p&gt;创建一个 &lt;code&gt;Dockerfile&lt;/code&gt; 文件，用于定义编译环境镜像。该镜像基于 Ubuntu 22.04，安装了所有 OpenWrt 官方推荐的编译依赖，并创建了一个名为 &lt;code&gt;builder&lt;/code&gt; 的非 root 用户以增强安全性。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 使用官方推荐的 Ubuntu 22.04 LTS 作为基础镜像
FROM ubuntu:22.04

# 设置环境变量，避免在包安装过程中出现交互式提示
ENV DEBIAN_FRONTEND=noninteractive

# 一次性更新软件源并安装 OpenWrt 编译所需的所有依赖包
RUN apt-get update &amp;amp;&amp;amp; \
    apt-get install -y --no-install-recommends \
    # 编译工具链
    build-essential \
    clang \
    flex \
    bison \
    g++ \
    gawk \
    gcc-multilib \
    g++-multilib \
    # 文本和版本控制工具
    gettext \
    git \
    subversion \
    # 核心库和开发头文件
    libncurses5-dev \
    libssl-dev \
    zlib1g-dev \
    # Python 相关工具
    python3-distutils \
    python3-docutils \
    python3-pyelftools \
    python3-setuptools \
    # 常用辅助工具
    rsync \
    swig \
    texinfo \
    unzip \
    wget \
    file \
    time \
    # 解决 SSL 证书验证问题的关键包
    ca-certificates \
    # 为非 root 用户提供提权能力的工具
    sudo \
    &amp;amp;&amp;amp; \
    # 清理 apt 缓存，减小最终镜像大小
    apt-get clean &amp;amp;&amp;amp; \
    rm -rf /var/lib/apt/lists/*

# 创建一个名为 &apos;builder&apos; 的非 root 用户，并赋予其免密 sudo 权限
# 这是为了安全起见，避免在容器中直接使用 root 用户进行编译等操作
RUN useradd -m -s /bin/bash builder &amp;amp;&amp;amp; \
    adduser builder sudo &amp;amp;&amp;amp; \
    echo &apos;builder ALL=(ALL) NOPASSWD:ALL&apos; &amp;gt;&amp;gt; /etc/sudoers

# 切换到新创建的非 root 用户
USER builder

# 为 builder 用户设置工作目录
WORKDIR /home/builder

# 设置容器启动时执行的默认命令，即启动一个 bash shell
CMD [&quot;/bin/bash&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. docker-compose.yml&lt;/h3&gt;
&lt;p&gt;创建 &lt;code&gt;docker-compose.yml&lt;/code&gt; 文件来管理容器的构建和运行。该配置会自动构建镜像，并设置一个关键的卷挂载，将宿主机的 &lt;code&gt;./openwrt&lt;/code&gt; 目录映射到容器内，使得源码和编译产物能够持久化保存在宿主机上。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;version: &apos;3.8&apos;

services:
  openwrt-builder:
    # 使用当前目录下的 Dockerfile 来构建镜像
    build: .
    # 给构建出的镜像命名
    image: openwrt-builder-image
    # 容器的名字
    container_name: openwrt_build_env
    # 覆盖 Dockerfile 中的 CMD，确保我们能进入 bash
    command: /bin/bash
    # 保持容器在前台运行，并分配一个伪终端 (等同于 -it)
    stdin_open: true
    tty: true
    # 网络模式
    network_mode: host
    # 挂载卷
    volumes:
      # 将宿主机的 ./openwrt 目录挂载到容器的 /home/builder/openwrt
      - ./openwrt:/home/builder/openwrt
    # 设置工作目录，进入容器后会自动切换到这个目录
    working_dir: /home/builder/openwrt
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 推荐目录结构&lt;/h3&gt;
&lt;p&gt;为了便于管理，建议采用以下目录结构：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;~/openwrt_dev/
├── Dockerfile
├── docker-compose.yml
└── openwrt/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中 &lt;code&gt;openwrt&lt;/code&gt; 目录为 OpenWrt 源码。&lt;/p&gt;
&lt;h2&gt;编译步骤&lt;/h2&gt;
&lt;h3&gt;1. 获取OpenWrt源码&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;~/openwrt_dev/&lt;/code&gt; 目录下，克隆 OpenWrt 官方源码。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone https://git.openwrt.org/openwrt/openwrt.git
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 构建镜像与启动容器&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;~/openwrt_dev/&lt;/code&gt; 目录下执行以下命令，Docker Compose 会自动根据 &lt;code&gt;Dockerfile&lt;/code&gt; 构建镜像，并根据 &lt;code&gt;docker-compose.yml&lt;/code&gt; 的配置启动容器。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 作者安装的docker比较新，把docker-compose整合到docker工具链中了，使用方式和旧版不太一样
docker compose up --build -d

# 旧版 docker-compose
docker-compose up --build -d

# 查看容器情况
docker ps -a

# 启动或关闭我们的容器
docker start openwrt_build_env
docker stop openwrt_build_env
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用 &lt;code&gt;-d&lt;/code&gt; 参数可以在后台启动容器。&lt;/p&gt;
&lt;h3&gt;3. 进入容器&lt;/h3&gt;
&lt;p&gt;容器启动后，使用 &lt;code&gt;docker exec&lt;/code&gt; 命令进入容器内部的 &lt;code&gt;bash&lt;/code&gt; 环境。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker exec -it openwrt_build_env bash
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. 执行编译&lt;/h3&gt;
&lt;p&gt;进入容器后，所有操作都在 &lt;code&gt;/home/builder/openwrt&lt;/code&gt; 目录下进行。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 1. 更新并安装 Feeds
./scripts/feeds update -a
./scripts/feeds install -a

# 2. 生成默认配置文件，并进入配置菜单
#    在这里选择你的目标设备型号、软件包等
#    本文章的重点不在于此，不再展开
make menuconfig

# 3. 下载编译所需的依赖包
make download -j$(nproc)

# 4. 开始编译 (使用与CPU核心数相当的线程数以加快速度)
make -j$(nproc) V=s
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;编译产物&lt;/h2&gt;
&lt;p&gt;编译成功后，生成的固件及相关文件会位于宿主机的 &lt;code&gt;~/openwrt_dev/openwrt/bin/targets/&lt;/code&gt; 目录下，具体路径取决于你在 &lt;code&gt;make menuconfig&lt;/code&gt; 中选择的目标平台。例如，&lt;code&gt;mediatek/filogic&lt;/code&gt; 平台的产物路径如下：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;路径:&lt;/strong&gt; &lt;code&gt;~/openwrt_dev/openwrt/bin/targets/mediatek/filogic/&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://markdownforyuanhao.oss-cn-hangzhou.aliyuncs.com/img1/20250802005325556.png&quot; alt=&quot;Snipaste_2025-08-02_00-33-20&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>使用chroot和qemu调试开发板的rootfs</title><link>https://haoyn231.github.io/posts/linux/%E4%BD%BF%E7%94%A8chroot%E5%92%8Cqemu%E8%B0%83%E8%AF%95%E5%BC%80%E5%8F%91%E6%9D%BF%E7%9A%84rootfs/</link><guid isPermaLink="true">https://haoyn231.github.io/posts/linux/%E4%BD%BF%E7%94%A8chroot%E5%92%8Cqemu%E8%B0%83%E8%AF%95%E5%BC%80%E5%8F%91%E6%9D%BF%E7%9A%84rootfs/</guid><description>一般用于系统移植或者调试</description><pubDate>Mon, 28 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、&lt;code&gt;chroot&lt;/code&gt; 与 qemu 简介&lt;/h2&gt;
&lt;h3&gt;&lt;code&gt;chroot&lt;/code&gt; (Change Root)&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;chroot&lt;/code&gt; 是 Linux 系统中的一个命令及系统调用，用于将一个进程及其子进程的根目录切换到文件系统中的一个新位置。这使得该进程无法访问或命名新根目录之外的文件。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;主要用途：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;系统修复&lt;/strong&gt;：当系统无法启动时，可以使用 Live CD 启动，然后 &lt;code&gt;chroot&lt;/code&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;：在 x86 主机上为 ARM 等不同架构的设备构建和测试软件。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;环境测试&lt;/strong&gt;：在当前系统中模拟另一个 Linux 发行版环境，例如在 Ubuntu 中运行 Debian 环境。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;code&gt;qemu-user-static&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;当在 x86 架构的主机上 &lt;code&gt;chroot&lt;/code&gt; 到一个为 ARM 等异构平台构建的根文件系统时，会因为 CPU 指令集不兼容而导致无法执行任何程序。&lt;code&gt;qemu-user-static&lt;/code&gt; 通过 &lt;code&gt;binfmt_misc&lt;/code&gt; 机制解决了这个问题，它能够透明地解释和执行异构架构的二进制文件，从而在主机上模拟目标硬件环境。&lt;/p&gt;
&lt;h2&gt;二、操作流程&lt;/h2&gt;
&lt;p&gt;以用&lt;strong&gt;Ubuntu&lt;/strong&gt;系统调试立创泰山派的&lt;strong&gt;Ubuntu22.04 rootfs&lt;/strong&gt;为例&lt;/p&gt;
&lt;h3&gt;1. 安装 &lt;code&gt;qemu-user-static&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;首先，在主机系统中安装 &lt;code&gt;qemu-user-static&lt;/code&gt; 软件包。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo apt update
sudo apt install qemu-user-static
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装完成后，将对应目标架构的 QEMU 静态二进制文件复制到开发板根文件系统的 &lt;code&gt;usr/bin&lt;/code&gt; 目录下。例如，对于 ARM 架构：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 假设 &quot;ubuntu/&quot; 是开发板根文件系统的挂载点
sudo cp /usr/bin/qemu-arm-static ubuntu/usr/bin/
sudo cp /usr/bin/qemu-aarch64-static ubuntu/usr/bin/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;为了确保 QEMU 能够自动处理异构架构的二进制文件，需要检查 &lt;code&gt;binfmt_misc&lt;/code&gt; 的配置是否已启用。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ls -l /proc/sys/fs/binfmt_misc/
# 检查对应的 qemu-aarch64 解释器是否启用
cat /proc/sys/fs/binfmt_misc/qemu-aarch64
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;理想情况下，&lt;code&gt;cat&lt;/code&gt; 命令的输出应显示 &lt;code&gt;enabled&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;默认情况下qemu会自动注册，但是笔者使用的wsl2可能有bug，不会自动注册，这时候需要手动注册：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# rsicv64
sudo sh -c &apos;echo &quot;:qemu-riscv64:M::\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00:\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:/usr/bin/qemu-riscv64-static:F&quot; &amp;gt; /proc/sys/fs/binfmt_misc/register&apos;

# aarch64
sudo sh -c &apos;echo &quot;:qemu-aarch64:M::\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7\x00:\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:/usr/bin/qemu-aarch64-static:F&quot; &amp;gt; /proc/sys/fs/binfmt_misc/register&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 挂载根文件系统 (rootfs)&lt;/h3&gt;
&lt;p&gt;为了方便地挂载和卸载 &lt;code&gt;chroot&lt;/code&gt; 环境所需的虚拟文件系统，可以创建一个脚本。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;创建 &lt;code&gt;mount.sh&lt;/code&gt; 脚本：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash
# mount.sh - Mounts/unmounts essential filesystems for chroot

function mnt() {
    echo &quot;MOUNTING...&quot;
    sudo mount -t proc /proc ${2}proc
    sudo mount -t sysfs /sys ${2}sys
    sudo mount -o bind /dev ${2}dev
    sudo mount -o bind /dev/pts ${2}dev/pts
    echo &quot;Entering chroot environment...&quot;
    sudo chroot ${2}
}

function umnt() {
    echo &quot;UNMOUNTING...&quot;
    sudo umount ${2}proc
    sudo umount ${2}sys
    sudo umount ${2}dev/pts
    sudo umount ${2}dev
}

if [ &quot;$1&quot; == &quot;-m&quot; ] &amp;amp;&amp;amp; [ -n &quot;$2&quot; ]; then
    mnt $1 $2
elif [ &quot;$1&quot; == &quot;-u&quot; ] &amp;amp;&amp;amp; [ -n &quot;$2&quot; ]; then
    umnt $1 $2
else
    echo &quot;Usage: $0 [-m|-u] &amp;lt;rootfs_path&amp;gt;&quot;
    echo &quot;  -m: Mount and chroot into the rootfs&quot;
    echo &quot;  -u: Unmount the rootfs&quot;
    echo &quot;Example: $0 -m /path/to/rootfs/&quot;
fi
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;赋予脚本执行权限：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;chmod +x mount.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也可以使用这个健壮性更好的python脚本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os
import sys
import subprocess
import shutil

# --- 用户配置 ---
# 在这里修改为要挂载的根文件系统的绝对路径
ROOTFS_PATH = &quot;/home/hao/k230_sdk/output/k230_canmv_lckfb_defconfig/images/debian13&quot;

# 如果需要跨架构 chroot (例如在 x86 电脑上 chroot ARM 系统),
# 指定 QEMU 静态二进制文件的路径。如果不需要，请留空 &quot;&quot;。
# 通常在 /usr/bin/ 目录下, 例如 qemu-aarch64-static, qemu-arm-static 等。
QEMU_STATIC_BINARY = &quot;/usr/bin/qemu-riscv64-static&quot;
# --- 配置结束 ---


def run_command(command, check=True):
    &quot;&quot;&quot;执行一个 shell 命令并处理可能发生的错误&quot;&quot;&quot;
    print(f&quot;🚀 执行: {&apos; &apos;.join(command)}&quot;)
    try:
        subprocess.run(command, check=check)
    except FileNotFoundError:
        print(f&quot;❌ 命令未找到: {command[0]}。请确保该程序已安装并在 PATH 环境变量中。&quot;, file=sys.stderr)
        sys.exit(1)
    except subprocess.CalledProcessError as e:
        print(f&quot;❌ 命令执行失败: {&apos; &apos;.join(e.cmd)} (返回码: {e.returncode})&quot;, file=sys.stderr)
        if not check:
             print(&quot;   (警告: 此命令失败，但程序将继续执行)&quot;)
        else:
            sys.exit(1)


def unmount_filesystems():
    &quot;&quot;&quot;
    按正确顺序卸载所有 chroot 文件系统。
    该函数设计得非常健壮，会检查每个挂载点是否存在且已挂载。
    &quot;&quot;&quot;
    print(&quot;\n🧹 开始安全卸载程序...&quot;)
    mount_points = [&apos;dev/pts&apos;, &apos;dev&apos;, &apos;sys&apos;, &apos;proc&apos;]

    for mp in mount_points:
        target_path = os.path.join(ROOTFS_PATH, mp)
        if os.path.exists(target_path) and os.path.ismount(target_path):
            run_command([&apos;sudo&apos;, &apos;umount&apos;, &apos;-l&apos;, target_path], check=False) # 使用 -l (lazy) 更安全

    if QEMU_STATIC_BINARY:
        qemu_dest_path = os.path.join(ROOTFS_PATH, &apos;usr&apos;, &apos;bin&apos;, os.path.basename(QEMU_STATIC_BINARY))
        if os.path.exists(qemu_dest_path):
            print(f&quot;   清理 QEMU 模拟器: {qemu_dest_path}&quot;)
            run_command([&apos;sudo&apos;, &apos;rm&apos;, qemu_dest_path])

    print(&quot;✅ 清理完成。&quot;)


def mount_and_chroot():
    &quot;&quot;&quot;
    挂载所需的文件系统并进入 chroot 环境。
    使用 try...finally 确保无论 chroot 内部发生什么，都会执行卸载。
    &quot;&quot;&quot;
    try:
        print(&quot;🛠️  开始挂载 chroot 所需的文件系统...&quot;)
        mounts = [
            (&apos;proc&apos;, &apos;proc&apos;, &apos;proc&apos;, None),
            (&apos;sysfs&apos;, &apos;sys&apos;, &apos;sysfs&apos;, None),
            (&apos;/dev&apos;, &apos;dev&apos;, None, &apos;bind&apos;),
            (&apos;/dev/pts&apos;, &apos;dev/pts&apos;, None, &apos;bind&apos;),
        ]

        for source, dest_subdir, fstype, options in mounts:
            target_path = os.path.join(ROOTFS_PATH, dest_subdir)
            if not os.path.exists(target_path):
                print(f&quot;   创建缺失的目录: {target_path}&quot;)
                run_command([&apos;sudo&apos;, &apos;mkdir&apos;, &apos;-p&apos;, target_path])

            command = [&apos;sudo&apos;, &apos;mount&apos;]
            if fstype: command.extend([&apos;-t&apos;, fstype])
            if options == &apos;bind&apos;: command.extend([&apos;-o&apos;, &apos;bind&apos;])
            command.extend([source, target_path])
            run_command(command)

        if QEMU_STATIC_BINARY:
            if not os.path.exists(QEMU_STATIC_BINARY):
                print(f&quot;❌ 错误: QEMU 模拟器 &apos;{QEMU_STATIC_BINARY}&apos; 未在您的主机上找到。&quot;, file=sys.stderr)
                print(&quot;   如果您正在进行跨架构 chroot，请安装它 (例如: &apos;sudo apt install qemu-user-static&apos;)。&quot;, file=sys.stderr)
                return

            ### --- 新增功能：检查 binfmt_misc 是否启用 --- ###
            # 从 QEMU 二进制文件名推断出 binfmt_misc 的处理器名
            # 例如 &quot;qemu-aarch64-static&quot; -&amp;gt; &quot;qemu-aarch64&quot;
            handler_name = os.path.basename(QEMU_STATIC_BINARY).removesuffix(&apos;-static&apos;)
            binfmt_path = f&quot;/proc/sys/fs/binfmt_misc/{handler_name}&quot;
            
            print(f&quot;   检查 QEMU binfmt_misc 处理器: {binfmt_path}&quot;)
            if not os.path.exists(binfmt_path):
                print(f&quot;❌ 错误: QEMU binfmt_misc 处理器未启用 ({binfmt_path} 不存在)。&quot;, file=sys.stderr)
                print(&quot;   这意味着您的主机内核可能无法直接运行目标架构的二进制文件。&quot;, file=sys.stderr)
                print(&quot;   请尝试通过重启服务来注册处理器，例如运行:&quot;, file=sys.stderr)
                print(&quot;     sudo systemctl restart systemd-binfmt.service&quot;, file=sys.stderr)
                print(&quot;   或者:&quot;, file=sys.stderr)
                print(&quot;     sudo update-binfmts --enable&quot;, file=sys.stderr)
                return # 提前中止，finally 块会负责清理
            
            print(&quot;   ✅ QEMU binfmt_misc 处理器已启用。&quot;)
            ### --- 新增功能结束 --- ###

            qemu_dest_path = os.path.join(ROOTFS_PATH, &apos;usr&apos;, &apos;bin&apos;, os.path.basename(QEMU_STATIC_BINARY))
            print(f&quot;   复制 QEMU 模拟器到 chroot 环境: {qemu_dest_path}&quot;)
            run_command([&apos;sudo&apos;, &apos;cp&apos;, QEMU_STATIC_BINARY, qemu_dest_path])

        print(&quot;\n✅ 挂载完成。即将进入 chroot 环境...&quot;)
        print(&quot;   在 chroot 环境中，您可以执行所需命令。&quot;)
        print(&quot;   完成后，请键入 &apos;exit&apos; 以退出 chroot 并自动卸载所有文件系统。&quot;)
        
        run_command([&apos;sudo&apos;, &apos;chroot&apos;, ROOTFS_PATH])

    except Exception as e:
        print(f&quot;\n❌ 在挂载或 chroot 过程中发生意外错误: {e}&quot;, file=sys.stderr)
    finally:
        print(&quot;\n🚪 已退出 chroot 环境或发生错误。&quot;)
        unmount_filesystems()


def main():
    &quot;&quot;&quot;脚本主入口&quot;&quot;&quot;
    if os.geteuid() != 0:
        print(&quot;❌ 错误: 此脚本需要 root 权限。请使用 &apos;sudo&apos; 运行。&quot;, file=sys.stderr)
        sys.exit(1)

    if not os.path.isdir(ROOTFS_PATH):
        print(f&quot;❌ 错误: 配置的根文件系统路径 &apos;{ROOTFS_PATH}&apos; 不是一个有效的目录。&quot;, file=sys.stderr)
        sys.exit(1)

    if len(sys.argv) != 2 or sys.argv[1] not in [&apos;-m&apos;, &apos;-u&apos;]:
        print(f&quot;用法: sudo {sys.argv[0]} [-m|-u]&quot;)
        print(&quot;  -m: 挂载文件系统并进入 chroot 环境&quot;)
        print(&quot;  -u: 仅卸载文件系统 (用于手动清理)&quot;)
        sys.exit(1)

    action = sys.argv[1]
    if action == &apos;-m&apos;:
        mount_and_chroot()
    elif action == &apos;-u&apos;:
        unmount_filesystems()

if __name__ == &apos;__main__&apos;:
    main()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用方式&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;配置&lt;/strong&gt;：打开 &lt;code&gt;manage_chroot.py&lt;/code&gt; 文件，修改顶部的 &lt;code&gt;ROOTFS_PATH&lt;/code&gt; 变量，使其指向你的根文件系统目录（例如 &lt;code&gt;/home/user/my_project/rootfs&lt;/code&gt;）。如果需要，同时配置 &lt;code&gt;QEMU_STATIC_BINARY&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;挂载并进入 Chroot&lt;/strong&gt;： 在终端中运行以下命令。脚本将挂载所有必要的文件系统，然后自动带你进入 &lt;code&gt;chroot&lt;/code&gt; 环境。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo python3 manage_chroot.py -m
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当你完成在 &lt;code&gt;chroot&lt;/code&gt; 环境中的工作后，只需输入 &lt;code&gt;exit&lt;/code&gt; 并按回车。脚本会自动检测到退出，并执行所有卸载和清理操作。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;仅执行卸载&lt;/strong&gt;： 如果在某些异常情况下，你需要手动执行清理，可以运行以下命令。它会安全地卸载所有相关的挂载点。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo python3 manage_chroot.py -u
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;3. 进入 &lt;code&gt;chroot&lt;/code&gt; 环境并进行系统配置&lt;/h3&gt;
&lt;p&gt;使用上一步创建的脚本挂载并进入开发板的根文件系统。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 假设开发板根文件系统位于当前目录下的 &quot;ubuntu/&quot;
sudo ./mount.sh -m ubuntu/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功进入后，将处于开发板的模拟环境中，可以执行系统配置任务。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;以下是曾经给泰山派移植ubuntu22.04时的记录，仅供参考&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;泰山派sdk可用：&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;CmST0us/tspi-linux-sdk&quot;}&lt;/p&gt;
&lt;h4&gt;a. 更新软件源及安装必要软件&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;# 某些系统可能需要赋予/tmp目录写权限
chmod 777 /tmp

apt update
apt install -y sudo vim udev net-tools ethtool udhcpc netplan.io \
               language-pack-en-base iputils-ping openssh-sftp-server \
               ntp usbutils alsa-utils libmtp9 language-pack-zh-han* \
               bluetooth* bluez* blueman* wireless-tools network-manager dialog

# 清理不需要的软件包
apt-get remove --purge libreoffice* -y
apt-get autoremove -y
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;b. 添加用户及配置权限&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;# 1. 添加一个新用户（例如 tspi）
adduser tspi

# 2. 将新用户添加到 sudo 组以赋予管理员权限
adduser tspi sudo

# 3. 修改 root 用户的密码
passwd root
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;c. 优化开机网络等待时间&lt;/h4&gt;
&lt;p&gt;默认情况下，系统启动时可能会等待网络配置长达5分钟。通过修改 systemd 服务，可以缩短或禁用此等待时间。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;编辑服务文件：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vim /etc/systemd/system/network-online.target.wants/systemd-networkd-wait-online.service
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;修改 &lt;code&gt;ExecStart&lt;/code&gt; 行：&lt;/strong&gt;
将 &lt;code&gt;timeout&lt;/code&gt; 参数改为一个较小的值（如 5 秒）或 &lt;code&gt;0&lt;/code&gt;（禁用）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Service]
ExecStart=/usr/bin/nm-online -s -q --timeout=5
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;d. 添加分区自动扩容服务（重要）&lt;/h4&gt;
&lt;p&gt;为了让烧录到 SD 卡或 eMMC 的文件系统在首次启动时自动扩展以使用整个分区空间，可以创建一个系统服务。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;(1) 创建 &lt;code&gt;resize2fs.sh&lt;/code&gt; 脚本：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vim /etc/init.d/resize2fs.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;添加以下内容：&lt;/strong&gt;
该脚本会检查一个标志文件，如果不存在，则执行分区扩容并创建该标志文件，确保扩容只执行一次。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash -e
# Resize the root filesystem partition, e.g., /dev/mmcblk0p6

PARTITION_TO_RESIZE=&quot;/dev/mmcblk0p6&quot;
FLAG_FILE=&quot;/usr/local/boot_flag&quot;

if [ ! -e &quot;$FLAG_FILE&quot; ] ; then
  echo &quot;Resizing $PARTITION_TO_RESIZE...&quot;
  resize2fs $PARTITION_TO_RESIZE
  touch &quot;$FLAG_FILE&quot;
  echo &quot;Resize complete.&quot;
fi
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;(2) 赋予脚本执行权限：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;chmod +x /etc/init.d/resize2fs.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;(3) 创建 systemd 服务来执行此脚本：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vim /lib/systemd/system/resize2fs.service
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;添加服务定义：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Unit]
Description=Resize root filesystem on first boot
DefaultDependencies=no
After=systemd-remount-fs.service
Before=local-fs.target

[Service]
Type=oneshot
ExecStart=/etc/init.d/resize2fs.sh
StandardOutput=journal
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;(4) 启用该服务：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl enable resize2fs.service
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. 退出并卸载根文件系统&lt;/h3&gt;
&lt;p&gt;完成所有配置后，退出 &lt;code&gt;chroot&lt;/code&gt; 环境。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;exit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;返回到主机系统后，使用脚本卸载之前挂载的虚拟文件系统（如果用上面提供的python脚本，当退出时会自动卸载，不用手动操作）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo ./mount.sh -u ubuntu/
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>从嘉立创导入元器件到kicad</title><link>https://haoyn231.github.io/posts/tools/%E4%BB%8E%E5%98%89%E7%AB%8B%E5%88%9B%E5%AF%BC%E5%85%A5%E5%85%83%E5%99%A8%E4%BB%B6%E5%88%B0kicad/</link><guid isPermaLink="true">https://haoyn231.github.io/posts/tools/%E4%BB%8E%E5%98%89%E7%AB%8B%E5%88%9B%E5%AF%BC%E5%85%A5%E5%85%83%E5%99%A8%E4%BB%B6%E5%88%B0kicad/</guid><description>使用easyeda2kicad把嘉立创EDA在线元器件导入Kicad</description><pubDate>Sun, 27 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、Windows中miniconda的安装与使用&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;安装&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.anaconda.com/docs/getting-started/miniconda/install#windows-powershell&quot;&gt;官方安装指南&lt;/a&gt;，以此执行一下三条指令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wget &quot;https://repo.anaconda.com/miniconda/Miniconda3-latest-Windows-x86_64.exe&quot; -outfile &quot;.\miniconda.exe&quot;
Start-Process -FilePath &quot;.\miniconda.exe&quot; -ArgumentList &quot;/S&quot; -Wait
del .\miniconda.exe
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;常用命令&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;列出当前已有虚拟环境 &lt;code&gt;conda env list&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;创建虚拟环境&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;conda create -n easyeda python=3.10 # 创建名为easyeda的虚拟环境，指定python版本

# 激活对应虚拟环境
conda activate easyeda

# 离开当前虚拟环境
conda deactivate
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;二、easyeda2kicad脚本的安装与使用&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/uPesy/easyeda2kicad.py&quot;&gt;easyeda2kicad仓库&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;在对应虚拟环境中执行&lt;code&gt;pip install easyeda2kicad&lt;/code&gt;即可安装&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;基于元器件客编号来导入元器件&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://markdownforyuanhao.oss-cn-hangzhou.aliyuncs.com/img1202507142248226.png&quot; alt=&quot;Snipaste_2025-07-14_22-47-54&quot; /&gt;&lt;/p&gt;
&lt;p&gt;找到立创商城里需要的元件的编号，在虚拟环境中把它导入到名为&lt;code&gt;easyeda2kicad&lt;/code&gt;的元件库中&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;easyeda2kicad --full --lcsc_id=C32710423


# 显示创建的元件名与库的地址
[INFO] Created Kicad symbol for ID : C32710423
       Symbol name : LCKFB-LSPI-SKYSTAR-STM32F407VGT6-PRO
       Library path : C:\Users\hao\Documents\Kicad\easyeda2kicad/easyeda2kicad.kicad_sym
[INFO] Created Kicad footprint for ID: C32710423
       Footprint name: COMM-TH_LCKFB-LSPI-SKYSTAR-STM32F407VGT6-PRO
       Footprint path: C:\Users\hao\Documents\Kicad\easyeda2kicad/easyeda2kicad.pretty\COMM-TH_LCKFB-LSPI-SKYSTAR-STM32F407VGT6-PRO.kicad_mod
[WARNING] No 3D model available for this component
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;导入easyeda对应的符号库和封装库&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在kicad开始界面的设置中的符号库与封装库中，分别导入对应文件和路径&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://markdownforyuanhao.oss-cn-hangzhou.aliyuncs.com/img1202507142254049.png&quot; alt=&quot;Snipaste_2025-07-14_22-51-38&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://markdownforyuanhao.oss-cn-hangzhou.aliyuncs.com/img1202507142254068.png&quot; alt=&quot;Snipaste_2025-07-14_22-52-04&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;在kicad中找到导入的元件&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://markdownforyuanhao.oss-cn-hangzhou.aliyuncs.com/img1202507142255980.png&quot; alt=&quot;Snipaste_2025-07-14_22-54-57&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>使用pyocd进行mcu的烧录和调试</title><link>https://haoyn231.github.io/posts/tools/%E4%BD%BF%E7%94%A8pyocd%E8%BF%9B%E8%A1%8Cmcu%E7%9A%84%E7%83%A7%E5%BD%95%E5%92%8C%E8%B0%83%E8%AF%95/</link><guid isPermaLink="true">https://haoyn231.github.io/posts/tools/%E4%BD%BF%E7%94%A8pyocd%E8%BF%9B%E8%A1%8Cmcu%E7%9A%84%E7%83%A7%E5%BD%95%E5%92%8C%E8%B0%83%E8%AF%95/</guid><pubDate>Wed, 23 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;简介&lt;/h1&gt;
&lt;p&gt;pyOCD 是一个开源 Python 软件包，用于多种受支持的硬件调试器（DAP-Link、J-Link、ST-Link,CMSIS-DAP v1(HID)、CMSIS-DAP v2(WinUSB)、SEGGER J-Link、ST-LINK v2和ST-LINK v3）下编程和调试Arm Cortex-M微控制器。它是完全跨平台的，并支持Linux，macOS和Windows。它内置支持多达70种流行的MCU。通过使用CMSIS-Pack，几乎支持市场上的所有Cortex-M设备。pyOCD还可以作为GDB Service配合GDB调试芯片。&lt;/p&gt;
&lt;p&gt;使用环境：Ubuntu22.04&lt;/p&gt;
&lt;h2&gt;一、安装pyocd&lt;/h2&gt;
&lt;p&gt;利用pip进行安装，确保当前环境中python版本大于3.6&lt;/p&gt;
&lt;p&gt;安装pip&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo apt update
sudo apt install python3-pip
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;更新pip&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python3 -m pip install --upgrade --force pip
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装pyocd&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python3 -m pip install -U pyocd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装成功后重启Ubuntu，查看是否安装成功&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pyocd -V # 注意是大写V
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;二、连接烧录设备&lt;/h2&gt;
&lt;p&gt;列出当前连接上的烧录设备&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pyocd list

#   Probe/Board    Unique ID                  Target  
--------------------------------------------------------
  0   STM32 STLink   00380043320000054E575359   n/a    
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;三、添加对MCU的支持&lt;/h2&gt;
&lt;p&gt;列出当前支持的所有mcu：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pyocd list -t

Name                      Vendor                   Part Number                  Families   Source   
------------------------------------------------------------------------------------------------------
  air001                    AirM2M                   Air001                                  builtin  
  air32f103xb               AirM2M                   Air32F103xB                             builtin  
  air32f103xc               AirM2M                   Air32F103xC                             builtin  
  air32f103xe               AirM2M                   Air32F103xE                             builtin  
  air32f103xg               AirM2M                   Air32F103xG                             builtin  
  air32f103xp               AirM2M                   Air32F103xP                             builtin  
  cc3220sf                  Texas Instruments        CC3220SF                                builtin  
......
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查询对指定型号的支持：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pyocd list -t | grep stm32*

stm32f051                 STMicroelectronics       STM32F051     builtin  
stm32f103rc               STMicroelectronics       STM32F103RC   builtin  
  
......
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1、查询并添加对某款MCU的支持&lt;/h3&gt;
&lt;p&gt;更新包索引&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pyocd pack update
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查找mspm0g3507&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pyocd pack -f mspm0g3507

Part         Vendor              Pack                          Version   Installed  
--------------------------------------------------------------------------------------
  MSPM0G3507   Texas Instruments   TexasInstruments.MSPM0G_DFP   1.2.1     False     
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果查询到了就可以直接安装&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pyocd pack -i mspm0g3507

Downloading packs (press Control-C to cancel):
    TexasInstruments.MSPM0G_DFP.1.2.1
Downloading descriptors (001/001)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时再执行&lt;code&gt;pyocd pack -f mspm0g3507&lt;/code&gt;，&lt;code&gt;Installed &lt;/code&gt;就会变成True，说明已经支持&lt;/p&gt;
&lt;h3&gt;2、手动添加对某款MCU的支持&lt;/h3&gt;
&lt;p&gt;有时候pyocd没有初始支持对某款mcu，查询也查询不到，可以手动下载对应的pack包并进行安装&lt;/p&gt;
&lt;p&gt;比如默认就找不到stm32f407zgt6：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pyocd pack -i stm32f407zgt6

0000550 W No matching devices. Please make sure the pack index is up to date. [pack_cmd]
Downloading packs (press Control-C to cancel):
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以从Keil官网下载对应的DFP包，在对目标mcu进行操作的时候作为参数来添加进去&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pyocd list -t --pack ./Keil.STM32F4xx_DFP.3.0.0.pack
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样就可以列出包中支持的mcu&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;可以把需要的pack放置到指定目录，在对需要的单片机进行操作时，加入&lt;code&gt;--pack&lt;/code&gt;参数&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;三、擦除和烧录MCU&lt;/h2&gt;
&lt;h3&gt;烧录&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;pyocd flash --target stm32f407zgtx ./build/demo.hex --pack ~/Keil.STM32F4xx_DFP.3.0.0.pack 

pyocd flash --target mspm0g3507 ./build/empty.hex 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果需要操作的mcu已有支持，不需要加&lt;code&gt;--pack&lt;/code&gt;参数&lt;/p&gt;
&lt;p&gt;**注意：**烧录的单片机是stm32f407zgt6，但是下载的pack中对应的型号是stm32f407zgtx，可以通过&lt;code&gt;pyocd list -t&lt;/code&gt;来查看。&lt;/p&gt;
&lt;h3&gt;VScode快捷任务&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;.vscode/tasks.json&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &quot;version&quot;: &quot;2.0.0&quot;,
    &quot;tasks&quot;: [
        {
            &quot;label&quot;: &quot;Flash&quot;,
            &quot;type&quot;: &quot;process&quot;,
            &quot;command&quot;: &quot;pyocd&quot;,
            &quot;args&quot;: [
              &quot;flash&quot;,
              &quot;--target&quot;,
              &quot;stm32f407zgtx&quot;,
              &quot;--pack&quot;,
              &quot;~/Keil.STM32F4xx_DFP.3.0.0.pack&quot;,
              &quot;./build/demo.hex&quot;
            ],
            &quot;problemMatcher&quot;: [],
            &quot;group&quot;: {
              &quot;kind&quot;: &quot;build&quot;,
              &quot;isDefault&quot;: true
            },
            &quot;presentation&quot;: {
              &quot;reveal&quot;: &quot;always&quot;,
              &quot;panel&quot;: &quot;dedicated&quot;,
              &quot;clear&quot;: true
            },
            &quot;detail&quot;: &quot;Flash demo.hex to STM32 via ST-Link (pyocd)&quot;
          }
    ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;四、调试MCU&lt;/h2&gt;
&lt;p&gt;**注意：**可执行文件使用&lt;code&gt;.elf&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;调试的时候需要下载VScode插件&lt;strong&gt;Cortex Debug&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.vscode/launch.json&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &quot;version&quot;: &quot;0.2.0&quot;,
    &quot;configurations&quot;: [
        {
            &quot;name&quot;: &quot;Cortex Debug&quot;,
            &quot;cwd&quot;: &quot;${workspaceFolder}&quot;,
            &quot;executable&quot;: &quot;./build/demo.elf&quot;,
            &quot;request&quot;: &quot;launch&quot;,
            &quot;type&quot;: &quot;cortex-debug&quot;,
            &quot;runToEntryPoint&quot;: &quot;main&quot;,
            &quot;servertype&quot;: &quot;pyocd&quot;,
            &quot;targetId&quot;: &quot;stm32f407zgtx&quot;,
            &quot;cmsisPack&quot;: &quot;~/Keil.STM32F4xx_DFP.3.0.0.pack&quot;
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;调试结束后，确定gdb进程关闭，否则下次进入调试会报错&lt;/p&gt;
</content:encoded></item><item><title>USBIPD的使用</title><link>https://haoyn231.github.io/posts/tools/usbipd%E7%9A%84%E4%BD%BF%E7%94%A8/</link><guid isPermaLink="true">https://haoyn231.github.io/posts/tools/usbipd%E7%9A%84%E4%BD%BF%E7%94%A8/</guid><description>使用USBIPD把windows上的USB设备挂载进WSL2中</description><pubDate>Mon, 17 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;使用USBIPD将USB设备共享给WSL2&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;在windows的powershell中安装&lt;strong&gt;USBIPD&lt;/strong&gt;，安装后重启powershell&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;winget install usbipd //需要代理
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;找到esp32对应的设备，记住BUSID，如：6-4&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;usbipd list //列出当前usb设备

6-4    1a86:7522  USB-SERIAL CH340K (COM3)                                      Not shared
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;绑定BUSID，运行它被共享到WSL2&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;usbipd bind --busid &amp;lt;BUSID&amp;gt;
# 如：
usbipd bind --busid 6-4

# 取消共享
usbipd unbind --busid 6-4
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;将USB设备附加到WSL2&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;usbipd attach --wsl --busid &amp;lt;BUSID&amp;gt;
# 如：
usbipd attach --wsl --busid 6-4

# 取消附加到wsl2上（根据guid）
usbipd unbind --guid 58e8d17f-29ee-41ba-96b0-10cba8173253
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;运行结果：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# usbipd attach --wsl --busid 6-4
usbipd: info: Using WSL distribution &apos;Ubuntu&apos; to attach; the device will be available in all WSL 2 distributions.
usbipd: info: Using IP address 127.0.0.1 to reach the host.
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在Ubuntu上运行&lt;code&gt;lsusb&lt;/code&gt;，可以看到多出一个沁恒（ch343）&lt;code&gt;QinHeng&lt;/code&gt;的设备&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 002: ID 1a86:7522 QinHeng Electronics USB Serial
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;图形化软件&lt;/h2&gt;
&lt;p&gt;::github{repo=&quot;nickbeth/wsl-usb-manager&quot;}&lt;/p&gt;
</content:encoded></item></channel></rss>