41

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

4

1 回答 1

1

有两个潜在的问题:

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

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

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

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

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

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

于 2014-07-24T00:24:51.723 回答