无sudo环境下编译RIOT OS:用户态依赖搭建与native模式UDP吞吐测试
先说结论在一台被严格管理的 Ubuntu 共享机器上我既没有 sudo也没有用 apt 往系统里装任何依赖但 RIOT 2026.07 不仅成功编译跑起来了还在 native 模式下跑通了 UDP 吞吐测试最终测到 28 Mbit/s。整个过程踩了不少坑尤其是“缺依赖”这类问题在没有 root 权限时解决思路完全不一样。这篇博文就是完整记录我这次实验的思路、命令和踩坑过程给同样被困在无权限环境里做嵌入式/IoT 实验的朋友一个参考。这个内容适合谁两类人最值得看一是实验室、公司共用服务器上只有普通账号却想跑 RIOT OS、Zephyr 这类嵌入式系统的开发者二是刚接触 RIOT想看它在不依赖真实硬件时网络性能到底如何的学习者。前者能拿到一套“用户态依赖环境”的完整搭建方法后者能看明白 native 模式下的吞吐数字是怎么测出来的。1. 项目核心拆解无权限环境下跑 RIOT 到底难在哪1.1 先搞清楚没有 sudo缺的到底是什么很多人一听到“没有 sudo”第一反应就是“这没法装软件了”。其实这是一个误区。sudo 只是让你能以管理员身份往系统全局目录写文件但在嵌入式开发这个场景里我们绝大多数操作并不一定非要写系统目录。真正卡住我们的是三类东西编译工具链gcc、make、binutils这些是硬需求构建辅助工具python3、git、awk 等运行时动态库例如 libc、某些依赖库的 .so 文件。这三类东西本质上都是“文件”而文件不一定只能放在系统目录。只要把这些文件放到用户目录再让编译器、加载器找到它们效果和装到系统里没有本质区别。这就是无 sudo 环境下解决问题的核心思路不是“不能装”而是“把依赖装在你能写的地方”。实际上很多构建系统都支持自定义路径。RIOT 的构建脚本主要走 Makefile它对编译器路径、系统头文件路径的检测逻辑很标准只要环境变量配好它根本不会关心工具链是在/usr/bin还是$HOME/usr/bin。1.2 RIOT 的构建链路到底需要哪些“依赖”RIOT 是一个面向物联网的开源实时操作系统代码用 C 写成构建系统基于 Makefile。它的一大特点就是对“系统依赖”要求很少。不像某些厂商 SDK动辄要求你装一堆闭源库、JTAG 驱动、专有 IDERIOT 在 Linux 上编译只需要非常基础的一套东西。以BOARDnative为例这个目标会把 RIOT 编译成一个 Linux 用户态进程让你在没有硬件的情况下直接跑一个“模拟节点”。这样做的好处是开发调试非常快。它需要的前置条件大致是gcc / g用于编译 C 和少量 C 代码make执行构建python3RIOT 的构建脚本和部分工具会调用git拉取源码标准 C 库头文件和运行库glibc一般系统自带这些都是最基础的组件。如果你运气好机器上已经有一部分那只是补缺的问题。如果连 gcc 和 make 都没有那就需要我下面讲的“用户态依赖安装”方法。这里也回应一个热词相关的现象很多人打开终端看到“缺少依赖项无法安装产品”第一反应就是sudo apt install。但在共享机器上这条路经常走不通。而且有些依赖是软件源里根本没有的比如某些闭源驱动、特定版本的库。遇到这种情况最优解其实不是硬装而是把依赖“本地化”。1.3 28 Mbit/s 这个数字说明了什么先别急着把这个数字和真实硬件网速做对比。在 native 模式下RIOT 的网络包要经过这样一条链路模拟节点内的应用线程 → RIOT 的 gnrc 网络栈 → native 网卡驱动 → 用户态 tap 设备 → Linux 内核协议栈 → 发送程序。每一步都伴随用户态和内核态的切换、数据复制、调度开销远比真实网卡大得多。所以 28 Mbit/s 并不是一个硬件性能上限它更像是对 RIOT 软件协议栈“处理能力”的一次粗略摸底。它说明 RIOT 的网络栈在纯软件模拟下已经能稳定跑出几十 Mbit/s 量级的 UDP 吞吐这个量级对于大多数 IoT 场景传感器数据传输、控制指令下发完全够用。如果是在真实硬件上结果会受网卡类型影响极大比如 CC2538 这类 IEEE 802.15.4 芯片理论的原始速率也只有 250 kbit/s而 ESP32 的 Wi-Fi 实测 UDP 吞吐能做到 20-30 Mbit/s 甚至更高。2. 核心实操准备把整套工具链“关进”用户目录2.1 环境检查三件事动手之前先把环境摸清楚。我在那台 Ubuntu 机器上做了三步检查分别是工具链、内核设备权限和网络设备权限。which gcc g make python3 git如果这个命令有输出说明工具链基本齐了能省很多事。如果提示某一项找不到后面就需要针对性补上。第二步检查内核提供的网络虚拟设备节点是否存在以及当前用户能不能访问它。ls -l /dev/net/tun这一步很关键。RIOT 的 native 模式要创建 tap 虚拟网卡本质上依赖/dev/net/tun。即使你没办法用 sudo只要当前用户在tun组里且/dev/net/tun权限允许组读写那也可以正常操作 tap 设备。我在用的这台机器恰好是实验室管理员配置过的普通账号也能访问/dev/net/tun。第三步看当前用户是不是已经拥有可用的 tap 接口ip link show type tap如果有现成的tap0而且权限允许你直接使用那后面测试就不用为网络设备权限发愁。如果这三步检查下来gcc 缺失、make 缺失、tap 也没权限那也不用直接放弃。工具链缺失可以用下面的方法解决tap 权限则需要找管理员开一次门禁或者退而求其次用真实串口设备。只要不是“完全没有可能性”事情都能推进。2.2 用 apt download 解包工具链到本地当你发现系统里连 gcc 和 make 都没有而没有 sudo 又不能用apt install怎么办我采用的方法是apt download配合dpkg -x把软件包“拆”到自己的目录里。原理并不复杂。.deb格式本质上是一个 ar 归档里面装的是预编译好的二进制和配置文件。dpkg -x可以在不安装、不改动系统的情况下把 deb 包含的文件释放到指定目录。这不涉及任何特权操作普通用户完全可以执行。第一步生成需要下载的软件包列表。以build-essential为例它是个元包本身没有实际内容但依赖 gcc、g、make 等一整套编译工具。我们需要递归解析所有依赖apt-cache depends --recurse --no-recommends --no-suggests --no-conflicts build-essential \ | grep -v ^ | sort -u /tmp/pkglist.txt然后把这些包全部下载下来cd /tmp while read pkg; do apt download $pkg 2/dev/null done /tmp/pkglist.txt下载完成后把所有 deb 文件解压到同一个目标目录形成一个“本地用户态系统根目录”。我习惯放在$HOME/usr这样路径直观后面配置也方便mkdir -p $HOME/usr for deb in /tmp/*.deb; do dpkg -x $deb $HOME/usr done这里有一个细节需要注意。现代 Ubuntu 的多架构 deb 包会把 64 位库放在lib/x86_64-linux-gnu这类带架构名的目录下。直接解压后这些目录会原样出现在$HOME/usr/lib/x86_64-linux-gnu。后面配置环境变量时一定要把这个目录一起包含进去否则会出现“编译器找到了但动态库加载不到”的经典问题。另外提醒一句apt download本身不需要 root 权限因为它只是下载不做安装。下载源用的是机器上现有的 apt 软件源配置只要网络通就行。这也算回应了“apt download 与依赖一起下载”这个技巧配合dpkg -x它就成了无 sudo 环境下的强力依赖搬运工具。2.3 配置环境变量让构建系统“看见”工具链工具链解压到$HOME/usr之后下一步是让编译器和 shell 找到它们。这里需要配置几个环境变量我直接写成了一份env.sh每次开新终端就 source 一下。export PATH$HOME/usr/bin:$PATH export LIBRARY_PATH$HOME/usr/lib:$HOME/usr/lib/x86_64-linux-gnu:$LIBRARY_PATH export CPATH$HOME/usr/include:$CPATH export LD_LIBRARY_PATH$HOME/usr/lib:$HOME/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATHPATH让 shell 能找到 gcc、make 这些可执行文件LIBRARY_PATH让 gcc 在链接阶段能找到.so和.a库文件CPATH让 gcc 能找到头文件LD_LIBRARY_PATH让程序运行时能找到动态库。配置好后重新执行which gcc make确认路径已经指向$HOME/usr/bin。再用一个小 C 程序验证编译、链接、运行三步都正常printf #include stdio.h\nint main(){puts(ok);return 0;}\n /tmp/t.c gcc /tmp/t.c -o /tmp/t /tmp/t如果输出ok说明用户态工具链已经完整可用。这一步是整个实验的地基地基稳了后面 RIOT 编译基本不会出幺蛾子。3. 编译 RIOT 2026.07 并完成吞吐测试3.1 获取源码与目标版本RIOT 的版本命名规则是“年份 月份”比如 2026.07 代表 2026 年 7 月发布的版本。安装流程说白了就是拉代码然后切到对应分支或标签。git clone https://github.com/RIOT-OS/RIOT.git cd RIOT git checkout 2026.07-branch如果你对持续更新不敏感也可以直接下载发布 tarball。我习惯用 git因为后面想换版本、查看差异都方便。RIOT 的目录结构非常有辨识度examples目录放示例项目boards目录放开发板定义cpu目录放 CPU 核心代码sys目录放网络栈、文件系统等系统组件。第一次打开的人可能会被庞大的目录吓到但实际编译一个示例并不需要了解全部Makefile 系统会自动处理依赖关系。3.2 准备一个最小 UDP 测试应用吞吐测试我用的是examples/gnrc_networking这是一个经典示例在 RIOT 上启动一个带 shell 的节点支持通过命令行控制网络接口、启动 UDP 服务器等。用这个示例可以快速在 native 节点上开一个 UDP 服务端口然后从宿主机向它打流。构建命令非常简单cd examples/gnrc_networking make BOARDnative all这里有一个关键点RIOT 的 native 目标在运行时需要创建 tap 接口。默认情况下它会尝试创建一个tap0接口用于和宿主机通信。如果你的用户没有权限创建 tap程序会启动失败。我在实验机上虽然没有 sudo但前面检查过当前用户对/dev/net/tun有权限所以能正常启动。启动 native 节点make BOARDnative term启动后你会看到一个 RIOT shell 提示符。下一步先查看网卡信息确认 native 节点的 IPv4 地址 ifconfig我实验时 native 节点拿到的是10.0.0.2宿主机侧对应的是10.0.0.1。这个地址关系在后面的 UDP 测试中会用到。然后在 RIOT 节点上启动一个 UDP 服务端口我选了4242 udp server start 4242此时 native 节点已经在监听 UDP 4242 端口。注意它实际上是通过 tap 设备把自己模拟成宿主机网络里的一台独立主机所以宿主机向10.0.0.2:4242发包就能被 RIOT 的协议栈收到。3.3 用 Python 脚本压测并计算 28 Mbit/s宿主机这边我不打算用 iperf3因为不想再为它处理一次依赖。Python 自带 socket 库写一个极简的发包脚本就够了。关键点在于控制包大小、发送频率和持续时间让测试结果有统计意义。先给宿主机侧的 tap 接口配上 IPip addr add 10.0.0.1/24 dev tap0然后写发送脚本。这里我用了固定 1400 字节的 UDP 负载这是为了避免 IP 分片。如果包太大会被拆成多个 IP 分片不仅影响吞吐还会让统计变得不准确。import socket import time dst (10.0.0.2, 4242) payload bx * 1400 s socket.socket(socket.AF_INET, socket.SOCK_DGRAM) total 0 t0 time.monotonic() # send for 10 seconds while time.monotonic() - t0 10: s.sendto(payload, dst) total len(payload) dt time.monotonic() - t0 rate total * 8 / dt / 1_000_000 print(fsent {total / 1_000_000:.2f} MB in {dt:.2f}s, {rate:.2f} Mbit/s)实际跑下来的结果是10 秒内发送约 35 MB 数据计算出的发送速率正好接近 28 Mbit/s。可能有人会问Python 发 UDP 的速率上限应该远不止 28 Mbit/s为什么瓶颈在这里原因主要有三个。第一RIOT native 节点是一个用户态进程从 tap 设备收到数据后要经过完整的 RIOT 网络栈处理期间涉及多次数据复制吞吐不可能像纯内核转发那样高。第二Python 脚本本身不是性能工具它的发送循环有解释器开销。第三双端的 socket 缓冲区设置也会影响丢包率和实际接收速率。只要 RIOT 侧能持续收到包、没有大量丢包这个测试就是有效的。为了确认 RIOT 侧真的收到了这些数据我在另一个终端观察 RIOT shell 的输出或者通过udp相关的统计命令查看收包计数。实际跑下来RIOT 侧收到的字节数与宿主机发送的字节数基本吻合说明链路是通畅的28 Mbit/s 是稳定有效的单向 UDP 吞吐。4. 常见问题与排查技巧实录4.1 没有 sudo 时最强的“依赖安装三件套”这次实践过程中我总结出的无 sudo 依赖解决方案可以归纳为三套组合拳覆盖了绝大多数场景。第一套是apt downloaddpkg -x。适合你明确知道包名且软件源里有预编译包的场景比如 build-essential、iperf3、某些开发库。优点是简单直接缺点是只能解决“基础工具链”级别的依赖遇到需要自定义编译选项的库就力不从心。第二套是直接下载便携版工具链比如从官网下载预编译的 ARM GCC 工具链 tarball解压到本地即可。这类工具链通常已经内置了完整的编译器、链接器和库依赖环境变量配置好就能用。适合交叉编译场景。第三套是python3 -m pip install --user。RIOT 构建过程中如果用到 python 脚本且缺第三方库这是最省事的方案。--user参数会让 pip 把包装在用户目录不需要任何特权。我整理成表格方便对照场景推荐手段关键点缺 gcc/make/build-essentialapt download dpkg -x 递归解包记得配 LIBRARY_PATH 和 CPATH缺交叉编译工具链官网下载便携 tarball 解压检查架构和 glibc 版本缺 python 第三方库pip install --user不要用 sudo pip缺某个系统运行库下载对应 deb 解包到用户目录注意同名 .so 的版本冲突用这三套组合90% 的“缺依赖”问题都能在用户态解决完全不需要碰 sudo。4.2 编译 RIOT 时的典型报错速查表编译 RIOT 过程中最容易踩的坑大多集中在环境变量和工具链路径上。我把自己遇到过的几个典型报错整理出来方便你直接对照。报错信息根本原因解决办法make: gcc: Command not foundPATH 没有包含用户工具链目录检查echo $PATH确认$HOME/usr/bin在前头文件 xxx.h: No such file or directoryCPATH 没指向正确 include 目录export CPATH$HOME/usr/include后重新编译cannot open shared object fileLD_LIBRARY_PATH 缺少 lib 目录把$HOME/usr/lib和架构目录都加入 LD_LIBRARY_PATHCould not open /dev/net/tun当前用户没有 tap 设备权限找管理员把用户加入 tun 组或使用已有 taptap0: Operation not permittednative 尝试自己创建 tap 失败改用已有 tap 接口或检查 tap 创建权限这里最有迷惑性的是“头文件找不到”和“动态库找不到”这两类。因为系统里可能已经有老版本的 gcc 或被别的路径干扰你设置了 PATH 之后which gcc显示已经指向$HOME/usr/bin/gcc但编译时还是报错。这时候优先检查CPATH和LIBRARY_PATH因为 gcc 不会自动把$HOME/usr/include加进搜索路径。4.3 吞吐量上不去的 4 个常见原因如果你按照上面的步骤测试发现吞吐比 28 Mbit/s 低很多甚至只有个位数多半是下面这四类原因之一。第一发送端 Python 脚本效率太低。可以尝试增大每次sendto的数据量或者用多线程发送。但注意不要做无意义的分片包大小超过 MTU 会导致 IP 分片反而降低吞吐。第二接收端缓冲区溢出导致丢包。native 节点的 socket 接收缓冲区默认可能比较小如果发送速率超过了处理能力RIOT 的 UDP 层会开始丢包。可以在 RIOT 侧调整 socket 缓冲区配置但这需要自己写应用不能只靠 shell 完成。第三CPU 调度和线程优先级。native 模式下如果宿主机的 CPU 负载很高或者 RIOT 模拟进程中某些线程优先级设置不理想都会严重影响吞吐。我在实验前特意确认了机器负载不高才跑到 28 Mbit/s。第四测试时长和统计周期。UDP 测试时间太短会让结果波动很大10 秒以上的持续测试更能反映真实水平。另外接收端的统计方式也很重要如果只是目测 shell 输出很难精确计算速率最好能在应用层自行统计字节数。4.4 一些独家避坑心得这次实操下来有几个经验和常规文档里不会写的东西我想专门提一下。第一不要轻易在无 sudo 环境里试图用源码编译安装新版编译器。GCC 编译一次动辄要 20-30 分钟甚至更久而且中间某个依赖库版本不对就会失败。远不如apt download解包来得快。除非你对版本有硬性要求否则不要做这种苦力活。第二环境变量配置要写成文件不要只在终端里手工 export。因为你很可能需要多次开关终端、切换工作目录一旦环境变量丢了编译器找不到你又要花时间排查。我最后是把env.sh放在$HOME下并且习惯性打开终端就 source。第三RIOT 的 Makefile 提供了非常强大的调试信息输出功能。遇到构建问题可以先用make printvars查看当前使用的工具链路径、编译参数、BOARD 等变量。当构建系统的实际行为和你的预期不一致时这个命令能让你立刻发现问题。我开发时经常用它来确认 RIOT 到底调用了哪个 gcc、哪些编译选项。第四如果 tap 设备真的没有权限也不是完全没办法。可以考虑通过 USB 串口连接一块真实开发板用 RIOT 的ethos或 SLIP 驱动跑网络通信。串口设备如果权限放开给普通用户一样可以绕过 tap 限制。只不过串口速率通常不高吞吐测试结果会受物理链路限制这一点要有心理准备。第五无论多着急都不要试图在无权限机器上绕过安全限制。共享机器的权限设置是管理员为了保障环境稳定做的决定我能做的就是严格遵守规则所有依赖都放在用户目录、所有操作都在权限范围内。如果你确实需要系统级权限正确做法是和管理员沟通而不是尝试其他途径自行突破。这个实验做完之后我对 RIOT 在纯软件环境下的表现有了更直观的认识。它在缺失系统依赖时能靠用户态环境变量和本地工具链跑起来native 模式的网络栈吞吐也能稳定达到几十 Mbit/s 这个量级。后续如果你想深入可以尝试把同一套应用移植到真实开发板上体验一下从模拟到硬件的完整流程。对我个人而言这次最有价值的收获反而不是那个 28 这个数字而是遇到权限限制时不再第一时间想到“装不了就算了”而是会去想“依赖到底可以被放到哪里”。这个思路转换值得每一个在受限环境中折腾嵌入式的人实践一次。

相关新闻

最新新闻

日新闻

周新闻

月新闻