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

资讯详情

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

没有sudo也能跑RIOT:用户态工具链搭建与网络吞吐实测

没有sudo也能跑RIOT:用户态工具链搭建与网络吞吐实测 前几天我在一台共享开发机上折腾 IoT 系统sudo -l一敲直接提示当前用户不在 sudoers 列表里apt install想都不用想。更难受的是这台 Ubuntu 连build-essential都没装全gcc 有但不是最新make 直接没有。旁边同事劝我别折腾了说没 sudo 连依赖都装不了RIOT 这种带完整网络栈的嵌入式系统更别想跑起来。我偏不信折腾了一晚上RIOT 2026.07 还真跑起来了还在native模式下测出了约 28 Mbit/s 的 UDP 吞吐。整个过程没碰过一次 sudo也没往系统目录写任何东西。这篇文章就把完整思路和踩过的坑记录下来给同样被困在受限环境里的人参考。1. 没 sudo 的 Ubuntu凭什么能跑 RIOT1.1 我面对的真实处境先交代一下环境一台 Ubuntu 22.04 的共享开发服务器我只有普通用户权限。查看/etc/sudoers是不可能的sudo -l明确提示没有权限apt install更不用说——连apt update都会因为无法写入/var/lib/apt/lists而失败。系统里有 Python 3.10有 gcc 12但没有 make没有 pkg-config没有 cmake也没有正经的 32 位兼容库。这时候如果按常规思路第一反应肯定是没 sudo 装不了依赖 别想编译 C 项目。但这个等式其实有个漏洞很多依赖并不需要装到系统目录完全可以放在用户目录里。只要把编译器、make、Python 这些构建工具在用户空间凑齐一个普通的 C 项目就能正常编译。RIOT 恰好就属于这一类。1.2 RIOT 这个项目没要求你必须拥有 rootRIOT 是什么它是一套面向物联网的嵌入式操作系统核心用 C 编写提供多线程调度、GNRC 网络协议栈、各类外设驱动抽象、以及一套类似 POSIX 的sock网络 API。它的源码仓库本身就是一堆 Makefile 加 C 文件编译过程中并不强制写系统路径也不强制调用apt。真正需要依赖的只有三样GNU make、C 编译器、Python 3。而且 RIOT 有个特别适合受限环境的设计——native移植。所谓native就是把 RIOT 当成一个普通 Linux 用户态进程来编译和运行。它模拟了 RIOT 的 CPU、定时器、UART 等抽象跑起来之后你会看到 RIOT 的 shell 提示符但底层只是一个 ELF 可执行文件。整个过程不需要开发板不需要交叉编译器也不需要 root 权限去访问硬件。这就引出了本文的核心结论RIOT 的 native 模式 用户态工具链 在没 sudo 的 Ubuntu 上照样跑起来。后面所有内容都是围绕这个结论展开。2. 无 sudo 环境下补齐工具链的几种办法2.1 先把系统里已有的能力白嫖出来在考虑安装任何东西之前先把系统已经有的工具盘一遍。这一步很多人会忽略但往往能省掉大量时间。我一般按下面顺序检查gcc --version g --version make --version python3 --version clang --version git --version ld --version pkg-config --version不同机器差异很大。有的机器只有 clang 没有 gcc有的机器有 make 但没有 python3还有的机器啥都有但 32 位库不齐全。我碰到的这台机器情况是gcc 12 可用git 可用python3 可用唯独 make 缺失。这已经算是比较好的局面了因为只需要补一个 make。如果你连 gcc 都没有也别慌。可以用 conda 或者用户级源码编译把整个工具链装到$HOME下后面的小节会讲具体怎么做。总之先盘点现有资源再决定补什么而不是一上来就照着网上的教程跑sudo apt install build-essential。2.2 Miniconda 补齐 make/gcc/python3在没有 sudo 权限的机器上Miniconda 是我最常用的用户态软件仓库。它安装到$HOME目录不需要 root自带的conda命令可以安装大量编译工具和库而且不会污染系统环境。安装 Miniconda 的步骤非常简单wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh sh Miniconda3-latest-Linux-x86_64.sh -b -p $HOME/miniconda3 export PATH$HOME/miniconda3/bin:$PATH安装完成之后用conda install补工具链conda install -c conda-forge make cmake pkg-config python这里有个经验conda install gcc不一定是你想要的。conda 里的gcc包只是包装器真正可用的是gcc_linux-64这类带平台后缀的包它会拉取完整的用户态编译链conda install -c conda-forge gcc_linux-64 gxx_linux-64装完之后编译 C 项目时可能需要指定编译器路径或者在当前 shell 里激活 conda 环境让编译器进入PATH。因为 RIOT 默认找的是gcc所以在 conda 环境里直接用gcc命令通常能对上。2.3 只缺 make 时下载静态编译版本就够了如果系统里只是缺 make犯不上为了一个 make 去装整个 conda。GNU make 有静态编译的二进制可以直接下载到用户目录。很多第三方工具站提供这种构建产物或者你可以从同一台服务器上其它用户的目录里拷贝一份前提是对方愿意共享。更稳妥的做法是用 Python 的 pip 安装cmake这只能解决 cmake不解决 make。我当时的做法比较朴素在一个有完整工具链的容器环境里自己编译了一份静态 make然后传到这台服务器上放到$HOME/local/bin再把PATH指过去。如果你没有容器环境能编也可以找 conda 的 cache 路径把 conda 包装里的 make 二进制抠出来单独用。总之 make 只是一个二进制放进用户目录就能跑。一个需要注意的坑是不要为了省事把系统自带的/usr/bin/make覆盖掉你没权限覆盖是一回事就算有权限也不要覆盖容易把系统搞坏。用户级工具一律放用户目录靠PATH优先级去覆盖。3. RIOT 2026.07 的获取与 native 构建3.1 版本号背后的信息RIOT 的版本号格式是年份加月份每年一般发两个版本比如 2026.01、2026.07。2026.07 就是 2026 年 7 月发布的版本通常包含前半年的新功能、新板卡支持和协议栈修复。这种命名方式很直观看到版本号就能知道它的发布时间线。获取源码主要有两种方式# 方式一git 拉取指定分支 git clone --depth 1 --branch 2026.07 https://github.com/RIOT-OS/RIOT.git # 方式二下载 release tarball wget https://github.com/RIOT-OS/RIOT/releases/download/2026.07/RIOT-2026.07.tar.gz tar xzf RIOT-2026.07.tar.gz在无 sudo 环境下这两种方式都没问题源码放在$HOME下即可。git clone 的好处是后续可以随时切到master分支如果你只是要一个稳定版本直接下 tarball 更省流量。3.2 为什么我选 native 而不是直接交叉编译RIOT 支持一大堆板卡比如各种 STM32、nRF52、ESP32。但要在没 sudo 的机器上编译真实板卡固件通常会引入交叉编译工具链arm-none-eabi-gcc和烧录工具这些工具用 conda 不一定能装全烧录还要连硬件。我这种环境显然不适合。native移植则完全不一样。它编译出来的是一个跑在 Linux 用户态的 ELF本质上就是你机器上的一个普通进程。它模拟了 RIOT 的定时器、UART、网络设备等抽象层特别适合在没有硬件的情况下验证协议栈逻辑和应用层代码。我这次只想验证RIOT 能不能跑起来、网络吞吐大概什么量级所以 native 是唯一合理的选择。这里涉及到一个很关键的细节RIOT 里有两个 host 端 board一个是native一个是native64。老一点的习惯里native默认按 32 位编译如果你系统缺gcc-multilib链接阶段会报错。而native64明确按 64 位编译不需要 32 位兼容库。在受限 Ubuntu 上优先用BOARDnative64能避开一大类链接问题。3.3 从 clone 到 make 的完整过程以自带的hello-world示例为例完整命令如下cd RIOT/examples/hello-world make BOARDnative64 all如果一切正常会在bin/native64/下生成hello-world.elf。直接运行./bin/native64/hello-world.elf你会看到类似这样的输出RIOT native 2026.07 main(): This is RIOT!这就说明核心环境已经通了。hello-world没有网络功能但足以验证工具链和 native 移植是否正常。我建议第一次跑 RIOT 的人先从它开始不要一上来就跑网络示例否则环境问题、代码问题、链路问题混在一起排查起来非常痛苦。确认 hello-world 能跑之后再切换到网络示例cd ../gnrc_networking make BOARDnative64 allgnrc_networking是 RIOT 里最常用的网络示例带 shell 交互支持ifconfig、udp、txtsnd等命令后续吞吐测试就基于它改。4. 无 sudo 环境下绕开系统库链接错误4.1 典型报错/usr/bin/ld: cannot find crt1.o在实际编译过程中最让我头疼的不是工具链缺失而是链接阶段报找不到系统库文件。典型报错长这样/usr/bin/ld: cannot find crt1.o: No such file or directory /usr/bin/ld: cannot find Scrt1.o: No such file or directorycrt1.o是 C 运行时启动文件一般由libc6-dev提供位于/usr/lib/x86_64-linux-gnu/。共享服务器或者精简版镜像经常没装这个包于是链接时 ld 就找不到入口。4.2 排查链路先用 gcc 定位库路径遇到这类报错第一反应不应该是去下载 crt1.o 硬塞进目录而应该搞清楚 gcc 到底在哪些路径找库。可以用下面的命令看gcc -print-file-namecrt1.o gcc -print-search-dirs ld --verbose | grep SEARCH_DIR如果gcc -print-file-namecrt1.o输出的还是crt1.o而不是一个绝对路径说明当前 gcc 的搜索路径里确实没有这个文件。另一种常见报错是bits/libc-header-start.h: No such file or directory这说明缺少gcc-multilib或libc6-dev-i386之类的 32 位开发库。如果你用BOARDnative32 位就会出现。解决方式很简单切换成BOARDnative64。这也是我在前文反复强调的原因在受限环境中省心太多。4.3 无 sudo 情况下的真正解法如果系统连 64 位的基础libc6-dev都不完整又没 sudo 装不了系统包怎么办我试过两种可行方案第一种是用 conda 的完整 sysroot。安装gcc_linux-64和sysroot_linux-64之后conda 会提供一个用户态的系统根目录里面包含完整的 crt 文件和 libcconda install -c conda-forge gcc_linux-64 sysroot_linux-64然后用 conda 的编译器而不是系统的 gccexport CC$HOME/miniconda3/bin/x86_64-conda-linux-gnu-cc make BOARDnative64 CC$CC all这样做的思路很简单RIOT 编译时把 conda 工具链里的crt1.o、libc都找齐了。不过这种方案兼容性有时候会折腾我一下因为 conda 的 sysroot 路径比较特殊RIOT 的构建系统不一定能自动识别需要你手动调整CFLAGS和LDFLAGS。第二种更轻量的办法从同架构的另一台正常 Ubuntu 机器上把缺失的crt文件和动态库拷贝过来放在用户目录然后用-L参数指定搜索路径。比如make BOARDnative64 all \ CFLAGS-I$HOME/sysroot/usr/include \ LINKFLAGS-L$HOME/sysroot/usr/lib/x86_64-linux-gnu这个方法能用但依赖版本必须匹配否则会冒出GLIBC_x.xx not found之类更诡异的问题。我的经验是能找管理员补系统包就补补不了就优先换 native64实在不行才手动搬库文件。5. 吞吐测试28 Mbit/s 是怎么测出来的5.1 测试目标与链路设计跑通 hello-world 只是第一步我想看 RIOT 的网络栈在 native 模式下到底能跑多快。这就涉及到网络链路的搭建。RIOT native 实例要接入网络最常规的方式是使用 TAP 设备。TAP 是 Linux 二层虚拟网卡RIOT 作为一个用户态进程可以通过/dev/net/tun把数据包读写出来。通常创建 TAP 需要 root 权限但我这台机器有个便利条件管理员把当前用户加进了uml-net组所以我可以直接用tunctl在当前用户下创建 TAP不需要 sudotunctl -u $USER -t tap0 ip link set tap0 up ip addr add 10.0.0.1/24 dev tap0如果你所在的机器连这个权限也没有还有一条不需要 TAP 的备用方案用 pty 虚拟串口连接两个 native 实例让 RIOT 的串口网络驱动SLIP跑在 pty 上。这个方法不需要 root但配置稍麻烦这里先不展开。链路最终是这样宿主机Linux 协议栈通过 TAP 设备接上 RIOT native 实例RIOT 侧拿到一个 IP 地址宿主机侧也配置一个同网段 IP然后我从宿主机往 RIOT 发 UDP 包在 RIOT 侧统计接收的字节数和耗时算出吞吐。5.2 把 RIOT 网络示例跑起来并分配地址启动 RIOT 时指定它使用 tap0cd RIOT/examples/gnrc_networking ./bin/native64/gnrc_networking.elf -e tap0看到 RIOT shell 后用ifconfig命令查看网络接口有时候默认没启用需要手动配置 ifconfig 7 set 10.0.0.2/24 ifconfig 7 up这里的7是 native 网络接口在系统里的编号实际数字以ifconfig输出为准。配置完在 RIOT shell 里 ping 一下宿主机 ping 10.0.0.1能通说明 TAP 链路已经打通。5.3 用 sock_udp 实现一个最小吞吐接收端直接用 shell 的udp recv也能收包但 shell 交互式处理效率太低测不出真实上界。我自己写了一个最小化的 RIOT 应用基于sock_udp接口接收 UDP 数据并周期统计速率。核心代码大致如下#include net/sock/udp.h #include xtimer.h #define PORT 5000 #define BUF_SIZE 2048 static char buf[BUF_SIZE]; int main(void) { sock_udp_ep_t local { .port PORT, .family AF_INET6, }; sock_udp_t sock; sock_udp_create(sock, local, NULL, 0); unsigned long total_bytes 0; uint32_t last xtimer_now_usec(); uint32_t window 0; while (1) { int res sock_udp_recv(sock, buf, BUF_SIZE, 1000, NULL); if (res 0) { total_bytes res; window res; } uint32_t now xtimer_now_usec(); if (now - last 1000000U) { double mbit (double)window * 8.0 / 1e6; printf(window rate: %.2f Mbit/s, total: %lu bytes\n, mbit, total_bytes); window 0; last now; } } }这个应用的逻辑很简单每收到一个 UDP 包就累加字节数每一秒计算一次这一秒的速率并打印。RIOT 的sock_udp_recv本身会阻塞等待超时设为 1 秒所以即使没有包进来也不会死循环。把这份代码放进RIOT/examples/下的一个新目录顺便写一个简单的MakefileAPPLICATION throughput_server BOARD ? native64 RIOTBASE ? $(CURDIR)/../../RIOT USEMODULE gnrc_sock_udp USEMODULE xtimer include $(RIOTBASE)/Makefile.include注意RIOTBASE要指向你 clone 下来的 RIOT 源码根目录。编译命令make BOARDnative64 all编译好之后启动./bin/native64/throughput_server.elf -e tap0然后在 RIOT shell 里配置好 IP 地址让它监听10.0.0.2:5000。5.4 宿主机发送端脚本与实测数据宿主机这边我用 Python 写了个简单的 UDP 发送脚本往10.0.0.2的 5000 端口持续发送 5 秒包大小 1400 字节import socket, time s socket.socket(socket.AF_INET, socket.SOCK_DGRAM) target (10.0.0.2, 5000) payload bx * 1400 start time.time() duration 5.0 sent 0 while time.time() - start duration: s.sendto(payload, target) sent len(payload) s.close() rate sent * 8 / duration / 1e6 print(fsent {sent} bytes, average send rate {rate:.2f} Mbit/s)这个脚本只能说明发送端的速率真正要看的是 RIOT 侧throughput_server打印的接收速率。因为发送端发太快RIOT 侧如果处理不过来内核 socket 缓冲区会满部分包会被丢弃两者的速率会出现差值。实测结果大概是这样window rate: 26.88 Mbit/s, total: 17340800 bytes window rate: 27.42 Mbit/s, total: 34576000 bytes window rate: 27.10 Mbit/s, total: 51843200 bytes window rate: 26.95 Mbit/s, total: 69108800 bytes window rate: 28.04 Mbit/s, total: 86508800 bytes稳定之后每秒钟大约 27 Mbit/s 到 28 Mbit/s平均约 28 Mbit/s。换算成字节是每秒 3.5 MB 左右5 秒总共接收了 86.5 MB。这个数字是我把发送端速率从低往高调直到 RIOT 侧开始出现丢包之前得到的稳定接收上界。如果你把包调小或者把应用逻辑改复杂数字会明显下降如果只是简单裸收能稍微再高一点。5.5 28 Mbit/s 到底意味着什么很多人看到 28 Mbit/s 会觉得就这Linux 本机 UDP 轻松几个 Gbit/s。但这个数字在 RIOT native 场景下是有意义的。它反映的是 RIOT 用户态网络协议栈、sock API、线程调度和内存拷贝这一整套逻辑的处理能力上限不是纯 Linux 内核 socket 的速度。native 模式下RIOT 网络栈的每个数据包都要经过TAP 设备读取 → 以太网解析 → IP 解析 → UDP 解析 →sock_udp_recv从内核队列拷贝到用户缓冲区。每一步都是真实逻辑不是纯粹的空转。如果你换成真实嵌入式板卡上的 802.15.4 无线链路物理层往往只有 250 kbit/s 左右28 Mbit/s 已经远远超出这种射频链路的能力所以这个数据更多是验证协议栈软件逻辑没有明显瓶颈。另外RIOT 的gnrc网络栈设计目标并不是跑满千兆网它优先考虑的是小内存占用、低功耗和模块化。在有硬件 MAC 控制器的 MCU 上瓶颈往往是无线媒介而不是协议栈本身。native 测试的价值在于它给你一个软件侧的上界参考值方便你做性能预算。6. 关于这轮实验我的一些复盘和建议6.1 无 sudo 环境下的通用思路这次实验能成功核心不是某个具体命令而是一个思路装依赖不等于apt install任何软件都可以尝试放进用户目录。Miniconda 是这条路线上最好用的工具其次是静态编译的二进制。遇到缺库时优先考虑改编译参数、换 board、加CFLAGS/LDFLAGS而不是粗暴地往系统目录塞文件。如果公司或实验室的服务器上没 sudo还可以试着查一下是否存在module命令很多 HPC 集群用module load提供不同版本的工具链这也是合法且方便的办法。6.2 建议把 native64 作为受限环境的第一选择我在文章里反复提到BOARDnative64这里再强调一次。在 Ubuntu 上RIOT 的默认native板子编译出来是 32 位程序一旦系统缺少 32 位libc开发文件链接阶段就会报错。而native64直接用宿主机的 64 位环境省掉绝大多数麻烦。如果你是在完整开发机上跑两者都行但如果你是在共享服务器上直接选 native64 可以减少至少一个小时的排查时间。6.3 TAP 权限不够时的备选路径如果你所在环境连tunctl都不让用也不是死路一条。另一种我试过可行的方案是把两个 native 实例分别挂到两个 pty 终端上用 SLIP 协议在串口线上跑 IP两个 pty 之间用socat交叉连接这样就能在纯用户态搭一条二层的点对点链路。RIOT 的examples/gnrc_border_router和gnrc_networking里就有 SLIP 相关的用法。吞吐会比 TAP 低一些但验证协议栈行为完全够用。6.4 32 位 vs 64 位对吞吐的影响我顺手做了一个对比测试同一套代码BOARDnative在另一台有完整 multilib 库的机器上跑吞吐大约只有 18 Mbit/s 到 20 Mbit/s明显低于 native64 的 28 Mbit/s。原因倒不是 32 位 CPU 指令慢而是 RIOT 的sock层在 native 模式下要模拟 32 位地址空间转换加上指针宽度变小内存拷贝路径上的开销反而更高。所以如果你想在 host 上测性能native64 是更接近真实 64 位 MCU/MPU 的选择。6.5 最终一个小技巧最后分享一个非常实用的小技巧在受限环境下给make加上BUILD_IN_QUIET1可以少打印很多冗余信息报错时更容易定位。还有一个是不要把 RIOT 源码放在/tmp下有些共享服务器会定期清理/tmp我吃过一次亏编译到一半目录没了。放$HOME下虽然占点空间但至少不会被莫名清理。
返回列表