Как заставить 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
Любые идеи? Большое спасибо!
Ответы
Ответ 1
man 3 tcsetpgrp:
Если tcsetpgrp() вызывается членом группы фонового процесса в своем сеансе, а вызывающий процесс не блокирует или игнорирует SIGTTOU, сигнал SIGTTOU отправляется всем членам этой фоновой группы процессов.
Вам нужно вызвать tcsetpgrp() в родительском процессе, а не в дочернем. Однако, если ваш родительский процесс запущен и перемещен в фоновый режим, он получит SIGTTOU и будет остановлен.
Ответ 2
Выяснил это. Я должен игнорировать любые сигналы SIGTTOU.
Я сделал это, добавив:
signal(SIGTTOU, SIG_IGN);
Перед вызовом tcsetpgrp()
.
Ответ 3
Это родительский, а не дочерний, который должен вызывать 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 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;
}