首页 > 解决方案 > 使用 select() 向子进程发送信号

问题描述

我正在尝试根据对子进程的不同用户输入从父进程发送一些信号(例如 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),我应该如何以及在哪里做呢?

谢谢!

标签: processocamlsignalsfork

解决方案


目前还不是很清楚你要做什么。以下是对您显示的代码的一些评论。

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

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

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

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

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

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

更新

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

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

您还没有指定孩子的输入应该来自哪里,但我怀疑您正计划通过另一个管道发送输入。

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

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

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

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

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

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 个字节,您将看到人们尝试设置此双管道方案时通常发生的死锁。

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


推荐阅读