【问题标题】:use select() to send signal to child process使用 select() 向子进程发送信号
【发布时间】:2020-03-25 12:47:38
【问题描述】:

我正在尝试根据不同的用户输入从父进程向子进程发送一些信号(例如 SIGSTOP、SIGUSR1)。父进程不断等待用户输入并将相应的信号发送给子进程。如果没有用户输入,孩子会自己做事。

我将我的 Ocaml 代码放在这里,但我不确定我使用了正确的方法。 我正在使用 OCaml 编写,但也欢迎使用其他语言(例如 C/Python)的解决方案。

let cr, pw = Unix.pipe () in
let pr, cw = Unix.pipe () in

match Unix.fork () with
| 0 -> (* child *)
    Unix.close pr;
    Unix.close pw;
    Unix.dup2 cw Unix.stdout;
    Unix.execvp ... (* execute something *)
| pid -> (* parent *)
    Unix.close cr;
    Unix.close cw;
    Unix.dup2 pr Unix.stdin;
    while true do
        try
            match Unix.select [pr] [] [] 0.1 with
            | ([], [], []) -> (* no user input *)
              (* I assume it should do next iteration and wait for next user input *)
              raise Exit
            | (_, _, _) -> (* some user input *)
              let i = read_int () in
              (* send signal to the child process *)
              if i = 1 then Unix.kill pid Sys.sigstop
              else if i = 2 then Unix.kill pid Sys.sigusr1;
        with Exit -> ()
    done

同时,如果我想定义一些信号(使用 SIGUSR1),我应该如何以及在哪里做呢?

谢谢!

【问题讨论】:

    标签: process ocaml signals fork


    【解决方案1】:

    目前还不是很清楚您要做什么。这是您显示的代码上的一些 cmets。

    父进程似乎正在读取它自己创建的管道 (pr)。但是你说父进程正在等待用户输入。用户输入不会显示在您自己创建的管道中。

    您几乎总是通过阅读标准输入来查找用户输入,Unix.stdin

    该代码创建了另一个似乎是供子进程使用的管道,但没有安排子进程访问管道的文件描述符。子级将改为读取父级的标准输入并写入父级的标准输出。

    您的代码调用select,超时时间为 0.1 秒。这意味着无论管道中是否有任何输入,调用都会每秒返回 10 次。每次它返回时都会写一个换行符。因此输出将以每秒 10 次左右的速度出现一串换行符。

    您说您想定义信号,但完全不清楚这意味着什么。如果您的意思是要为孩子定义信号 handlers,那么这在您显示的代码中是不可能的。对Unix.execvp 的调用不会保留信号处理程序。如果您考虑一下,这是唯一可行的方法,因为 execvp 调用会删除父进程中的所有代码,并将其替换为其他可执行文件中的代码。

    分叉一个孩子然后发送信号并不难。但目前尚不清楚您要对管道和选择做什么。而且还不清楚您期望信号在子进程中做什么。如果你解释这些,就会更容易给出更详细的答案。

    更新

    一般来说,修改您已发布到 StackOverflow 的代码(除了修复错别字)不是一个好主意,因为以前的 cmets 和答案不再适用于新代码。最好发布更新的代码。

    您的新代码看起来更像是在尝试通过管道读取子项的输入。这更明智,但这不是我所说的“用户输入”。

    您尚未指定孩子的输入应该来自哪里,但我怀疑您打算通过另一个管道发送输入。

    如果是这样,这是一个众所周知的不可靠设置,因为子进程和它的管道之间存在缓冲。如果您自己没有为子进程编写代码,则无法确保它会从读取管道读取适当大小的数据并在适当的时间将其输出刷新到写入管道。通常的结果是事情立即停滞不前。

    如果您正在为子进程编写代码,则需要确保它以父进程正在写入的大小读取数据。如果孩子要求比这更多的数据,它将阻止。如果父母正在等待答案出现在其读取管道中,您将陷入死锁(这是通常的结果,除非您非常小心)。

    您还需要确保子进程在准备好被父进程读取时刷新其输出。并且您还需要在希望子进程读取父进程的输出时随时刷新它。 (并且父母必须以孩子期望的大小写入数据。)

    你还没有解释你想用信号做什么,所以我无能为力。 (对我来说)读取子进程写入的整数值并向子进程发送信号作为响应没有多大意义。

    这是一些有效的代码。它对信号没有任何作用,因为我不明白您要做什么。但它正确设置了管道并通过子进程发送了一些固定长度的文本行。子进程只是将所有字符都改成大写。

    let cr, pw = Unix.pipe ()
    let pr, cw = Unix.pipe ()
    
    let () =
    match Unix.fork () with
    | 0 -> (* Child process *)
      Unix.close pr;
      Unix.close pw;
      Unix.dup2 cr Unix.stdin;
      Unix.dup2 cw Unix.stdout;
      Unix.close cr;
      Unix.close cw;
      let rec loop () =
        match really_input_string stdin 6 with
        | line ->
          let ucline = String.uppercase_ascii line in
          output_string stdout ucline;
          flush stdout;
          loop ()
        | exception End_of_file -> exit 0
      in
      loop ()
    | pid ->
      (* Parent process *)
      Unix.close cr;
      Unix.close cw;
      Unix.dup2 pr Unix.stdin;
      Unix.close pr;
      List.iter (fun s ->
        let slen = String.length s in
        ignore (Unix.write_substring pw s 0 slen);
        let (rds, _, _) =
          Unix.select [Unix.stdin] [] [] (-1.0)
        in
        if rds <> [] then
          match really_input_string stdin 6 with
          | line -> output_string stdout line
          | exception End_of_file ->
            print_endline "unexpected EOF"
        )
        ["line1\n"; "again\n"];
      Unix.close pw;
      exit 0
    

    当我运行它时,我会看到:

    $ ocaml unix.cma m.cmo
    LINE1
    AGAIN
    

    如果您将任一测试行更改为短于 6 个字节,您将看到人们尝试设置此双管道方案时通常发生的死锁。

    您发送信号的代码看起来不错,但我不知道您发送信号时会发生什么。

    【讨论】:

    • 我更新了一些代码。希望这能让事情更清楚。让我知道用户交互是否仍有问题。至于定义信号,你的意思是我不应该直接调用execvp,而是我应该使用实际代码并在执行期间捕获信号SIGUSR1
    猜你喜欢
    • 1970-01-01
    • 2013-07-30
    • 1970-01-01
    • 2022-06-15
    • 2015-05-11
    • 1970-01-01
    • 1970-01-01
    • 2019-04-11
    • 1970-01-01
    相关资源
    最近更新 更多