十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

无sudo环境下运行RIOT 2026.07:从编译到28Mbit/s吞吐测试

无sudo环境下运行RIOT 2026.07:从编译到28Mbit/s吞吐测试 最近我在一台只给了普通用户的 Ubuntu 机器上折腾 RIOT 2026.07全程没用 sudo也没往系统里装任何依赖最后不光把系统跑起来了还在两个 RIOT 节点之间测出了 28 Mbit/s 的吞吐量。整个过程比我想象中要曲折踩了几个跟权限、构建系统、网络接口相关的坑也顺手把“受限环境下怎么干嵌入式开发”这件事想明白了。如果你也遇到过“机器不是我的、root 密码也没有但偏偏要跑物联网系统”的尴尬场景这篇内容应该能帮你省下不少时间。1. 为什么非要在受限环境里跑 RIOT 2026.071.1 RIOT 2026.07 是来干什么的RIOT 是一个面向物联网的开源实时操作系统主要跑在 MCU 这类资源受限设备上比如 STM32、ESP32、NRF52 这些芯片。它和 FreeRTOS、Zephyr 站的是同一条赛道但也有自己的特点网络协议栈完整支持 6LoWPAN、IPv6、RPL、UDP/TCP 这些还有一个让我最看重的能力——可以编译成 Linux 下的原生进程来跑也就是native目标。这意味着我不需要一块真实的开发板也能在普通 PC 上验证协议栈行为、调试网络代码。2026.07 是 RIOT 的版本号RIOT 的版本规则简单粗暴直接按年份和月份来比如 2024.01、2025.07 这种。2026.07 就是 2026 年 7 月发布的版本属于比较新的主线版本里面默认启用了很多新的网络模块构建规则也有了一些调整。如果你在别的旧教程里看到make BOARDnative all报出各种模块缺失的错很可能就是版本差异导致的。1.2 没有 sudo 和装不了依赖究竟意味着什么我在的这台 Ubuntu 是一台共享开发机管理员只给了我一个普通用户sudo 权限是完全没有的。apt install想都不要想系统里缺什么包我没法用 root 去补。一开始我确实有点慌因为平时做开发缺了依赖第一反应就是sudo apt install xxx但在这里这条路直接被堵死了。但你静下来想一下会发现“不能 sudo 安装系统包”不等于“不能编译代码”。编译一个用户态程序其实只需要可执行的工具链gcc、make、ld 这些和对应的头文件/库文件。这些不一定要装在系统目录里放在用户目录下、编译时通过环境变量指过去效果也是一样的。不过有个前提是系统本身得有基础工具链。很幸运这台 Ubuntu 开发机上已经有gcc、make、git、python3连pkg-config都有。这些大概率是管理员为了其他开发任务装的。所以在我的场景里“没 sudo、没装依赖”更准确的说法是我没有通过 sudo 去安装任何额外的系统级依赖所有事情都靠系统里已经存在的东西加上用户目录里的临时方案完成。1.3 我的整体思路用 native 目标绕开交叉编译只要一提到嵌入式开发很多人首先想到的是交叉编译。比如 STM32你要装arm-none-eabi-gcc还得配 OpenOCD、JTAG 之类的调试工具在一个没有 sudo 的环境里根本玩不转。RIOT 的native目标直接把这个问题绕过去了它把整个 RIOT 内核编成一个 Linux 用户态可执行文件跑起来以后就是一个模拟的“RIOT 设备”但底层用的就是宿主机的 x86_64 架构和 glibc不需要任何交叉工具链。所以我的路线图就变成了先用系统自带的 gcc/make 编译出 RIOT 的 native 可执行文件再想办法让它接入网络最后在两个 native 节点之间做吞吐量测试。整个过程不碰交叉编译不往 /usr 里写任何东西RIOT 源码扔在$HOME/riot编译产物扔在$HOME/riot/examples/xxxx/bin/native问题一下就简化了。2. 动手前的环境盘点受限环境到底还剩下哪些武器2.1 查系统版本、用户组、基础工具链我在做任何编译之前先花了几分钟把“家底”摸清楚。这一步非常重要环境受限的时候更得先确认有哪些东西可用不然容易白忙一场。cat /etc/os-release id command -v gcc command -v make command -v git command -v python3 gcc --version | head -n 1 make --version | head -n 1我这边的输出大概是这样的Ubuntu 22.04.3 LTS用户名是 dev组里有 dev、netdevgcc 11.4.0make 4.3git 2.34.1python3 3.10.12。netdev这个组在后面的网络模拟里非常关键先记住这一点。如果你执行command -v gcc发现没有输出说明系统连基础编译工具都没装那在没有 sudo 的情况下就比较难受了得跳到 2.3 节用apt download这种“本地解包”的方式解决。2.2 检查网络设备与 /dev/net/tun 权限RIOT 的 native 目标默认通过/dev/net/tun来模拟网络接口也就是 Linux 的 TUN/TAP 设备。TAP 设备可以看成是一个虚拟以太网口RIOT native 进程把这个设备打开就能通过宿主机的网络栈收发以太网帧。既然要在 Ubuntu 里跑网络吞吐测试我提前确认了三件事内核是否加载了 tun 模块、/dev/net/tun是否存在、当前用户有没有权限打开它。ls -l /dev/net/tun ip link show如果/dev/net/tun存在一般权限长这样crw-rw---- 1 root netdev 10, 200 ...也就是说 root 和 netdev 组的成员可以打开。当前用户必须在netdev组里否则运行 native 程序时会在打开 TUN 设备的地方直接报Operation not permitted。还有一点要检查系统里是否已经有可用的 tap 接口。ip link show可以看到tap0、tap1之类的虚拟接口。这一步在后文会详细说因为创建 tap 接口要用到 root 权限但在某些环境下管理员已经提前创建好了。2.3 真缺依赖时的补救方案apt download dpkg -x 本地解包虽然我这台机器工具链齐全但“没 sudo 还得补依赖”这个场景太常遇到了我还是把方案整理出来了。核心思路就是apt download不需要 root它只是把.deb包下载到当前目录然后再用dpkg -x把 deb 包解压到用户目录的某个前缀下而不是安装到系统目录。mkdir -p $HOME/local cd $HOME/local apt download libpcap-dev dpkg -x libpcap-dev_*.deb ./rootfs之后编译时把这些路径告诉编译器export PATH$HOME/local/rootfs/usr/bin:$PATH export LD_LIBRARY_PATH$HOME/local/rootfs/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH export PKG_CONFIG_PATH$HOME/local/rootfs/usr/lib/x86_64-linux-gnu/pkgconfig export CPPFLAGS-I$HOME/local/rootfs/usr/include export LDFLAGS-L$HOME/local/rootfs/usr/lib/x86_64-linux-gnu注意不要用dpkg -i来安装它会把文件写入/usr等系统目录普通用户根本没有写权限而且容易污染系统环境。用dpkg -x解包到自己的目录是最干净的。这个技巧本质上就是把 deb 包当成一个压缩包来处理deb 本身是ar归档格式dpkg -x只是把里面的文件释放出来并不触发任何安装流程。3. 获取 RIOT 源码与构建系统快速入门3.1 没有 git 也能拿到源码获取 RIOT 的方法有很多最简单的是 git clone。RIOT 的官方仓库在 GitHub 上版本 tag 一般是2026.07这种格式。git clone --branch 2026.07 https://github.com/RIOT-OS/RIOT.git $HOME/riot但如果你的机器上连 git 都没有也别慌。可以去 GitHub 的 releases 页面直接下载对应的 tar 包比如RIOT-2026.07.tar.gz用curl或者浏览器下载下来然后解压tar xzf RIOT-2026.07.tar.gz mv RIOT-2026.07 $HOME/riot源码目录解压之后你会看到boards/、core/、sys/、drivers/、examples/、tests/这些目录。examples/下面有大量可以直接编译运行的示例比如hello-world、gnrc_networking、sock_udp等等。我这次用的主要是examples/gnrc_networking它把一个完整的 IPv6 UDP 网络栈带起来了而且内置了 shell 命令可以方便地查看地址、ping、发送 UDP 包。3.2 看懂 RIOT 的 Makefile 构建逻辑RIOT 的构建系统是基于 Makefile 的看起来复杂但核心规则其实很清晰。每个 example 目录下都有一个Makefile里面一般只写几行APPLICATION gnrc_networking BOARD ? native RIOTBASE ? $(CURDIR)/../.. USEMODULE gnrc_netdev_default USEMODULE auto_init_gnrc_netif USEMODULE gnrc_ipv6_default USEMODULE gnrc_icmpv6_echo include $(RIOTBASE)/Makefile.include关键变量就三个APPLICATION编译出的可执行文件名。BOARD目标平台默认是native。USEMODULE要启用哪些协议模块比如gnrc_ipv6_default会把 IPv6 协议栈拉进来gnrc_icmpv6_echo是 ping6 支持。RIOTBASE用来告诉构建系统 RIOT 源码根目录在哪里绝大多数情况下 Makefile 里已经用$(CURDIR)/../..指好了。你在examples/gnrc_networking下直接执行make不需要额外设置RIOTBASE。3.3 选对 BOARDnative 与其他板级的区别RIOT 支持几十种板级BOARD变量决定了编译出来的目标文件用哪种架构和链接脚本。在我这个场景下我只能选nativenative编译成 Linux x86_64 用户态进程不需要交叉编译跑起来就是模拟一个 RIOT 节点。stm32f429i-disc1、nrf52dk这类需要对应的 GCC 交叉工具链没有 sudo 基本装不上。qemu-riscv-virt这类需要 riscv 工具链同样不是默认环境具备的。所以在受限环境里native几乎是唯一能快速跑通的选择。它的好处是调试方便坏处是时序和真实硬件不一样性能数字只能作为相对参考不能直接等同于板子上的实测值。4. 在无 sudo 环境下把 RIOT 编译出来4.1 准备编译环境变量开始编译之前我先把环境变量整理了一遍。因为不打算往系统目录里写任何东西所以所有路径都指向用户目录。如果你的机器上基础工具链齐全其实不需要任何额外环境变量直接编译就行。但如果前面用了apt download本地解包的方式补依赖那一定要在编译前把PATH、CPPFLAGS、LDFLAGS、PKG_CONFIG_PATH这些变量 export 好。export BOARDnative export DEVELHELP1 export PATH$HOME/local/rootfs/usr/bin:$PATH export LD_LIBRARY_PATH$HOME/local/rootfs/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH export PKG_CONFIG_PATH$HOME/local/rootfs/usr/lib/x86_64-linux-gnu/pkgconfig export CPPFLAGS-I$HOME/local/rootfs/usr/include export LDFLAGS-L$HOME/local/rootfs/usr/lib/x86_64-linux-gnuDEVELHELP1会开启 RIOT 的调试帮助模式编译时加入更多断言和检查对开发期排查问题很有用。但它的确会稍稍拉低性能后面做吞吐量测试的时候我把它关掉了。4.2 编译 examples/gnrc_networking 的完整过程准备工作做完直接进入示例目录编译cd $HOME/riot/examples/gnrc_networking make clean make -j$(nproc)第一次编译需要一点时间因为 RIOT 会编译整个内核和所选模块即使只是 native 目标也要生成不少中间文件。编译完成后可执行文件在bin/native/gnrc_networking.elf。用file命令看一眼如果是ELF 64-bit LSB executable, x86-64就说明它确实是一个普通的 Linux 用户态程序。file bin/native/gnrc_networking.elf这一步成功以后RIOT 2026.07 就已经算在 Ubuntu 上“跑起来”了。你可以在命令行直接运行它./bin/native/gnrc_networking.elf如果一切正常它会进入一个交互式 shell里面能执行help、ifconfig、ping6、udp这些命令。这个 shell 本身就是 RIOT 系统在跑只不过底层是 Linux 进程。4.3 编译期常见报错与处理编译过程中很可能会遇到一些报错我把自己踩过的问题整理成表格方便直接对着查。报错现象可能原因处理方式pkg-config: command not found系统缺少 pkg-config用apt download pkg-config然后dpkg -x解包到用户目录把 bin 加入 PATHfatal error: stdio.h: No such file or directory缺少 libc6-dev 或 build-essential在另一台同版本 Ubuntu 机器上用apt download libc6-dev后本地解包或拷贝/usr/include需要管理员配合You must specify a BOARD没有设置 BOARD 变量在命令行加BOARDnative或export BOARDnativeundefined reference to xxx依赖的静态库或模块没编进去检查 Makefile 里的USEMODULE确认相关模块已启用Permission denied出现在编译过程用户对输出目录无写权限确认在$HOME/riot下操作不要写/usr/local等系统路径这里的核心感悟是大多数编译报错和权限没有关系而是和“头文件路径”“库路径”有关。只要把路径指对普通用户完全能完成整个构建。5. 让两个 RIOT 节点互联并测出 28 Mbit/s5.1 网络准备tap 设备的创建与权限说明RIOT native 跑起来容易但要让两个节点互联就需要 TAP 设备了。TAP 设备相当于一个虚拟以太网口RIOT native 进程通过/dev/net/tun打开它并“挂”上去从宿主机的视角看这个网络接口就像连了一台真实的设备。这里要说实话创建 tap 设备本身需要 root 权限。RIOT 官方仓库里提供了一个脚本tapsetup.sh执行sudo ./tapsetup.sh create可以一次性创建tap0、tap1和tapbr0桥接设备。我在那台开发机上没有 sudo所以这一步是找管理员帮忙预先执行了一次后续运行 RIOT 就完全不需要 root 了。# 由管理员预先执行一次 cd $HOME/riot/dist/tools/tapsetup sudo ./tapsetup.sh create执行完之后用ip link show可以看到tap0、tap1和tapbr0。如果你所在的系统管理员已经为你准备好了这些接口那你可以直接用如果没有也可以考虑用 QEMU 的用户态网络做替代但那个方案会损失一些性能而且配置更繁琐。还有非常重要的一点用户必须能读/dev/net/tun。一般的 Ubuntu 上它的权限是root:netdev所以当前用户要属于netdev组。我在 2.1 节特意确认过自己的组就是为这一步准备的。如果用户不在netdev组运行 native 程序时会报告open(/dev/net/tun) failed: Operation not permitted。5.2 启动两个 native 节点并配置 IPv6 地址网络准备就绪后开两个终端分别启动两个 RIOT 节点终端 Acd $HOME/riot/examples/gnrc_networking ./bin/native/gnrc_networking.elf tap0终端 Bcd $HOME/riot/examples/gnrc_networking ./bin/native/gnrc_networking.elf tap1启动之后两个节点都会进入 RIOT shell。用ifconfig命令查看各自的 IPv6 地址。因为 tap0 和 tap1 都被桥接在tapbr0上所以两个 RIOT 节点实际上已经处于同一个二层网络里。在终端 A 的 shell 里用ping6去 ping 终端 B 的地址ping6 终端B的IPv6地址如果 ping 通了说明两个 RIOT 实例之间的网络通道已经建立后面就能做吞吐量测试了。5.3 自定义 UDP 吞吐测试与结果分析RIOT 自带的gnrc_networking示例里有一个简单的 UDP 收发命令但它的设计不是用来“打流量”的发送固定几个包可以测吞吐量就不够看了。所以我基于sock_udp写了一个非常简单的吞吐测试模块逻辑很简单接收端监听一个端口统计收到的字节数发送端循环往接收端发 1200 字节的 UDP 数据报发满 10000 个以后计算总耗时。核心逻辑类似这样基于 RIOT sock API#include net/sock/udp.h #define SEND_COUNT 10000 #define PAYLOAD_LEN 1200 static char payload[PAYLOAD_LEN]; int main(void) { sock_udp_ep_t local { .family AF_INET6, .port 4242 }; sock_udp_ep_t remote { .family AF_INET6, .port 4242 }; sock_udp_t sock; sock_udp_create(sock, local, NULL, 0); /* 设置远端地址为对端节点 IPv6 地址 */ ipv6_addr_from_str(remote.addr.ipv6, fe80::xxxx...); remote.netif SOCK_ADDR_ANY_NETIF; uint32_t start xtimer_now_usec(); for (unsigned i 0; i SEND_COUNT; i) { sock_udp_send(sock, payload, sizeof(payload), remote); } uint32_t elapsed (xtimer_now_usec() - start) / US_PER_SEC; printf(sent %u packets in %u s\n, SEND_COUNT, elapsed); }当然直接把它们放到gnrc_networking的 shell 代码里会比较麻烦我是新建了一个独立 example 目录然后把USEMODULE gnrc_sock_udp写进 Makefile。这样编译出来的程序会直接执行吞吐测试逻辑不依赖 shell 的人工输入。最终结果在我那台 CPU 是 2.5GHz 左右的 x86 服务器上两个 native 节点之间通过 tap 桥接传输10000 个 1200 字节 UDP 包不到 4 秒就发完了算下来应用层净荷吞吐量大约 28 Mbit/s。28 Mbit/s 这个数字怎么来的很简单10000 * 1200 * 8 96,000,000 bit也就是 96 Mbit 的净荷除以 3.4 秒左右时长约等于 28.2 Mbit/s。注意我算的是应用层 UDP 净荷如果算上 UDP 头、IPv6 头、以太网头物理链路的实际吞吐会更高一些大约在 29 Mbit/s 左右。这个成绩在真实嵌入式系统里已经算不错了但和宿主机的千兆网卡比又有差距原因后面一节详细说。6. 实测中的坑与排查速查表6.1 /dev/net/tun 不可访问的表现与对策如果你运行 native 程序时看到类似下面的输出RIOT native: failed to initialize tap device (tap0): Operation not permitted那问题基本出在/dev/net/tun权限上。第一步先确认设备是否存在ls -l /dev/net/tun如果不存在可能是内核 tun 模块没加载或者容器环境没映射/dev/net/tun这需要管理员处理。如果存在但权限是crw-rw---- 1 root root ...那就说明系统没有把设备开放给 netdev 组需要 root 执行一次sudo chown root:netdev /dev/net/tun sudo chmod 660 /dev/net/tun还有一种情况是你不在netdev组里但拿到了组权限之后当前会话没有立刻生效。执行newgrp netdev切换组会话或者干脆重新登录一次。6.2 tap 设备已被占用的处理在反复调试的过程里最容易遇到的问题是上次运行的 native 进程没有退出干净导致tap0或tap1被占用。表现是启动第二个实例时报RIOT native: failed to initialize tap device (tap1): Device or resource busy这种情况先检查后台进程ps aux | grep gnrc_networking kill PID如果进程已经没了但还是提示占用就用 root 权限把 tap 删掉重建sudo ip link delete tap0 sudo ip link delete tap1注意删除接口也需要 root。如果你没有 sudo那就只能等管理员处理或者换个编号比如把 native 程序挂到tap5上如果系统里恰好有这样一个空闲接口。6.3 吞吐量上不去时先查这五个地方28 Mbit/s 的数字看着还行但我在测试过程中也遇到过吞吐量掉到个位数甚至发不出去的情况总结下来有这么几个关键点问题现象可能原因解决方向吞吐量远低于预期CPU 单核跑满native 模式把所有中断和协议栈都放在一个进程里调整任务优先级、减少日志输出UDP 包发送速度很慢每次发送都调用了usleep或 shell 刷新在吞吐测试代码里去掉人为延时assert failed频繁触发DEVELHELP1开启了额外断言某些缓冲区临界条件被触发正式测试时关掉DEVELHELP网络丢包率升高发送速率超过接收端gnrc_pktbuf处理能力增大GNRC_PKTBUF_SIZE或减少并发连接IPv6 邻居缓存失效对端地址变化频繁用static地址绑定减少邻居发现开销其中GNRC_PKTBUF_SIZE是一个特别值得说的参数。RIOT 的网络数据包缓冲区默认大小通常只有几 KB 到几十 KB一旦接收侧来不及处理缓冲区塞满后面的包就会被直接丢。提高它的方法是在 Makefile 里加一行CFLAGS -DGNRC_PKTBUF_SIZE8192但注意这个值不是越大越好native 模式还好如果放到真实 MCU 上缓冲区太大会直接吃掉有限的 RAM。6.4 关于 28 Mbit/s 的进一步思考最后聊一下 28 Mbit/s 这个数字的意义。在 native 模式下RIOT 的网络帧要从用户态进程写进内核的 tap 设备然后经过桥接再从另一个 tap 设备读到另一个用户态进程。每一次跨越都涉及用户态/内核态切换、数据拷贝、socket 缓冲这些开销都是真实硬件上不会存在的。所以在 x86 服务器上跑出 28 Mbit/s代表的其实是“RIOT 协议栈在通用 Linux 内核模拟环境下的性能”而不能直接等同于在 100 MHz MCU 上的表现。不过这个数字依然有很多参考价值第一它验证了 RIOT 的 IP 层和 UDP 层在长包下的处理能力没有明显缺陷第二通过对比不同GNRC_PKTBUF_SIZE、不同包长下的吞吐可以侧面判断协议栈的瓶颈是缓冲区、调度还是拷贝第三这套测试方法完全可以搬到 CI 里每次提交代码之后自动跑一遍作为性能回归的参考指标。我个人在实际操作中的体会是受限环境最大的价值不是让你把东西跑通而是逼着你去理解每一层到底依赖了什么。比如同样是跑 RIOT有 sudo 的情况下你会无脑apt install把工具链补齐然后把它当成一个黑盒但没 sudo 的时候你得自己判断缺的是编译器、内核模块、还是设备节点权限这种判断过程反而能把人训练得更扎实。如果你后续想在这个方向继续扩展可以考虑两条线一是把native测试接入自动化流水线每次构建完跑一遍 UDP 吞吐和 ping6 连通性检查二是换成 QEMU 模拟其他指令集平台在同样不依赖真实硬件的情况下验证架构相关的代码。RIOT 2026.07 的构建系统对这两种场景都做了支持照着官方文档里的BOARD列表选一个模拟平台剩下的就是改一个变量重新make的事。
返回列表