我根据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));
}
我可以在大多数代码中使用这些绑定发送和接收消息。但是,我有一个场景,我想在信号处理程序内部发送和接收,直到在其他代码的后台传递消息。以这个简化的例子为例:
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)
;;
从顶层来看,输出失败了:
enter handler
0
end handler
Fatal error: exception Sys_blocked_io
类似于上面的OCaml的C代码工作得很好。什么是OCaml添加到导致此异常的等式中?
答案 0 :(得分:1)
有两个潜在的问题:
在信号处理程序中,您只能调用异步信号安全函数。大多数功能都不是异步信号安全。
限制的原因是可以在同一函数的执行过程中调用函数。因此,内部状态可能被破坏。很少有函数是异步信号安全的,任何动态分配内存的函数都不是。在OCaml中,许多分配都是“幕后”发生的,因此您的代码很可能不是异步信号安全的。
在您的情况下,您正在调用写入标准输出的函数。在C中,这是从不异步信号安全,有一个例外:原始write()
函数。这是原始系统调用(对文件描述符进行操作)并且异步信号是安全的,原因很简单,因为内核本身并不关心您是否处于信号处理程序中,并且在将控制权交还给您之前会完全清除。
从信号处理程序调用不安全的函数,当信号是异步的(这里的情况)并且本身中断了一个不安全的函数是C中的未定义的行为。这意味着任何事情都可能发生< / em> - 包括您的程序正常工作,但也包括分段错误或其他错误,以及允许攻击者执行任意代码。这通常与C等低级语言相关联,并且通常不会出现在OCaml中。
OCaml使用一个巧妙的技巧:当接收到已在OCaml中设置了处理程序的信号时,它会延迟执行处理程序直到安全点。结果是,在处理程序中,将未装箱的数量设置为ref
变量是安全的。但是,print
等其他函数可能不具有可重入性,因为它们可能具有内部状态。通常,在信号处理程序中,您应该尽量避免设置标志并立即返回。在OCaml中,标志应该是31位或63位整数或布尔值,因为它们是未装箱的。在C中,标志必须是volatile sig_atomic_t
或(我不确定)C11原子类型。
@TheCodeArtist给出了错误的其他可能原因。