我根据http://www.linux-nantes.org/~fmonnier/ocaml/ocaml-wrapping-c.php上的指南为CZMQ编写了一些OCaml绑定,这些绑定似乎运行得很好.例如,这是zstr_send:
CAMLprim value
caml_zstr_send(value socket_val, value string_val)
{
CAMLparam2 (socket_val, string_val);
void *sock = CAML_CZMQ_zsocket_val(socket_val);
char *string = String_val(string_val);
int rc = zstr_send(sock, string);
CAMLreturn (Val_int(rc));
}
Run Code Online (Sandbox Code Playgroud)
我可以在大多数代码中使用这些绑定发送和接收消息.但是,我有一个场景,我想在信号处理程序内部发送和接收,直到在其他代码的后台传递消息.举个简单的例子:
open ZMQ
exception SocketBindFailure
let bg_ctx = zctx_new ();;
let pub_sock = zsocket_new bg_ctx ZMQ_PUB;;
let handler _ =
print_endline "enter handler";
print_endline (string_of_int (zstr_send pub_sock "hello"));
print_endline "end handler";
;;
let () =
(try (
(* bind pub socket *)
let rc = zsocket_bind pub_sock "tcp://*:5556" in
if (rc < 0) then ( raise SocketBindFailure );
Sys.set_signal
Sys.sigalrm
(Sys.Signal_handle handler);
ignore
(Unix.setitimer
Unix.ITIMER_REAL
{ Unix.it_interval = 0.01 ; Unix.it_value = 0.01 });
(* do some work *)
)
with
| SocketBindFailure -> raise SocketBindFailure)
;;
Run Code Online (Sandbox Code Playgroud)
从顶层来看,输出失败了:
enter handler
0
end handler
Fatal error: exception Sys_blocked_io
Run Code Online (Sandbox Code Playgroud)
类似于上面的OCaml的C代码工作得很好.什么是OCaml添加到导致此异常的等式中?
有两个潜在的问题:
在信号处理程序内部,您只能调用异步信号安全函数。大多数函数都不是异步信号安全的。
限制的原因是函数可以在同一函数执行的中间被调用。因此,内部状态可能会被破坏。很少有函数是异步信号安全的,任何动态分配内存的函数都不是。在 OCaml 中,许多分配发生在“幕后”,因此您的代码可能不是异步信号安全的。
在您的情况下,您正在调用一个写入标准输出的函数。在 C 中,这绝不是异步信号安全的,但有一个例外:原始write()
函数。这是原始系统调用(在文件描述符上操作)并且是异步信号安全的,原因很简单,内核本身并不关心您是否在信号处理程序中,并且在将控制权返回给您之前会完全清理。
当信号是异步的(这里的情况)并且本身中断时,从信号处理程序调用不安全函数是C 中未定义的行为。这意味着任何事情都可能发生- 包括您的程序正常工作,但也包括分段错误或其他错误,以及允许攻击者执行任意代码。这通常与 C 等低级语言相关,并且在 OCaml 中通常不会发生。
OCaml 使用了一个巧妙的技巧:当收到已在 OCaml 中设置处理程序的信号时,它会推迟执行处理程序,直到安全点。结果是,在处理程序中,可以安全地将未装箱的数量设置到ref
变量中。然而,其他函数print
可能是不可重入的,因为它们可能具有内部状态。一般来说,在信号处理程序中,您应该尽量避免除了设置标志并立即返回之外执行更多操作。在 OCaml 中,标志应该是 31 位或 63 位整数或布尔值,因为它们是未装箱的。在 C 中,标志必须是volatile sig_atomic_t
C11 原子类型或(我对此不确定)。
@TheCodeArtist 给出了错误的其他可能原因。