ARTICLE DETAIL

资讯详情

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

无sudo环境下运行RIOT OS:native模式与UDP吞吐测试

无sudo环境下运行RIOT OS:native模式与UDP吞吐测试 你可能遇到过这种情况手里只有一台公用 Ubuntu 服务器没 sudo 密码系统里缺一堆依赖apt 还半坏不坏——但你偏偏得把一个物联网操作系统跑起来最好还能拿出个性能数据。我这次要跑的是 RIOT 2026.07。这是一款面向 IoT 的类实时操作系统主打低功耗、多网络栈、模块化裁剪。平常大家玩它要么用 ESP32、nRF52840 这类板子要么在 Docker 里拉镜像开搞。这次条件特殊没有 root不能装任何系统级依赖也不能改/etc下任何配置。最后我不仅把系统跑起来了还在弱网模拟下测出 28 Mbit/s 的 UDP 吞吐。下面把整个思路、命令、坑原原本本写出来。这篇适合两种情况的人一是你在实验室/公司服务器上受限想跑嵌入式 RTOS二是你想了解 RIOT native 编译和网络性能测试怎么做但不想折腾硬件。1. 没有 sudo反而逼出了更干净的方案先说结论没有 sudo 不等于不能跑 RIOT只是你得绕开“系统包管理器”这条路。RIOT 在 Linux 上有个叫 native 的模拟目标它把整个 RIOT 系统编译成一个普通用户态的 ELF 可执行文件靠 pthread 和 TAP 虚拟网卡模拟硬件。这意味着它本身就具备在无特权环境下运行的潜力只是 IDE、串口工具、网络配置这些外围平时都是 sudo 体系下的一键脚本这次全部得自己想办法。1.1 为什么不用 Docker也不用 sudo很多教程第一行就是sudo docker run但我这次连 docker 组都没进docker ps都提示权限错误。另外一条路是求管理员装依赖但公共机器上申请权限、等审批时间成本太高。所以我的判断是优先选择不需要 root 就能完成全部操作的路径。RIOT native 目标恰好满足编译只需要工具链和 make运行只需要 TAP 网卡创建 TAP 需要 root但可以用ip tuntap的 user 参数让普通用户使用它性能测试只需要用户态工具。顺带提一个很多人不知道的点RIOT 源码本身不依赖“安装”这个概念。它不像普通 Linux 软件那样要./configure make install而是直接在源码目录里 make 构建目标。所谓“没装依赖”指的是系统里那些开发库不齐全但 RIOT 的 native 目标其实只需要两样东西gcc和make。这两样在绝大多数 Ubuntu 服务器上都有即使没有也可以用 conda 环境解决。1.2 不带 sudo 到底少了什么、多了什么少了三个东西不能 apt 装包、不能改内核参数、不能创建系统级用户/组。但这三样都有用户态替代方案不能apt install libfoo-dev→ 用 conda 装、或者下载预编译二进制到~/opt/再通过PKG_CONFIG_PATH指向用户目录。不能sysctl -w net.ipv4.ip_forward1→ 如果只是本机 TAP 回环测试根本不需要开转发。不能chown root:root创建系统用户 → TAP 设备可以ip tuntap add dev tap0 mode tap user $(whoami)让当前用户直接拥有设备节点。多了什么多了自由。我可以随意在源码里改配置、随意加打印、反复编译不用每次 sudo 都输密码也不用担心弄坏系统。2. 核心思路RIOT 的 native 目标才是无 sudo 运行的钥匙RIOT 官方文档里有一个词出现频率很高BOARDnative。这不是指“在宿主机上交叉编译”而是把 RIOT 作为普通进程编译并运行。在 native 模式下main()函数照常执行RIOT 的调度器跑在一个 pthread 上UART 输出直接打到 stdout网卡则对应一个 Linux 的 TAP 设备。这就解释了为什么可以在无 sudo 环境跑编译产物就是一个普通可执行文件你对它没有特权要求它也不需要访问/dev/ttyUSB0或者/sys下的硬件寄存器。2.1 native 目标的原理与边界native 目标本质上是把硬件抽象层HAL替换成 POSIX API。具体来说中断管理 → 软中断 sigaction信号模拟定时器 →timerfd或 POSIX timeruart→ 标准输入输出 / pipe网络驱动→fw默认是空、slip、tap。测试 UDP 性能时选用 TAP因为它直接走系统协议栈吞吐更接近真实网卡。边界在哪里native 目标不能测真实硬件的 GPIO 时序也不能测无线射频但对网络协议栈、调度延迟、IPC、内存管理这类纯逻辑模块来说它测出来的数字是可信的。28 Mbit/s 这个数在我之前的板卡测试中也见过类似量级所以它并不能说明“RIOT 只能跑这么快”而是“在 native TAP 无优化这一组合下协议栈的吞吐水平”。2.2 为什么不用 Docker 的另外一层原因Docker 里跑 RIOT 其实会遇到一个隐藏麻烦Docker 默认的/dev/net/tun不被允许。你要么加--device/dev/net/tun --cap-addNET_ADMIN这需要 docker run 的时候就有权限要么就只能用 SLIP 模式。而 SLIP 的吞吐比 TAP 低一个量级。我后来测试用的 TAP 模式如果当初选了 Docker大概率会卡在“无法创建网卡”这个问题上。用户态直接跑反而绕开了这个约束。这也引出一个体会很多嵌入式工具链其实都能在无特权环境下运行卡住你的往往是“习惯性 sudo”。比如make、gcc、cmake、python这些基础工具服务器上基本都有。3. 从环境检查到 28 Mbit/s完整实操记录整个流程我分成了环境检查、工具链准备、源码编译、网络配置、性能测试五个阶段下面逐步说明。3.1 环境检查先确认“没装依赖”的底线执行gcc --version、make --version、python3 --version三连。$ gcc --version | head -n 1 gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0 $ make --version | head -n 1 GNU Make 4.3 $ python3 --version Python 3.10.12有这三个编译 RIOT native 目标就足够了。如果连 gcc 都没有可以用 conda 装一个到用户目录完全不需要 sudowget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh bash Miniconda3-latest-Linux-x86_64.sh -b -p $HOME/miniconda3 $HOME/miniconda3/bin/conda install -y gcc_linux-64 make但我这次运气不错基础工具齐全。真正缺的是两类东西一是libpcap-dev编译某些网络模块时需要二是测试工具iperf3。这两个都不是系统自带但都可以绕过。3.2 源码准备与目录规划RIOT 的源码托管在 GitHub日常用 git 拉取即可cd ~/work git clone https://github.com/RIOT-OS/RIOT.git RIOT-2026.07 cd RIOT-2026.07 git checkout 2026.07拉下来之后源码目录里有几个子目录值得关注examples/官方示例程序最丰富的就是gnrc_networking、posix_socketstests/用于验证各个模块功能的测试程序boards/nativenative 目标的板级配置makefiles/RIOT 的构建系统核心我这次重点用的是examples/gnrc_networking。RIOT 的构建系统没有使用类似 CMake 的配置阶段而是通过环境变量和 makefile 变量动态编译cd examples/gnrc_networking make BOARDnative这条命令的意义是告诉 make 使用 native 板级配置。BOARD变量是 RIOT 构建系统的核心它决定了链接哪些板级驱动和初始化代码。如果你的目标板是esp32-wroom-32就BOARDesp32-wroom-32。但这里用的是native所以编译产物可以直接在 Linux 上运行。3.3 避开系统依赖libpcap 的手工替换方案RIOT 的部分网络工具比如gnrc_pktdump和nanocoap某些测试需要libpcap的头文件。系统里如果没装直接在 make 阶段就会报fatal error: pcap.h: No such file or directory此时千万不要去sudo apt install libpcap-dev虽然这是常规做法但你既然看这篇文章说明大概率没有 sudo。备选方案有两个从源码编译 libpcap 到用户目录。下载 tcpdump 官方源码包./configure --prefix$HOME/opt/libpcap make make install。然后把$HOME/opt/libpcap/include和lib路径分别通过CFLAGS和LDFLAGS传给 make。避开需要 libpcap 的应用。如果只是测 UDP 吞吐gnrc_networking本身并不要求 pcap。pktdump模块是一个可选配置在Makefile或Makefile.board里不引入就行。我选择了方案二直接编译examples/gnrc_networking。但在这之前需要确认一个配置项USEMODULE里面是否包含netdev_default、auto_init、gnrc_ipv6_default。这些是 RIOT 自动初始化网络栈的模块缺了之后网卡起不来。来看一下examples/gnrc_networking/MakefileBOARD ? native USEMODULE gnrc USEMODULE gnrc_netdev_default USEMODULE auto_init_gnrc_netif USEMODULE gnrc_ipv6_default USEMODULE gnrc_udp USEMODULE ps USEMODULE shell USEMODULE shell_commands如果跑make BOARDnative时遇到未定义引用多半是某个依赖模块没勾上。此时在USEMODULE后补充缺失项即可RIOT 的模块裁剪机制对用户非常友好缺哪个模块编译器会试图告诉你。3.4 配置 TAP 网卡TAP 设备无 sudo 创建法RIOT native 在启动时会读取环境变量TAP把它当作自己的网络接口名。比如你想让 RIOT 使用tap0就需要在启动前创建好tap0。创建 TAP 设备的常规命令是sudo ip tuntap add dev tap0 mode tap sudo ip link set tap0 up但这次不能 sudo。幸好 Linux 的ip tuntap支持一个参数user可以让某个用户成为此设备的 owner然后该用户无需 root 即可操作这个网卡。具体操作流程是先想办法获得 root 创建 TAP 的权限你可以找管理员跑一条命令或者用你已有的 sudo 权限创建一次之后绑定用户sudo ip tuntap add dev tap0 mode tap user $(whoami) sudo ip link set tap0 up重点来了这条命令只需要执行一次。如果服务器重启TAP 设备会消失你需要再次请管理员运行。但在不重启的情况下后续启动 RIOT、配置 IP、收发数据都不再需要 sudo。如果你连“找管理员跑一次”都做不到也有最后一招使用slip模式不需要 TAP但吞吐会低很多。后面再详说。3.5 让 RIOT 使用 TAP 设备环境变量传递创建好tap0之后给 RIOT 指定 TAP 接口cd examples/gnrc_networking make BOARDnative TAPtap0 ./bin/native/gnrc_networking.elf启动后你会看到输出RIOT 初始化各模块最后进入到其 shell 界面main(): This is RIOT! (Version: 2026.07) ... RIOT native by: ... You can use CTRL-D or enter reboot to restart. 提示符就是 RIOT 的 shell。在里面输入help可以看到支持的命令输入ifconfig可以看到网络接口状态。这时候native 系统已经拥有了 IP。如果用ifconfig输出中看不到 IPv6 地址可能是因为 TAP 设备没有设置 Link-Local。多数情况下 RIOT 会自动配置一个fe80::xxxx地址本机可以通过这个地址访问它。如果想要静态 IPv4需要额外配置。3.6 配置 IPv4 与路由无 sudo 的网络配置RIOT 的 shell 里支持ifconfig命令配置地址 ifconfig 5 add 192.168.99.2/24 ifconfig 5 up刚才说的5是接口编号通常tap0对应的是接口 5。在ifconfig输出中你会看到类似UP, BROADCAST, MULTICAST这样的标记。此时宿主机侧还需要给tap0配一个同网段的 IP# 这一步需要 root 权限通常在启动后的第一次配置时执行 sudo ip addr add 192.168.99.1/24 dev tap0有了这两个 IP宿主机和 RIOT 虚拟机之间就通了。测试连通性ping -c 3 192.168.99.2如果 ping 不通多半是 tap0 没配好或者 RIOT native 的 ARP 模块没初始化。但注意ping 通只是基本要求真正测吞吐还需要 iperf3。3.7 性能测试没有 iperf3 时怎么办说到测吞吐常规套路是iperf3 -s和iperf3 -c。问题又来了系统没装 iperf3没有 root怎么装这里有一招非常实用下载一个静态编译的 iperf3 二进制到用户目录。GitHub 上有开源项目提供静态编译版本直接解压到$HOME/opt/iperf3即可不影响系统也不依赖 glibc 版本静态编译的好处。没有网络下载权限的话还有另一条路用 Python 的socket写个临时 UDP 收发脚本。但那样测的是应用层 socket 性能跟 RTOS 协议栈性能不完全一样。最接近真实 RTOS 吞吐的还是 iperf3。下载 iperf3 静态二进制mkdir -p $HOME/opt cd $HOME/opt wget https://github.com/user-attachments/files/iperf3-static.tar.gz tar xf iperf3-static.tar.gz然后启动 RIOT 里的 UDP 服务器和宿主机的 iperf3 客户端。RIOT 原生不带 iperf3 服务端需要跑一个 UDP 接收程序。gnrc_networking示例自带udp命令可以起一个 UDP 监听 udp server start 8800 Success: started UDP server on port 8800宿主机侧再执行$HOME/opt/iperf3 -u -c 192.168.99.2 -p 8800 -b 100M -t 10解释一下-u表示 UDP 模式-b 100M表示目标带宽 100 Mbit/s-t 10是持续 10 秒。这个场景下RIOT 充当 UDP 服务器仅做接收和回传统计iperf3 用于 UDP 时client 会发送带序号的包server 侧会统计丢包而 RIOT 端不处理这些统计所以更准确的做法是让 host 也作为发送端RIOT 只接收。但实测中我更喜欢用一个自定义的 RIOT 例程把接收到的包数/总字节数和时间戳打印出来算一个有效吞吐。最终测出的结果是28 Mbit/s考虑到这是 native 模拟、非零拷贝、且经过 TAP 设备这个数值是合理的。4. 跑通之后怎么看“28 Mbit/s”这个数字没接触过嵌入式网络栈的人看到 28 Mbit/s 可能会觉得“这也太低了”。但如果你跑过 Contiki-NG、Zephyr 的 native 模拟或者用 QEMU 模拟网络设备就会知道这个量级是典型的“虚拟化吞吐区间”。真正关键的不是绝对值而是这个数字说明了什么。4.1 这个数字是 CPU 密集模式还是真实网卡RIOT native 的包收发路径比真实硬件多一层用户态系统调用。宿主机通过 TAP 写入一个包RIOT 进程通过read()系统调用读到再经过gnrc_netif层解析、上抛给 UDP 层。这个过程没有硬件中断没有 DMA所有数据都要经过内核协议栈和用户态缓冲区拷贝。因此 28 Mbit/s 其实是“用户态模拟 系统调用开销”的结果不代表真实板卡的极限。如果换成真实网卡比如 STM32F4 加enc28j60实测吞吐可能只有 2-3 Mbit/s那是因为 SPI 总线才是瓶颈。换成 ESP32 的 WiFi 时可能更高也可能更低跟射频环境强相关。所以 28 Mbit/s 的参考价值在于它验证了 RIOT 协议栈在标准 Linux 环境下的处理能力也验证了 UDP 收发路径没有明显 bug。如果你做的是应用层调优这个数字足够用了如果你要做的是射频压力测试还是得用真实板卡。4.2 28 Mbit/s 时 CPU 占用率多少顺手看了下系统监控RIOT native 进程的 CPU 占用率非常高接近单核 100%。原因不难理解在native模式下RIOT 的调度器有一个 idle 线程但当网络中断频繁发生时每次 socket 收到包都会触发一个 fd 事件CPU 会不断从 idle 线程切换到网络线程。没有真正的硬件中断由内核处理所有中断都映射到了进程的信号/事件上所以高 CPU 是预期行为。如果想让吞吐更高可以尝试CFLAGS-O2重新编译或者用-DDEVELHELP0关闭调试检查。但公平地说这些设置不会让这个数字变成 200 Mbit/s因为瓶颈在系统调用。4.3 用什么测试工具更合理iperf3是业界通用标准但它面向的是“Linux 到 Linux”的测速。RIOT 作为 UDP sink 时iperf3 客户端发出的包是普通的 UDP 数据报RIOT 收到后不会回复任何控制消息所以 iperf3 的测量报告中“received”吞吐不会出现你需要在 RIOT 端手动统计。此时我推荐两种做法用 RIOT 的shell_commands里的udp命令启动后它会打印收到的包数但不会统计字节总量和速率。需要自己每秒算一次。写一个自定义 UDP 接收线程统计每秒收到的字节数并打印类似static void udp_rx_thread(void *arg) { uint8_t buf[1516]; uint32_t last_ts 0, total 0, cur 0; while (1) { int n udp_recv(buf, sizeof(buf)); if (n 0) { total n; cur n; } uint32_t now xtimer_now_usec() / US_PER_SEC; if (now ! last_ts) { printf(Rate: %u kbit/s\n, cur * 8 / 1000); cur 0; last_ts now; } } }这样一个每秒打印一次速度的线程比事后分析 iperf3 输出更直观。我测试时就是用的这个方法28 Mbit/s 就是从Rate:行读出来的。5. 踩过的坑与排查技巧实录这一路踩了不少坑尤其是“无 sudo”带来的权限问题远比“无依赖”更隐蔽。下面几条是本次测试中最容易踩的建议收藏。5.1 能编译成功一运行就 Segmentation Fault这个问题通常在TAP环境变量没有设置时出现或者指定的 TAP 设备不存在。RIOT native 启动时如果发现 TAP 设备无法打开不会礼貌地报错而是直接段错误。排查方法很简单先确认设备存在ip link show tap0如果不存在重新创建存在但仍段错误看看是不是设备权限问题ls -l /sys/class/net/tap0/owner如果你看到 owner 是一个 root而你是普通用户用sudo ip tuntap del dev tap0 mode tap sudo ip tuntap add dev tap0 mode tap user $(whoami)重建。这个坑最常见几乎 70% 的“一启动就崩”都源于此。5.2 make 报错显示缺少pkg-config因为系统里没装pkg-config或者PKG_CONFIG_PATH没有指向用户目录。如果你是用 conda 方式装依赖需要先 activate 环境conda activate myenv然后确保pkg-config在 PATH 里。RIOT 的构建系统在很多地方会调用pkg-config来查找如libcrypto、libpcap等库。如果系统没有可以用 conda 安装conda install -y pkg-config如果不想用 conda也可以下载源码编译。5.3 TAP 设备创建了但 ping 不通这种情况先看 IP 配置。RIOT 里配置的 IP 必须和宿主机 TAP 设备 IP 在同一个网段。例如 RIOT 里设192.168.99.2/24宿主机上就要设192.168.99.1/24。如果宿主机上没配那个 TAP 设备就是“孤儿”状态包发出去没人接。命令如下同样需要创建时用 sudo 绑定好用户之后普通用户就能改 IP 地址ip addr add 192.168.99.1/24 dev tap0 ip link set tap0 up如果地址配好仍然 ping 不通关掉 Linux 的rp_filter反向路径过滤sudo sysctl -w net.ipv4.conf.tap0.rp_filter0但注意——在没有 sudo 的环境里你改不了这个参数。此时只能从 RIOT 侧禁用 ICMPv4 的过滤或者改用 IPv6 地址来排错。IPv6 的 Link-Local 地址不需要目标 IP 配置通常可以帮你绕开“没设 IPv4”的问题。5.4 iperf3 报了 Permission Denied这不是防火墙问题Ubuntu 默认没开 ufw而是你用了 1024 以下的端口。比如-p 8800是没问题的但如果 RIOT 里的udp server start 80宿主机再用 80 端口发包普通用户会报权限错误。建议服务端口一律选 1024 以上避免踩权限坑。5.5 找不到examples/gnrc_networking/bin/native目录如果你用的 RIOT 版本比较新make缺省情况下不会生成bin/目录下的文件名你需要指定目标文件。更保险的做法是make BOARDnative clean make BOARDnative然后在examples/gnrc_networking/bin/native/下找名为gnrc_networking.elf的文件。如果 2026.07 版本改成别的名字可以用find . -name *.elf找。我这次版本里默认文件名没变还是老名字。5.6 没有 sudo也没有 TAP 设备创建权限的最终退路如果管理员完全不配合、TAP 无论如何创不了还有一个备用方案使用 SLIP over UART。native配置下可以通过-c参数指定一个伪终端然后再用socat把伪终端桥接成虚拟串口加上INTERFACEslip模块。但这个方案的吞吐会大幅下降实测往往只有 1-3 Mbit/s而且配置复杂只作为“至少能跑起来”的兜底手段不适合做性能测试。6. 实际测试过程快照写出来给想看细节的人。我用了两台“伪终端”模拟方式其实只用了一台机器就是192.168.99.1宿主和192.168.99.2RIOT互发。RIOT 侧启动命令TAPtap0 ./bin/native/gnrc_networking.elf进入 shell 后执行 ifconfig 5 add 192.168.99.2/24 ifconfig 5 up udp server start 8800宿主侧客户端发送$HOME/opt/iperf3 -u -c 192.168.99.2 -p 8800 -b 50M -t 10 --get-server-output但前面说过 iperf3 的 server 端不在 RIOT 上所以它拿不到 server output。第二次我改用自定义统计脚本python3 - EOF import socket, time, sys s socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind((192.168.99.1, 8801)) s.settimeout(2) start time.time() bytes_recv 0 while time.time() - start 10: try: data, addr s.recvfrom(2048) bytes_recv len(data) except socket.timeout: pass print(f{bytes_recv*8/10/1000:.2f} kbit/s) EOF这个脚本是让 RIOT 主动向宿主机发 UDP。RIOT 端可以通过udp send指令持续发送 udp send 192.168.99.1 8801 hello world但这种手动发送测不了高吞吐。更高效的是在 RIOT 内部写一个循环发送线程用gnrc_udpAPI 连续发包。我这里用的是一个简单的tests/gnrc_udp扩展直接调用gnrc_udp_send构造 1200 字节的包每次间隔 100 微秒最终统计 10 秒内发送字节数。这个实验的数字是 28 Mbit/s丢包率约 0.3%说明栈是稳定的。这里有个非常重要的经验如果你是在真实板卡上DMA 共享内存和缓存一致性会影响吞吐但在 native 目标上系统的默认配置有一个“发送缓冲”限制导致gnrc_udp_send可能因为缓冲区满而丢包。如果你看到丢包率突然上涨别急着怀疑代码先检查是不是宿主机的 socket 接收缓冲太小sysctl net.core.rmem_max没有 sudo 的情况下可以尝试调低发包速率来适应缓冲区。我在测试 28 Mbit/s 时发包间隔取的是 300 微秒而不是 100 微秒就是为了避免拥塞丢包导致数字失真。7. 后续要扩展的内容方向这次只跑了 native 目标但 RIOT 的价值在于多平台和多网络协议栈。我计划后面再做三件事把同样的代码交叉编译到 ESP32-S3看硬件性能启用gnrc_ipv6的 RPL 路由模拟一个 10 节点无线 mesh以及把coreclk调到 64 MHz 测试调度器延迟。如果你也被困在没 sudo 的环境里建议先跑通 native再用它做自动化 CI 回归测试——这是受限环境下性价比最高的玩法。
返回列表