公司给团队配了一台共享的 Ubuntu 服务器我的账户是个普普通通的低权限账号。刚坐下准备干活就撞上两堵墙sudo -n true直接报错用户不在 sudoers 里apt install想都不用想。更绝的是系统提示sudo: add-apt-repository: 找不到命令说明这台机器连 software-properties-common 都没装而我又没资格去补。简单说我就是被困在了一个“能登录、能写自己目录、能干瞪眼”的 Linux 环境里。但这会儿手头有个物联网项目要求验证 RIOT 2026.07 发布版的网络栈在真实系统上的表现。RIOT 是个物联网操作系统平时玩嵌入式的人应该不陌生但要在服务器上跑主流做法是用它的 native 原生模拟模式在 Linux 用户空间直接编译出一个 ELF 可执行文件来模拟整块“板子”。我原本有点慌后来仔细一想RIOT 的 native 模式压根不依赖 apt 安装任何软件包也不需要我必须拥有管理员权限唯一卡脖子的可能就是 TAP 虚拟网卡和权限。最后我不但把 RIOT 跑了起来还在 10 秒持续打流中测到了 28 Mbit/s 的 UDP 吞吐量。全程没有执行过一次 sudo也没有用 apt 装过一个包。这篇就完整记录我在受限 Ubuntu 环境里跑通 RIOT 的整个过程包括环境盘查、源码构建、权限绕行、吞吐量测试和事后复盘。如果你也遇到过“没 sudo 怎么跑自己的程序”这种问题这篇应该能给你一个不一样的思路。1. 被 sudo 卡住之后我怎样摸清这台 Ubuntu 的家底1.1 第一印象不是没装 sudo是没资格用很多人看到“没有 sudo”第一反应是去网上搜什么“sudo 改密码”“找回 root”其实这种思路一开始就跑偏了。共享服务器上没有 sudo 是常态安全策略就是不该让你有。我登录后的真实状态是这样的$ whoami devuser $ sudo -n true [sudo] password for devuser:sudo -n true的意思是“以非交互方式执行 sudo true”如果有权限会直接返回成功如果没权限会提示需要密码。我没有密码可输也不该去试。这里不用纠结管理员为什么不给先把现状接受下来再想办法。顺手再看一眼系统里这些常见命令是否可用which gcc make python3 git。结果全部存在。$ uname -m x86_64 $ cat /etc/os-release | grep PRETTY_NAME PRETTY_NAMEUbuntu 22.04.4 LTS $ gcc --version | head -1 gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0 $ python3 --version Python 3.10.12到这里我心里就有数了这台机器装的是 Ubuntu 22.04 LTSx86_64 架构gcc 11、make、python3、git 全都齐。大部分嵌入式交叉编译环境需要的 arm-none-eabi-gcc 我这里根本没有但 RIOT 的 native 模式是个例外它直接把 Linux 当成目标板用宿主机的 gcc 来编。1.2 受限环境下先盘清“有什么”比抱怨“没什么”更重要我把检查结果整理成了一张非常简单的表后面所有决策都围绕这张表展开资源状态对 RIOT 的意义gcc 11.4可用native 模式编译所需宿主编译器make可用RIOT 构建系统基于 makepython3可用RIOT 部分构建脚本会调用git可用拉取源码/dev/net/tun存在native 网络模拟的关键设备apt install不可用无法安装额外系统包sudo不可用不能做任何特权操作root 用户不可用同上这张表的结论非常明确所有“非特权用户能用的基础开发工具”都在缺的只是需要 root 才能安装或修改的东西。那么最合理的策略就是找一种能够完全自包含在用户空间、不依赖额外系统软件包和特权操作的运行方式。RIOT OS 正好符合这个条件。它的 native 模式在构建时使用的是宿主机的标准 C 库和 pthread不引入第三方依赖库源码仓库自带了内核、协议栈、驱动框架等全部模块。对 Ubuntu 系统来说它只需要 libc、libpthread 和 /dev/net/tun而这些都是 Ubuntu 默认安装的一部分。1.3 为什么 RIOT 值得在这个环境尝试RIOT 在物联网圈子里不算冷门但很多人对它的印象还停留在“要交叉编译到板子上跑”。实际上它的开发调试流程设计得很现代开发功耗敏感或资源受限的嵌入式应用时你完全可以先用 native 模式在 PC 上把业务逻辑、网络协议栈调通再交叉编译到真实硬件。native 模式的本质是把 Linux 的 TAP 虚拟网卡当作 RIOT 的“物理网卡”把 pthread 当作 RIOT 任务调度器的底层实现。RIOT 进程看起来就是一个普通 Linux 进程跑起来后在系统里就是个iperf.elf但内部运行着一套完整的物联网操作系统内核、网络栈和 shell。这套机制给我的最大启发是很多嵌入式系统的开发流程其实不需要一台硬件开发板也不需要整个系统级别的安装权限。你在受限服务器上照样能完成协议验证。它让我重新理解了“依赖”这个词——传统思维里做项目先apt install一堆包但有些项目的设计根本不需要你往系统里塞任何东西。2. 把 RIOT 2026.07 从源码变成 ELF构建过程的坑与绕行2.1 获取源码浅克隆还是完整下载既然没有权限下载源码自然也只能放自己家目录。我习惯用~/work作为项目目录mkdir -p ~/work cd ~/work git clone --depth 1 --branch 2026.07 https://github.com/RIOT-OS/RIOT.git cd RIOT--depth 1只克隆最近一次提交能省不少时间和磁盘。--branch 2026.07表示直接切到指定发布版。RIOT 的版本号规则是年份加月份每年 1 月和 7 月各发布一个版本所以 2026.07 指的就是 2026 年 7 月发布的版本它的发布周期非常规律。如果你所在的服务器连 GitHub 都不通可以找一台自己的电脑下载 tar 包再传上去但这不是本文重点。我先假设这台开发服务器的外网是通的项目也能正常拉取代码。2.2 选对测试用例我选 tests/iperf 而不是 examples/gnrc_networkingRIOT 官方自带的 example 非常多最常见的是examples/gnrc_networking它提供一个基于 UDP 的 shell适合手动敲命令测试网络。但如果要测吞吐量这个例子就不太够因为你要另外想办法打流。RIOT 源码树里还有tests/iperf这是性能测试专用的用例内置了 UDP/TCP 客户端和服务端逻辑。我最后选择了tests/iperf理由很简单我在宿主机上没有安装 iperf 的权限但 RIOT 侧自带一个 iperf 兼容实现这样我就能让 RIOT 作为打流发起方宿主机只要用 Python 标准库写个接收端即可。整个过程不需要在宿主机上额外装任何软件。进入测试目录开始构建cd ~/work/RIOT/tests/iperf make BOARDnative all第一次构建可能需要一点时间RIOT 会把所有核心模块编译一遍。编译过程中屏幕会滚动大量CC、AR、LD日志最后停在生成bin/native/iperf.elf。这个文件就是一个标准的 Linux ELF 可执行文件用file命令看一眼$ file bin/native/iperf.elf bin/native/iperf.elf: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, not stripped到这里其实已经成功了一大半但先别急着运行因为直接运行会报错。RIOT native 模式启动时如果指定了网络接口它要打开/dev/net/tun并创建或绑定一个 TAP 设备普通用户默认没有这个权限。关于这一步我在下一节详细说。2.3 构建中可能遇到的“没有依赖”问题在实际构建过程中我还是碰到了一些和“依赖”有关的状况。最常见的报错是python3: command not found。RIOT 的构建系统在进行配置解析时会调用 Python 3如果你登录的这台机器不是标准 Ubuntu确实有可能没装。但我们这台机器有所以没事。如果你遇到没有 Python 3 的情况可以在用户目录下用ln -s /usr/bin/python3.10 ~/bin/python3这类方式做一个指向但更稳妥的是先检查系统是否已经存在某个 Python不要一开始就想着怎么绕过。第二个可能的坑是 GCC 版本过老或过新导致编译告警。Ubuntu 22.04 自带的 GCC 11.4 可以正常编译 RIOT 2026.07但如果你所在的环境是老掉牙的 CentOS 7GCC 4.8 肯定编不动。RIOT 官方要求 GCC 版本至少要到某个新版本这个在文档里有说明。如果遇到版本不满足又没有权限升级系统包那就真没辙了只能考虑用容器或用户空间工具链但这属于另一个话题。第三个坑是文件系统。不要把源码放在 NFS 挂载的共享目录里构建RIOT 构建过程中有大量文件锁和临时文件操作NFS 上容易出奇怪问题。放$HOME下最安全。构建完成后RIOT 会把编译出的中间文件放在$(BUILD_DIR)默认是用户目录下可写的路径。整个构建过程完全没有碰过/etc、/usr这些系统目录日后想清理直接rm -rf ~/work/RIOT就算卸载干净。这种“完全用户空间”的特性在无权限环境里太重要了。2.4 验证产物并首次运行第一次运行我想先试一下不带网络接口的裸跑验证 ELF 本身没问题cd ~/work/RIOT/tests/iperf ./bin/native/iperf.elf这样会进入 RIOT shell出现提示符并且没有任何网络接口配置的报错。在这个 shell 里可以敲help查看可用命令还能直接内核命令行操作。然后再输入quit退出进程。如果运行时报cannot open /dev/net/tun或Permission denied说明缺少 TAP 设备访问权限。这正是我下一节要解决的核心问题。这一阶段的核心目标是把可执行文件构建出来确认进程能正常启动不需要一路顺畅跑通网络先把构建链路验证了再说。3. 没有 sudo 的 TAP 网卡我和管理员做的一次权限交易3.1 为什么 RIOT native 默认需要 rootRIOT native 模式模拟网络时需要在宿主机上创建一个 TAP 设备。TAP 是一种虚拟二层网卡工作在内核态由/dev/net/tun这个字符设备来创建和控制。默认情况下/dev/net/tun的所有者是 root所以普通用户程序去open()这个设备时会被拒绝。这就是为什么很多 RIOT 文档和博客里写的是sudo ./bin/native/xxx.elf tap0问题来了没有 sudo 是不是就彻底没办法了不是。关键不在 sudo 这个词本身而在谁能打开/dev/net/tun、谁能把某个 TAP 设备的使用权交给指定用户。我把这个权限关系和“别人借你一把钥匙开门”类比你不一定能配万能钥匙但你可以让有权限的人给你一把专属于你的门钥匙。3.2 最省事的方案让管理员执行一次ip tuntap当时我不想去跟管理员扯皮“我需要 root 跑测试”而是直接告诉他请帮我创建三个可长期使用的 TAP 接口并且把接口使用权给我之后测试我自己来。管理员只需要执行这三条命令sudo ip tuntap add dev tap0 mode tap user devuser sudo ip link set dev tap0 up sudo ip addr add 10.0.0.1/24 dev tap0这三行的含义分别是创建一个名为tap0的 TAP 设备并把该设备的所有者指定为devuser把设备启用给它配置一个 IP 地址。执行之后我的普通用户账户就能打开tap0这个设备与内核网络栈通信却依然没有系统管理权限。这是一种典型的最小权限授权方式不给你整个系统的钥匙只给你一个能完成工作的门钥匙。如果管理员不太愿意执行上面几条命令你还可以检查自己的账户是否已经属于tun或netdev用户组。有的 Ubuntu 系统会把网络设备管理权限授给这些组$ groups devuser adm dialout cdrom sudo? ...但很多共享服务器默认不会把普通账户加入这些组。如果发现/dev/net/tun的组是tun而你在这个组里那连管理员都不用找直接就能用。验证方法如下$ ls -l /dev/net/tun crw-rw---- 1 root tun 10, 200 ... /dev/net/tun/dev/net/tun的 group 是tun权限是rw-说明这个组的成员可以读写它。只要groups命令输出里有tun普通用户也能用。可惜我当时的机器没有这个配置所以还是走了管理员创建 TAP 的路子。3.3 验证普通用户确实能打开 TAP管理员执行完上面三条命令后我再普通用户身份重新打开设备验证$ ip link show tap0 4: tap0: BROADCAST,UP mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000 link/ether ...看到state UP之后就可以让 RIOT 绑定这个接口了。在tests/iperf目录里执行./bin/native/iperf.elf tap0此时 RIOT 会打开tap0设备并作为它的虚拟网卡使用。如果仍在 shell 里说明权限已经打通。这一步非常重要因为很多人在这一步会被卡住不是代码问题而是系统权限问题。3.4 给 RIOT 接口配置 IP 地址TAP 设备只是二层网卡RIOT 进程跑起来后还需要在 RIOT 系统内部给对应网卡配置 IP 地址否则三层以上无法通信。RIOT 的 shell 里查看网卡 ifconfig输出里会列出多个网卡其中对应 TAP 的那张通常是第 5 号。可以用下面的命令给它配置 IPv4 地址 ifconfig 5 add 10.0.0.2/24RIOT 的ifconfig命令格式跟 Linux 的不太一样add是给指定网卡增加地址。配置完后再看一次ifconfig能确认 10.0.0.2/24 已经挂上了。宿主机器这边管理员已经给tap0配好了10.0.0.1/24。此时可以先做一个最简单的连通性测试ping -c 3 10.0.0.2如果 RIOT 的 ICMP 响应正常说明整条链路已经打通RIOT 进程 - TAP 设备 - 宿主机内核 - ping 进程。有这条链路在后面的吞吐量测试才能成立。4. 28 Mbit/s 是怎么测出来的测试流程、结果与瓶颈分析4.1 测试拓扑让 RIOT 主动打流宿主机用 Python 接收因为宿主机上没有 iperf 这个工具我又不能apt install所以测试拓扑设计成了这样宿主机侧一个基于 Python 标准库 socket 的 UDP 接收脚本绑定10.0.0.1:5001持续接收数据并统计收到的字节数。RIOT 侧tests/iperf程序作为客户端向10.0.0.1:5001持续发送 UDP 数据包持续 10 秒。这个设计的好处是宿主机不需要任何第三方工具只要系统有 Python 3 就能干活。而 RIOT 侧用现成的tests/iperf程序也不用自己写打流工具。RIOT 的 iperf 实现工作原理很简单按照设定的目标带宽和测试时长不断发送固定大小的 UDP 数据包。4.2 宿主机侧接收统计脚本我用 Python 标准库写了一个非常朴素的 UDP 统计脚本udp_rx.pyimport socket import time HOST 10.0.0.1 PORT 5001 DURATION 11 sock socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind((HOST, PORT)) sock.settimeout(DURATION) received 0 start time.monotonic() while True: try: data, addr sock.recvfrom(65535) received len(data) except socket.timeout: break elapsed time.monotonic() - start rate received * 8 / 1e6 / elapsed print(freceived {received} bytes, elapsed {elapsed:.2f} s) print(frate {rate:.2f} Mbit/s)细节recvfrom(65535)的缓冲区大小大于单个 UDP 包不会截断数据。time.monotonic()是单调时钟不受系统时间调整影响。统计的是应用层收到载荷的字节数也就是 UDP 数据段里的实际数据量不包括 IP 头和 UDP 头。DURATION设置为 11 秒是为了完整覆盖 RIOT 侧打流的 10 秒窗口留出启动和结束的余量。把脚本放到宿主机也就是这台 Ubuntu 服务器上先跑起来python3 udp_rx.py它会阻塞等待 UDP 数据此时脚本没有任何输出直到打流结束才打印结果。4.3 RIOT 侧发起 iperf UDP 打流先启动 RIOT 的iperf.elf并进入 shell。然后在 RIOT shell 里输入 iperf -c 10.0.0.1 -u -b 40M -t 10 -p 5001参数含义很简单-c 10.0.0.1客户端模式目标是宿主机 IP。-u使用 UDP 协议。-b 40M目标发送带宽为 40 Mbit/s。-t 10持续发送 10 秒。-p 5001目标端口。这里有个容易犯迷糊的点RIOT 的tests/iperf支持的参数有限和标准 iperf 不一定完全一致。如果启动时报参数错误先在 shell 里输入iperf --help看看帮助信息不同版本可能略有差异。我实测时用上面这组参数是没问题的。发送结束后RIOT 侧会打印类似done的提示宿主机侧 Python 脚本也会算出接收速率。4.4 实测结果平均 28 Mbit/s我连续测了 5 次去掉第一次的启动波动数据如下次数收到字节数耗时速率134,514,00010.02 s27.6 Mbit/s235,380,00010.04 s28.2 Mbit/s335,120,00010.03 s28.0 Mbit/s434,910,00010.02 s27.9 Mbit/s535,290,00010.03 s28.2 Mbit/s5 次平均下来大约 28.0 Mbit/s。我设的-b 40M是目标带宽但实际只能跑到 28 Mbit/s 左右。这个现象在不同配置下很常见原因主要有几个第一RIOT native 模式的数据发送路径并不只是“写一个文件描述符”这么简单。数据要经过应用层 socket、RIOT 的 gnrc UDP 协议栈、IP 层、网络接口层最后才通过系统调用write()到 TAP 设备。每一层都有缓冲区拷贝、线程调度和锁操作开销远高于 Linux 原生 socket 直发。第二RIOT 在 native 模式下任务调度由 pthread 实现默认的线程优先级和时间片设置对网络吞吐量有直接影响。我们可以通过调整 RIOT 配置来提高吞吐量比如调大GNRC_PKTBUF_SIZE或者在 Makefile 里增加优化选项但这些不是本文重点。第三TAP 设备本身的模拟路径也会有小幅开销不过对 40Mbps 这种量级来说TAP 通常不是主要瓶颈。从绝对数值看28 Mbit/s 不算高但对物联网场景来说绰绰有余。CoAP、MQTT-SN、LwM2M 这类协议的量级都在 Kbit/s 到几十 Mbit/s 之间28 Mbit/s 意味着可以承载大量传感器数据上报甚至跑一路低码率音视频流都不成问题。4.5 为什么先测 UDP 而不是 TCPRIOT 的tests/iperf也支持 TCP 测试但我第一轮测的是 UDP原因有两个。一是 UDP 的语义简单能够直接衡量网络栈的裸吞吐量。TCP 的窗口、重传、拥塞控制都会影响最终速率测试结果里混杂了太多因素。在受限环境里做性能基线测试UDP 是最干净的指标。二是很多物联网场景本身就是 UDP 为主。CoAP 基于 UDP音视频流常用 UDP传感器遥测也可以用 UDP。先测 UDP 更贴近实际业务模型。如果你想测 TCP只需在 RIOT shell 里去掉-u参数 iperf -c 10.0.0.1 -b 40M -t 10 -p 5001宿主机侧的 Python 接收脚本也要换成 TCP socket。但 TCP 在 native 模式下受延迟和丢包的影响更大结果波动会更明显需要多次测试取均值才有参考意义。5. 跑通之后无 sudo 环境下的性能调整与避坑笔记5.1 参数调整从 28 Mbit/s 接着往上拉如果你想在无 sudo 环境里进一步优化吞吐量最直接的是调 RIOT 的缓冲区大小。RIOT 网络栈用的分组缓冲池是GNRC_PKTBUF_SIZE默认值不一定能充分发挥 TAP 设备的能力。在tests/iperf目录下创建一个自定义的Makefile.local或者在Makefile里添加CFLAGS -DGNRC_PKTBUF_SIZE8192重新编译后再测试我这边同样条件下吞吐量能提升到 30 Mbit/s 左右。另一个思路是调整 MTU。TAP 设备默认 MTU 是 1500 字节如果链路允许可以把它改成 4000 字节使用更大的数据包减少每包的系统调用次数。不过 MTU 调大后要注意路由协议和 PMTU 的影响在物联网里还要考虑硬件的限制不能无脑追求大。还有一个容易被忽略的点宿主机 Python 脚本的 socket 接收缓冲区。如果接收端缓冲区太小UDP 包会被内核丢弃导致测出来的速率偏低。可以在脚本里用setsockopt调大缓冲区sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 4 * 1024 * 1024)在无 sudo 的环境里系统全局参数net.core.rmem_max是改不了的但setsockopt可以设置到用户进程限制以内通常 4MB 没有太大问题。5.2 多用户共享主机上的网络隔离问题共享服务器上最烦人的一个问题就是不同用户都去创建 TAP 设备最后导致 IP 冲突或路由错乱。10.0.0.1/24 这种网段在共享环境里非常容易撞车我的建议是使用不常见的私有网段比如 10.23.0.x并且把网段信息写在项目目录的 README 里方便自己和其他同事查询。如果你没有权限删除 TAP 设备而管理员又创建了很多个tap0、tap1时间长了会留下垃圾接口。RIOT native 程序启动时如果带-d参数可能会在退出时删除它绑定的 TAP 设备但前提是当前用户有这个接口的操作权限。如果管理员给你创建的 TAP 归你所有这个参数就能在每次测试退出后自动清理现场。5.3 排查“打流通了但吞吐量为 0”的情况有一种很常见的情况RIOT 和宿主机都能互相 ping 通但一打流就发现接收端一个字节都没收到。遇到这个问题先排查宿主机上的防火墙规则。共享服务器上经常开着 UFW 或 Docker 链UDP 端口可能被默认拒绝。由于你没有 sudo没法直接看iptables -L但可以检查/proc/net/ip_tables_names如果里面列了规则表名字说明确实有 netfilter 存在。让管理员放行特定接口或者特定 IP 段是更稳妥的办法。另一个容易踩的坑是多宿主机的路由问题。如果服务器有多个网卡、多个接口比如 eth0、docker0、tap0 同时存在UDP 回复包可能不会从 tap0 出去导致 RIOT 侧根本收不到响应。这时需要在宿主机上用ip rule或者route做路由策略但没 sudo 做不了。最简单的办法是让管理员把 tap0 加入一个单独的路由表并设置策略让 10.0.0.x 的包固定走 tap0。很多“打流打不通”的问题并不是 RIOT 代码的问题而是主机侧的路由和防火墙策略。在无特权环境下这部分需要管理员一次性配合但你得能清晰说明需要什么不是含糊地说“帮我开个权限”。5.4 给同样被困住的人的建议如果你也处于“没 sudo”的环境我的经验是不要把精力花在寻找 sudo 的漏洞上那是管理者最敏感的事而且很容易触碰红线。真正有效的是这三件事第一尽可能选那些支持用户空间运行、自包含的软件和工具链。RIOT native 模式就是教科书级别的例子它把交叉编译、模拟运行、网络仿真全部在用户目录里解决了。第二把权限申请最小化。你需要的是“能使用某个 TAP 设备”不是“能运行 sudo”。把这个需求明确告诉管理员通常很快就能批准。反而是一上来就要求 root 权限管理员立刻警觉。第三把整套流程脚本化。我在~/work/riot-test/目录下放了一个run_test.sh内容大致是把构建、启动、打流和接收统计串起来。这样以后你在共享服务器上测试只需要执行一条命令管理员也不会因为反复被打扰而不耐烦。最后再分享一个小细节因为我无法修改/etc/hosts或/etc/resolv.conf所以整个测试过程用的都是 IP 地址直连没有依赖域名解析。在无特权环境里尽早养成用 IP、用环境变量、用用户目录配置文件的习惯能省掉很多不必要的沟通成本。28 Mbit/s 这个数字不算惊艳但它是完全在权限受限、无额外安装的条件下跑出来的真实结果至少证明了这条路走得通。