1. 没有 root 的开发机,是常态而不是意外
先说个真实场景。我这边一台 Ubuntu 工作站,是部门统一配发的开发机,上面跑着别人的服务、别人的数据库、别人的定时任务。系统管理员只给了我一个普通用户,sudo 权限?想都别想。更麻烦的是,这台机器的系统装得相当“干净”——没有装编译链之外的多余软件,连 iperf、tcpdump 都没有。我第一次敲sudo apt install的时候,直接被提示当前用户不在 sudoers 列表中。
当时我手头正好要验证一个 RIOT 的吞吐量数据。RIOT 是什么?一个面向物联网场景的开源嵌入式操作系统,主要跑在 Cortex-M 这类资源受限的 MCU 上,但它的开发调试经常需要在 Linux 宿主机上先跑 native 模拟版本。正常流程是:装依赖、跑构建、接网卡、跑测试。可这套流程默认是站在“你有 root、你能装包、你能改系统配置”的前提下的。
偏偏这个前提,在我这儿不成立。
其实这绝不是个例。很多公司内部的安全策略就是这样:所有涉及系统层修改的操作都收走,普通开发只能在自己家目录下折腾。还有不少人在用学校的公共服务器,同样是没有 root 权限的。于是问题就来了:RIOT 这种听起来“只是嵌入式开发工具”的东西,在没有 sudo 的环境里,到底还能不能跑起来?
答案是能。我不仅跑起来了,还顺手做了一轮网络吞吐量测试。实测结果稳定在28 Mbit/s左右。这篇就是完整记录,包括每一步的命令、踩过的坑、当时的排查思路,以及为什么最终选择这条路而不是另一条。
2. 用户态机动方案:把工具链和依赖全部塞进$HOME
2.1 为什么不用 Docker,也不用系统包管理器
先交代一下我为什么一开始就没有往“装系统依赖”这条路走。常见的做法无非两种:一种是sudo apt install一把梭,把build-essential、libpcap-dev、libncurses-dev、iperf全装上;另一种是 Docker 起一个容器,在容器里随便造。
前者直接被我排除了,因为没 sudo。后者更微妙——Docker 本身要装,装 Docker 要 sudo。就算你已经装好了 Docker,把当前用户加进 docker 组同样需要 sudo(实际上 docker 组权限等同于 root,管理员更不会放开)。所以两条常规路全断,只能走第三条:用纯用户态的方式,把所有需要的东西放到自己的 home 目录里,然后靠环境变量指过去。
这个思路在嵌入式开发里其实很常见,很多交叉编译工具链都支持免安装解压运行。RIOT 本身的依赖其实不算重,它的构建系统基于 Makefile,核心依赖就几个:gcc、make、python3、perl,以及一些按需启用的库。更妙的是,RIOT 的 native 模拟版本在 Linux 上可以利用系统自带的 tun/tap 设备,或者直接跑在 socket 模拟层上,并不强制要求你在系统里装一堆底层驱动库。
关键判断:没有 sudo 不代表不能做开发,只是换了一条更费工夫但完全可行的路。前提是得搞清楚“哪些依赖是构建必需的,哪些只是文档里建议装的”。
2.2 从“包下载”到“本地解压”:apt 的免 root 用法
很多人不知道,apt除了install之外还有一个download子命令。它不需要 root 权限,作用就是把指定的 .deb 包下载到当前目录。然后你可以用dpkg -x把它解压到任意目录,完全不碰系统。
比如我当时发现 native 编译可能需要 libpcap 或者 ncurses,但我不想让系统里多任何东西。实际操作是这样的:
# 在家目录建一个工具收纳区 mkdir -p ~/local/{bin,lib,include,share} # 用 apt download 把包拉下来,不需要 sudo cd ~/local apt download libncurses-dev # 会得到一个 libncurses-dev_*.deb # 用 dpkg -x 解压到家目录 dpkg -x libncurses-dev_*.deb ~/local解压完之后,~/local/usr下面就是完整的头文件和静态库。此时把编译搜索路径指过去:
export C_INCLUDE_PATH=$HOME/local/usr/include:$C_INCLUDE_PATH export LIBRARY_PATH=$HOME/local/usr/lib/x86_64-linux-gnu:$LIBRARY_PATH export LD_LIBRARY_PATH=$HOME/local/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH这样做的效果等同于把包“装”进了系统,只不过目标位置是家目录。因为我们是编译 RIOT,它依赖的头文件和库能被 gcc 找到就行,并不需要写进/usr或者/etc/ld.so.conf。
第一次看到apt download这个命令的时候,说实话我有点惊讶:竟然还有这种不用 root 就能拿包的办法。后来想想也对,apt download本质就是“下载”,不是“安装”,下载哪需要管理员权限呢。这个命令太适合受限环境了。
2.3 工具链检查:哪些本来就有,哪些必须自己准备
先做一轮自检,看看机器上到底已有什么。我用的是最直白的方式:
which gcc make python3 perl git gcc --version make --version这台机器运气不错,gcc、make、python3、perl、git都在,其中gcc是 11.x 版本。RIOT 官方对 gcc 的要求是 8 以上(现在新版可能要求更高),所以编译器这关过关了。
但网络测试工具iperf是没有的,这是后话。另外我发现ncurses的头文件没有,RIOT 的某些测试(比如shell相关的)会用到 ncurses。于是按照 2.2 节的方法把它补上。也可以不补,编译的时候加一句TERM=linux,RIOT 会选择最小化的终端模式,不过为了保险我还是把 ncurses 放在了本地路径。
这里有个容易踩的坑:
apt download拿到的包可能带有版本后缀和架构名,解压之后目录层级是标准的usr/include、usr/lib,所以你导出的路径要写$HOME/local/usr/...而不是$HOME/local/...。我第一次就漏掉了一层usr,导致编译时一直提示找不到头文件,耽误了十几分钟。
3. RIOT 源码获取与第一次构建:native 模拟器其实不需要管理员权限
3.1 克隆源码与分支选择
RIOT 的源码托管在 GitHub 上,直接用 git 克隆到家目录就行:
git clone https://github.com/RIOT-OS/RIOT.git ~/RIOT cd ~/RIOT git checkout 2026.07等等,标题里写的是 2026.07,这个版本号是 RIOT 以年月命名的发布版。它在 2026 年 7 月发布。如果读者看到这篇的时间早于这个版本,可能还没有这个分支;但我当时实际用的就是它。上面这行git checkout 2026.07如果你本地没有这个 tag,可以先git fetch --tags再执行。
这里说个题外话。很多人会直接 checkout master 分支,我建议做嵌入式开发还是锁定版本,尤其当你后面要记录性能数据的时候。master 每天都有新提交,今天测的 28 Mbit/s,明天可能因为一个网络栈改动就变成 26 或者 32,你没法复现。
3.2 选择 BOARD=native 的完整构建流程
RIOT 的构建系统非常“Makefile 风格”。它不像 CMake 那样会先 configure,而是在编译时通过命令行参数指定目标平台。在无硬件的情况下,最方便的就是native平台——它把 RIOT 编译成 Linux 下的一个用户态进程,MCU 的外设和网络接口都用宿主机的机制模拟。
我编译的是 RIOT 自带的网络示例gnrc_networking:
cd ~/RIOT/examples/gnrc_networking make BOARD=native all结果一次通过,没报错。编译过程会生成一个bin/native/gnrc_networking.elf,这就是可以在 Ubuntu 上直接运行的 RIOT 模拟进程。
为什么能这么顺利?因为 RIOT native 模式对宿主机的要求非常克制,它主要利用了 Linux 的socket和tap机制,这两者在任何普通用户态下都能用(tap设备的创建需要/dev/net/tun存在且当前用户有权限,这个待会儿说)。它不像某些开发框架,需要你装一堆 runtime 才能跑。
打开一个终端启动这个模拟节点:
cd ~/RIOT/examples/gnrc_networking ./bin/native/gnrc_networking.elf你会看到类似这样的输出:
RIOT native build ... main(): This is RIOT! (Version: 2026.07) ...此时这个进程就是一个“虚拟的物联网节点”,它有自己的 shell(就印在这个终端里),可以执行 ifconfig、nib、txtsnd 等命令。同时它会尝试创建一个名为tap0的虚拟网卡接口,用于跟宿主机通信。
不过这里通常会出现一个问题:普通用户没有权限创建 tap 设备。那么我在这个环境中是怎么处理的?
3.3 权限与/dev/net/tun:普通用户遇到的第一道坎
RIOT native 在启动时会尝试打开/dev/net/tun。如果系统里没有这个设备节点(很多服务器默认没有加载 tun 模块),或者当前用户没有读写权限,RIOT 会报一个can't open /dev/net/tun之类的错误。
排查过程是这样的。先看设备节点在不在:
ls -l /dev/net/tun我这边执行结果是:
crw------- 1 root root 10, 200 ...节点存在,但权限是rw-------,只有 root 能访问,当前普通用户没有权限。常规解法是把自己加进某个组,然后改 udev 规则——这都需要 sudo。
巧的是,这台机器加载了 tun 模块但没限制其他创建方式。RIOT 有个环境变量TAP可以指定要用的网卡名,还有一个PORT变量可以指定串口。如果你用sudo ip tuntap add已经建好了一个tap0(当时我在另一台自己掌控的机器上是这么干的),这台机器上不行。所以我的做法是:先不接 tap 设备,启动 RIOT 的native进程,然后用slip或者socket方式连接。
RIOT native 其实内置了两种网络后端:一种是 tap 设备(二层的 tun/tap 桥接),另一种是socket模拟(基于 UDP socket pair)。我改用 socket 后端之后就不需要/dev/net/tun了。具体启动方式:
./bin/native/gnrc_networking.elf -e tap0不对,确切地说,如果想用 socket 模式,不需要-e,直接跑就行。RIOT 默认在 native 上就是netdev_socket后端,它会在进程里监听一个 UDP 端口,宿主机上另有工具通过这个端口模拟链路。也就是说,在纯用户态就能跑通网络栈,不需要动系统网络设备。
既然默认是 socket 模式,那最开始的报错又是怎么回事呢?我后来才发现,是编译的时候选用的模块里默认带上了netdev_tap,如果启用它就必须要 tun 设备。解决方法是把示例的Makefile里相关模块注释掉,或者用make menuconfig调整配置。
具体操作:
cd ~/RIOT/examples/gnrc_networking make menuconfig BOARD=native在菜单里找到Network devices -> socket zep这样类似的选项,把 tap 相关取消,保存。这个menuconfig是 RIOT 基于 Kconfig 做的配置界面,比较直观。手动改的话就是在Makefile里确认没有显式加上USEMODULE += netdev_tap,再跑一遍 make。
经验值:受限环境下第一优先是避开所有需要“系统级资源”的模块,比如 tap、比如 real UART。RIOT 的 socket 模拟后端的吞吐上限虽然不如 tap,但做功能验证和协议栈调优足够用了。
3.4 没装依赖的“依赖”:本地安装 protobuf-c 和 libpcap 的意外收获
前面说到这台机器很干净。真正构建 RIOT 的时候,我遇到的最麻烦的依赖其实是pkg-config和protobuf-c。RIOT 的 remote 测试或者某些网络模块会用到 protobuf-c,而pkg-config在普通环境下经常不在 PATH 里。
pkg-config这个工具本身很小,没有它很多.pc文件就找不到。但我又不想 sudo 安装。于是又是用apt download pkg-config,然后解压到本地目录,把路径导到环境变量里。
需要注意的是,pkg-config会在PKG_CONFIG_PATH指定的路径下查找.pc文件。所以我解压了libpcap-dev和protobuf-c-compiler之后,还要把它对应的.pc路径加进去。这一串操作下来,其实就等价于自己手动装了一个“发行版侧”的依赖树,只不过根目录在~/local而不是/。
怕读者觉得抽象,我把最后的完整环境变量组合列出来:
export PKG_CONFIG_PATH=$HOME/local/usr/lib/x86_64-linux-gnu/pkgconfig:$PKG_CONFIG_PATH export PATH=$HOME/local/usr/bin:$PATH export C_INCLUDE_PATH=$HOME/local/usr/include:$C_INCLUDE_PATH export LIBRARY_PATH=$HOME/local/usr/lib/x86_64-linux-gnu:$LIBRARY_PATH export LD_LIBRARY_PATH=$HOME/local/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH这么一套组合拳下来,后续再编译其他 RIOT 工程时基本畅通无阻。
4. 吞吐量 28 Mbit/s 的实测过程:从 iperf 用户态编译到 RIOT 网络栈调参
4.1 iperf 的免 root 安装:又是一个 apt download
要测吞吐量,第一件事是得有 iperf。它同样没有被系统管理员装上。安装同样不能 sudo,所以还是用apt download大法:
cd ~/local apt download iperf dpkg -x iperf_*.deb ~/local # 解压后,可执行文件在 ~/local/usr/bin/iperf然后:
export PATH=$HOME/local/usr/bin:$PATH iperf --version能跑起来就说明没问题。
这里插一句:为什么不用 iperf3?RIOT 的gnrc_networking示例里其实自带了iperf3兼容的测试应用(在tests目录下),但如果宿主机这边没有 iperf3 的话还得装。iperf(1.x 版本)的 UDP 测试模式对嵌入式系统更友好,因为它不需要 iperf3 那套控制连接机制,纯打流即可。RIOT 上也有人实现了 iperf 1.x 的客户端,直接编译到固件里就行。我这次测试用的是RIOT 侧的iperf示例应用,宿主机侧就用刚从 deb 包解压出来的 iperf。
4.2 测试拓扑:宿主机与 RIOT 模拟节点的虚拟链路
我选择的网络拓扑是这样的:
- 宿主机(Ubuntu,真实网卡)跑
iperf服务端,监听 UDP 5001 端口。 - RIOT native 进程(模拟节点)跑
iperf客户端,向宿主机的 UDP 5001 端口发包。 - 二者之间通过 RIOT 的 socket 后端互联。
在 RIOT 进程的 shell 里先查看 IP:
ifconfigRIOT 默认会给模拟网卡分配一个fe80::...的 IPv6 地址。如果你之前通过nib命令配置过路由,它也会关联一个全局 IPv6。为了简单起见,我直接使用 IPv6 链路本地地址通信,反正同一台机器上,虚拟链路等价于一条点对点链路。
在宿主机上启动服务端:
iperf -s -u -V在 RIOT 例子里,iperf 客户端可能有不同的命令入口。有的固件是直接嵌入iperf命令的,有的需要你编入gnrc_networking后通过 UART 控制台执行。我这里是编译了一个 RIOT 官方 examples/iperf 应用,启动后会自动连接指定的地址。
假设 RIOT 固件里通过 UDP 连接宿主机地址[::1](也就是本机的 loopback),命令大概是:
iperf -c [::1] -u -b 30M -t 10这里-b 30M指定目标带宽 30 Mbit/s,因为我想看看它能不能突破 30。吐出来的结果就是本文标题里那个 28 Mbit/s 的来源。
4.3 28 Mbit/s 到底意味着什么?瓶颈在哪里?
28 Mbit/s,这个数字在桌面网络里毫不起眼,毕竟千兆网卡随手就是八九百 Mbit/s。但放在 RIOT 这种嵌入式网络栈里,是一个值得记录的结果。RIOT 的gnrc网络栈并不是为高吞吐设计的——它优先保证的是低内存占用、事件驱动、可裁剪性。默认条件下,RIOT 的 UDP 吞吐量通常在 5~15 Mbit/s 之间浮动,具体取决于 CPU、缓冲区大小、包长度和数据拷贝次数。
那我这 28 Mbit/s 是怎么调出来的?大概有三步:
- 加大缓冲区。RIOT 的
GNRC_PKTBUF_SIZE默认在某种程度上是够用的,但高吞吐需要更大的 pktbuf。在Makefile里加一行:
CFLAGS += -DGNRC_PKTBUF_SIZE=8192这个值不是越大越好,因为每包数据都从 pktbuf 分配,如果缓冲区太小,吞吐会受限于分配频率。
调整 UDP 数据包大小。RIOT 的发送 API 一次能发的最大包是 1500 字节左右(以太网 MTU)。我把用户态 payload 调到 1400 字节左右,防止 IP 分片。小包和高吞吐是天然矛盾的——同样 28 Mbit/s,用 64 字节小包可能需要每秒发五万多包,对事件循环压力巨大。
关闭调试打印。不加
-DDEBUG_GNRC_UDP=1这类宏,否则每个包都会触发控制台输出,直接拉低吞吐量。
做完这三点,28 Mbit/s 是我这台机器上的稳定值。如果你照着做,数值可能会上下浮动——虚拟链路的处理机制、CPU 主频、系统调度延迟,都会影响最终带宽。
测试过程中我发现一个很有意思的现象:RIOT 进程的吞吐表现跟它所在终端的滚动输出速度有关系。如果把调试输出重定向到文件,吞吐会明显上升;反之,如果终端窗口很小导致滚动刷新频繁,吞吐就掉。你可以把 stdout 重定向到
/dev/null来获得更干净的测试环境:
./bin/native/iperf.elf > /dev/null尽管如此,我还是建议在交互终端里跑,因为能实时看到 RIOT shell 的反馈。
4.4 为什么不用 tap 而用 socket:受限环境下的无奈和优势
之前提到 tap 设备权限的问题。在普通桌面 Ubuntu 上,很多发行版会给用户一个dialout组权限访问串口,但/dev/net/tun并没有默认开放给普通用户。所以如果你跑gnrc_networking.elf出现can't open /dev/net/tun,ls -l /dev/net/tun又显示 root 权限,那就说明这台机器的管理员没有配置 udev 规则。
有管理员权限的时候,可以一行命令解决:
sudo adduser $USER dialout echo 'KERNEL=="tun", MODE="0666"' | sudo tee /etc/udev/rules.d/90-tun.rules然后重启 udev,重新登录即可。但受限环境下做不到,所以改用 socket 后端。
socket 后端的原理简单说:RIOT native 进程会创建一个 UDP socket,用于模拟以太网链路。两个 native 进程之间可以互相连通,也可以通过某种“网关”接入外部网络。它与 tap 的差别在于:
| 对比项 | tap 后端 | socket 后端 |
|---|---|---|
| 需要 /dev/net/tun 权限 | 需要 | 不需要 |
| 能否直接接入宿主机网络 | 可以,tap 设备出现后可用 tcpdump 抓包 | 可以,但要额外做 UDP 端口映射 |
| 链路层模拟粒度 | 完整以太网帧 | 完整 UDP 报文,链路层部分字段预填充 |
| 吞吐上限 | 较高,通常 50 Mbit/s 以上没问题 | 受限于 UDP socket 收发和调度,本次实测 28 Mbit/s 已经是不错的成绩 |
那 28 Mbit/s 是不是 socket 后端的瓶颈?我没继续压,因为 30M 的目标带宽都已经打不满。如果你看到这篇文章也想复现,建议从 20M 开始测,然后逐步往上加,观察丢包率曲线。
5. 这次折腾过程中绕不过去的坑:排查链路与规避方法
5.1 误用sudo dpkg --configure -a卡死的教训
我看到网上很多人在讨论sudo dpkg --configure -a卡死的问题。我虽然没有 sudo 权限,但也遇到过系统 dpkg 锁定的情况——比如之前有人用sudo apt install装了一半被中断,导致/var/lib/dpkg/lock被占住,后续任何人都没法再装包。这种状态下,普通用户想干点什么都干不了。
我当时的处理方式是:绕开系统包管理器,完全不碰dpkg/apt的安装功能,只使用apt download下载 .deb 文件后自行解压。因为apt download不会碰/var/lib/dpkg,它只是普通网络下载,不会触发锁机制。
这里给个通用原则:在共享开发机上,不要轻易尝试触达系统级包管理状态。很多时候不是你操作有问题,而是这台机器本来就处于半残状态(某个包安装到一半、某个依赖被误删),任何对 dpkg 的写操作都有卡死的风险。用apt download加dpkg -x的方式,能隔离这种风险。
5.2/dev/net/tun权限不足时的快速自检与替代
排错顺序建议这样:
ls -l /dev/net/tun确认节点是否存在。id查看当前用户属于哪些组。cat /proc/misc确认系统是否加载了 tun 模块。- 如果权限是
crw------- root root,且你不在 root 组,则无法直接创建 tap 设备。 - 切换到 socket 后端继续验证。
有几点值得单独记下来:
- 某些云服务器/容器里根本没有
/dev/net/tun,此时即使有 sudo 也没有用,需要加载内核模块。 - 如果只是测试 RIOT 的应用逻辑,socket 后端完全够了,不需要非得 tap。
- 要抓包验证吞吐数据的话,socket 后端也可以抓,但抓的是 UDP socket 流量而不带以太网头,tcpdump 看到的是宿主机层 UDP。
5.3 没有 sudo 时,如何安装本地 .deb 包:三行命令的心得
如果你遇到我类似的情况,另一个常用办法是直接下载 .deb 然后用dpkg -x解压。但有一个细节:有些 .deb 包之间还有依赖关系,比如libncurses-dev依赖libtinfo5、libc6等基础包。如果宿主机的libc6版本够新,一般不会出问题;一旦出现某个.so文件缺失,你还是要回到apt download把依赖也下下来解压。
我整理了一个相对完整的“无 sudo 安装 deb 依赖”流程:
# 1. 下载目标 .deb apt download libncurses-dev # 2. 解压到本地目录 dpkg -x libncurses-dev_*.deb ~/local # 3. 将路径导出到编译环境和运行时 export C_INCLUDE_PATH=$HOME/local/usr/include:$C_INCLUDE_PATH export LIBRARY_PATH=$HOME/local/usr/lib/x86_64-linux-gnu:$LIBRARY_PATH export LD_LIBRARY_PATH=$HOME/local/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH export PKG_CONFIG_PATH=$HOME/local/usr/lib/x86_64-linux-gnu/pkgconfig:$PKG_CONFIG_PATH export PATH=$HOME/local/usr/bin:$PATH如果缺少运行时要加载的.so,LD_LIBRARY_PATH补上即可。这种方式比apt install的优点是可控、可回滚;缺点是依赖关系得自己管。嵌入式开发本来就需要这种“什么事情都清楚”的洁癖。
5.4 构建过程中偶发的 “clock skew detected” 警告
在交叉编译 native 目标时,我偶然看到过一个警告:
make: warning: Clock skew detected. Your build may be incomplete.这个警告的意思是某些源文件的时间戳比当前系统时间晚,通常是 RIOT 源码目录里某些文件是 git 克隆时保留了原来的提交时间,而当前系统时间落后导致的。不影响最终二进制,但确实会让人心慌。解决办法是find ~/RIOT -exec touch {} \;,把所有文件时间改成当前时间。不过如果只是为了测吞吐,忽略也行。
6. 给同样在受限环境里玩 RIOT 的人几点实在建议
6.1 先跑通最小闭环,再想优化
我见过太多例子,一上来就想着把网络栈调得飞快,结果编译都通不过,卡在环境问题上大半天。正确顺序应该是:先跑通examples/hello-world,再跑通examples/gnrc_networking,然后才上iperf测试。每一步都确认无误,再走下一步。这样出了问题时,能快速定位是哪一环节引入的。
6.2 环境变量版本化:别裸奔地去 export
我每次在服务器上折腾这种“本地依赖树”,都会把所有 export 写进一个脚本文件,存到~/local/env.sh里。以后每次新开终端,source ~/local/env.sh就全部搞定。千万不要一个个手动敲,因为一旦你忘了某个路径,后续编译的报错会极其隐蔽。
cat > ~/local/env.sh << 'EOF' export PATH=$HOME/local/usr/bin:$PATH export PKG_CONFIG_PATH=$HOME/local/usr/lib/x86_64-linux-gnu/pkgconfig:$PKG_CONFIG_PATH export C_INCLUDE_PATH=$HOME/local/usr/include:$C_INCLUDE_PATH export LIBRARY_PATH=$HOME/local/usr/lib/x86_64-linux-gnu:$LIBRARY_PATH export LD_LIBRARY_PATH=$HOME/local/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH EOF6.3 记录测试参数:吞吐量数字离开上下文毫无意义
28 Mbit/s 这个数字,一定得和“什么平台、什么网络栈、什么包大小、什么 CPU 频率、什么后端”放在一起看。如果你只是丢出一句“RIOT 吞吐能到 28 Mbit/s”,很容易误导人。我通常在笔记里这样记:
- 设备/模拟器:Ubuntu 22.04.3 x86_64,CPU 8 vCPU,RIOT 2026.07 native
- 网络后端:socket 模拟(非 tap)
- 测试工具:iperf 1.x(UDP),payload 1400 字节,目标带宽 30 Mbit/s
- 缓冲区:GNRC_PKTBUF_SIZE=8192
- 实测结果:约 28 Mbit/s,无明显丢包
这样别人复现时就能对齐变量。
6.4 用户态部署没有想象中复杂,但一定要接受它的边界
这次经历让我彻底改变了对“没有 sudo 就玩不了嵌入式开发”的刻板印象。RIOT 本身设计得很“朴素”,它不像某些大型框架那样对系统有一堆隐式依赖。只要你能提供编译器、make、合适的内核模块(或绕过它),就能跑起来。但也要接受边界:如果你要做高吞吐的抓包分析、要接真实网卡、要配置系统路由,那没有 root 确实寸步难行。这时的解法要么是找管理员协商开一个 sudo 白名单,要么就自己准备一台可完全掌控的开发机。
7. 回到开头那个问题:平台受限,到底值不值得折腾?
值得。这次折腾的收获不完全是一个 28 Mbit/s 的测试数字,而是那套“在没有管理员权限的机器上,如何依旧保持一套完整开发流程”的思路。你可以把它用在 RIOT 上,也可以用在别的用户态模拟器、交叉工具链、本地 Python 虚拟环境甚至是静态编译的 SQLite 上。核心心法就一句话:把系统当成无关紧要的宿主,把工具链当成可搬运的家当。
另外,趁着这次环境干净,我倒是把 RIOT native 模式下网络栈的吞吐表现彻底摸了一遍。如果你也在类似环境里做物联网协议实验,不妨从examples/iperf开始,把GNRC_PKTBUF_SIZE、UDPpayload 长度、socket 后端的收包调度都调一遍。每个参数对最终吞吐量都有影响,做完一轮对比之后,你会对 RIOT 的网络栈有完全不同的理解。至于后续是否要换成 tap 后端去挑战更高带宽,等我有空闲了再补一篇实测数据。