[{"content":"今天是周天，学校开了两天运动会，加上一个周末，也算是放了一个小长假。本来计划要去南昌玩的，因为身体原因就取消了计划。就当我取消了计划的时候，全世界的事情好像都给我爆发了。修改了一个PPT，完成了嵌入式的环境配置和个人作业又写了一个创新创业计划书。甚至在开幕式第一天还去代表学生会拍摄照片，经过两天的高强度任务，我决定去个农家乐玩上一天。在抖音上看到一家合适了，结果觉得这个地方很棒，超出了我的预期。\n地方在哪我就不在博客里面说了，要是有西安的朋友，想知到在哪里，也想去玩，可以给我发邮箱。\n先来一张全景图\n山清水秀，里面很大的面积，前面是可以围炉煮茶的地方。我没有过多停留，直奔到了后面的鱼塘，现在正值好天气，也没有夏天那么热，钓鱼是非常爽的，而且里面鱼非常多，像我这样没有任何钓鱼基础的人，也在十分钟左右上了一条大鲈鱼。\n而且鱼也没有咬我下的钩子，它的肚子被我钓到了，我真是新手保护期，碰到了一条笨鱼。\n玩了一会感觉非常不错，突然又来了一只可爱的布鲁斯。找了一些零食给它吃，他吃的很高兴，给我们一直摇尾巴。\n里面还有很多娱乐设施，从小孩玩的滑滑梯，秋千，还有台球，大草地等等等，我觉得完全是一个世外桃源可以完全的放松身心。\n我最喜欢这个吊床，真想一辈子都在这里躺平\n玩了一下午，肚子也饿了，准备开始烧烤。\n烧烤也特别容易，我本来觉得两个人去烧烤基本就是不可能的事情，两个人太麻烦了，而且我们没有汽车，也带不了那么多东西。 但是这个农家乐里面超级方便，我只要打个空手去就完全可以了。老板会给你准备好所有需要用的，从烧烤架到煤炭，从食材到饮料，一应俱全，而且食材绝对新鲜，老板就在前台一直处理食材，现成穿烧烤串串。\n这是老板给我们准备的，真是高估我们了，我们开始也是眼睛大肚子小，感觉自己能吃完，结果发现我们根本吃不完这么多。\n我要开始大展我的烧烤身手了，其实我很小就会烤烧烤了，只是深藏不露而已，给对象漏了一手。\n看着就很诱人吧，我去完之后就感觉这地方真是相见恨晚，以后可以和更多朋友一起去，绝对可以让每个人都开心离去。\n到这里就应该结束了，可是我在这次出去发生了意外。因为春天万物竞发，生机勃勃。一路上有很多的花粉和小虫子，加上骊山这边的风特别大，导致主播回来的路上骑车根本就睁不开眼睛。回到学校直接泪眼婆娑，所以有了这个经验之后我直接入手了一顶全盔(其实也有一部分我车瘾大的原因)\n期待了几天，今天头盔就到了，因为是武汉发货的，就特别快。太让我激动了，头盔很帅，下次出门就可以帅帅的骑车了。\n好了就这些，其实我日常也很无聊，就是一个无聊的人给自己找一点乐子。\n","date":"2026-04-26T00:00:00Z","permalink":"https://ren517.xyz/p/%E5%87%BA%E5%8E%BB%E9%87%8E%E9%A4%90%E7%8E%A9%E5%95%A6/","title":"出去野餐玩啦！！"},{"content":"写一个文章，记录一下配置好环境需要的操作，完成最简单的一个软件调试。 目的：在主机Ubuntu上开发并生成可执行文件，把可执行文件传到开发板上，执行这个程序。\nQ1: 为什么不在开发板上编辑并调试 因为在开发板上性能有限，很难自主一套完成一套任务。现在虽然开发板性能上在提升，但作为程序员角度来说，还是不够优雅。\nQ2: 为什么要用Ubuntu而不用Windows开发 在老师发的文档中有提到 原因解释： CPU 只能理解其原生指令集 x86_64 指令 ≠ ARM64 指令 就像让只会中文的人读英文书一样，CPU “看不懂” 错误的指令格式\n实验条件 已经成功的让虚拟机加入桥接网卡，IP ：192.168.137.100。 ssh连接Ubuntu用的NAT模式的网卡 IP ：192.168.5.128。\n在开发板上\n1 ping 192.168.137.100 这样即证明虚拟机和开发板连通\n1 2 3 4 debian@lubancat:/mnt/nfs$ ping 192.168.137.100 PING 192.168.137.100 (192.168.137.100) 56(84) bytes of data. 64 bytes from 192.168.137.100: icmp_seq=1 ttl=64 time=2.94 ms 64 bytes from 192.168.137.100: icmp_seq=2 ttl=64 time=2.06 ms 同理，在Ubuntu上\n1 ping 192.168.137.2 两边都成功则肯定没有问题了\n在Ubuntu上执行以下命令，检测NFS是否开启\n1 sudo systemctl status nfs-kernel-server 出现绿色的 在开发板上运行active √\n1 2 df -h | grep nfs ls -la /mnt/nfs 若没有报错，且看到了Ubuntu上的共享文件，就OK\n如果检测到NFS没有开启，或有其他错误，请检查上一篇野火imx8m开发板开发配置指南4.1,4.2,4.3，看看是否配置正确\n开发板上的共享文件夹路径\n1 2 debian@lubancat:/mnt/nfs$ pwd /mnt/nfs 若想切换到这个路径，在开发板软件里面终端输入\n1 cd /mnt/nfs Ubuntu上的共享文件夹路径\n1 2 fyj@fyj-virtual-machine:/home/frank/nfs_share$ pwd /home/frank/nfs_share 若想切换到这个路径，在vm终端输入\n1 cd /home/frank/nfs_share 解决方案一（文档里的默认方法） 在Ubuntu上(用vm进入)\n1 2 3 # 若没有这个路径就自己创建一下 cd /home/frank/nfs_share/projects/hello sudo nano main.c 写入代码\n1 2 3 4 5 #include \u0026lt;stdio.h\u0026gt; int main(int argc, char *argv[]) { printf(\u0026#34;Hello from i.MX8M!\\n\u0026#34;); return 0; } 运行指令编译\n1 aarch64-linux-gnu-gcc -static main.c -o hello 现在进入开发板(用ModaXterm)\n1 2 3 4 5 6 7 8 9 10 11 # 进入 NFS 挂载点 cd /mnt/nfs/projects/hello # 查看文件（应该能看到刚编译的 hello 程序） ls -la hello # 赋予执行权限 chmod +x hello # 运行程序 ./hello 然后就能看到结果了\n1 2 debian@lubancat:/mnt/nfs/projects/hello$ ./hello Hello from i.MX8M! 方案二(笔者更推荐的一种) 我总觉得方案一不够直观，不够方便编辑，用了nano编辑器，又不想在Ubuntu里面下载vscode 所以我用了Windows的vscode，利用了ssh(不会的可以自己去学一下，我这边已经配置好了)\n这就是进来以后的界面\n优势：秒更新，可视化，很清晰的看到两边的同步。傻瓜式流程，就能感觉和Windows上开发别无两样。\n用法：左边是Ubuntu，在上面用ui创建文件/文件夹，右侧即可编辑 编辑好后用vscode内置终端，执行和上面相同操作即可\n","date":"2026-04-23T00:00:00Z","permalink":"https://ren517.xyz/p/%E5%B5%8C%E5%85%A5%E5%BC%8F%E5%AE%8C%E6%88%90%E6%9C%80%E7%AE%80%E5%8D%95%E5%BC%80%E5%8F%91/","title":"嵌入式完成最简单开发"},{"content":"野火 i.MX8M 开发板环境配置与开发工作流完整指南 目录 认识野火 i.MX8M 开发板 MobaXterm 下载、安装及串口连接配置 开发板网络配置（USB0 共享 + ETH0 调试） VMware + Ubuntu NFS 共享配置及 VSCode 开发工作流 一、认识野火 i.MX8M 开发板 本章节帮助您快速了解野火 i.MX8M Mini 开发板的硬件接口，重点关注 USB 和网络接口。\n1.1 硬件概览 请通过以下官方文档详细了解开发板的硬件规格和接口布局：\n📖 参考文档：野火 i.MX8MM 硬件介绍\n重点关注的接口：\nUSB 接口：用于 USB Gadget 网络共享（usb0） 以太网接口（ETH0）：用于稳定的 SSH 远程调试和文件传输 串口（UART）：用于初始系统访问和调试 💡 提示：无需记忆所有细节，只需通过上述链接了解 USB 和网络接口的位置和基本功能即可。后续配置时会具体说明如何使用这些接口。\n二、MobaXterm 下载、安装及串口连接配置 2.1 MobaXterm 下载与安装 获取安装包：\n教师将提供 MobaXterm 安装包（推荐 Professional Edition） 或从官网下载：https://mobaxterm.mobatek.net/download.html 安装步骤：\n运行安装程序，按照向导完成安装 首次启动时，可选择便携版（Portable）或安装版（Installer edition） 建议创建桌面快捷方式以便快速访问 2.2 通过串口连接开发板 硬件连线 请参考官方文档完成串口线的物理连接：\n📖 参考文档：开发板启动与串口连接\n基本连线步骤：\n使用 USB 转串口线（Type-C） 一端连接开发板的 USB OTG 接口 另一端连接 Windows 主机的 USB 端口 MobaXterm 串口配置 配置步骤：\n启动 MobaXterm\n点击左上角 \u0026ldquo;Session\u0026rdquo; → 选择 \u0026ldquo;Serial\u0026rdquo;\n查找 COM 端口号（重要！）：\n方法一：通过设备管理器查看\n在 Windows 中，右键点击 \u0026ldquo;此电脑\u0026rdquo; 或 \u0026ldquo;我的电脑\u0026rdquo; → 选择 \u0026ldquo;管理\u0026rdquo; 或者按 Win + X 键，选择 \u0026ldquo;设备管理器\u0026rdquo; 或者按 Win + R，输入 devmgmt.msc 后回车 在设备管理器窗口中：\n展开 \u0026ldquo;端口 (COM 和 LPT)\u0026rdquo; 或 \u0026ldquo;Ports (COM \u0026amp; LPT)\u0026rdquo; 类别 查找包含 \u0026ldquo;USB-SERIAL\u0026rdquo;、\u0026ldquo;CH340\u0026rdquo;、\u0026ldquo;CP210x\u0026rdquo;、\u0026ldquo;FTDI\u0026rdquo; 或类似字样的设备 括号中的数字即为 COM 端口号，例如：USB-SERIAL CH340 (COM3) 记下这个 COM 编号（如 COM3、COM4 等） 💡 提示：如果找不到串口设备，请检查：\nUSB 转串口线是否已正确连接 是否需要安装驱动程序（常见芯片：CH340、CP2102、FT232） 尝试拔插 USB 线后刷新设备管理器（按 F5） 方法二：通过 MobaXterm 自动检测\n在 Serial session 配置界面，点击 \u0026ldquo;Serial port\u0026rdquo; 下拉菜单 MobaXterm 会自动列出可用的 COM 端口 通常只有一个选项时，直接选择即可 配置串口参数：\nSerial port：选择刚才找到的 COM 端口（如 COM3） Speed (baud)：115200 Data bits：8 Stop bits：1 Parity：None Flow control：None 点击 \u0026ldquo;OK\u0026rdquo; 建立连接\n给开发板上电，应能看到启动日志\n⚠️ 注意：如果看不到输出，请检查：\nCOM 端口号是否正确 波特率是否为 115200 串口线是否连接牢固 开发板是否正常供电 登录系统：\n默认用户名：root 或 debian（根据镜像版本） 默认密码：temppwd 三、开发板网络配置（USB0 共享 + ETH0 调试） 本章节基于 LubanCat 开发板双网卡配置经验，适配野火 i.MX8M 开发板的双网络接口场景。\n3.1 网络拓扑与 IP 规划 接口 用途 开发板 IP 子网掩码 网关 DNS usb0 上网 192.168.7.2 255.255.255.252 192.168.7.1 8.8.8.8, 114.114.114.114 eth0 调试 192.168.137.2 255.255.255.0 无 - 重要说明：\nusb0：通过 USB Gadget 技术共享 Windows 主机网络，作为开发板的默认互联网出口 eth0：连接到路由器或开发主机，用于稳定的 SSH 远程调试，不参与互联网访问 Windows 主机侧需正确配置对应虚拟网卡 IP（usb0 对应 192.168.7.1，eth0 对应 192.168.137.1） 3.1.1 Windows 主机 USB 网络共享配置 在配置开发板网络之前，必须先在 Windows 主机上启用 USB 网络共享功能。请按照以下步骤操作：\n步骤 1：连接开发板到 Windows 主机 使用 USB Type-C 数据线连接开发板的 USB OTG 接口到 Windows 主机的 USB 端口 给开发板上电并等待系统启动完成 通过串口确认开发板已正常启动 步骤 2：打开网络连接设置 按 Win + R 键，输入 ncpa.cpl 后回车 或者：右键点击任务栏网络图标 → \u0026ldquo;网络和 Internet 设置\u0026rdquo; → \u0026ldquo;更改适配器选项\u0026rdquo; 这将打开\u0026quot;网络连接\u0026quot;窗口，显示所有网络适配器 步骤 3：启用 Internet 连接共享（ICS） 找到您当前正在使用的上网网卡（通常是 WiFi 或以太网适配器）\n状态应显示为\u0026quot;已启用\u0026quot;或\u0026quot;Connected\u0026quot; 右键点击该网卡 → 选择 \u0026ldquo;属性\u0026rdquo; 切换到 \u0026ldquo;共享\u0026rdquo; 选项卡\n勾选 \u0026ldquo;允许其他网络用户通过此计算机的 Internet 连接来连接\u0026rdquo;\n在 \u0026ldquo;家庭网络连接\u0026rdquo; 下拉菜单中，选择开发板对应的 USB 网络设备\n通常显示为：\u0026ldquo;以太网 X\u0026rdquo; 或 \u0026ldquo;USB Ethernet/RNDIS Gadget\u0026rdquo; 如果不确定是哪个，可以拔掉开发板 USB 线观察哪个适配器消失来判断 点击 \u0026ldquo;确定\u0026rdquo; 保存设置\n⚠️ 重要提示：\n启用共享后，Windows 会自动将该 USB 网卡的 IP 设置为 192.168.7.1 如果提示需要重启或其他警告，请点击\u0026quot;是\u0026quot;继续 某些情况下可能需要重新插拔 USB 线才能使共享生效 步骤 4：验证 USB 网卡配置 在网络连接窗口中，找到刚才选择的 USB 网卡 右键点击 → \u0026ldquo;状态\u0026rdquo; → \u0026ldquo;详细信息\u0026rdquo; 确认 IPv4 地址为 192.168.7.1 子网掩码应为 255.255.255.252 如果 IP 不是 192.168.7.1，可以手动设置：\n右键 USB 网卡 → \u0026ldquo;属性\u0026rdquo; 双击 \u0026ldquo;Internet 协议版本 4 (TCP/IPv4)\u0026rdquo; 选择 \u0026ldquo;使用下面的 IP 地址\u0026rdquo;： IP 地址：192.168.7.1 子网掩码：255.255.255.252 默认网关：留空 点击 \u0026ldquo;确定\u0026rdquo; 保存 步骤 5：测试连通性 在 Windows 命令提示符中测试：\n1 ping 192.168.7.2 如果能 ping 通，说明 USB 网络共享配置成功！\n📖 参考文档：更多详细信息请参考 野火论坛 - USB 网络共享配置教程\n3.2 前置准备：清理冲突服务 野火 i.MX8M Debian 系统可能安装了多个网络管理器（如 ConnMan、NetworkManager），它们会相互冲突。在修改配置前，请务必执行以下步骤以锁定唯一的网络管理器。\n通过 MobaXterm 串口连接登录后，依次执行：\n1 2 3 4 5 6 7 8 9 10 11 # 1. 停止所有潜在冲突的网络服务 sudo systemctl stop connman NetworkManager systemd-networkd # 2. 禁用开机自启 sudo systemctl disable connman NetworkManager systemd-networkd # 3. 彻底屏蔽（Mask），防止被其他依赖意外唤醒 sudo systemctl mask connman NetworkManager systemd-networkd # 4. 启用传统的 networking 服务 sudo systemctl enable networking 3.3 核心配置步骤 3.3.1 编辑网络配置文件 使用编辑器打开 /etc/network/interfaces：\n1 sudo nano /etc/network/interfaces 3.3.2 写入标准配置 请确保文件内容与下方完全一致（您可以删除原有的其他配置）：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 # 环路接口(必须保留) auto lo iface lo inet loopback # eth0: 开发调试网口(静态IP, 不设网关) auto eth0 iface eth0 inet static address 192.168.137.2 netmask 255.255.255.0 # 重要：此处千万不要写 gateway，否则会与 usb0 冲突 # usb0: USB共享上网口(静态IP, 设为默认网关) auto usb0 iface usb0 inet static address 192.168.7.2 netmask 255.255.255.252 gateway 192.168.7.1 dns-nameservers 8.8.8.8 114.114.114.114 操作提示：\n在 nano 中，按 Ctrl + O 然后回车保存 按 Ctrl + X 退出编辑器 3.3.3 应用配置 有两种方式使配置生效：\n方式 A（推荐）：重启开发板\n1 sudo reboot 方式 B：重启网络服务\n1 sudo systemctl restart networking 3.4 验证与测试 重启或重启服务后，请重新通过串口登录开发板，执行以下检查：\n3.4.1 检查路由表 1 route -n 预期结果：\n第一行应为：0.0.0.0 192.168.7.1 ... UG ... usb0（默认网关指向 usb0） 应包含：192.168.137.0 ... eth0（局域网路由） 应包含：192.168.7.0 ... usb0（USB 网段路由） 3.4.2 检查 IP 地址 1 ip addr show 预期结果：\neth0 显示 inet 192.168.137.2/24 usb0 显示 inet 192.168.7.2/30 3.4.3 连通性测试 1 2 3 4 5 # 测试外网(百度) ping -c 4 www.baidu.com # 测试内网 (Windows 主机 eth0 侧) ping -c 4 192.168.137.1 3.5 常见问题排查 Q1: ping 不通百度，提示 \u0026ldquo;Network is unreachable\u0026rdquo; 原因：默认路由缺失或错误 解决： 检查 route -n 是否有 UG 标志的行指向 usb0 如果没有，检查 /etc/network/interfaces 中 usb0 是否写了 gateway 192.168.7.1 手动修复：sudo ip route add default via 192.168.7.1 dev usb0 Q2: ping 不通百度，提示 \u0026ldquo;Temporary failure in name resolution\u0026rdquo; 原因：DNS 配置未生效 解决： 查看 cat /etc/resolv.conf 如果为空或错误，手动添加： 1 echo \u0026#34;nameserver 8.8.8.8\u0026#34; | sudo tee /etc/resolv.conf 长期解决：确保 /etc/network/interfaces 中 usb0 下有 dns-nameservers 字段，并安装 resolvconf (sudo apt install resolvconf) Q3: SSH 连接 eth0 经常断开 原因：Windows 主机防火墙拦截或 IP 冲突 解决： 确保 Windows 端 192.168.137.1 网卡开启了\u0026quot;文件和打印机共享\u0026quot; 尝试在 Windows 命令行 ping 192.168.137.2 看是否通畅 Q4: 重启后配置失效，又变回 ConnMan 管理 原因：屏蔽（Mask）操作未成功或被撤销 解决：重新执行第 3.2 节中的 sudo systemctl mask connman ... 命令，并确认返回结果为 Created symlink ... -\u0026gt; /dev/null 四、VMware + Ubuntu NFS 共享配置及 VSCode 开发工作流 本章节详细介绍如何配置 VMware Ubuntu 虚拟机与开发板之间的 NFS 共享，以及完整的 VSCode 交叉编译开发和部署流程。\n4.1 VMware 网络适配器配置 4.1.1 添加第二块网络适配器 关闭 Ubuntu 虚拟机 右键虚拟机 → 设置 → 添加 → 网络适配器 选择新添加的网络适配器，设置为： 网络连接: 桥接模式 复制物理网络连接状态: ✓ 勾选 4.1.2 配置 VMware 虚拟网络编辑器 打开 VMware → 编辑 → 虚拟网络编辑器 点击 更改设置（需要管理员权限） 找到 VMnet0（桥接模式）： 类型: 桥接模式 桥接到: 选择宿主机的物理网卡（确保该网卡在 192.168.137.x 网段） 点击 确定 保存 4.2 Ubuntu 主机配置 4.2.1 确认网络接口名称 1 ip addr show 记录新增的网卡名称（如 ens38）\n4.2.2 配置静态 IP 地址 编辑 Netplan 配置文件：\n1 sudo nano /etc/netplan/01-network-manager-all.yaml 添加以下内容（根据实际情况修改网卡名称）：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # Let NetworkManager manage all devices on this system network: version: 2 renderer: NetworkManager ethernets: ens38: dhcp4: no addresses: - 192.168.137.100/24 routes: - to: default via: 192.168.137.1 nameservers: addresses: - 8.8.8.8 - 114.114.114.114 应用配置：\n1 2 sudo chmod 600 /etc/netplan/01-network-manager-all.yaml sudo netplan apply 验证 IP 配置：\n1 ip addr show ens38 应看到类似输出：\n1 inet 192.168.137.100/24 brd 192.168.137.255 scope global ens38 4.2.3 安装 NFS 服务器 1 2 sudo apt update sudo apt install -y nfs-kernel-server 4.2.4 创建共享目录 1 2 sudo mkdir -p /home/frank/nfs_share sudo chmod 777 /home/frank/nfs_share 4.2.5 配置 NFS 导出 编辑 exports 文件：\n1 sudo nano /etc/exports 添加以下行（允许整个 192.168.137.0/24 网段访问）：\n1 /home/frank/nfs_share 192.168.137.0/24(rw,sync,no_subtree_check,no_root_squash) 参数说明：\nrw: 读写权限 sync: 同步写入 no_subtree_check: 禁用子目录检查（提高性能） no_root_squash: 允许 root 用户保持 root 权限 4.2.6 启动并启用 NFS 服务 1 2 3 sudo exportfs -ra sudo systemctl restart nfs-kernel-server sudo systemctl enable nfs-kernel-server 4.2.7 验证 NFS 共享 1 showmount -e localhost 应看到：\n1 2 Export list for localhost: /home/frank/nfs_share 192.168.137.0/24 4.2.8 配置防火墙（如有启用） 1 2 3 4 5 6 7 8 # 检查防火墙状态 sudo ufw status # 如果启用了 UFW，允许 NFS 相关端口 sudo ufw allow from 192.168.137.0/24 to any port nfs sudo ufw allow from 192.168.137.0/24 to any port mountd sudo ufw allow from 192.168.137.0/24 to any port rpc-bind sudo ufw reload 4.3 开发板配置 4.3.1 安装 NFS 客户端 在开发板上执行（通过 MobaXterm SSH 连接，见 4.5 节）：\n1 2 sudo apt update sudo apt install -y nfs-common 如果遇到 \u0026ldquo;bad option\u0026rdquo; 错误，就是因为缺少 nfs-common 包。\n4.3.2 创建挂载点 1 sudo mkdir -p /mnt/nfs 4.3.3 手动挂载 NFS 共享 1 sudo mount -t nfs 192.168.137.100:/home/frank/nfs_share /mnt/nfs 4.3.4 验证挂载 1 2 df -h | grep nfs ls -la /mnt/nfs 应能看到 Ubuntu 主机共享的文件。\n4.3.5 测试读写权限 1 2 3 4 5 6 7 # 在开发板上创建测试文件 touch /mnt/nfs/test_from_board.txt echo \u0026#34;Hello from development board\u0026#34; \u0026gt; /mnt/nfs/test_from_board.txt # 在 Ubuntu 主机上验证 ls -la /home/frank/nfs_share/ cat /home/frank/nfs_share/test_from_board.txt 4.3.6 配置开机自动挂载（可选） 编辑 fstab 文件：\n1 sudo nano /etc/fstab 添加以下行：\n1 192.168.137.100:/home/frank/nfs_share /mnt/nfs nfs defaults,_netdev 0 0 _netdev 选项确保在网络就绪后再挂载。\n测试 fstab 配置：\n1 2 sudo umount /mnt/nfs sudo mount -a 4.4 常见问题排查 问题 1: 开发板无法 ping 通 Ubuntu 1 2 # 在开发板上测试 ping 192.168.137.100 解决方法：\n检查 Ubuntu 的 IP 配置：ip addr show ens38 检查 VMware 网络适配器是否设置为桥接模式 检查 Windows 防火墙是否阻止了 ICMP 请求 确保 Ubuntu 和开发板在同一网段 问题 2: NFS 挂载失败 常见错误及解决：\n1 2 3 4 5 6 7 8 9 10 11 12 13 # 错误: \u0026#34;mount.nfs: access denied by server\u0026#34; # 解决: 检查 /etc/exports 配置，确保 IP 范围正确 sudo exportfs -ra sudo systemctl restart nfs-kernel-server # 错误: \u0026#34;mount.nfs: Connection timed out\u0026#34; # 解决: 检查网络连接和防火墙 ping 192.168.137.100 sudo ufw status # 错误: \u0026#34;mount.nfs: Protocol not supported\u0026#34; # 解决: 指定 NFS 版本 sudo mount -t nfs -o vers=3 192.168.137.100:/home/frank/nfs_share /mnt/nfs 问题 3: 权限问题 1 2 3 4 5 6 # 如果在开发板上无法写入 NFS 共享 # 检查 Ubuntu 上的目录权限 ls -la /home/frank/nfs_share # 确保权限为 777 或适当的所有者 sudo chmod 777 /home/frank/nfs_share 4.5 MobaXterm SSH 连接及 VSCode 开发工作流 本节介绍如何通过 MobaXterm SSH 连接开发板，并将 VSCode 中编译好的程序传送到开发板上运行的完整流程。\n4.5.1 配置 MobaXterm SSH 会话 前提条件：\n开发板 eth0 已配置为 192.168.137.2 Windows 主机对应网卡已配置为 192.168.137.1 两者可以互相 ping 通 SSH 连接步骤：\n启动 MobaXterm 点击左上角 \u0026ldquo;Session\u0026rdquo; → 选择 \u0026ldquo;SSH\u0026rdquo; 配置 SSH 参数： Remote host: 192.168.137.2（开发板 eth0 IP） Specify username: 勾选，输入用户名（如 debian 或 root） Port: 22（默认 SSH 端口） 点击 \u0026ldquo;Advanced SSH settings\u0026rdquo;（可选）： 如需密钥认证，在此配置私钥文件 点击 \u0026ldquo;OK\u0026rdquo; 建立连接 首次连接时会提示接受主机密钥，点击 \u0026ldquo;Yes\u0026rdquo; 输入密码完成登录 💡 提示：可以将此会话保存，方便下次快速连接：\n连接成功后，左侧 Sessions 栏会自动保存 右键会话 → Edit session 可修改配置 双击保存的会话即可快速重连 4.5.2 VSCode 交叉编译程序 在 Ubuntu VM 中编译：\n在 VSCode 中打开项目文件夹（位于 NFS 共享目录或其子目录） 配置交叉编译工具链（示例 .vscode/tasks.json）： 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 { \u0026#34;version\u0026#34;: \u0026#34;2.0.0\u0026#34;, \u0026#34;tasks\u0026#34;: [ { \u0026#34;label\u0026#34;: \u0026#34;Build for i.MX8M\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;shell\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;aarch64-linux-gnu-gcc\u0026#34;, \u0026#34;args\u0026#34;: [ \u0026#34;-o\u0026#34;, \u0026#34;${workspaceFolder}/output/myapp\u0026#34;, \u0026#34;${workspaceFolder}/src/main.c\u0026#34;, \u0026#34;-Wall\u0026#34;, \u0026#34;-O2\u0026#34; ], \u0026#34;group\u0026#34;: { \u0026#34;kind\u0026#34;: \u0026#34;build\u0026#34;, \u0026#34;isDefault\u0026#34;: true }, \u0026#34;problemMatcher\u0026#34;: [\u0026#34;$gcc\u0026#34;] } ] } 按 Ctrl+Shift+B 触发构建任务 编译完成后，可执行文件生成在 ${workspaceFolder}/output/myapp 📌 注意：由于使用了 NFS 共享，编译输出的文件会自动出现在开发板的 /mnt/nfs 目录下！\n4.5.3 方法一：通过 NFS 直接运行（推荐） 这是最便捷的方式，利用已配置的 NFS 共享：\n在开发板上（通过 MobaXterm SSH）：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 # 1. 进入 NFS 挂载目录 cd /mnt/nfs # 2. 查看编译好的程序 ls -la output/ # 3. 赋予执行权限（如果需要） chmod +x output/myapp # 4. 直接运行程序 ./output/myapp # 或者带参数运行 ./output/myapp arg1 arg2 优点：\n✅ 无需额外传输步骤 ✅ 修改代码后重新编译即可立即运行 ✅ 适合频繁调试的开发阶段 缺点：\n⚠️ 依赖 NFS 网络连接稳定性 ⚠️ 性能略低于本地存储（对于大型程序） 4.5.4 方法二：通过 MobaXterm SFTP 传输 如果不想使用 NFS，或需要将程序复制到开发板本地存储：\n使用 MobaXterm 内置 SFTP：\n建立 SSH 连接后，MobaXterm 左侧会自动显示 SFTP 浏览器 右侧窗口显示开发板文件系统 左侧窗口显示 Windows 本地文件系统 传输步骤：\n在 Ubuntu VM 中，将编译好的程序复制到 Windows 可访问的位置：\n1 2 # 在 Ubuntu 终端 cp /home/frank/nfs_share/output/myapp /mnt/hgfs/Shared/myapp （假设已配置 VMware 共享文件夹）\n在 MobaXterm SFTP 浏览器中：\n导航到 Windows 端的程序位置 拖拽文件到开发板的目标目录（如 /home/debian/） 在 SSH 终端中运行：\n1 2 3 cd /home/debian chmod +x myapp ./myapp 4.5.5 方法三：使用 SCP 命令传输 从 Ubuntu VM 发送到开发板：\n1 2 # 在 Ubuntu 终端执行 scp /home/frank/nfs_share/output/myapp debian@192.168.137.2:/home/debian/ 从开发板拉取文件到 Ubuntu：\n1 2 # 在 Ubuntu 终端执行 scp debian@192.168.137.2:/home/debian/logfile.txt /home/frank/nfs_share/ 4.5.6 方法四：使用 rsync 同步（高级） 适合大量文件或需要同步整个目录的场景：\n1 2 3 4 5 # 从 Ubuntu 同步到开发板 rsync -avz /home/frank/nfs_share/output/ debian@192.168.137.2:/home/debian/output/ # 从开发板同步到 Ubuntu rsync -avz debian@192.168.137.2:/home/debian/logs/ /home/frank/nfs_share/logs/ 4.6 完整开发工作流示例 以下是从零开始到程序在开发板上运行的完整流程：\n步骤 1：准备工作 1 2 3 4 5 6 # 【Ubuntu VM】确保 NFS 服务正常运行 sudo systemctl status nfs-kernel-server # 【开发板】确保 NFS 客户端已挂载 df -h | grep nfs # 应看到: 192.168.137.100:/home/frank/nfs_share on /mnt/nfs 步骤 2：编写代码 在 VSCode 中编辑源代码（文件位于 NFS 共享目录）：\n1 2 3 4 5 6 7 // /home/frank/nfs_share/projects/hello/main.c #include \u0026lt;stdio.h\u0026gt; int main() { printf(\u0026#34;Hello from i.MX8M!\\n\u0026#34;); return 0; } 步骤 3：交叉编译 在 VSCode 中按 Ctrl+Shift+B，或在终端执行：\n1 2 cd /home/frank/nfs_share/projects/hello aarch64-linux-gnu-gcc -o hello main.c -Wall -O2 步骤 4：在开发板上运行 通过 MobaXterm SSH 连接开发板后：\n1 2 3 4 5 6 7 8 9 10 11 # 进入 NFS 挂载点 cd /mnt/nfs/projects/hello # 查看文件（应该能看到刚编译的 hello 程序） ls -la hello # 赋予执行权限 chmod +x hello # 运行程序 ./hello 预期输出：\n1 Hello from i.MX8M! 步骤 5：调试与迭代 修改源代码（在 VSCode 中） 重新编译（Ctrl+Shift+B） 直接在开发板 SSH 终端中再次运行 ./hello 重复直到满意 4.6.1 测试：NFS共享与跨平台运行对比测试 本案例将通过实际的 malloc_frag.c 程序，演示完整的 NFS 共享、交叉编译、文件传输以及在 Ubuntu x86_64 和开发板 ARM64 平台上运行对比的全过程。\n案例目标 ✅ 掌握将源代码复制到 NFS 共享目录的方法 ✅ 学习使用 gcc（本地）和交叉编译器（aarch64）分别编译 ✅ 通过 NFS 和 MobaXterm SSH 两种方式传输可执行文件 ✅ 对比同一程序在不同架构平台上的运行结果 ✅ 理解内存管理在嵌入式系统中的重要性 步骤 1：准备源代码 方法 A：从 docs 目录复制到 NFS 共享目录\n在 Ubuntu VM 终端中执行：\n1 2 3 4 5 6 7 8 # 创建项目目录 mkdir -p /home/frank/nfs_share/projects/malloc_test # 复制源代码 cp /home/frank/embedded/docs/notebooks/malloc_frag.c /home/frank/nfs_share/projects/malloc_test/ # 验证文件已复制 ls -la /home/frank/nfs_share/projects/malloc_test/ 方法 B：直接在 VSCode 中打开并另存为\n在 VSCode 中打开 /home/frank/embedded/docs/notebooks/malloc_frag.c 点击 文件 → 另存为 保存到 /home/frank/nfs_share/projects/malloc_test/malloc_frag.c 💡 提示：由于使用了 NFS 共享，此时在开发板上已经可以看到这个文件了！\n步骤 2：在 Ubuntu x86_64 上编译并运行 编译为本机可执行文件：\n1 2 3 4 5 6 7 cd /home/frank/nfs_share/projects/malloc_test # 使用本机 gcc 编译（生成 x86_64 架构的可执行文件） gcc -o malloc_frag_x86 malloc_frag.c -Wall -O2 # 查看生成的文件信息 file malloc_frag_x86 预期输出：\n1 malloc_frag_x86: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, ... 在本机运行：\n1 ./malloc_frag_x86 预期输出：\n1 2 3 4 5 6 7 8 9 10 [Step 1] Heap Init (10 Blocks). [Status] Alloc A,B,C,D,E: X X X X X X X X X X [Step 3] Free B and D to create holes... [Status] Free B, D : X X _ _ X X _ _ X X [Step 4] Try Alloc Block F (Size=3)... Error: Allocation Failed! (Fragmentation detected) Reason: Total Free blocks = 4 (Size\u0026gt;3), but Max Contiguous Space = 2. Conclusion: 这就是为什么嵌入式系统慎用 malloc 的原因。 步骤 3：交叉编译为 ARM64 架构 使用交叉编译工具链：\n1 2 3 4 5 6 7 cd /home/frank/nfs_share/projects/malloc_test # 使用 aarch64 交叉编译器编译（生成 ARM64 架构的可执行文件） aarch64-linux-gnu-gcc -o malloc_frag_arm64 malloc_frag.c -Wall -O2 # 查看生成的文件信息 file malloc_frag_arm64 预期输出：\n1 malloc_frag_arm64: ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, ... ⚠️ 重要说明：\nmalloc_frag_x86 只能在 x86_64 架构（Ubuntu VM）上运行 malloc_frag_arm64 只能在 ARM64 架构（开发板）上运行 如果尝试在错误的平台上运行，会报错：Exec format error 步骤 4：方法一 - 通过 NFS 直接在开发板上运行 由于我们已经配置了 NFS 共享，编译好的 ARM64 程序已经自动出现在开发板的 /mnt/nfs 目录下了！\n在开发板上（通过 MobaXterm SSH 连接）：\n1 2 3 4 5 6 7 8 9 10 11 # 进入 NFS 挂载的项目目录 cd /mnt/nfs/projects/malloc_test # 查看文件列表（应该能看到两个可执行文件） ls -la # 赋予执行权限 chmod +x malloc_frag_arm64 # 运行 ARM64 版本 ./malloc_frag_arm64 预期输出：（与 Ubuntu 上相同）\n1 2 3 4 5 6 7 8 9 10 [Step 1] Heap Init (10 Blocks). [Status] Alloc A,B,C,D,E: X X X X X X X X X X [Step 3] Free B and D to create holes... [Status] Free B, D : X X _ _ X X _ _ X X [Step 4] Try Alloc Block F (Size=3)... Error: Allocation Failed! (Fragmentation detected) Reason: Total Free blocks = 4 (Size\u0026gt;3), but Max Contiguous Space = 2. Conclusion: 这就是为什么嵌入式系统慎用 malloc 的原因。 步骤 5：方法二 - 通过 VSCode 下载 + MobaXterm SFTP 上传 这是最推荐的离线传输方式，适合需要将程序保存到开发板本地存储的场景。\n第一阶段：在 VSCode 中下载编译好的文件到 Windows 操作步骤：\n确保 NFS 共享已挂载\n在 Ubuntu VM 终端中确认：\n1 2 3 df -h | grep nfs_share # 应看到类似输出： # 192.168.137.100:/home/frank/nfs_share on /home/frank/nfs_share type nfs4 在 VSCode 中定位文件\n打开 VSCode 的文件资源管理器（左侧边栏） 导航到：/home/frank/nfs_share/projects/malloc_test/ 找到编译好的文件：malloc_frag_arm64 下载到 Windows 本地\n方法 A：右键菜单下载\n在 VSCode 文件树中，右键点击 malloc_frag_arm64 选择 \u0026ldquo;Download\u0026hellip;\u0026rdquo; 或 \u0026ldquo;另存为\u0026rdquo; 选择 Windows 本地的保存位置（如 C:\\Users\\YourName\\Downloads\\） 方法 B：拖拽下载\n直接从 VSCode 文件树拖拽 malloc_frag_arm64 到 Windows 桌面或文件夹 方法 C：命令行复制（如果配置了 VMware 共享文件夹）\n1 2 3 # 在 Ubuntu VM 终端执行 cp /home/frank/nfs_share/projects/malloc_test/malloc_frag_arm64 /mnt/hgfs/Shared/ # 然后在 Windows 的 C:\\Shared\\ 目录中找到该文件 验证文件已下载\n在 Windows 文件资源管理器中确认文件存在：\n文件大小应与 Ubuntu 上的一致 文件名保持为 malloc_frag_arm64（无扩展名） 💡 提示：VSCode 的下载功能依赖于 Remote-SSH 或本地文件系统访问。如果使用 NFS 挂载，可以直接在 Windows 资源管理器中访问 \\\\ubuntu-ip\\nfs_share（需要配置 Samba 共享）。\n第二阶段：通过 MobaXterm SFTP 上传到开发板 操作步骤：\n启动 MobaXterm 并连接 SSH\n打开 MobaXterm 双击之前保存的 SSH 会话（或新建 SSH 连接到 192.168.137.2） 输入用户名和密码完成登录 使用内置 SFTP 浏览器\n连接成功后，MobaXterm 左侧会自动显示 SFTP 面板：\n上半部分：显示开发板的文件系统（远程） 下半部分：显示 Windows 本地文件系统 导航到文件位置\n在 Windows 侧（下半部分）：\n浏览到你刚才下载文件的目录 例如：C:\\Users\\YourName\\Downloads\\ 找到 malloc_frag_arm64 文件 在开发板侧（上半部分）：\n导航到目标目录，例如：/home/debian/ 或者创建专门的项目目录： 1 2 3 # 在 MobaXterm SSH 终端中执行 mkdir -p /home/debian/projects/malloc_test cd /home/debian/projects/malloc_test 拖拽上传文件\n从 Windows 侧（下半部分）选中 malloc_frag_arm64 拖拽到开发板侧（上半部分）的目标目录 等待传输完成（底部会显示进度条） 验证文件已上传\n在 MobaXterm SSH 终端中执行：\n1 2 cd /home/debian/projects/malloc_test ls -la malloc_frag_arm64 预期输出：\n1 -rw-r--r-- 1 debian debian 16384 Apr 9 10:30 malloc_frag_arm64 赋予执行权限并运行\n1 2 3 4 5 # 赋予执行权限 chmod +x malloc_frag_arm64 # 运行程序 ./malloc_frag_arm64 预期输出：\n1 2 3 4 5 6 7 8 9 10 [Step 1] Heap Init (10 Blocks). [Status] Alloc A,B,C,D,E: X X X X X X X X X X [Step 3] Free B and D to create holes... [Status] Free B, D : X X _ _ X X _ _ X X [Step 4] Try Alloc Block F (Size=3)... Error: Allocation Failed! (Fragmentation detected) Reason: Total Free blocks = 4 (Size\u0026gt;3), but Max Contiguous Space = 2. Conclusion: 这就是为什么嵌入式系统慎用 malloc 的原因。 优势与适用场景 ✅ 优势：\n离线可用：文件保存在 Windows 本地，无需持续网络连接 版本管理：可以在 Windows 上保留多个版本的备份 灵活性强：可以随时重新上传，不受 NFS 挂载状态影响 可视化操作：MobaXterm SFTP 界面直观，支持拖拽 ⚠️ 注意：\n需要额外的下载和上传步骤 修改代码后需要重新编译、下载、上传 适合最终部署或存档，不适合频繁调试 步骤 6：方法三 - 使用 SCP 命令直接从 Ubuntu 发送 这是最快速的命令行传输方式，适合熟悉 Linux 命令的用户。\n在 Ubuntu VM 终端中执行：\n1 2 3 4 5 6 7 8 # 确保交叉编译已完成 cd /home/frank/nfs_share/projects/malloc_test ls -la malloc_frag_arm64 # 使用 SCP 直接发送到开发板 scp malloc_frag_arm64 debian@192.168.137.2:/home/debian/projects/malloc_test/ # 输入开发板密码（默认为 temppwd） SCP 命令详解：\n1 scp [选项] 源文件 用户名@目标IP:目标路径 malloc_frag_arm64：要传输的文件 debian：开发板上的用户名 192.168.137.2：开发板的 IP 地址 /home/debian/projects/malloc_test/：目标目录 常用 SCP 选项：\n1 2 3 4 5 6 7 8 # 递归传输整个目录 scp -r /home/frank/nfs_share/projects/malloc_test/ debian@192.168.137.2:/home/debian/projects/ # 显示传输进度 scp -v malloc_frag_arm64 debian@192.168.137.2:/home/debian/ # 压缩传输（适合大文件） scp -C malloc_frag_arm64 debian@192.168.137.2:/home/debian/ 然后在开发板 SSH 终端中运行：\n1 2 3 4 5 6 7 8 9 10 11 # 连接到开发板（如果还未连接） ssh debian@192.168.137.2 # 进入目标目录 cd /home/debian/projects/malloc_test # 赋予执行权限 chmod +x malloc_frag_arm64 # 运行程序 ./malloc_frag_arm64 反向传输：从开发板拉取文件到 Ubuntu 如果需要将开发板上生成的日志或数据文件传回 Ubuntu：\n1 2 # 在 Ubuntu VM 终端执行 scp debian@192.168.137.2:/home/debian/projects/malloc_test/output.log /home/frank/nfs_share/projects/malloc_test/ 优势与适用场景 ✅ 优势：\n速度快：一条命令完成传输 可脚本化：可以轻松集成到自动化脚本中 双向传输：支持上传和下载 无需图形界面：纯命令行操作，适合远程服务器 ⚠️ 注意：\n需要记住命令语法 每次传输都需要输入密码（除非配置 SSH 密钥） 适合熟悉 Linux 的用户 步骤 7：对比分析运行结果 对比项 1：输出内容一致性\n平台 架构 输出内容 是否一致 Ubuntu VM x86_64 内存碎片化演示 ✅ 开发板 ARM64 内存碎片化演示 ✅ 结论：程序的逻辑输出完全一致，因为这是纯计算型程序，不涉及平台特定的硬件操作。\n对比项 2：可执行文件格式\n1 2 3 4 5 6 # 在 Ubuntu 上检查 file malloc_frag_x86 # 输出: ELF 64-bit LSB pie executable, x86-64, ... file malloc_frag_arm64 # 输出: ELF 64-bit LSB pie executable, ARM aarch64, ... 对比项 3：文件大小差异\n1 ls -lh malloc_frag_* 可能会观察到：\n两个文件大小略有不同（由于不同的指令集编码） ARM64 版本可能稍大或稍小，取决于编译器优化 对比项 4：性能差异（可选测试）\n可以使用 time 命令测量执行时间：\n1 2 3 4 5 # 在 Ubuntu 上 time ./malloc_frag_x86 # 在开发板上 time ./malloc_frag_arm64 📊 注意：由于这是一个非常小的程序，执行时间差异可能不明显。对于复杂程序，ARM 处理器（尤其是嵌入式级别）通常会比桌面级 x86_64 慢。\n步骤 8：深入理解 - 为什么需要交叉编译？ 尝试在开发板上运行 x86_64 版本（会失败）：\n1 2 3 4 5 6 7 # 先将 x86_64 版本传到开发板 scp /home/frank/nfs_share/projects/malloc_test/malloc_frag_x86 debian@192.168.137.2:/home/debian/ # 在开发板上尝试运行 cd /home/debian chmod +x malloc_frag_x86 ./malloc_frag_x86 预期错误：\n1 -bash: ./malloc_frag_x86: cannot execute binary file: Exec format error 原因解释：\nCPU 只能理解其原生指令集 x86_64 指令 ≠ ARM64 指令 就像让只会中文的人读英文书一样，CPU \u0026ldquo;看不懂\u0026rdquo; 错误的指令格式 反过来也一样：\n1 2 # 在 Ubuntu 上尝试运行 ARM64 版本 ./malloc_frag_arm64 同样会报错：\n1 cannot execute binary file: Exec format error 步骤 9：实验总结 通过这个实践案例，我们学习了：\n✅ NFS 共享的优势：编译后立即可见，无需手动传输 ✅ 交叉编译的必要性：不同架构需要不同的编译器 ✅ 多种文件传输方式：NFS、SFTP、SCP 各有适用场景 ✅ 平台差异性：同一源码在不同架构上产生不同的可执行文件 ✅ 嵌入式内存管理：通过 malloc_frag.c 理解了内存碎片化问题 关键收获：\n🔑 NFS 是最便捷的开发方式：修改代码 → 重新编译 → 直接在开发板运行，整个流程无缝衔接！\n🔑 交叉编译是嵌入式开发的基石：必须为目标平台选择合适的编译工具链。\n🔑 理解底层原理很重要：知道为什么不能混用不同架构的可执行文件，有助于排查问题。\n4.7 高级技巧（实用小技巧，非必需掌握内容，根据自己的情况选择学习） 4.7.1 自动化部署脚本 在项目根目录创建 deploy.sh：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #!/bin/bash # deploy.sh - 自动编译并部署到开发板 PROJECT_NAME=\u0026#34;myapp\u0026#34; BUILD_DIR=\u0026#34;/home/frank/nfs_share/projects/${PROJECT_NAME}\u0026#34; BOARD_IP=\u0026#34;192.168.137.2\u0026#34; BOARD_USER=\u0026#34;debian\u0026#34; echo \u0026#34;=== Building ${PROJECT_NAME} ===\u0026#34; cd ${BUILD_DIR} make clean \u0026amp;\u0026amp; make if [ $? -ne 0 ]; then echo \u0026#34;❌ Build failed!\u0026#34; exit 1 fi echo \u0026#34;✅ Build successful\u0026#34; echo \u0026#34;💡 Program is available at: /mnt/nfs/projects/${PROJECT_NAME}/${PROJECT_NAME}\u0026#34; echo \u0026#34;💡 Run on board: ssh ${BOARD_USER}@${BOARD_IP}\u0026#34; echo \u0026#34; Then execute: cd /mnt/nfs/projects/${PROJECT_NAME} \u0026amp;\u0026amp; ./${PROJECT_NAME}\u0026#34; 4.7.2 VSCode Remote SSH 扩展（替代方案） 如果希望直接在 VSCode 中编辑开发板上的文件：\n在 VSCode 中安装 Remote - SSH 扩展 按 F1 → 输入 \u0026ldquo;Remote-SSH: Connect to Host\u0026rdquo; 输入：debian@192.168.137.2 输入密码 现在可以直接在 VSCode 中编辑开发板上的文件，并使用集成终端运行命令 ⚠️ 注意：这种方式不经过 NFS，适合小项目或不需要交叉编译的场景。\n4.7.3 性能优化建议 NFS 挂载选项优化：\n1 2 # 在开发板 /etc/fstab 中使用更优化的选项 192.168.137.100:/home/frank/nfs_share /mnt/nfs nfs rw,sync,hard,intr,rsize=8192,wsize=8192,_netdev 0 0 减少编译时间：\n使用 ccache 缓存编译结果 并行编译：make -j4 网络稳定性：\n优先使用有线以太网而非 WiFi 避免在大文件传输时进行其他网络密集型操作 附录：常用命令速查 网络诊断 1 2 3 4 5 6 7 8 9 10 11 # 检查 IP 配置 ip addr show # 检查路由表 route -n # Ping 测试 ping -c 4 192.168.137.100 # 检查 DNS nslookup www.baidu.com NFS 相关 1 2 3 4 5 6 7 8 9 10 11 # 查看 NFS 共享 showmount -e 192.168.137.100 # 挂载 NFS sudo mount -t nfs 192.168.137.100:/home/frank/nfs_share /mnt/nfs # 卸载 NFS sudo umount /mnt/nfs # 检查挂载状态 df -h | grep nfs 系统服务 1 2 3 4 5 6 7 # 查看服务状态 sudo systemctl status nfs-kernel-server sudo systemctl status networking # 重启服务 sudo systemctl restart nfs-kernel-server sudo systemctl restart networking 总结 本文档涵盖了从认识开发板、串口连接、网络配置到 NFS 共享和完整开发工作流的全部内容。关键要点：\n串口连接是初始访问开发板的唯一方式，务必掌握 MobaXterm 串口配置 双网卡配置中，usb0 负责上网，eth0 负责调试，两者分工明确 NFS 共享极大简化了文件传输流程，实现\u0026quot;编译即部署\u0026quot; MobaXterm SSH 提供了便捷的远程终端和文件传输功能 VSCode + NFS 组合实现了高效的交叉编译开发体验 祝您开发顺利！🎉\n文档版本: v1.0\n最后更新: 2026年4月9日\n适用平台: 野火 i.MX8M Mini 开发板 + VMware Ubuntu + Windows 主机\n","date":"2026-04-20T00:00:00Z","permalink":"https://ren517.xyz/p/%E9%87%8E%E7%81%ABimx8m%E5%BC%80%E5%8F%91%E6%9D%BF%E5%BC%80%E5%8F%91%E9%85%8D%E7%BD%AE%E6%8C%87%E5%8D%97/","title":"野火imx8m开发板开发配置指南"},{"content":"鉴于最近学习了git的命令行操作，不再依赖于图形界面，所以决定将git的常用命令记录下来，顺便给博客添加temp分支。\n基础命令\n初始化仓库\n1 git init 添加文件, 可以一次添加一个或多个文件。 如 git add file1 file2 file3\n或可以直接添加所有文件\n1 git add . 提交\n1 git commit -m \u0026#34;message\u0026#34; 推送\n1 git push 注： 在创建了分支之后，语法会有所变化\n创建gitignore文件 创建.gitignore文件，并添加需要忽略的文件。\n例如\n1 2 3 4 5 public/ resources/ .hugo_build.lock .DS_Store node_modules/ 在里面添加需要忽略的文件即可自动忽略添加。\n创建分支\n1 git switch -c [分支名] 例如 创建分支temp\n1 git switch -c temp 删除分支\n1 git branch -d [分支名] 切换分支\n1 git switch [分支名] 拉取分支\n1 git pull origin [分支名] 推送分支\n1 git push origin [分支名] 合并分支\n切换到目标分支 1 git switch [目标分支名] 合并源分支 1 git merge [源分支名] 或\n1 git rebase [源分支名] merge和rebase的区别 merge：合并两个分支，合并后的结果会生成一个新的commit，并生成一个新的分支。 rebase：将源分支的commit合并到目标分支，并生成一个新的commit。\nmerge 是非线性的，rebase 是线性的。\n图解二者区别\nmerge 后的树状图\nrebase 后的树状图\n","date":"2026-04-08T00:00:00Z","image":"https://ren517.xyz/p/git%E6%B7%BB%E5%8A%A0%E5%88%86%E6%94%AF%E5%8F%8A%E4%B8%80%E4%BA%9B%E5%9F%BA%E7%A1%80%E6%93%8D%E4%BD%9C%E8%AE%B0%E5%BD%95/images/image_hu_382bb56fccdf1828.png","permalink":"https://ren517.xyz/p/git%E6%B7%BB%E5%8A%A0%E5%88%86%E6%94%AF%E5%8F%8A%E4%B8%80%E4%BA%9B%E5%9F%BA%E7%A1%80%E6%93%8D%E4%BD%9C%E8%AE%B0%E5%BD%95/","title":"git添加分支及一些基础操作记录"},{"content":"本文将利用LSTM处理nlp分类任务，数据集为各大平台的评论，经过训练后，可以预测一句话的情感是否为正。\n1.准备数据集 数据集来源于github https://github.com/SophonPlus/ChineseNlpCorpus\n2.创建项目结构 创建项目结构如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 📁 目录结构 LSTM-review-analyze/ ├── data/ # 数据目录 │ ├── raw/ # 原始数据 │ └── processed/ # 处理后的数据 (train.jsonl, test.jsonl) │ ├── models/ # 模型存储目录 │ ├── model.pth # 训练好的模型权重 │ └── vocab.txt # 词汇表 │ ├── src/ # 源代码目录 │ ├── config.py # 配置文件（超参数） │ ├── model.py # LSTM模型定义 │ ├── dataset.py # 数据加载器 │ ├── tokenizer.py # Jieba分词器 │ ├── train.py # 训练脚本 │ ├── predict.py # 预测脚本 │ └── process.py # 数据处理 │ └── logs/ # TensorBoard日志目录 3.数据处理 设置config.py文件，设置超参数。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from pathlib import Path ROOT_DIR = Path(__file__).parent.parent RAW_DATA_DIR = ROOT_DIR / \u0026#34;data\u0026#34; / \u0026#34;raw\u0026#34; PROCESSED_DATA_DIR = ROOT_DIR / \u0026#34;data\u0026#34; / \u0026#34;processed\u0026#34; LOGS_DIR = ROOT_DIR / \u0026#34;logs\u0026#34; MODELS_DIR = ROOT_DIR / \u0026#34;models\u0026#34; SEQ_LEN = 128 BATCH_SIZE = 64 EMBEDDING_DIM = 128 HIDDEN_SIZE = 256 LEARNING_RATE = 1e-3 EPOCHS = 10 导入tokenizer.py文件\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 import jieba from tqdm import tqdm class JiebaTokenizer: unk_token = \u0026#34;\u0026lt;unk\u0026gt;\u0026#34; pad_token = \u0026#34;\u0026lt;pad\u0026gt;\u0026#34; def __init__(self, vocab_list): self.vocab_list = vocab_list self.vocab_size = len(vocab_list) self.word2index = {word: index for index, word in enumerate(vocab_list)} self.index2word = {index: word for index, word in enumerate(vocab_list)} self.unk_token_index = self.word2index[self.unk_token] self.pad_token_index = self.word2index[self.pad_token] @staticmethod def tokenize(text): return jieba.lcut(text) def encode(self, text, seq_len): tokens = self.tokenize(text) # 截取或填充 if len(tokens) \u0026gt; seq_len: tokens = tokens[:seq_len] elif len(tokens) \u0026lt; seq_len: tokens += [self.pad_token] * (seq_len - len(tokens)) return [self.word2index.get(token, self.unk_token_index) for token in tokens] @classmethod def build_vocab(cls, sentences, vocab_path): vocab_set = set() for sentence in tqdm(sentences, desc=\u0026#34;构建词表\u0026#34;): vocab_set.update(jieba.lcut(sentence)) vocab_list = [cls.pad_token, cls.unk_token] + [ token for token in vocab_set if token.strip() != \u0026#34;\u0026#34; ] print(f\u0026#34;词表大小:{len(vocab_list)}\u0026#34;) # 保存词表 with open(vocab_path, \u0026#34;w\u0026#34;, encoding=\u0026#34;utf-8\u0026#34;) as f: f.write(\u0026#34;\\n\u0026#34;.join(vocab_list)) return cls(vocab_list) @classmethod def from_vocab(cls, vocab_path): \u0026#34;\u0026#34;\u0026#34; 从文件加载词汇表并创建 Tokenizer 实例 :param cls: 类本身 :param vocab_path: 词汇表文件的路径 (例如 \u0026#39;vocab.txt\u0026#39;) :return: JiebaTokenizer 实例 \u0026#34;\u0026#34;\u0026#34; with open(vocab_path, \u0026#34;r\u0026#34;, encoding=\u0026#34;utf-8\u0026#34;) as f: # 读取每一行，去掉首尾空白符（如换行符 \\n） vocab_list = [line.strip() for line in f.readlines()] # 使用读取到的列表初始化类实例 return cls(vocab_list) def decode(self, indices): \u0026#34;\u0026#34;\u0026#34;将索引序列解码回词语列表\u0026#34;\u0026#34;\u0026#34; return [self.index2word.get(idx, self.unk_token) for idx in indices] def save(self, path): \u0026#34;\u0026#34;\u0026#34;保存词汇表到文件\u0026#34;\u0026#34;\u0026#34; with open(path, \u0026#34;w\u0026#34;, encoding=\u0026#34;utf-8\u0026#34;) as f: for word in self.vocab_list: f.write(word + \u0026#34;\\n\u0026#34;) @classmethod def load(cls, path): \u0026#34;\u0026#34;\u0026#34;从文件加载词汇表并创建 tokenizer 实例\u0026#34;\u0026#34;\u0026#34; with open(path, \u0026#34;r\u0026#34;, encoding=\u0026#34;utf-8\u0026#34;) as f: vocab_list = [line.strip() for line in f if line.strip()] return cls(vocab_list) 编写创建数据处理脚本process.py\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 import config import pandas as pd from sklearn.model_selection import train_test_split from tokenizer import JiebaTokenizer def process(): print(\u0026#34;开始处理数据\u0026#34;) df = ( pd.read_csv( config.RAW_DATA_DIR / \u0026#34;online_shopping_10_cats.csv\u0026#34;, usecols=[\u0026#34;label\u0026#34;, \u0026#34;review\u0026#34;], # 选择列，用label和review列 encoding=\u0026#34;utf-8\u0026#34;, ) .dropna() # 删除DataFrame中包含缺失值的行或列 .sample(frac=1) # 取100%数据 ) # 划分数据集 train_df, test_df = train_test_split(df, test_size=0.2, stratify=df[\u0026#34;label\u0026#34;]) # stratify 按label分层抽样 # 构建词表 JiebaTokenizer.build_vocab( (train_df[\u0026#34;review\u0026#34;].tolist()), vocab_path=config.MODELS_DIR / \u0026#34;vocab.txt\u0026#34; ) # 创建tokenizer tokenizer = JiebaTokenizer.from_vocab(config.MODELS_DIR / \u0026#34;vocab.txt\u0026#34;) # 计算序列长度 # max_len = train_df[\u0026#34;review\u0026#34;].apply(lambda x: len(tokenizer.tokenize((x)))).quantile(0.95) # 取整为128 # 编码训练集 train_df[\u0026#34;review\u0026#34;] = train_df[\u0026#34;review\u0026#34;].apply (lambda x: tokenizer.encode(x, 128)) # 导出训练集 train_df.to_json( config.PROCESSED_DATA_DIR / \u0026#34;train.jsonl\u0026#34;, orient=\u0026#34;records\u0026#34;, lines=True, ) # lines = True 表示每行是一个JSON对象 为jsonl文件， 没有换行符和方括号 # 编码测试集 test_df[\u0026#34;review\u0026#34;] = test_df[\u0026#34;review\u0026#34;].apply(lambda x: tokenizer.encode(x, 128)) # 导出测试集 test_df.to_json( config.PROCESSED_DATA_DIR / \u0026#34;test.jsonl\u0026#34;, orient=\u0026#34;records\u0026#34;, lines=True, ) print(\u0026#34;数据处理完成\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: process() 运行process.py文件，生成训练集和测试集。\n1 2 3 4 5 6 data/ # 数据目录 │ ├── raw/ # 原始数据 │ └── processed/ # 处理后的数据 (train.jsonl, test.jsonl) │ | test.jsonl │ | train.jsonl └── 4.创建数据加载器 创建数据加载器dataset.py\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 import config import pandas as pd import torch from torch.utils.data import DataLoader, Dataset class ReviewAnalyzeDataset(Dataset): def __init__(self, path): self.data = pd.read_json(path, lines=True, orient=\u0026#34;records\u0026#34;).to_dict( orient=\u0026#34;records\u0026#34; ) # data设置为字典类型 def __len__(self): return len(self.data) def __getitem__(self, index): input_tensor = torch.tensor(self.data[index][\u0026#34;review\u0026#34;], dtype=torch.long) # BCEWithLogitsLoss 需要标签为 float 类型 target_tensor = torch.tensor(self.data[index][\u0026#34;label\u0026#34;], dtype=torch.float) return input_tensor, target_tensor def get_dataloader(train=True): path = config.PROCESSED_DATA_DIR / (\u0026#34;train.jsonl\u0026#34; if train else \u0026#34;test.jsonl\u0026#34;) dataset = ReviewAnalyzeDataset(path) return DataLoader(dataset, batch_size=config.BATCH_SIZE, shuffle=True) # shuffle 打乱数据 if __name__ == \u0026#34;__main__\u0026#34;: train_dataloader = get_dataloader(train=True) test_dataloader = get_dataloader(train=False) print(len(train_dataloader)) print(len(test_dataloader)) for input_tensor, target_tensor in train_dataloader: print(input_tensor.shape) print(target_tensor.shape) break 运行dataset.py文件，测试数据加载器。\n1 2 3 4 785 197 torch.Size([64, 128]) torch.Size([64]) 5.创建模型 创建模型model.py\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 import config import torch from torch import nn class ReviewAnalyzeModel(nn.Module): def __init__(self, vocab_size, padding_index): super(ReviewAnalyzeModel, self).__init__() self.embedding = nn.Embedding( vocab_size, config.EMBEDDING_DIM, padding_idx=padding_index ) self.lstm = nn.LSTM( input_size=config.EMBEDDING_DIM, hidden_size=config.HIDDEN_SIZE, batch_first=True, ) self.linear = nn.Linear(config.HIDDEN_SIZE, 1) def forward(self, x): # x.shape = [batch_size, seq_len] embed = self.embedding(x) # embed.shape = [batch_size, seq_len, embedding_dim] output, (_, _) = self.lstm(embed) # output.shape = [batch_size, seq_len, hidden_size] # 取出最后一个时间步的隐藏状态 batch_indexs = torch.arange(0, output.shape[0]) # output = output[:, -1, :] # 由于x中第二维有很多0填充，需要改善 lengths = (x != self.embedding.padding_idx).sum( dim=1 ) - 1 # 计算每个样本的实际长度，减去1得到最后一个非0位置的索引 # 取出最后一个时间步的隐藏状态 last_hidden = output[batch_indexs, lengths] # last_hidden.shape = [batch_size, hidden_size] output = self.linear(last_hidden) return output.squeeze(-1) # output.shape = [batch_size] 定义embedding层\n1 2 3 4 5 self.embedding = nn.Embedding( vocab_size, config.EMBEDDING_DIM, padding_idx=padding_index ) # vocab_size 表示有多少个单词，padding_idx 表示填充的索引 # embedding_dim 表示每个单词的维度 这个在前面计算得出为128 定义LSTM层\n1 2 3 4 5 6 self.lstm = nn.LSTM( input_size=config.EMBEDDING_DIM, hidden_size=config.HIDDEN_SIZE, batch_first=True, # 设置batch_first=True意味着输入和输出的张量形状为 (batch, seq, feature)，这使得处理批量数据时更符合直觉 ) 定义全连接层 线性层Linear\n1 self.linear = nn.Linear(config.HIDDEN_SIZE, 1) 计算句子的实际长度\n1 2 3 lengths = (x != self.embedding.padding_idx).sum( dim=1 ) - 1 # 计算每个样本的实际长度，减去1得到最后一个非0位置的索引 如图示\n1 2 output.squeeze(-1) # output.shape = [batch_size] 6.训练模型 创建训练模型train.py\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 import time import config import torch from dataset import get_dataloader from model import ReviewAnalyzeModel from tokenizer import JiebaTokenizer from torch.utils.tensorboard import SummaryWriter from tqdm import tqdm def train_one_epoch(model, dataloader, loss_fn, optimizer, device): total_loss = 0 model.train() for inputs, targets in tqdm(dataloader, desc=\u0026#34;Training\u0026#34;): inputs = inputs.to(device) # inputs.shape = [batch_size, seq_len] targets = targets.to(device) # targets.shape = [batch_size] output = model(inputs) loss = loss_fn(output, targets) loss.backward() optimizer.step() optimizer.zero_grad() total_loss += loss.item() return total_loss / len(dataloader) def train(): # 1.设备 device = torch.device(\u0026#34;cuda\u0026#34; if torch.cuda.is_available() else \u0026#34;cpu\u0026#34;) # 2.数据 dataloader = get_dataloader() # 3.分词器 tokenizer = JiebaTokenizer.from_vocab(config.MODELS_DIR / \u0026#34;vocab.txt\u0026#34;) # 4.模型 model = ReviewAnalyzeModel(tokenizer.vocab_size, tokenizer.pad_token_index).to( device ) # 5.损失函数 loss_fn = torch.nn.BCEWithLogitsLoss() # 6.优化器 optimizer = torch.optim.Adam(model.parameters(), lr=config.LEARNING_RATE) # 7.Tensorboard Writer writer = SummaryWriter(log_dir=config.LOGS_DIR / time.strftime(\u0026#34;%Y-%m-%d-%H-%M-%S\u0026#34;)) best_loss = float(\u0026#34;inf\u0026#34;) # 8.训练 for epoch in range(1, config.EPOCHS + 1): print(f\u0026#34;========= Epoch {epoch} =========\u0026#34;) loss = train_one_epoch(model, dataloader, loss_fn, optimizer, device) # noqa: F821 print(f\u0026#34;loss: {loss:.4f}\u0026#34;) writer.add_scalar(\u0026#34;loss\u0026#34;, loss, epoch) # 添加loss到Tensorboard # 保存模型 if loss \u0026lt; best_loss: best_loss = loss torch.save(model.state_dict(), config.MODELS_DIR / \u0026#34;model.pth\u0026#34;) print(\u0026#34;模型已保存！\u0026#34;) writer.close() if __name__ == \u0026#34;__main__\u0026#34;: train() 函数train_one_epoch() 训练一个epoch\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 def train_one_epoch(model, dataloader, loss_fn, optimizer, device): total_loss = 0 model.train() # tqdm用于显示进度条 for inputs, targets in tqdm(dataloader, desc=\u0026#34;Training\u0026#34;): inputs = inputs.to(device) # inputs.shape = [batch_size, seq_len] targets = targets.to(device) # targets.shape = [batch_size] output = model(inputs) loss = loss_fn(output, targets) # 反向传播 loss.backward() # 优化器更新参数 optimizer.step() # 梯度清零 optimizer.zero_grad() # 计算loss total_loss += loss.item() return total_loss / len(dataloader) 7.预测结果 创建预测模型predict.py\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 import config import torch from model import ReviewAnalyzeModel from tokenizer import JiebaTokenizer def predict_batch(model, input_tensor): model.eval() with torch.no_grad(): # 传入模型 outputs = model(input_tensor) # output.shape = [batch_size] # 将输出映射到[0,1] outputs = torch.sigmoid(outputs) batch_result = outputs.cpu().numpy() return batch_result.tolist() def predict(text, model, tokenizer, device): # 1.处理输入 # 将输入转为tensor indexes = tokenizer.encode(text, config.SEQ_LEN) # indexes.shape = [seq_len] input_tensor = torch.tensor([indexes], dtype=torch.long) # input_tensor.shape = [1, seq_len] input_tensor = input_tensor.to(device) # 2.预测逻辑 batch_result = predict_batch(model, input_tensor) return batch_result[0] def run_predict(): # 1.设备 device = torch.device(\u0026#34;cuda\u0026#34; if torch.cuda.is_available() else \u0026#34;cpu\u0026#34;) # 2.词表 tokenizer = JiebaTokenizer.from_vocab(config.MODELS_DIR / \u0026#34;vocab.txt\u0026#34;) print(\u0026#34;词表加载成功\u0026#34;) # 3.模型 # 模型参数设置 model = ReviewAnalyzeModel(tokenizer.vocab_size, tokenizer.pad_token_index).to( device ) # 模型选择 model.load_state_dict( torch.load(config.MODELS_DIR / \u0026#34;model.pth\u0026#34;, map_location=device) ) print(\u0026#34;欢迎使用情感分析模型\u0026#34;) while True: user_input = input(\u0026#34;\u0026gt;\u0026#34;) if user_input in [\u0026#34;q\u0026#34;, \u0026#34;quit\u0026#34;]: print(\u0026#34;退出程序\u0026#34;) break if user_input.strip() == \u0026#34;\u0026#34;: print(\u0026#34;输入不能为空，请重新输入\u0026#34;) continue result = predict(user_input, model, tokenizer, device) if result \u0026gt; 0.5: print(\u0026#34;正向\u0026#34;) print(f\u0026#34;概率: {result:.4f}\u0026#34;) else: print(\u0026#34;负向\u0026#34;) print(f\u0026#34;概率: {1 - result:.4f}\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: run_predict() 运行predict.py\n可以看到，在简单任务上表现还不错\n但在稍微绕一点的语句上，结果会很差\n后续会利用其他模型继续训练，下一个将会评估我这个模型\n","date":"2026-03-28T00:00:00Z","permalink":"https://ren517.xyz/p/lstn%E9%A2%84%E6%B5%8B%E6%96%87%E6%9C%AC%E6%83%85%E6%84%9F/","title":"LSTN预测文本情感"},{"content":"上节，我用到了LSTM模型对文本进行分类，并成功训练了模型。 本次我来完成模型评估。\n分析之前训练的模型，利用tensorboard查看logs 可以看到loss从0.3843下降到了0.0136，效果非常不错。\n原理： 对测试集进行预测，计算预测结果与真实结果之间的差异，计算出准确率。\n创建evaluate.py文件\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 import config import torch from dataset import get_dataloader from model import ReviewAnalyzeModel from predict import predict_batch from tokenizer import JiebaTokenizer def evaluate(model, test_dataloader, device): model.eval() # 设置模型为评估模式 total_count = 0 correct_count = 0 # 评估逻辑 for inputs, targets in test_dataloader: inputs, targets = inputs.to(device), targets.to(device) target = targets.tolist() batch_result = predict_batch(model, inputs) # batch_result.shape = [batch_size] # target.shape = [batch_size] for result, target in zip(batch_result, target): # 将预测结果和真实标签进行配对 if result \u0026gt;= 0.5: result = 1 elif result \u0026lt; 0.5 and result \u0026gt;= 0: result = 0 total_count += 1 if result == target: correct_count += 1 accuracy = correct_count / total_count if total_count \u0026gt; 0 else 0 return accuracy def run_evaluate(): # 确定设备 device = \u0026#34;cuda\u0026#34; if torch.cuda.is_available() else \u0026#34;cpu\u0026#34; # 词表 tokenizer = JiebaTokenizer.from_vocab(config.MODELS_DIR / \u0026#34;vocab.txt\u0026#34;) # 模型 model = ReviewAnalyzeModel( vocab_size=tokenizer.vocab_size, padding_index=tokenizer.pad_token_index ).to(device) model.load_state_dict(torch.load(config.MODELS_DIR / \u0026#34;model.pth\u0026#34;)) print(\u0026#34;模型加载成功！\u0026#34;) # 数据集 test_dataloader = get_dataloader(train=False) # 评估逻辑 acc = evaluate(model, test_dataloader, device) print(f\u0026#34;准确率：{acc:.4f}\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: run_evaluate() 运行evaluate.py文件，结果如下：\n可以看到LSTM准确率还不错，后续会利用bert，Transformer等模型进行对比。\n","date":"2026-03-28T00:00:00Z","permalink":"https://ren517.xyz/p/lstn%E9%A2%84%E6%B5%8B%E6%96%87%E6%9C%AC%E6%83%85%E6%84%9F%E7%9A%84%E6%A8%A1%E5%9E%8B%E8%AF%84%E4%BC%B0/","title":"LSTN预测文本情感的模型评估"},{"content":"TensorBoard 是由 Google 开发、随 TensorFlow 一起提供的一个可视化工具，主要用来帮助你观察深度学习模型训练过程中的各种数据（比如 loss、准确率、网络结构等）。现在不仅 TensorFlow，可以配合 PyTorch 使用。同时，在论文中也被广泛使用。\npytorch里面怎么用 导包\n1 2 pip install torch pip install tensorboard 基本使用语法\n1 2 3 4 5 6 from torch.utils.tensorboard import SummaryWriter writer = SummaryWriter(\u0026#34;my_experiment\u0026#34;) for i in range(100): writer.add_scalar(\u0026#34;y=x\u0026#34;, i, i) writer.close() 运行后可以看见项目中多了一个 my_experiment 文件夹，里面有 events.out.tfevents.1629318657.DESKTOP-HJDJJ0H.log 文件。\n现在需要运行该文件，使用bash命令\n1 tensorboard --logdir=my_experiment 在浏览器中打开\n1 http://localhost:6006 可以看到\n导入图片\n1 2 3 4 5 6 7 8 9 10 11 from torch.utils.tensorboard import SummaryWriter import cv2 import numpy as np writer = SummaryWriter(\u0026#34;run\u0026#34;) img = cv2.imread(\u0026#34;dataset\\\\train\\\\ants_image\\\\0013035.jpg\u0026#34;) img = img.transpose(2,0,1) print(img.shape) writer.add_image(\u0026#34;图一\u0026#34;, img, 1) writer.close() 注意：输入的图片数据格式为[C,H,W]，C为通道数，H为高度，W为宽度。所以需要用transpose进行转换。 方案二\n1 writer.add_image(\u0026#34;图一\u0026#34;, img, 0, dataformats=\u0026#34;HWC\u0026#34;) ","date":"2026-03-16T00:00:00Z","permalink":"https://ren517.xyz/p/tensorboard%E7%9A%84%E4%BD%BF%E7%94%A8/","title":"Tensorboard的使用"},{"content":"本文将介绍在本地搭建 Hugo 并通过Nginx 和 服务器部署 Hugo 的方法。以下所叙之方法就是我在部署本站点时所使用的，防止自己以后忘记。如果对您有借鉴作用，或有问题欢迎留言。\n在部署 Hugo 之前，需要进行的准备：\n1.购买一个服务器（可以看看阿里云，腾讯云的学生优惠）\n2.租一个域名\n3.下载一个文本编辑器，方便后续写md文件，推荐：vscode\n自动化脚本\n安装Nginx 1 2 sudo apt update sudo apt install nginx -y 检查是否正常运行\n1 sudo systemctl status nginx 设置开机自启\n1 sudo systemctl enable nginx 查看是否成功\n1 sudo systemctl status nginx 如果遇到Nginx启动问题\n1 2 sudo systemctl start nginx # 启动 sudo systemctl reload nginx # 重启 创建博客发布根目录 1 2 3 4 5 6 7 8 9 # 创建网站根目录 sudo mkdir -p /var/www/blog # 创建 deploy 用户专门用于部署 sudo adduser deploy sudo usermod -aG sudo deploy # 让 deploy 用户拥有网站目录权限 sudo chown -R deploy:deploy /var/www/blog 安装Hugo 先切换到deploy用户身份\n1 2 su deploy sudo apt install hugo -y # 安装 Hugo Extended 初始化博客\n1 2 3 su - deploy hugo new site ~/blog-source cd ~/blog-source 完成后会出现以下文件\ndeploy 用户将来用来自动部署 Hugo 生成的文件。\n运行\n1 hugo server -D 可以查看静态网页，地址通常是 localhost:1313 。如果页面显示“Page not found”，说明此前的所有配置都是正常无误的。\n配置主题 Hugo 默认是没有主题的，需要到 官网 去下载主题。我使用的主题是 Jimmy Cai 创作的 Stack 主题。接下来的部分内容会以此主题为例。\n将主题下载完成后并解压至 themes 文件夹中，将 demo 文件夹中的 content 和 hugo.yaml 复制到主文件夹中，并删掉原来的 hugo.toml 和 Content/post/rich-content，避免出现不兼容的错误。\n注：下载下来的主题会带版本号，如我的是 hugo-theme-stack-3.34.2，删除版本号，只留下 hugo-theme-stack\n修改 hugo.yaml 中的 theme 选项，将其修改为与主题文件夹同名。\n再次在命令行输入 hugo server -D 启动服务，若此时能看见类似下图的样式，说明此前操作无误。\n看到类似的效果就可以了，我这个是后面还改了一些配置\n在 config.yaml 中输入相关配置。文件各项配置解释如下，用作参考：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 baseurl: https://example.com # 网站的基本 URL 。替换为你自己的网站域名。 languageCode: en-us # 网站的默认语言代码，zh-cn 指中文简体。 theme: hugo-theme-stack # 使用的 Hugo 主题，这里是 Stack 主题。 paginate: 3 # 每页显示的内容数量，通常用于分页设置。 title: Example Site # 网站的标题，会显示在浏览器标签上。 copyright: Example Person # 网站的版权信息，通常显示在页面底部。 # Theme i18n support # Available values: ar, bn, ca, de, el, en, es, fr, hu, id, it, ja, ko, nl, pt-br, th, uk, zh-cn, zh-hk, zh-tw DefaultContentLanguage: en # 设置网站的默认内容语言。可选值见上注释。 # Set hasCJKLanguage to true if DefaultContentLanguage is in [zh-cn ja ko] # This will make .Summary and .WordCount behave correctly for CJK languages. hasCJKLanguage: false # 如果默认语言是中文、日文或韩文，设置为 true 以确保摘要和字数统计正确。 languages: en: languageName: English # 英语语言配置 title: Example Site # 英文站点标题 weight: 1 # 语言权重，数值越小排序越靠前 params: description: Example description # 英文站点描述 zh-cn: languageName: 中文 # 中文语言配置 title: 演示站点 # 中文站点标题 weight: 2 # 中文站点语言权重 params: description: 演示说明 # 中文站点描述 ar: languageName: عربي # 阿拉伯语配置 languagedirection: rtl # 文字方向，从右到左 title: موقع تجريبي # 阿拉伯语站点标题 weight: 3 # 阿拉伯语站点语言权重 params: description: وصف تجريبي # 阿拉伯语站点描述 services: # Change it to your Disqus shortname before using disqus: shortname: \u0026#34;hugo-theme-stack\u0026#34; # Disqus 评论系统的短名称，需替换为你自己的 Disqus 短名称。 # GA Tracking ID googleAnalytics: id: # Google Analytics 追踪 ID，用于网站流量统计。 permalinks: post: /p/:slug/ # 博客文章的永久链接格式，使用文章的 slug 作为路径。 page: /:slug/ # 页面内容的永久链接格式。 params: mainSections: - post # 主内容区域，显示文章内容。 featuredImageField: image # 特色图片字段的名称。 rssFullContent: true # RSS 提要是否包含全文内容。 favicon: # e.g.: favicon placed in `static/favicon.ico` of your site folder, then set this field to `/favicon.ico` (`/` is necessary) # 网站的favicon路径，例如`/favicon.ico`。 footer: since: 2020 # 网站创建年份，通常显示在页脚。 customText: # 自定义页脚文本。 dateFormat: published: Jan 02, 2006 # 发布日期格式。 lastUpdated: Jan 02, 2006 15:04 MST # 最后更新日期格式。 sidebar: emoji: 🍥 # 侧边栏标题旁显示的 emoji。 subtitle: Lorem ipsum dolor sit amet, consectetur adipiscing elit. # 侧边栏的副标题。 avatar: enabled: true # 是否启用头像显示。 local: true # 是否使用本地头像。 src: img/avatar.png # 头像图片路径。 article: math: false # 是否支持数学公式渲染。 toc: true # 是否显示文章目录。 readingTime: true # 是否显示预计阅读时间。 license: enabled: true # 是否启用文章版权信息。 default: Licensed under CC BY-NC-SA 4.0 # 默认版权协议。 comments: enabled: true # 是否启用评论功能。 provider: disqus # 选择的评论提供商，默认为 Disqus。 disqusjs: shortname: # DisqusJS 的短名称。 apiUrl: # DisqusJS 的 API URL。 apiKey: # DisqusJS 的 API Key。 admin: # DisqusJS 的管理员用户名。 adminLabel: # DisqusJS 管理员标签。 utterances: repo: # Utterances 评论系统的 GitHub 仓库地址。 issueTerm: pathname # Utterances 评论系统的议题关联方式，使用页面路径。 label: # Utterances 评论系统的标签。 beaudar: repo: # Beaudar 评论系统的 GitHub 仓库地址。 issueTerm: pathname # Beaudar 评论系统的议题关联方式。 label: # Beaudar 评论系统的标签。 theme: # Beaudar 评论系统的主题。 remark42: host: # Remark42 的主机地址。 site: # Remark42 的站点标识符。 locale: # Remark42 的语言设置。 vssue: platform: # Vssue 使用的平台（例如 GitHub）。 owner: # Vssue 评论仓库的所有者。 repo: # Vssue 评论的 GitHub 仓库地址。 clientId: # Vssue 的 OAuth 应用 Client ID。 clientSecret: # Vssue 的 OAuth 应用 Client Secret。 autoCreateIssue: false # 是否自动创建评论议题。 waline: serverURL: # Waline 评论系统的服务器 URL。 lang: # Waline 的语言设置。 pageview: # 是否启用页面浏览统计。 emoji: # Waline 的 Emoji 表情包地址。 - https://unpkg.com/@waline/emojis@1.0.1/weibo requiredMeta: - name # 评论时需要填写的字段，用户名。 - email # 评论时需要填写的字段，电子邮件地址。 - url # 评论时需要填写的字段，网址。 locale: admin: Admin # Waline 评论系统的管理员名称。 placeholder: # Waline 评论框的占位符文本。 twikoo: envId: # Twikoo 评论系统的环境 ID。 region: # Twikoo 评论系统的部署区域。 path: # Twikoo 评论系统的路径。 lang: # Twikoo 评论系统的语言设置。 cactus: defaultHomeserverUrl: \u0026#34;https://matrix.cactus.chat:8448\u0026#34; # Cactus.Chat 的默认主服务器 URL。 serverName: \u0026#34;cactus.chat\u0026#34; # Cactus.Chat 的服务器名称。 siteName: \u0026#34;\u0026#34; # You must insert a unique identifier here matching the one you registered (See https://cactus.chat/docs/getting-started/quick-start/#register-your-site) # Cactus.Chat 的站点名称，需与注册的标识符匹配。 giscus: repo: # Giscus 评论系统的 GitHub 仓库地址。 repoID: # Giscus 仓库的唯一标识符。 category: # Giscus 的分类名称。 categoryID: # Giscus 分类的唯一标识符。 mapping: # Giscus 的议题关联方式。 lightTheme: # Giscus 的浅色主题设置。 darkTheme: # Giscus 的深色主题设置。 reactionsEnabled: 1 # 是否启用 Giscus 的反应功能。 emitMetadata: 0 # 是否启用 Giscus 的元数据发射功能。 gitalk: owner: # Gitalk 评论系统的仓库所有者。 admin: # Gitalk 评论系统的管理员用户名。 repo: # Gitalk 评论的 GitHub 仓库地址。 clientID: # Gitalk 的 OAuth 应用 Client ID。 clientSecret: # Gitalk 的 OAuth 应用 Client Secret。 cusdis: host: # Cusdis 评论系统的主机地址。 id: # Cusdis 的站点标识符。 widgets: homepage: - type: search # 首页的小部件，搜索功能。 - type: archives # 首页的小部件，文章归档。 params: limit: 5 # 显示的归档文章数量。 - type: categories # 首页的小部件，文章分类。 params: limit: 10 # 显示的分类数量。 - type: tag-cloud # 首页的小部件，标签云。 params: limit: 10 # 显示的标签数量。 page: - type: toc # 页面的小部件，显示文章目录。 opengraph: twitter: # Your Twitter username site: # 你的 Twitter 用户名，将在 OpenGraph 元数据中使用。 # Available values: summary, summary_large_image card: summary_large_image # Twitter 卡片类型。可以选择 `summary` 或 `summary_large_image`，后者显示大图。 defaultImage: opengraph: enabled: false # 是否为没有特色图片的页面启用默认 OpenGraph 图片。 local: false # 是否使用本地图片作为 OpenGraph 图片。 src: # 默认 OpenGraph 图片的路径。 colorScheme: # Display toggle toggle: true # 是否在页面上显示颜色模式切换按钮。 # Available values: auto, light, dark default: auto # 默认的颜色模式。可以选择自动切换（auto），或固定为亮色（light）或暗色（dark）。 imageProcessing: cover: enabled: true # 是否为封面图片启用自动处理功能，例如裁剪、缩放等。 content: enabled: true # 是否为内容图片启用自动处理功能。 ### Custom menu ### See https://stack.jimmycai.com/config/menu ### To remove about, archive and search page menu item, remove `menu` field from their FrontMatter menu: main: [] # 自定义主菜单的配置，可以在这里添加导航链接。 social: - identifier: GitHub # 社交链接的标识符，通常用于指定图标。 name: GitHub # 链接的显示名称。 url: https://GitHub.com/CaiJimmy/hugo-theme-stack # GitHub 个人主页的链接。 params: icon: brand-GitHub # 使用的社交图标，这里是 GitHub 图标。 - identifier: twitter # 另一个社交链接配置，这里是 Twitter。 name: Twitter # Twitter 链接的显示名称。 url: https://twitter.com # Twitter 的链接。 params: icon: brand-twitter # 使用的社交图标，这里是 Twitter 图标。 related: includeNewer: true # 是否在相关文章中包含较新的文章。 threshold: 60 # 相关文章匹配的相似度阈值，范围是0到100。 toLower: false # 是否将标签和分类转换为小写。 indices: - name: tags # 使用标签作为相关文章的匹配依据。 weight: 100 # 标签匹配的权重值。 - name: categories # 使用分类作为相关文章的匹配依据。 weight: 200 # 分类匹配的权重值。 markup: goldmark: renderer: ## Set to true if you have HTML content inside Markdown unsafe: true # 如果 Markdown 中包含 HTML 内容，设置为 true 以允许渲染这些 HTML。 tableOfContents: endLevel: 4 # 目录生成时的最大标题级别。 ordered: true # 目录项是否使用有序列表。 startLevel: 2 # 目录生成时的起始标题级别。 highlight: noClasses: false # 语法高亮时是否禁用 CSS 类名。 codeFences: true # 是否启用代码块语法高亮。 guessSyntax: true # 是否自动猜测代码块的语言进行语法高亮。 lineNoStart: 1 # 代码行号的起始值。 lineNos: true # 是否在代码块中显示行号。 lineNumbersInTable: true # 是否在表格样式中显示行号。 tabWidth: 4 # 代码块中 Tab 的宽度（空格数）。 更多相关配置参见官网，例如网站字体配置、自定义页眉或页脚等。\n撰写文章 在 你的站点名称/content/post 文件夹下新建文件夹，在新建文件夹中创建 index.md 文件，就代表创建一篇新文章了。 之后通过 VS Code 或其他编辑器，用 markdown 语言写文章。 在使用 hugo 命令生成的文章的最上面，都有一块被 +++ 或 \u0026mdash; 包裹出来的区域，它的官方名称是 “Front matter”， 用以指定文章的各项属性。下面是我在 stack 主题的一篇示例文章中摘取的 Front Matter 片段，并写出了注释：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 +++ author = \u0026#34;Hugo Authors\u0026#34; # 作者名称，用于标识文章的创作者。 title = \u0026#34;Markdown Syntax Guide\u0026#34; # 文章标题，将显示在页面和导航中。 date = \u0026#34;2019-03-11\u0026#34; # 文章的发布日期，用于排序和展示。 description = \u0026#34;Sample article showcasing basic Markdown syntax and formatting for HTML elements.\u0026#34; # 文章的简短描述，通常用于摘要或 SEO。 tags = [ \u0026#34;markdown\u0026#34;, # 文章的标签，用于分类和搜索。标签是灵活的，可以添加多个。 \u0026#34;css\u0026#34;, \u0026#34;html\u0026#34;, \u0026#34;themes\u0026#34;, ] categories = [ \u0026#34;themes\u0026#34;, # 文章的类别，用于组织和过滤内容。每篇文章可以属于一个或多个类别。 \u0026#34;syntax\u0026#34;, ] series = [\u0026#34;Themes Guide\u0026#34;] # 文章系列，通常用于将相关文章组织在一起，例如教程或主题指南系列。 aliases = [\u0026#34;migrate-from-jekyl\u0026#34;] # 别名，用于创建文章的旧路径重定向到新路径。例如，当迁移文章时使用。 image = \u0026#34;pawel-czerwinski-8uZPynIu-rQ-unsplash.jpg\u0026#34; # 文章的封面图片路径，用于展示文章时的视觉效果。 +++ 常用的Front Matter字段 title ：文章标题。自动生成的标题与文件名保持一致。 date：文章创建日期。默认生成的日期为文件创建时的日期。 lastmod：文章最后更新日期。 description：文章简要描述，用于摘要或 SEO 优化。 categories：文章分类。 tags：文章标签。 image：封面图片。 draft：是否为草稿。如果设置为 true，Hugo 将不会编译该文件，文章也不会在页面中显示。 hidden：是否隐藏文章。与 draft 的区别在于，设置为 true 的文章仍然存在，只是不会在页面中显示，但可以通过正确的网址访问。 配置 Nginx 发布 Hugo 配置 Nginx 发布 Hugo\n1 sudo nano /etc/nginx/sites-available/blog 不过我感觉nano并不好用，如果你用vscode连接远程服务器，可以用下面命令\n1 code /etc/nginx/sites-available/blog 下面内容里我都会用vscode编辑文件 示例内容\n1 2 3 4 5 6 7 8 9 10 11 server { listen 80; server_name ren517.xyz www.ren517.xyz; # 改成你的域名 root /var/www/blog; index index.html; location / { try_files $uri $uri/ /index.html; } } 启用并重启Nginx\n1 2 3 sudo ln -s /etc/nginx/sites-available/blog /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx 现在 Hugo 构建后的 public/ 目录内容将发布到 /var/www/blog。\n1 2 3 4 5 #!/bin/bash cd /home/deploy/blog-source hugo --minify rsync -av --delete public/ /var/www/blog/ sudo systemctl reload nginx 现在打开你的域名，已经部署成功了。 接下来我会讲一些自动化的内容，如果不需要到这一步就可以结束了。 后面每一次添加了文件，只需要把上面命令跑一遍即可\n部署脚本 在 /home/deploy/blog-source/ 创建：deploy.sh文件 里面添加以下内容\n1 2 3 4 cd /home/deploy/blog-source hugo --minify rsync -av --delete public/ /var/www/blog/ sudo systemctl reload nginx 给脚本权限：\n1 chmod +x deploy.sh 到这一步就不需要上面每次都敲命令行了 直接 ./deploy.sh 就自动执行上面四条命令了\n发布到GitHub 在GitHub上面创建仓库Blog(不要勾选README)，复制SSH链接 在服务器上初始化并完成一些配置：\n1 2 3 4 5 6 7 git config --global user.name \u0026#34;Ren517\u0026#34; git config --global user.email \u0026#34;你的邮箱\u0026#34; git config --global init.defaultBranch main # 默认主分支叫 main git config --global pull.rebase false # 避免 pull 警告 git config --global core.editor nano 在服务器上创建key：\n1 ssh-keygen -t ed25519 -C \u0026#34;blog-deploy\u0026#34; 然后一路回车到底 会生成两个文件\n1 2 ~/.ssh/id_ed25519 ~/.ssh/id_ed25519.pub 查看公钥\n1 cat ~/.ssh/id_ed25519.pub 复制整行，类似ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI....... blog-deploy 然后： GitHub仓库 → Settings → Deploy keys → Add deploy key 添加公钥\n测试\n1 ssh -T git@github.com 可以看到\n第一次提交 1 2 git init git branch -M main 创建 .gitignore\n1 code .gitignore 写入\n1 2 3 4 5 public/ resources/ node_modules/ .DS_Store .hugo_build.lock 解释\n目录 原因 public build产物，不需要版本管理 resources Hugo缓存 node_modules npm依赖 .hugo_build.lock 构建锁文件 1 2 git add . git commit -m \u0026#34;Initial commit: Hugo blog setup\u0026#34; 连接仓库推送\n1 git remote add origin git@github.com:ren517/ren517-blog.git 1 git push -u origin main 看到\n1 2 main -\u0026gt; main branch \u0026#39;main\u0026#39; set up to track \u0026#39;origin/main\u0026#39; 就代表成功了\nSSH配置 1.切换到deploy用户\n1 2 su - deploy ssh-keygen -t ed25519 -C \u0026#34;github-actions-deploy\u0026#34; 2.将公钥加入 authorized_keys，允许免密 SSH：\n1 2 3 cat ~/.ssh/id_ed25519.pub \u0026gt;\u0026gt; ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys chmod 700 ~/.ssh 1 ls ~/.ssh 输出\n1 2 deploy@iZuf6j10uvy5ilxu10oisgZ:~/blog-source$ ls ~/.ssh authorized_keys id_ed25519 id_ed25519.pub known_hosts known_hosts.old 把id_ed25519下载，移动到C:\\Users\\用户名字.ssh目录中 打开终端，测试免密登录\n1 ssh -i ~/.ssh/id_ed25519 deploy@139.196.39.87 # 换成你的服务器公共ip 这样即为成功\n更改部署脚本 打开deploy.sh文件，修改成以下内容\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #!/bin/bash # 切到源码目录 cd /home/deploy/blog-source # 拉取最新代码 git pull origin main # 构建 Hugo hugo --minify # 同步到 Nginx发布目录 rsync -av --delete public/ /var/www/blog/ # 重新加载 Nginx sudo systemctl reload nginx 给脚本加权限\n1 chmod +x deploy.sh GitHub Actions 自动部署 Name _ Secret _ SERVER_HOST 服务器 IP SERVER_USER deploy SERVER_SSH_KEY deploy 用户私钥（id_ed25519 内容） 分为三个秘密，分别添加 创建 workflow 文件 .github/workflows/deploy.yml： 内容 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 name: Deploy Hugo Blog on: push: branches: - main jobs: build-and-deploy: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v3 - name: Setup Hugo uses: peaceiris/actions-hugo@v3 with: hugo-version: \u0026#39;0.157.0\u0026#39; - name: Build Hugo run: hugo --minify - name: Deploy to Server uses: appleboy/ssh-action@v0.1.6 with: host: ${{ secrets.SERVER_HOST }} username: ${{ secrets.SERVER_USER }} key: ${{ secrets.SERVER_SSH_KEY }} port: 22 script: | cd /home/deploy/blog-source git pull origin main hugo --minify rsync -av --delete public/ /var/www/blog/ sudo systemctl reload nginx 提交workflow\n1 2 3 git add .github/workflows/deploy.yml git commit -m \u0026#34;Add GitHub Actions auto-deploy workflow\u0026#34; git push origin main 到这里就大功告成了 以后只需要\n1 2 3 git add . git commit -m \u0026#34;写文章或修改博客\u0026#34; git push 不需要再登入自己的服务器，就可以更改内容了 如果action挂了，手动紧急部署\n1 2 cd ~/blog-source ./deploy.sh 二更 发现了图片渲染的问题，如果直接用markdown语法\n1 ![图1](./images/1.png) 如果图片上面没有标题或代码块，会导致渲染问题 解决方案\n1 2 3 --- ![图1](./images/1.png) 加上一条分割线，成功解决\n标题，代码块，\u0026mdash;，图片上下都各空一行，减少渲染错误的情况\n","date":"2026-03-14T00:00:00Z","image":"https://ren517.xyz/p/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E6%90%AD%E5%BB%BAhugo-nginx%E6%90%AD%E5%BB%BA%E5%8D%9A%E5%AE%A2/images/image_hu_9d57dd2c449710cc.png","permalink":"https://ren517.xyz/p/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E6%90%AD%E5%BB%BAhugo-nginx%E6%90%AD%E5%BB%BA%E5%8D%9A%E5%AE%A2/","title":"从零开始搭建hugo+Nginx搭建博客"},{"content":"问题 由于我是一个2g内存的轻量服务器，经常才刚刚连上服务器，导致内存占用过高，经常时服务器崩溃 减少不必要的插件 删除类似\n1 2 3 4 5 6 7 8 9 Python Pylance ESLint Docker GitLens Jupyter Copilot C++ Java 这些不必要的插件 只留下如：git, ssh, 等基础插件，占用内存较小\n查看当前运行的所有插件 1 2 ctrl + shift + p 向里输入Remote: Show Running Extensions 设置vscode插件 在本地 VSCode： 打开：\n1 settings.json 加入：\n1 2 3 4 5 \u0026#34;remote.extensionKind\u0026#34;: { \u0026#34;ms-python.python\u0026#34;: [\u0026#34;ui\u0026#34;], \u0026#34;ms-toolsai.jupyter\u0026#34;: [\u0026#34;ui\u0026#34;], \u0026#34;ms-vscode.cpptools\u0026#34;: [\u0026#34;ui\u0026#34;] } 优化前\n1 2 3 4 deploy@iZuf6j10uvy5ilxu10oisgZ:~/blog-source$ free -h total used free shared buff/cache available Mem: 1.6Gi 1.0Gi 134Mi 2.6Mi 622Mi 604Mi Swap: 0B 0B 0B 优化后\n1 2 3 4 deploy@iZuf6j10uvy5ilxu10oisgZ:~/blog-source$ free -h total used free shared buff/cache available Mem: 1.6Gi 572Mi 708Mi 2.8Mi 485Mi 1.0Gi Swap: 0B 0B 0B 可以说是显著优化了\n更保险的加入swap 由于我服务器内存太小了，防止爆掉，加入2g的swap内存,类似Windows的虚拟内存，把硬盘暂时当内存使用\n只需要两步\n1 2 3 4 sudo fallocate -l 2G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile 使swap永久生效：\n1 echo \u0026#39;/swapfile none swap sw 0 0\u0026#39; | sudo tee -a /etc/fstab 1 2 3 4 deploy@iZuf6j10uvy5ilxu10oisgZ:~/blog-source$ free -h total used free shared buff/cache available Mem: 1.6Gi 591Mi 685Mi 2.8Mi 490Mi 1.0Gi Swap: 2.0Gi 0B 2.0Gi 还可以再做一个优化 调整 swap 使用策略： 查看：\n1 cat /proc/sys/vm/swappiness 我这边是10，阿里云比较保守\n1 2 0 → 几乎不用 swap 100 → 很积极用 swap 1 2 除非 RAM 完全用完 否则不用 swap 结果就是：内存接近满：不会提前交换然后突然OOM killer，触发直接杀进程。对远程开发来说：这是不好的。 ChatGPT给我建议修改为10\n1 2 sudo sysctl vm.swappiness=10 cat /proc/sys/vm/swappiness 永久化\n1 echo \u0026#34;vm.swappiness=10\u0026#34; | sudo tee -a /etc/sysctl.conf 应用\n1 sudo sysctl -p ","date":"2026-03-14T00:00:00Z","permalink":"https://ren517.xyz/p/%E4%BC%98%E5%8C%96%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%86%85%E5%AD%98/","title":"优化服务器内存"},{"content":"Tensor 1 2 3 import torch x = torch.randn(2,3,4) # x.shape-\u0026gt;[2,3,4] x.view(2,12) # 三维转换成二维, x.shape-\u0026gt;[2,12] x.transpose(1,2) # x.shape-\u0026gt;[2,4,3] 交换第一和第二列的维度 x.permute(0,2,1) # x.shape-\u0026gt;[2,4,3] 同上\nview 和 reshape的区别 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 x = torch.rand(3,4) x = x.transpose(0,1) print(x.is_contiguous()) # false x.view(3,4) # RuntimeError: invalid argument 2: view size is not compatible with input tensor\u0026#39;s.... # 内存地址不连续导致该错误 # 把tensor改成内存连续 x = x.contiguous() x.view(3,4) # 或可以用reshape解决 x.reshape(3,4) transopse和permute的区别 transpose()只能一次操作两个维度；permute()可以一次操作多维数据，且必须传入所有维度数，因为permute()的参数是int*。\n","date":"2026-03-13T00:00:00Z","permalink":"https://ren517.xyz/p/tensor/","title":"tensor"}]