简介:面向P4可编程数据平面学习与开发的环境配置安装包,适合需要搭建bmv2软件交换机与p4c编译工具链的高校学生、网络实验课程学习者及SDN方向研发人员。包内汇集behavioral-model、gmock-1.7.0、p4c、protobuf-3.2.0、thrift-0.9.2等常用组件,围绕P4程序编译、交换机行为模拟与依赖库版本匹配提供一套可直接使用的离线材料,能减少逐个检索兼容版本、处理编译依赖所耗费的时间。资源以gz压缩包形式提供,整体约147.8MB,组件可按名称定位取用。已有611人学习下载,可用于课程实验环境复现、协议无关转发原型验证以及版本冲突排查参考,帮助读者更快进入P4开发与调试环节。
1. P4 环境配置为什么总是失败:五个组件之间的版本绑定
先还原一个现场:执行 apt 安装好 protobuf,获取 bmv2 和 p4c 的最新源码,configure 阶段一路正常,make 执行到一半就报undefined reference to google::protobuf::internal::...;换个方式先把 behavioral-model 编过,simple_switch 能启动,但 simple_switch_CLI 连 9090 端口永远超时。这类问题并非水平不够,症结在于 p4 环境配置这条链上,behavioral-model、p4c、protobuf、thrift、gmock 五个组件存在版本绑定关系,任何一环版本不一致,后面全是连锁反应。
这套安装包把版本固定为 protobuf-3.2.0、thrift-0.9.2、gmock-1.7.0,配套 behavioral-model(bmv2 软件交换机)和 p4c 编译器,价值就是让这条编译链一次对齐。它适合两类人:要跑 P4_16 数据面实验、需要 load 一个 v1model 程序看转发行为的人;以及做可编程数据面原型、需要频繁改 P4 源码重编的人。下面按“版本为什么这么选 → 怎么编 → 怎么验证 → 问题在哪”走一遍。
2. 版本约束逻辑:protobuf 为什么只能停在 3.2.0、thrift 只能停在 0.9.2
2.1 依赖关系与编译拓扑:谁先编译、谁决定谁
先把三条依赖关系明确下来,后面所有编译顺序都是从这里推出来的。
第一条是 bmv2 对 thrift 的依赖。bmv2 的 runtime 接口(就是simple_switch_CLI和simple_switch_grpc之外的那套 thrift RPC)是靠 thrift 的 IDL 生成的 C++ 代码实现的,thrift/src/thrift/下的.thrift文件在编译期被thrift编译器翻译成*_types.cpp/*_constants.cpp。所以 thrift 必须是先装好的可执行文件 + 库,不能只在系统里有个头文件。0.9.2 这个版本号不是随便定的:bmv2 的configure.ac里对 thrift 有最低版本检查,而 0.10 之后 thrift 的 C++ 库 ABI 有变动,老 bmv2 链接会缺符号。
第二条是 bmv2 和 p4c 共同依赖 protobuf。p4c 用 protobuf 描述 IR(ir.proto)、解析 P4Runtime 的p4info.proto;bmv2 用 protobuf 序列化部分配置。两者必须链接同一个protobuf 版本,否则p4c生成的 JSON 里某些字段名和 bmv2 的解析器对不上,表现是 simple_switch 读 JSON 时报Error while parsing JSON或者字段被静默忽略——这种问题最难查,往往被误认为 P4 程序写错了。
第三条是 gmock 只被 bmv2 的单元测试依赖。gmock-1.7.0 自带一份 gtest,装在$PREFIX/include/gmock和$PREFIX/include/gtest下。如果你的系统里已经有别的 gtest(比如某个包管理器装的),configure 很可能优先找到它,然后因为 gtest 版本太新、API 不兼容而编译失败。
2.2 安装前缀规划:不要一股脑塞进 /usr/local
我一般不会把五个组件直接make install到/usr/local。原因很实际:一旦你要装第二套 P4 环境(比如换 p4c 的某个分支做对比),/usr/local下的头文件和库就开始互相覆盖,最后哪套都跑不起来,只能重装系统级依赖。
我的习惯是给每次环境打一个 tag,装到/opt/p4/<tag>下面:
# 统一前缀,后续所有组件都装到这里 export P4_TAG=proto320-thrift092 export P4_PREFIX=/opt/p4/${P4_TAG} export P4_SRC=$HOME/p4-src sudo mkdir -p ${P4_PREFIX} sudo chown -R $(id -u):$(id -g) ${P4_PREFIX} mkdir -p ${P4_SRC}P4_TAG取什么名字不重要,重要的是它要能提示你这套环境锁的版本。装完以后,把$P4_PREFIX/lib写进动态库搜索路径:
# 让运行时能找到 /opt/p4/<tag>/lib 下的 so echo "${P4_PREFIX}/lib" | sudo tee /etc/ld.so.conf.d/p4-${P4_TAG}.conf sudo ldconfig这两步的意义在于:ldconfig之后,libprotobuf.so.13、libthrift-0.9.2.so这些库才能被simple_switch和p4c在运行时定位到。很多“编译过了但一运行就报error while loading shared libraries”的情况,根子就在这一步没做或者做在了错误的顺序上。
2.3 编译顺序表与各组件产出的关键产物
把顺序固定成下面这个表,中间任何一步失败都不要往下走,这是省时间的做法。
| 顺序 | 组件 | 关键 configure/cmake 参数 | 产出物 | 被谁依赖 |
|---|---|---|---|---|
| 1 | gmock-1.7.0 | --prefix=$P4_PREFIX | libgmock.a、libgtest.a、include/gmock、include/gtest | bmv2 的 tests |
| 2 | protobuf-3.2.0 | --enable-shared | protoc、libprotobuf.so、libprotobuf-lite.so | p4c、bmv2 |
| 3 | thrift-0.9.2 | --with-cpp --with-python | thrift编译器、libthrift-0.9.2.so | bmv2 |
| 4 | behavioral-model | --with-thrift | simple_switch、simple_switch_CLI、libbmv2* | 运行期 |
| 5 | p4c | -DENABLE_BMV2=ON | p4c、p4c-bm2-ss、p4test | 编译期 |
注意顺序里的第 2 步和第 3 步,protobuf 必须在 thrift 之前装好。thrift 0.9.2 的configure会探测系统里有没有 protobuf(它对 TJSONProtocol 之类的功能有个可选的 protobuf 依赖),如果你先装 thrift 再装 protobuf,thrift 的探测结果会被缓存进config.status,后面去改就很别扭。
注意:这张表里 gmock 排第一不是因为它是底层,是因为它最独立、最容易编过。把它放在最前面,可以在动大件之前先确认工具链本身没问题。
3. 五步编译实操:从 gmock-1.7.0 到 p4c 的完整命令
3.1 系统依赖:把编译期和运行期需要的东西一次装齐
先补系统包。这一步按 Ubuntu/Debian 系的包名写,其他系自己换名字。关键点是:libboost系列必须装 dev 包而不是只装运行时,libgc-dev(bdwgc)是 p4c 的硬依赖,少了它 cmake 会直接停在Could NOT find LibGc。
sudo apt-get update sudo apt-get install -y \ git cmake build-essential autoconf automake libtool pkg-config \ bison flex \ libboost-dev libboost-system-dev libboost-thread-dev libboost-filesystem-dev \ libgmp-dev libpcap-dev libreadline-dev libssl-dev \ libgc-dev python3-devbison和flex是 p4c 解析 P4 语法用的,libgmp-dev是 p4c 做常量运算用的,libpcap-dev是 bmv2 抓包用的,libreadline-dev是simple_switch_CLI的命令行补全。装完以后pkg-config --version能输出版本号,就说明工具链基本可用。
3.2 gmock-1.7.0:旧代码配合新 GCC 的两处补丁
gmock 1.7.0 是 2013 年前后的代码,默认按 C++98 语义写。在 GCC 7 以上的机器上直接make,十有八九会在gtest-port.h附近中断,报ptrdiff_t does not name a type或者找不到nullptr。
cd ${P4_SRC} tar -xzf gmock-1.7.0.tar.gz cd gmock-1.7.0 # 补两行标准头,解决新 GCC 下 ptrdiff_t / nullptr 未声明 find gtest/include -name "gtest-port.h" -exec \ sed -i '1i #include <cstddef>\n#include <limits>' {} \; ./configure --prefix=${P4_PREFIX} CXXFLAGS="-std=c++11 -fPIC" make -j$(nproc) make install-std=c++11的作用是让 gmock 内部的GTEST_HAS_TR1_TUPLE检测走 C++11 分支,绕开对<tr1/tuple>的依赖——这个头文件在新的 libstdc++ 里已经被移走了,不绕开就会报No such file or directory。-fPIC是为了让静态库能被打进共享库,bmv2 在某些配置下会这么干。
装完检查一下产物是否齐全:
ls ${P4_PREFIX}/lib/libgmock.a ${P4_PREFIX}/lib/libgtest.a ls ${P4_PREFIX}/include/gmock/gmock.h如果libgtest.a没出来,说明 configure 阶段复用了系统已有的 gtest,回头看看config.log里checking for gtest那一段。常见做法是加--with-gtest=${P4_PREFIX}显式指定。
3.3 protobuf-3.2.0:autogen 的必要性和 ldconfig 的顺序
protobuf 从源码包编译要先跑autogen.sh生成configure,因为它发布包里带的是configure.ac而不是configure(部分版本带了,但还是跑一下更稳)。
cd ${P4_SRC} tar -xzf protobuf-3.2.0.tar.gz cd protobuf-3.2.0 ./autogen.sh ./configure --prefix=${P4_PREFIX} \ --enable-shared \ CXXFLAGS="-std=c++11 -fPIC" make -j$(nproc) make install sudo ldconfig--enable-shared会同时产出静态库和动态库,默认--disable-static是关的,不用额外加。真正要留意的是make install之后的ldconfig——protobuf 的libprotobuf.so.13装进/opt/p4/.../lib之后,如果不刷新缓存,紧接着编 thrift 时configure用-lprotobuf探测就会失败,报cannot find -lprotobuf。
编 protobuf 3.2.0 在 GCC 7 以上还有一个常见问题:
error: 'numeric_limits' is not a member of 'std'根因是某个.cc文件没显式包含<limits>,老版本编译器靠传递包含蒙对了,新版本不行。修法是找到报错文件,在顶部补一行#include <limits>再make。这种补丁不影响 ABI,可以放心打。
装完验证一下:
${P4_PREFIX}/bin/protoc --version # 期望输出形如 libprotoc 3.2.0 ldd ${P4_PREFIX}/bin/protoc | grep protobuf第二条命令必须指向${P4_PREFIX}/lib/libprotobuf.so.13,如果指向/usr/lib/x86_64-linux-gnu/,说明系统里还有另一套 protobuf 在竞争,后面一定会出问题,现在就要处理掉。
3.4 thrift-0.9.2:裁剪绑定、缩短编译时间和失败面
thrift 默认会尝试编译所有语言的绑定,Java、Node、Ruby 一路编下来半小时起步,而且任何一门语言的依赖缺失都会让make挂掉。bmv2 只需要 C++ 绑定,Python 绑定留着方便写脚本下发配置。
cd ${P4_SRC} tar -xzf thrift-0.9.2.tar.gz cd thrift-0.9.2 ./configure --prefix=${P4_PREFIX} \ --with-cpp --with-python \ --without-c_glib --without-csharp --without-java --without-erlang \ --without-nodejs --without-lua --without-perl --without-php \ --without-php_extension --without-ruby --without-haskell --without-go \ --without-qt4 \ CXXFLAGS="-std=c++11 -Wno-error" make -j$(nproc) make install sudo ldconfig-Wno-error是必须的。thrift 0.9.2 里有些代码会触发新版 GCC 的警告,而它的 Makefile 把部分警告当错误处理,一遇到就中断。关掉-Werror之后这些只是警告,不影响产出。--without-qt4是因为 0.9.2 时代还有 Qt4 的绑定,新系统上没装 Qt4 开发包会探测失败。
configure对不认识的--without-xxx只打一行unrecognized options警告,不会中断,所以上面这串开关即使有一两个在当前版本里不存在,也不影响结果。
装完确认两个东西:
${P4_PREFIX}/bin/thrift --version ls ${P4_PREFIX}/lib/libthrift-0.9.2.sothrift --version输出Thrift version 0.9.2才算对。如果输出的是系统里另一个版本,说明$PATH顺序有问题,export PATH=${P4_PREFIX}/bin:$PATH补一下。
3.5 behavioral-model:submodule、configure 探测和 PI 的取舍
bmv2 的源码里,PI(Protocol Independence)是以 git submodule 形式挂进来的,如果拿的是 git 仓库而不是打包好的 tar,第一步必须做 submodule 初始化,否则 configure 会报找不到PI/目录。
cd ${P4_SRC}/behavioral-model # 源码来自 git 仓库时必须执行,打包源码可跳过 git submodule update --init --recursive ./autogen.sh ./configure --prefix=${P4_PREFIX} \ --with-thrift \ CXXFLAGS="-std=c++11 -Wno-error" make -j$(nproc) make install sudo ldconfig--with-thrift是显式打开 thrift runtime 支持,不开的话simple_switch_CLI根本不会被编译出来,你就只能靠--console-logging看日志调程序,效率差很多。
如果 configure 阶段报thrift not found,别急着怀疑没装,先看config.log里那段探测命令。bmv2 用的是pkg-config --exists thrift或者直接找thrift可执行文件,两种都有可能。对应的解法分别是:
# 方案一:让 pkg-config 找到我们装的 thrift export PKG_CONFIG_PATH=${P4_PREFIX}/lib/pkgconfig:$PKG_CONFIG_PATH # 方案二:确保 thrift 编译器在 PATH 最前面 export PATH=${P4_PREFIX}/bin:$PATH有些老版本 bmv2 没有 PI submodule,configure 会去找系统里的 PI,找不到就报错。这种情况下加--with-pi=no跳过 PI 支持,只保留 v1model 的 simple_switch,对做 P4_16 + v1model 实验完全够用。
编译完成后必须有这几个可执行文件:
ls ${P4_PREFIX}/bin/simple_switch ${P4_PREFIX}/bin/simple_switch_CLIsimple_switch_CLI存在与否,是判断 thrift 有没有真正链进去的最直接信号。
3.6 p4c:CMake 选项和并行度控制
p4c 用 CMake,和前面四个 autotools 项目不一样。它最吃的资源是内存,make -j$(nproc)在 8 核机器上很容易把 16G 内存吃满然后被 OOM killer 终止,报c++: fatal error: Killed signal terminated program cc1plus。
cd ${P4_SRC}/p4c git submodule update --init --recursive mkdir -p build && cd build cmake .. \ -DCMAKE_INSTALL_PREFIX=${P4_PREFIX} \ -DCMAKE_CXX_FLAGS="-std=c++11" \ -DENABLE_BMV2=ON \ -DENABLE_EBPF=OFF \ -DENABLE_P4TEST=OFF \ -DENABLE_GTESTS=OFF \ -DENABLE_DOCS=OFF make -j4 make install几个参数的实际含义:-DENABLE_BMV2=ON决定是否编译 bmv2 backend,也就是p4c-bm2-ss和--target bmv2支持,这个是核心,必须开;-DENABLE_EBPF=OFF关掉 eBPF backend,它依赖 clang/LLVM,不用就别编;-DENABLE_P4TEST=OFF和-DENABLE_GTESTS=OFF一起关掉测试目标,能省掉一大半编译时间;-DENABLE_DOCS=OFF关文档,一般情况下不需要。
-j4是我在新机器上的保守值。如果编译过程中还是被终止,降到-j2,或者干脆make单线程跑,用时间换稳定。p4c 全量编译在 4 核上大概 20 分钟量级,不值得为了省 10 分钟冒着编译失败重来的风险。
装完验证:
${P4_PREFIX}/bin/p4c --version ${P4_PREFIX}/bin/p4c --target bmv2 --arch v1model --help | head -5p4c --version会同时打印它链接的 protobuf 版本,这里必须显示 3.2.0。如果显示 2.x 或者别的版本,说明 cmake 找到了系统的 protobuf,回头在CMakeCache.txt里搜PROTOBUF_相关变量,用-DPROTOBUF_LIBRARY=和-DPROTOBUF_INCLUDE_DIR=手工指到${P4_PREFIX}下。
4. 联调验证:让 simple_switch 跑起一个 P4_16 程序
4.1 p4c 编译产物:JSON、p4info 和它们各自的作用
写一个最小的 v1model 程序验证链路。核心逻辑就一条:按目的 IP 查表,命中就改目的 MAC 从指定端口转发出去。
// demo.p4 —— 最小可验证的 v1model 转发程序 #include <core.p4> #include <v1model.p4> header ethernet_t { bit<48> dstAddr; bit<48> srcAddr; bit<16> etherType; } header ipv4_t { bit<4> version; bit<4> ihl; bit<8> diffserv; bit<16> totalLen; bit<16> identification; bit<3> flags; bit<13> fragOffset; bit<8> ttl; bit<8> protocol; bit<16> hdrChecksum; bit<32> srcAddr; bit<32> dstAddr; } struct headers { ethernet_t ethernet; ipv4_t ipv4; } struct metadata { } parser MyParser(packet_in pkt, out headers hdr, inout metadata meta, inout standard_metadata_t std) { state start { pkt.extract(hdr.ethernet); transition select(hdr.ethernet.etherType) { 0x0800: parse_ipv4; default: accept; } } state parse_ipv4 { pkt.extract(hdr.ipv4); transition accept; } } control MyIngress(inout headers hdr, inout metadata meta, inout standard_metadata_t std) { action drop() { mark_to_drop(std); } action forward(macAddr_t dmac, bit<9> port) { hdr.ethernet.dstAddr = dmac; std.egress_spec = port; hdr.ipv4.ttl = hdr.ipv4.ttl - 1; } table ipv4_lpm { key = { hdr.ipv4.dstAddr: lpm; } actions = { forward; drop; } default_action = drop(); size = 1024; } apply { if (hdr.ipv4.isValid()) { ipv4_lpm.apply(); } } } control MyEgress(inout headers hdr, inout metadata meta, inout standard_metadata_t std) { apply {} } control MyDeparser(packet_out pkt, in headers hdr) { apply { pkt.emit(hdr.ethernet); pkt.emit(hdr.ipv4); } } control MyVerify(inout headers hdr, inout metadata meta) { apply {} } control MyUpdate(inout headers hdr, inout metadata meta) { apply {} } V1Switch(MyParser(), MyVerify(), MyIngress(), MyEgress(), MyUpdate(), MyDeparser()) main;编译:
mkdir -p out p4c --target bmv2 --arch v1model --std p4-16 \ --p4runtime-files out/demo.p4info.txt \ -o out demo.p4--target bmv2 --arch v1model这两个参数缺一不可,前者选后端,后者选架构模型。--p4runtime-files生成 P4Runtime 的静态描述文件,里面是 table id、action id、字段 id 的映射表;如果你只用 thrift CLI 下发(table_add),这个文件其实用不上,但留着以后接 P4Runtime 或控制器会省事。产物里out/demo.json是 bmv2 真正读取的配置,bmv2 靠它知道有哪些表、表大小、字段 bit 宽度和 action 的参数布局。
编译成功会静默退出,没有任何输出。如果打印了什么warning,先看清楚是不是unused,绝大多数不影响运行。
4.2 veth 对与 simple_switch 的端口绑定
bmv2 通过 AF_PACKET 直接绑在网卡上,绑上之后这块网卡就不走内核协议栈了。所以正确做法是创建 veth 对,把其中一端交给 bmv2,另一端留在内核里配 IP 用来发包。
# 造两对 veth:veth0<->veth1、veth2<->veth3 sudo ip link add veth0 type veth peer name veth1 sudo ip link add veth2 type veth peer name veth3 sudo ip link set veth0 up; sudo ip link set veth1 up sudo ip link set veth2 up; sudo ip link set veth3 up # 内核侧配 IP,bmv2 侧(veth0 / veth2)不配 sudo ip addr add 10.0.0.1/24 dev veth1 sudo ip addr add 10.0.0.2/24 dev veth3启动:
sudo simple_switch \ -i 0@veth0 -i 1@veth2 \ --log-console \ --thrift-port 9090 \ out/demo.json-i 0@veth0表示端口 0 绑到 veth0,-i 1@veth2表示端口 1 绑到 veth2,这个端口号就是 P4 里std.egress_spec要填的值。--log-console把日志打到标准输出,调程序时不开这个基本在使用时难以定位问题。--thrift-port 9090是 CLI 连接用的端口,不写默认也是 9090,但显式写出来能避免多实例冲突时的困惑。
4.3 runtime CLI 下发表项
另开一个终端连上去:
simple_switch_CLI --thrift-port 9090进去之后先看一眼表结构,确认 P4 程序被正确解析:
RuntimeCmd: table_info ipv4_lpm RuntimeCmd: show_actions然后下发两条转发规则。这里要注意forward的参数顺序必须和 P4 里action forward(macAddr_t dmac, bit<9> port)的声明顺序一致——先 MAC 后端口,顺序颠倒了 bmv2 不会报错,但转发的包会带着错误的 MAC 跑出去,这在抓包时非常具有迷惑性。
table_add ipv4_lpm forward 10.0.0.2/32 => 00:00:00:00:02:02 1 table_add ipv4_lpm forward 10.0.0.1/32 => 00:00:00:00:01:01 0第一行的含义是:目的 IP 命中10.0.0.2/32时,执行forward,参数为 MAC00:00:00:00:02:02和端口1。=>左边是 match key,右边是 action 参数,这个符号不能写错。
4.4 pcap 抓包和丢包定位
验证转发是不是真的生效,最直接的是在 veth3 侧抓 ICMP:
sudo tcpdump -i veth3 -n -e icmp同时从 veth1 侧发起请求:
ping -c 3 10.0.0.2抓到了 echo request 但抓不到 reply,说明反向表项没下发;两边都抓不到,说明包根本没进 bmv2,回去检查 veth 是不是 up、-i绑的对不对。
bmv2 自己也能出 pcap,参数是--pcap <目录>,每个端口一个文件:
sudo simple_switch -i 0@veth0 -i 1@veth2 \ --pcap out/pcap --log-console out/demo.json这个 pcap 是在 bmv2 内部、包进入 pipeline 之前抓的,和tcpdump在外部抓的对照着看,能快速判断包是被 parser 丢了、被 ingress 的drop丢了,还是在出队时丢的。这一招在查“表项明明下发对了但包不见了”的问题时特别有用。
5. 避坑与排查:五个把环境搞崩的典型操作
5.1 现象:编译全过,一运行就报找不到 libprotobuf.so.13
原因:系统里还有一套包管理器装的 protobuf(通常在/usr/lib/x86_64-linux-gnu/),ldconfig的搜索顺序把它排在了/opt/p4/.../lib前面。你的程序是链 3.2.0 编的,运行时却加载了另一套 soname 相同的库。
解决:先用ldd ${P4_PREFIX}/bin/simple_switch | grep protobuf确认实际加载路径,如果指向系统目录,检查/etc/ld.so.conf.d/下文件的加载顺序。ld.so.conf里的路径按文件名排序读取,p4-xxx.conf排在x86_64-linux-gnu.conf后面就可能被覆盖。改法是把文件名改成00-p4.conf之类能排在前面的名字,再sudo ldconfig。实在绕不开就把系统那套 protobuf 卸载掉,但要先确认没有别的软件在用它。
5.2 现象:thrift 编译到一半报error: 'tr1' has not been declared
原因:thrift 0.9.2 的部分代码用std::tr1::function做兼容,新版 libstdc++ 里<tr1/functional>还在但里面是空的,或者干脆找不到。C++11 模式下本该走std::function分支,但如果configure探测时判断错了,就会选到 tr1。
解决:在 configure 时显式传CXXFLAGS="-std=c++11 -Wno-error",把探测结果强行掰到 C++11 分支。如果已经 configure 过了,删掉config.cache(如果有)和整个源码目录重新解压最省事,autotools 的缓存一旦写错很难改回来。
5.3 现象:bmv2 编译报fatal error: gmock/gmock.h: No such file or directory
原因:gmock 装到了/opt/p4/.../include/gmock/,但 bmv2 的编译命令里没有加-I${P4_PREFIX}/include,或者加了但排在系统路径后面,找到了另一个版本。
解决:configure 时显式传头文件路径:CPPFLAGS="-I${P4_PREFIX}/include" LDFLAGS="-L${P4_PREFIX}/lib"。如果 gmock 库也找不到,再加LIBS="-lgmock -lgtest -lpthread"。顺带提一句,gmock 的静态库依赖 pthread,漏了会报一堆undefined reference to pthread_...。
5.4 现象:编译 p4c 时进程被 Killed,没有任何 error
原因:OOM。p4c 的ir.cpp、typeChecking.cpp这几个文件单文件编译峰值内存能到 2G 以上,多线程并行时总占用是倍数关系。
解决:把make -j$(nproc)降成-j2或单线程。机器内存小于 8G 的话,先加一块 swap 再编。判断依据是dmesg | tail里能看到Out of memory: Killed process ... cc1plus。另外-DENABLE_GTESTS=OFF和-DENABLE_P4TEST=OFF能显著降低编译量,值得加上。
5.5 现象:simple_switch_CLI 连不上 9090 端口,报TSocket::open() connect() <Host: localhost Port: 9090>
原因:三种可能,一是 simple_switch 没起来或者起来就退了(常见于 JSON 解析失败,启动日志里有一行Error while parsing JSON,但被其他输出淹没了);二是 thrift 端口被别的实例占了;三是simple_switch编译时没带--with-thrift,压根没监听端口。
解决:先看 simple_switch 的启动日志,确认最后一行是不是Thrift server listening on port 9090。没有这一行就回去查--with-thrift和libthrift链接情况。有这一行但连不上,用ss -lntp | grep 9090看是不是别的进程占着,pkill simple_switch清理干净再来。
6. 环境固化:把前缀隔离和自检脚本做成随手可用的工具
装一次成功不算本事,三个月后换个项目要重新搭,能把当时的确切版本一条命令还原回来才算。我用得最多的做法是给每套环境配一个激活脚本和一个自检脚本,放在${P4_PREFIX}/env.sh里,需要的时候source一下。
#!/usr/bin/env bash # /opt/p4/<tag>/env.sh —— 激活这套 P4 环境 export P4_PREFIX="/opt/p4/proto320-thrift092" export PATH="${P4_PREFIX}/bin:${PATH}" export LD_LIBRARY_PATH="${P4_PREFIX}/lib:${LD_LIBRARY_PATH}" export PKG_CONFIG_PATH="${P4_PREFIX}/lib/pkgconfig:${PKG_CONFIG_PATH}" export P4C_PATH="${P4_PREFIX}/bin/p4c" # 防止误用系统里的另一套 protobuf hash -r echo "P4 env active: ${P4_PREFIX}" protoc --versionhash -r这行很容易被忽略。bash 会缓存命令的绝对路径,你在同一个 shell 里先跑过系统的protoc,之后即使改了PATH,它还是执行旧的那个。清掉缓存,protoc --version的输出才是这套环境真实的版本。
配套的自检脚本我一般写成这样,装完立刻跑一遍,比事后猜要快得多:
#!/usr/bin/env bash # p4-doctor.sh —— 五个体检项,任何一项 FAIL 都别急着写 P4 程序 set -u PREFIX="${P4_PREFIX:-/opt/p4/proto320-thrift092}" fail=0 check() { # $1=名称 $2=期望值 $3=实际值 if [[ "$3" == *"$2"* ]]; then echo "OK $1: $3" else echo "FAIL $1: 期望含 $2,实际 $3"; fail=1; fi } check "protoc" "3.2.0" "$(${PREFIX}/bin/protoc --version 2>&1)" check "thrift" "0.9.2" "$(${PREFIX}/bin/thrift --version 2>&1)" check "p4c" "3.2.0" "$(${PREFIX}/bin/p4c --version 2>&1 | tr '\n' ' ')" # 关键可执行文件是否齐全 for b in simple_switch simple_switch_CLI p4c; do [[ -x "${PREFIX}/bin/${b}" ]] && echo "OK bin/${b}" || { echo "FAIL bin/${b} 缺失"; fail=1; } done # 运行时链接是否指向本前缀 ldd "${PREFIX}/bin/simple_switch" 2>/dev/null | grep -q "${PREFIX}/lib" \ && echo "OK simple_switch 运行时库路径" \ || { echo "FAIL simple_switch 链接到了前缀之外的库"; fail=1; } exit ${fail}最后那项ldd检查是最有价值的。它能在你写第一行 P4 代码之前,就把“编译期和运行期用了两套库”这个问题暴露出来,不用等到simple_switch起不来再回头翻ld.so.conf。
这套做法有一个实际限制需要说清楚:前缀隔离解决的是多套环境共存,不解决同一套环境内部的依赖冲突。如果你把 protobuf 装到前缀 A、p4c 装到前缀 B,那 p4c 运行时仍然会去找前缀
本文还有配套的精品资源,点击获取