问题 如何让tcsetpgrp()在C中工作?


我正在尝试给孩子一个过程(通过 fork())前台访问终端。

在我之后 fork(),我在子进程中运行以下代码:

setpgid(0, 0);

和:

setpgid(child, child);

在父进程中。

这为孩子提供了自己的过程组。打电话给 setpgid() 工作正常。

现在我想让孩子访问终端。

之后我给孩子添加了以下内容 setpgid() 呼叫:

if (!tcsetpgrp(STDIN_FILENO, getpid())) {
    perror("tcsetpgrp failed");
}

在那之后,有一个 execv() 命令生成 /usr/bin/nano

但是,而不是拥有 nano 什么都没有发生,终端看起来好像在期待用户输入。

此外,没有代码似乎执行后 tcsetpgrp() 呼叫。

我在某处读到了我需要发送的信息 SIGCONT 向子进程发出信号以使其工作。如果该过程停止,我该怎么办?父母是否必须发送信号?

我怎么去发送 SIGCONT 信号,如果这是解决方案?

raise(SIGCONT);

此外,我不确定这是否有帮助,但代码工作正常并产生 nano 如果我运行我的程序:

exec ./program

代替:

./program

有任何想法吗?非常感谢!


11216
2018-03-17 15:27


起源

会话负责人(读:shell)应该调用tcsetpgrp() - blaze


答案:


man 3 tcsetpgrp说:

如果tcsetpgrp()在其会话中由后台进程组的成员调用,并且调用进程未阻止或忽略SIGTTOU,则会向此后台进程组的所有成员发送SIGTTOU信号。

您需要在父进程中调用tcsetpgrp()而不是在子进程中。但是,如果您的父进程已启动并移至后台,则它将收到SIGTTOU并将被停止。


6
2017-08-09 18:08



您能否解释为什么需要在父级而不是子级中调用tcsetpgrp()? - ajfbiw.s
我读到“当您键入CTRL-C时,您的终端会向前台进程组内的每个进程发送一个信号。您可以使用”tcsetpgrp(int fd,pid_t pgrp)“更改终端前台中的哪个进程组。现在,我们在子(fork()== 0)或父对象中执行此操作吗? - ajfbiw.s


弄清楚了。我必须忽略任何SIGTTOU信号。

我这样做是通过添加:

signal(SIGTTOU, SIG_IGN);

之前 tcsetpgrp() 呼叫。


7
2018-03-17 17:03



为什么你需要忽略SIGTTOU?这与终端控制的丢失或共享有关吗? - tyree731
不确定...比忽略它更好,我已经决定,阻止它,调用tcsetpgrp(),然后取消阻止它。 - John Kurlak


应该调用tcsetpgrp()是父母而不是孩子。在setpgid()调用之后,子进程成为后台进程。有效的情况是前景组放弃其权限,让另一个背景组成为前景和自身背景。后台组中的进程无法获取控制终端。示例代码可能如下所示:

/* perror_act.h */
#ifndef PERROR_ACT_H
#define PERROR_ACT_H

#define PERROR_ACT(rtn, act) do { \
    perror(rtn);\
    act; \
} while (0)

#define PERROR_EXIT1(rtn) PERROR_ACT(rtn, exit(1))
#define PERROR_RETN1(rtn) PERROR_ACT(rtn, return -1)

#endif

/* invnano.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include "perror_act.h"

void sig_chld(int chld)
{
    exit(0);
}

int main(void)
{
    pid_t child;
    int p2c[2];
    struct sigaction sa = {.sa_handler = sig_chld};

    if (sigaction(SIGCHLD, &sa, NULL))
        PERROR_EXIT1("sigaction");
    if (pipe(p2c))
        PERROR_EXIT1("pipe");
    if ((child = fork()) < 0)
        PERROR_EXIT1("fork");
    if (child == 0) {
        char buff;
        size_t nread;
        if (close(p2c[1])) /* We must make sure this fd is closed. The reason is explained in following comments. */
                        PERROR_EXIT1("close");
        if ((nread = read(p2c[0], &buff, 1)) < 0) /* Just to receive a message from parent indicating its work is done. Content is not important.  */
            PERROR_EXIT1("read");
        if (nread == 0) /* When all the write ends of a pipe are closed, a read() to the read end of this pipe will get a return value of 0. We've closed the child's write end so if 0 as returned, we can sure the parent have exited because of error. */
            exit(1);
        close(p2c[0]);
        execlp("nano", "nano", (char *) 0);
        PERROR_EXIT1("execlp");
    } else {
        if (close(p2c[0]))
            PERROR_EXIT1("close");
        if (setpgid(child, child))
            PERROR_EXIT1("setpgid");
        if (tcsetpgrp(STDIN_FILENO, child))
            PERROR_EXIT1("tcsetpgrp");
        if (write(p2c[1], &child, 1) != 1) /* If all the read ends of a pipe are close, a write() to the write end of this pipe will let the calling process receive a SIGPIPE whose default deposition is to terminate. */
            PERROR_EXIT1("write");
        while (1) /* If parent exit here, login shell will see the news and grab the controlling terminal */
            pause();
    }
    return 0;
}

1
2018-01-02 14:03