ARTICLE DETAIL

资讯详情

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

Erlang/OTP 互操作性指南:Distributed Erlang、Port、NIF 与 C/Java 库全解析

Erlang/OTP 互操作性指南:Distributed Erlang、Port、NIF 与 C/Java 库全解析 编程语言语言运行时标准库编译器并发编程【免费下载链接】otpErlang/OTP项目地址https://gitcode.com/gh_mirrors/ot/otp点击查看免费下载Erlang/OTP 提供了多种与其他编程语言交换信息互操作interoperability的内置机制。本文以官方教程 overview.md 为主线系统梳理 Erlang 运行时中内置的三大互操作机制——分布式 Erlang、端口Ports与原生实现函数NIFs——以及面向 C 与 Java 的 Erl_Interface、C Node、jinterface 库和标准 TCP/UDP 协议支持并深入仓库源码与配套示例帮助读者理解每种机制的适用场景、底层原理与取舍最终能在真实项目中正确选型并落地实现。阅读前提本文假设读者已经是熟练的 Erlang 程序员熟悉 Erlang 数据类型、进程、消息与错误处理等概念。配套示例以 UNIX 环境下的 C 程序为主相关原则可推广到其他语言与平台。教程全景从问题示例到五种实现路径整个互操作性教程围绕同一个“问题示例”展开见 example.md假设你有两个希望从 Erlang 调用的 C 函数/* complex.c */ int foo(int x) { return x1; } int bar(int y) { return y*2; }从 Erlang 程序员的视角理想情况是无需关心它们是 C 函数即可直接调用% Erlang code ... Res complex:foo(X), ...这里的 C 通信细节被隐藏在complex.erl的实现中。教程的后续章节Ports、Port Drivers、NIFs、Erl_Interface、C Nodes分别演示了用五种不同机制实现同一个complex模块。教程源码均存放在仓库的 system/doc/tutorial 目录下包括complex.c、complex1.erl至complex6.erl、port.c、port_driver.c、ei.c、erl_comm.c、complex6_nif.c等完整可编译文件。为可读性考虑教程示例代码刻意保持极简例如不包含真实系统中至关重要的错误处理。下面是本文的核心导览内置机制、C/Java 库与标准协议三大板块全部是 overview 文档的直接延续。一、运行时内置的互操作机制Built-In MechanismsErlang 运行时系统内置了三种互操作机制分布式 Erlang、端口Ports以及NIFs端口的一种变体是Linked-In Drivers链接进运行时系统的驱动。它们分别适用于不同的集成深度与性能/安全权衡。分布式 ErlangDistributed Erlang给 Erlang 运行时系统命名后它就成为一个分布式 Erlang 节点。一个分布式节点可以连接并监视其他节点也能在其他节点上派生进程。不同节点上的进程之间消息传递与错误处理是透明的——程序员无需关心进程位于本地还是远端。在分布式 Erlang 系统中STDLIB 提供了一批实用的分布式编程模块。例如 global 提供全局名称注册服务。分发机制的底层实现基于 TCP/IP 套接字。适用场景分布式 Erlang 主要用于 Erlang 与 Erlang 之间的通信若 C 程序以 C Node 形式实现也可用于 Erlang 与 C 之间的通信见后文 C Nodes 一节。官方手册参考页均可在仓库lib下找到对应源码模块m:erlangERTS 手册描述 BIF— 对应 erts/preloaded/src/erlang.erlm:globalKernel— lib/kernel/src/global.erlm:net_admKernel— lib/kernel/src/net_adm.erlm:pgKernel— lib/kernel/src/pg.erlm:rpcKernel— lib/kernel/src/rpc.erlm:poolSTDLIB— lib/stdlib/src/pool.erlm:slaveSTDLIB— lib/stdlib/src/slave.erl其中rpc提供了跨节点的远程过程调用net_adm负责节点发现与连接管理pg是进程组管理pool与slave分别提供进程池与从节点启动能力。更多分布式编程技术可参考 Distributed Programming。端口Ports与 Linked-In Drivers从 Erlang 的角度看端口提供了与外部世界通信的基本机制。端口向外部程序提供面向字节的接口创建端口后Erlang 可以与其交换字节列表或二进制数据但不能直接传 Erlang 项term因此程序员往往需要自行设计一套编码/解码方案。端口机制的实现依赖平台在 UNIX 上使用管道pipe外部程序被假定从标准输入读取、向标准输出写入。只要外部程序能处理端口所基于的进程间通信机制它可以用任何编程语言编写。外部程序运行在与 Erlang 运行时系统不同的 OS 进程中。但在某些场景下这是不可接受的——例如对时间要求非常苛刻的驱动。因此可以按照特定原则用 C 编写程序并动态链接到 Erlang 运行时系统这就是linked-in driver。适用场景当 Erlang 程序与另一程序运行在同一台机器上时端口适用于各种互操作场景且编程相对直接。Linked-in drivers 的代价需要编写 C 回调函数且代码被链接进 Erlang 运行时系统对编程能力要求极高。官方明确建议优先使用 NIFs 而非 linked-in drivers因为 NIF 功能更丰富且能利用 dirty schedulers 处理耗时任务。警告.warning一个有缺陷的 linked-in driver 会导致整个 Erlang 运行时系统内存泄漏、挂起或崩溃。创建端口使用的 BIF 是open_port/2记录在m:erlang手册页编写 linked-in driver 的程序员需要阅读m:erl_ddll手册页对应源码 lib/kernel/src/erl_ddll.erl负责驱动库的动态加载。端口实战示例2 字节长度前缀协议以 Ports 为例Erlang 侧通过open_port({spawn, ExtPrg}, [{packet, 2}])创建端口。{packet, 2}表示使用 2 字节长度指示器简化 C 与 Erlang 的通信Erlang 端口会自动添加长度前缀但外部 C 程序必须显式处理它。连接进程还须设置trap_exit以便检测外部程序失败-module(complex1). -export([start/1, stop/0, init/1]). -export([foo/1, bar/1]). start(ExtPrg) - spawn(?MODULE, init, [ExtPrg]). stop() - complex ! stop. foo(X) - call_port({foo, X}). bar(Y) - call_port({bar, Y}). call_port(Msg) - complex ! {call, self(), Msg}, receive {complex, Result} - Result end. init(ExtPrg) - register(complex, self()), process_flag(trap_exit, true), Port open_port({spawn, ExtPrg}, [{packet, 2}]), loop(Port). loop(Port) - receive {call, Caller, Msg} - Port ! {self(), {command, encode(Msg)}}, receive {Port, {data, Data}} - Caller ! {complex, decode(Data)} end, loop(Port); stop - Port ! {self(), close}, receive {Port, closed} - exit(normal) end; {EXIT, Port, Reason} - exit(port_terminated) end. encode({foo, X}) - [1, X]; encode({bar, Y}) - [2, Y]. decode([Int]) - Int.假设 C 函数的参数与结果都小于 256这里采用极简编码方案foo用字节 1 表示、bar用字节 2 表示参数/结果各用一个字节。complex进程的工作循环是编码消息 → 发送到端口 → 等待回复 → 解码回复 → 回传给调用者。C 侧则需要实现带 2 字节长度前缀的收发函数见仓库 erl_comm.c/* erl_comm.c */ #include stdio.h #include unistd.h typedef unsigned char byte; int read_exact(byte *buf, int len) { int i, got0; do { if ((i read(0, bufgot, len-got)) 0){ return(i); } got i; } while (gotlen); return(len); } int write_exact(byte *buf, int len) { int i, wrote 0; do { if ((i write(1, bufwrote, len-wrote)) 0) return (i); wrote i; } while (wrotelen); return (len); } int read_cmd(byte *buf) { int len; if (read_exact(buf, 2) ! 2) return(-1); len (buf[0] 8) | buf[1]; return read_exact(buf, len); } int write_cmd(byte *buf, int len) { byte li; li (len 8) 0xff; write_exact(li, 1); li len 0xff; write_exact(li, 1); return write_exact(buf, len); }注意stdin/stdout是带缓冲的输入输出绝不能用于与 Erlang 的通信必须使用底层的文件描述符 0 和 1 直接read/write。主函数见 port.c在while (read_cmd(buf) 0)循环中监听 Erlang 消息用首字节决定调用哪个函数、第二字节作为参数再把结果写回/* port.c */ typedef unsigned char byte; int main() { int fn, arg, res; byte buf[100]; while (read_cmd(buf) 0) { fn buf[0]; arg buf[1]; if (fn 1) { res foo(arg); } else if (fn 2) { res bar(arg); } buf[0] res; write_cmd(buf, 1); } }while循环检查read_cmd/1的返回值是为了让 C 程序能检测端口关闭并随之终止。运行步骤$ gcc -o extprg complex.c erl_comm.c port.c$ erl Erlang/OTP 26 [erts-14.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns] Eshell V14.2 (press CtrlG to abort, type help(). for help) 1 c(complex1). {ok,complex1} 2 complex1:start(./extprg). 0.34.0 3 complex1:foo(3). 4 4 complex1:bar(5). 10 5 complex1:stop(). stopPort Driver把驱动链进虚拟机端口驱动port driver是一种以端口形式被 Erlang 程序访问的 linked-in driver。它是一个具有特殊入口点的共享库UNIX 下为 SOWindows 下为 DLL。由于端口驱动被动态链接进仿真器进程这是从 Erlang 调用 C 代码最快的方式——调用无需上下文切换但也是最不安全的驱动崩溃会直接拖垮仿真器。其完整示例见 Port Drivers关键区别在于创建端口前需用erl_ddll:load_driver/2加载驱动库open_port({spawn, DriverName}, [])直接以驱动名作为第一个参数C 侧用DRIVER_INIT(driver_name)宏声明特殊入口点、以driver_entry结构登记驱动名与各回调函数指针start/stop/output等通过driver_output回发数据见 port_driver.c。从源码结构可以推断driver_entry结构体ErlDrvEntry的字段顺序、ERL_DRV_EXTENDED_MARKER/ERL_DRV_EXTENDED_MAJOR_VERSION/ERL_DRV_EXTENDED_MINOR_VERSION版本标记都是 Erlang 驱动 ABI 的稳定契约相关 API 详见 erl_driver 参考文档。官方同时强调驱动实例不应依赖全局变量可能被多个 Erlang 进程同时 spawn应使用driver_alloc分配每个实例私有的结构体并回传指针。原生实现函数NIFsNIF 提供了另一种把 C 代码链接进 Erlang 运行时系统的途径可替代“端口 linked-in driver”方案。NIF 使得在需要与 OS 或外部库交互时能为普通 Erlang 函数提供 C 实现。警告.warning一个有缺陷的 NIF 会导致整个 Erlang 运行时系统内存泄漏、挂起、崩溃甚至泄漏敏感信息。适用场景由于缺陷 NIF 可能引发稳定性与安全性的诸多问题官方建议优先使用外部端口只有当端口开销不可接受时NIF 才是与 C、C 或 Rust 等原生代码交互的好方案。NIF 最适合类似示例中foo/bar这样的同步、计算量相对较小、无副作用的函数——它比端口驱动更简单高效调用无需上下文切换但因为动态链接进仿真器进程NIF 崩溃同样会带崩整个仿真器。NIF 的 API 详见 API functions for an Erlang NIF library。完整示例见 NIFs。NIF 示例Erlang 侧骨架即使一个模块的所有函数都是 NIF也必须保留 Erlang 模块原因有二NIF 库必须由同模块的 Erlang 代码显式加载模块的所有 NIF 都必须有对应的 Erlang 实现通常是抛异常的最小桩实现也可作为某些架构上没有原生实现时的 fallback。-module(complex6). -export([foo/1, bar/1]). -nifs([foo/1, bar/1]). -on_load(init/0). init() - ok erlang:load_nif(./complex6_nif, 0). foo(_X) - erlang:nif_error(nif_library_not_loaded). bar(_Y) - erlang:nif_error(nif_library_not_loaded).这里用on_load指令让模块加载时自动调用init/0。若init返回非ok例如 NIF 库加载失败模块会被卸载对其内函数的调用失败。加载成功后NIF 库会覆盖桩实现foo/bar的调用被分发到 NIF 实现。仓库中 complex6.erl 与该骨架完全对应NIF 库加载错误时的erlang:nif_error/1行为也大量见于 erts/preloaded/src/erlang.erl 等内置模块的实现。NIF 示例C 侧库代码每个 NIF 都是普通 C 函数。宏ERL_NIF_INIT配合结构体数组定义所有 NIF 的名字、元数arity与函数指针必须包含头文件erl_nif.h由于是共享库而非程序不能有 main 函数。参数以数组argv传入、argc为长度即函数元数第 N 个参数通过argv[N-1]访问。NIF 还接收一个环境参数env作为需要传给大多数 API 函数的不透明句柄包含调用 Erlang 进程的相关信息#include erl_nif.h extern int foo(int x); extern int bar(int y); static ERL_NIF_TERM foo_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { int x, ret; if (!enif_get_int(env, argv[0], x)) { return enif_make_badarg(env); } ret foo(x); return enif_make_int(env, ret); } static ERL_NIF_TERM bar_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { int y, ret; if (!enif_get_int(env, argv[0], y)) { return enif_make_badarg(env); } ret bar(y); return enif_make_int(env, ret); } static ErlNifFunc nif_funcs[] { {foo, 1, foo_nif}, {bar, 1, bar_nif} }; ERL_NIF_INIT(complex6, nif_funcs, NULL, NULL, NULL, NULL)ERL_NIF_INIT的参数含义第一个参数必须是 Erlang 模块名以 C 标识符形式给出宏会将其字符串化第二个参数是ErlNifFunc结构体数组包含每个 NIF 的名字、元数与函数指针其余参数是指向回调函数的指针可用于库初始化本示例未使用全部置为NULL。函数参数与返回值都以ERL_NIF_TERM类型表示通过enif_get_int/enif_make_int在 Erlang term 与 C 类型间转换。若argv[0]不是整数enif_get_int返回 false此时用enif_make_badarg抛出badarg异常。运行步骤编译命令见 complex6_nif.c 配套说明unix gcc -o complex6_nif.so -fpic -shared complex.c complex6_nif.c windows cl -LD -MD -Fe complex6_nif.dll complex.c complex6_nif.c1 c(complex6). {ok,complex6} 3 complex6:foo(3). 4 4 complex6:bar(5). 10 5 complex6:foo(not an integer). ** exception error: bad argument in function complex6:foo/1 called as comlpex6:foo(not an integer)二、C 与 Java 库Erl_Interface、C Node 与 jinterfaceErl_Interface端口另一侧的 C 程序常常需要帮助库于是 Ericsson 开发了Erl_Interface库。其核心基础是Erlang 外部项格式external term format——把 Erlang 项表示成字节序列即二进制。两种表示之间的转换由以下 BIF 完成Binary term_to_binary(Term) Term binary_to_term(Binary)端口可以被设置为使用二进制而非字节列表这样就无需再自创编码/解码方案。Erl_Interface 函数用于解包二进制并转换成与 Erlang 项类似的 C 结构体这种结构体可以被各种方式操作、转换成 Erlang 外部格式并发送给 Erlang。适用场景C 代码中与 Erlang 二进制配合使用。实战对比详见 Erl_Interface与普通端口示例相比Erlang 侧只有两处不同——端口必须设置为二进制模式编码/解码改用 BIF%% 原来是 open_port({spawn, ExtPrg}, [{packet, 2}]) %% 改为 open_port({spawn, ExtPrg}, [{packet, 2}, binary])%% 原来是 Port ! {self(), {command, encode(Msg)}}, receive {Port, {data, Data}} - Caller ! {complex, decode(Data)} end %% 改为 Port ! {self(), {command, term_to_binary(Msg)}}, receive {Port, {data, Data}} - Caller ! {complex, binary_to_term(Data)} end此时complex2:foo/1、complex2:bar/1会把元组{foo,X}或{bar,Y}发送给complex进程由它编码成二进制后送入端口——因此 C 程序必须能处理这两个元组。C 侧使用 ei.c 中的ei_decode_version、ei_decode_tuple_header、ei_decode_atom、ei_decode_long等函数解码用ei_x_new_with_versionei_x_encode_long构造回复read_cmd/write_cmd仍复用端口示例中的 erl_comm.c。ei.h头文件位于仓库 lib/erl_interface/include/ei.h。编译需要提供ei.h的 include 路径与ei库路径。在 R5B 及以后的 OTP 中include与lib目录位于$OTPROOT/lib/erl_interface-VSN$OTPROOT为 OTP 安装根目录VSN为 Erl_interface 应用版本R4B 及更早版本则位于$OTPROOT/usr。$ gcc -o extprg -I/usr/local/otp/lib/erl_interface-3.9.2/include \ -L/usr/local/otp/lib/erl_interface-3.9.2/lib \ complex.c erl_comm.c ei.c -lei -lpthread运行效果支持超过 255 的参数因为不再受单字节编码限制2 complex2:start(./extprg). 0.34.0 3 complex2:foo(3). 4 4 complex2:bar(5). 10 5 complex2:bar(352). 704 6 complex2:stop(). stopC Nodes一个使用 Erl_Interface 函数建立连接、并与分布式 Erlang 节点通信的 C 程序被称为C Node或hidden node。C Node 最大的优点是从 Erlang 程序员角度看通信极其简单——因为 C 程序表现得就像是一个分布式 Erlang 节点。适用场景C Node 通常用于设备处理器相对于控制处理器场景即由于内存限制或应用特性或两者兼有而更适合用 C 而非 Erlang 的地方。要创建 C Node需要阅读ei_connect部分的 Erl_Interface 文档并熟悉 TCP/IP 套接字见后文 Sockets与分布式 Erlang 机制。教程中的 C Node 示例见 C Nodes仓库还提供 cnode_c.c、cnode_s.c、cnode_s2.c 三个可编译的 C 源码文件。C Node 的详细创建方法可参考 erl_interface 用户指南中关于如何创建 C nodes 的章节。jinterface在 Erlang/OTP R6B 中加入了一个与 Erl_Interface 类似的 Java 库jinterface为 Java 程序提供与 Erlang 节点通信的工具。其 Java 源码位于仓库 lib/jinterface/java_src/com/ericsson/otp/erlang包含AbstractNode、OtpErlang系列数据类型、连接与链接管理等类共 57 个 Java 文件可以从源码结构推断其覆盖了节点连接、外部项编解码、RPC 与链路link管理等完整功能。三、标准协议Sockets 与 SNMP/HTTP/IIOP有时Erlang 程序与另一程序之间采用标准协议通信更为合适。Erlang/OTP 当前支持 TCP/IP 与 UDP 套接字以及以下基于它们的标准协议SNMPHTTPIIOPCORBA使用后三者要求对相应协议有深入了解不属于本教程覆盖范围——请分别参阅 SNMP、Inets 和 Orber 应用。Sockets简单来说**面向连接的套接字通信TCP/IP**由两方构成在某主机某端口号上启动的发起方套接字“服务器”以及知晓发起方主机名与端口号、可以连接并互相传输数据的连接方套接字“客户端”。**无连接的套接字通信UDP**则是发起方套接字在某主机某端口监听连接方套接字直接向它发送数据。在 Erlang/OTP 中对 TCP/IP 与 UDP 套接字的访问由 Kernel 中的gen_tcp与gen_udp模块提供两者都易用且无需深入了解套接字概念。其源码分别在 lib/kernel/src/gen_tcp.erl 与 lib/kernel/src/gen_udp.erl。更底层的 socket 实现可参考 erts 的 erts/preloaded/src/prim_socket.erl预加载模块与 Kernel 指南 socket_usage。适用场景程序与 Erlang 程序运行在同一台或另一台机器上时。深入学习套接字概念的详细描述可参考网络编程专著例如 W. Richard Stevens 的UNIX Network Programming, Volume 1: Networking APIs - Sockets and XTIISBN 013490012X。四、IC 与 CORBAICErlang IDL Compiler是一个接口生成器给定一份 IDL 接口规范它能自动生成 Erlang、C 或 Java 的桩代码stub code。详细信息见 IC 用户指南与 IC 参考手册CORBA 相关实现位于独立的 corba 仓库。五、NIF 与驱动开发的调试利器由于 NIF 与端口驱动运行在 Erlang VM 的 OS 进程“Beam”内、被执行 Erlang beam 代码的同一线程直接调用并拥有该 OS 进程全部内存的访问权限一个出错的 NIF/驱动可能因内存破坏造成严重损害详见 Debugging NIFs and Port Drivers。内存破坏往往不会在错误写入时立刻暴露而是等到很晚例如调用进程被垃圾回收时才显现此时通过 core dump 定位根因极其困难。官方为此推荐四类工具它们也是 Erlang 运行时系统自身开发、测试与排障时实际使用的工具。Debug 仿真器用debug目标构建的仿真器能提高早期发现 bug 的概率包含大量额外的运行时检查确保内部接口与数据结构被正确使用生成更易分析的 core dump关闭编译器优化避免变量被“优化掉”便于检查其状态检测锁顺序违规运行时锁检查器验证 erl_nif 与 erl_driver API 中的锁按一致顺序获取避免死锁。官方建议在 NIF 与驱动开发期间默认使用 debug 仿真器——有些微妙 bug 普通仿真器检测不到、只是碰巧能运行而已换一个仿真器版本或不同环境条件就可能引发各种问题。主要缺点是性能下降额外检查与缺少优化可能带来 2 倍以上减速内存占用大致相同。若 debug 仿真器随 OTP 安装可用-emu_type选项启动 erl -emu_type debug Erlang/OTP 25 [erts-13.0.2] ... [type-assertions] [debug-compiled] [lock-checking] Eshell V13.0.2 (abort with ^G) 1若安装中不包含 debug 仿真器需从 Erlang/OTP 源码构建参见 HOWTO/INSTALL.md 与安装指南的进阶配置章节。构建后可直接在源码树中用cerl脚本运行 $ERL_TOP/bin/cerl -debugcerl还可方便地启动gdb分析 core dump-debug -core core.12345在 Emacs 中运行 gdb-debug -rcore core.12345直接在终端运行并自动读取$ERL_TOP/erts/etc/unix/etp-commands仓库中对应模板为 erts/etc/unix/etp-commands.in其中包含大量检查 beam core dump 的 gdb 命令例如etp命令可把 Erlang 项Eterm以普通 Erlang 语法打印出来。Address SanitizerasanAddressSanitizer 是基于编译器插桩的开源工具可检测缓冲区溢出、use-after-free 与内存泄漏gcc 和 clang 均支持。asan 仿真器运行约慢 2~3 倍内存占用约为正常的 3 倍。要获得完整效果应同时用-fsanitizeaddress编译自己的 NIF/驱动代码与 Erlang 仿真器建议附加-fno-common、-fno-omit-frame-pointer。构建与运行方式同 debug 仿真器只是目标换为asan源码树运行只需设置ASAN_LOG_DIR指向错误日志目录$ERL_TOP/bin/cerl -asan如需检测到错误即崩溃可加ASAN_OPTIONShalt_on_errortrue已安装 OTP 运行erl -emu_type asan时用ASAN_OPTIONSlog_path/my/asan/log/file指定日志文件为避免仿真器自身产生误报内存泄漏可设置LSAN_OPTIONSsuppressions$ERL_TOP/erts/emulator/asan/suppress该suppress文件目前不随安装分发需从源码树手动复制仓库路径为 erts/emulator/asan/suppress。内存破坏错误会在发生时立即报告而内存泄漏默认只在仿真器终止时检查并报告。ValgrindValgrind 是更重量级的调试工具能类似 asan 地发现内存破坏与泄漏它在缓冲区溢出方面不如 asan但能发现 asan 检测不到的未定义数据使用。Valgrind 比 asan 慢得多且无法利用多核官方建议优先选 asan。Valgrind 以虚拟机方式模拟执行硬件指令几乎任何程序无需修改即可运行但 beam 可执行文件经过针对 valgrind 的特化编译效果更好。用valgrind目标构建构建前需先安装 valgrind源码树中设置VALGRIND_LOG_DIR后运行$ERL_TOP/bin/cerl -valgrind。rrRecord and Replayrr 是 Mozilla 开发的开源交互调试工具意为“录制与重放”。core dump 只是崩溃瞬间的静态快照而 rr 可以录制 OS 进程从启动到崩溃的完整会话随后在 gdb 中重放可以单步、设置断点与观察点甚至反向执行。rr 非常轻量在 Linux 任意现代 x86 CPU 上即可运行录制模式约 2 倍减速最大弱点是无法利用多核若 bug 是并发线程间的竞态条件则难以复现。rr 不需要特化编译但配合 debug 仿真器体验更佳源码树中用cerl脚本启动 $ERL_TOP/bin/cerl -debug -rr rr: Saving execution to trace directory /home/foobar/.local/share/rr/beam.debug.smp-1. Erlang/OTP 25 [erts-13.0.2] Eshell V13.0.2 (abort with ^G) 1 mymod:buggy_nif(). Segmentation fault崩溃后用rr replay重放continue到 SIGSEGV 处backtrace查看调用栈。典型排障流程是在可疑内存位置设置观察点watch -l *hp然后reverse-continue反向执行调试器会在该内存位置被写入的确切位置停下注意反向执行时 “Old value” 与 “New value” 是颠倒的再backtrace找出肇事代码。选型速查与总结机制进程/地址空间通信内容典型场景风险等级分布式 Erlang独立 VM网络Erlang 项透明消息Erlang 间通信、跨节点进程与 RPC低有分发协议保护Ports独立 OS 进程字节列表 / 二进制同机异构程序、需隔离的场景中进程隔离崩溃不牵连 VMLinked-In DriversVM 进程内字节高性能驱动高代码注入 VM 内存NIFsVM 进程内Erlang term同步函数短小同步计算、库封装高官方建议优先端口C Node Erl_Interface独立 OS 进程分布式协议Erlang 外部项格式设备端 C 程序、隐藏节点低标准分发协议jinterface独立 JVMErlang 外部项格式Java 程序与 Erlang 节点互通低gen_tcp / gen_udp独立 OS 进程网络标准 TCP/UDP 字节流跨机器、标准协议通信低官方选型建议能选端口就优先端口稳定性与安全性最好若端口开销不可接受再考虑 NIFNIF 优于 linked-in driver功能更丰富、可用 dirty schedulers需要与 Erlang 分布式网络互通的 C/Java 程序则走 Erl_Interface/C Node 与 jinterface。开发 NIF 与驱动期间务必以 debug 仿真器为默认运行环境并结合 asan/Valgrind/rr 尽早发现内存类缺陷。所有示例源码与可编译文件均可直接在仓库 system/doc/tutorial 目录下查看与编译运行。赞分享编程语言语言运行时标准库编译器并发编程【免费下载链接】otpErlang/OTP项目地址https://gitcode.com/gh_mirrors/ot/otp点击查看免费下载相关推荐3 步批量解除 PDF 复制打印限制PDF 补丁丁实操指南3 步批量解除 PDF 复制打印限制PDF 补丁丁实操指南 想把 PDF 里的一段话复制出来阅读器却弹出权限不足想打印按钮是灰的。这类锁由文档自身的编程语言语言运行时标准库编译器并发编程Tolaria v2026-05-06 版本解析系统主题跟随、快速日期编辑与默认笔记宽度配置Tolaria v2026 05 06 版本解析系统主题跟随、快速日期编辑与默认笔记宽度配置 本文围绕 Tolaria 桌面端笔记应用的 v2026 05 0编程语言语言运行时标准库编译器并发编程Pydantic AI 与 LangChain/LangGraph 概念映射实战指南从 Agent 循环到持久化的系统性迁移参考Pydantic AI 与 LangChain/LangGraph 概念映射实战指南从 Agent 循环到持久化的系统性迁移参考 导读 本文基于 pydant编程语言语言运行时标准库编译器并发编程上一篇为 Checkov 贡献 YAML 自定义策略从声明式语法到图扫描测试的完整实战指南下一篇Expenso-iOS部署指南从开发到App Store上架的完整流程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表