【问题标题】:Using setpgid in a mini-shell breaks interactive commands在 mini-shell 中使用 setpgid 会破坏交互式命令
【发布时间】:2021-06-12 02:50:29
【问题描述】:

我正在尝试使用this template 在 C 中编写一个迷你 shell。但是每当我尝试使用诸如lessvi 之类的交互式命令时,shell 就会卡在waitpid 上(启用WUNTRACED 这些命令会立即返回,因为它们被ps 指示的作业控制信号停止) .其他不需要输入的命令,例如ls 也可以。根本原因是setpgid,这似乎将分叉的子进程(例如lessvi)放到了不再共享终端的不同进程组中。因此,子进程被作业控制信号停止。删除setpgid 将使mini-shell 再次工作,但它不能被删除,因为mini-shell 需要将其前台进程作为一个组来控制(例如,如果前台进程P 派生额外的进程P1 和P2,则shell , 在收到用户的SIGTSTP 后, 应该停止 P, P1, P2. 如果 P, P1, P2 在同一个进程组, pgid 与 P 的 pid 相同, 这可以很方便地完成. 我们可以发送SIGTSTP 到整个进程组)。

我尝试使用tcsetpgrp 来修复我的shell。虽然它会使vi 等命令再次起作用,但迷你shell 会在分叉子进程完成后自动退出,可能是因为父shell 错误地将分叉子进程的完成视为迷你shell 的完成.

是否有一个修复程序仍然允许我保留 setpgid?

// full code is in the provided link
if (!builtin_command(argv)) {
    if ((pid = Fork()) == 0) {   /* Child runs user job */
        if (execve(argv[0], argv, environ) < 0) {
            execvp(argv[0], argv);
            printf("%s: Command not found.\n", argv[0]);
            exit(0);
        }
    }
    // call wrapper function for error handling
    // set process group id of child to the pid of child
    Setpgid(pid, 0);
    if (!bg) {
        // foreground process, should wait for completion
        // tcsetpgrp does make vi and less work, 
        // but their completion also terminates the mini-shell
        // tcsetpgrp(STDERR_FILENO, pid);
        int status;
        if (waitpid(pid, &status, 0) < 0) {
            unix_error("waitfg: waitpid error");
        }
    } else {
        // background process
        printf("%d %s", pid, cmdline);
    }
}

【问题讨论】:

  • 其他 shell 如何解决这些问题?其中有几个是开源的,您可以研究它们。
  • 删除 setpgid 将使 mini-shell 再次工作,但无法删除,因为 shell 需要分组控制分叉进程。 确切地说如何 你想控制 进程分组吗?也就是说,它对你有什么作用?进程[复数]和组[复数]是什么意思?而且,究竟什么是“冻结”?当“shell”是你的 shell 时会发生什么(即你分叉然后子进程调用它自己的main)?
  • @CraigEstey 我编辑了这个问题,因为我的措辞不清楚。之所以需要 setpgid,是因为“如果前台进程 P 派生出额外的进程 P1 和 P2,shell 在收到用户的 SIGTSTP 后,应该停止 P、P1 和 P2。如果 P、P1、P2 在同一进程组,其 pgid 与 P 的 pid 相同。我们可以向整个进程组发送 SIGTSTP"。外壳“冻结”,因为它卡在 waitpid 上。启用 WUNTRACED 后,waitpid 立即退出,因为分叉的进程“被作业控制信号停止”。在这两种情况下,vi 都不会出现
  • 我会看看 [如果你还没有这样做的话]:man 2 setsidman credentials 以获取有关建立新的“控制 TTY”的更多信息。另外,我会将源代码拉到bash 并查看它的作用。当然,如果没有那个 TTY,vi 就会出现问题。同样,设置您的 shell,使其可以fork 并自行运行(通过main./myshell)。我会从另一个窗口运行gdb,然后将attach 运行到子shell。或者,将更多的调试printf 发送到(例如)/tmp/mylog/log.&lt;pid&gt; 您的子shell 应该与另一个程序有相同的问题,但更容易调试。
  • bash,查看jobs.c。这有点复杂,但是在执行setpgid 之后,它调用give_terminal_to,它调用sigprocmasktcsetpgrp。其他 shell 可能有更简单的实现。

标签: c linux shell unix posix


【解决方案1】:

解决方案是使用tcsetpgrp 将tty 的控制权交给另一个进程组,当子进程完成后,再次使用tcsetpgrp 收回对tty 的控制权。注意tcsetpgrpSIGTTOU 发送给它的调用者if the calling process belongs to a background process group,所以SIGTTOUSIGTTIN 必须被阻止。

// error handling is omitted for brevity
// these two signal functions can be anywhere before tcsetpgrp
// alternatively, they can be masked during tcsetpgrp
signal(SIGTTOU, SIG_IGN);
signal(SIGTTIN, SIG_IGN);

if (!builtin_command(argv)) {
    if ((pid = Fork()) == 0) {
        if (execve(argv[0], argv, environ) < 0) {
            execvp(argv[0], argv);
            printf("%s: Command not found.\n", argv[0]);
            exit(0);
        }
    }
    if (!bg) {
        setpgid(pid, 0);
        tcsetpgrp(STDERR_FILENO, pid);
        int status;
        if (waitpid(pid, &status, 0) < 0) {
            unix_error("waitfg: waitpid error");
        }
        tcsetpgrp(STDERR_FILENO, getpgrp());
    } else {
        printf("%d %s", pid, cmdline);
    }
}

这是一个相当粗略的实现。有关此问题,请咨询 Craig 的 cmets,了解在哪里可以找到 bash 的实现。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-01
    相关资源
    最近更新 更多