从信号处理程序调用 OCaml 包装的 ZeroMQ 代码

Posted

技术标签:

【中文标题】从信号处理程序调用 OCaml 包装的 ZeroMQ 代码【英文标题】:Calling OCaml-wrapped ZeroMQ code from signal handler 【发布时间】:2013-05-15 05:24:48 【问题描述】:

我已经根据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 在导致此异常的方程式中添加了什么?

【问题讨论】:

一般来说,在我审核代码时,从信号处理程序中调用任何例程都是一个危险信号。原因多种多样,但要点是您必须 100% 确定例程是异步安全的。 See this for more details. 我不能告诉你为什么你会得到这个异常,只是说你已经在从处理程序中尝试 IO 处于危险的基础上,无论是 C 还是 OCaml。我相信上面的链接为您提供了一些安全记录信号处理程序调用的方法。 感谢您提供的信息,戴夫。那我会采取不同的方法。 也许你的 OCaml 代码中有一个Unix.set_nonblock()。众所周知,OCaml 在尝试任何阻塞 I/O 时通常会生成 Fatal error: exception Sys_blocked_io 能否也给我们C代码? 【参考方案1】:

有两个潜在的问题:

在信号处理程序中,您只能调用异步信号安全函数。大多数函数都不是异步信号安全的。

限制的原因是一个函数可以在同一个函数的执行过程中被调用。因此,内部状态可能被破坏。很少有函数是异步信号安全的,任何动态分配内存的函数都不是。在 OCaml 中,许多分配发生在“幕后”,因此您的代码很可能不是异步信号安全的。

在您的情况下,您正在调用一个写入标准输出的函数。在 C 中,这是 never 异步信号安全的,但有一个例外:原语 write() 函数。这是原始系统调用(在文件描述符上运行)并且是异步信号安全的,原因很简单,内核本身并不关心您是否在信号处理程序中,并且在将控制权返回给您之前会完全清理。

从信号处理程序调用不安全函数,当信号是异步的(这里的情况)并且本身中断不安全函数时,在 C 中是未定义的行为。这意味着 任何事情都可能发生 - 包括您的程序正常工作,但也包括分段错误或其他错误,以及允许攻击者执行任意代码。这通常与 C 等低级语言相关联,并且通常不会出现在 OCaml 中。

OCaml 使用了一个巧妙的技巧:当接收到在 OCaml 中设置了处理程序的信号时,它会将处理程序的执行推迟到安全点。结果是在处理程序中,将 unboxed 数量设置为 ref 变量是安全的。然而,像print 这样的其他函数可能是不可重入的,因为它们可能有内部状态。通常,在信号处理程序中,您应该尽量避免做更多的事情,而不仅仅是设置一个标志并立即返回。在 OCaml 中,标志应该是 31 位或 63 位整数或布尔值,因为它们是未装箱的。在 C 中,标志必须是 volatile sig_atomic_t 或(我不确定)C11 原子类型。

@TheCodeArtist 给出了错误的另一个可能原因。

【讨论】:

以上是关于从信号处理程序调用 OCaml 包装的 ZeroMQ 代码的主要内容,如果未能解决你的问题,请参考以下文章

在处理数据结构中的复杂键(如 int*int*int)时,F# 比 Ocaml 慢得多

ocaml 中的匹配是不是调用构造函数?

OCaml 中的 D 类不可变数据切片

有哪些 OCaml 库用于惰性列表处理?

从 Javascript 调用包装器函数会导致应用程序崩溃 - React-Native

包装 jQuery 的 $.ajax() 方法来定义全局错误处理