1

我正在尝试构建一个外壳,并且我已经设法编写了大部分功能,但是我有一个小问题。

说我打字firefox &。Firefox 将作为后台进程打开。激活一个 BG 标志,&使父进程不等待子进程。

然后我输入gedit. Gedit 将作为前台进程打开。这意味着当前父进程正在等待进程关闭。

此时,父进程有两个进程 -firefoxgedit. Firefox 没有被等待,目前处于后台,而我们目前正在等待 Gedit 完成。到现在为止还挺好。

但是,如果我决定按 发送一个 SIGINT 信号ctrl-c,两者firefoxgedit都会关闭。不好,只能gedit关门了。

这是我的信号处理函数:

pid_t suspended_process[10];
int last_suspended = -1;

void signal_handler(int signo){
    pid_t process = currentpid();

    // Catches interrupt signal
    if(signo == SIGINT){
        int success = kill(process, SIGINT);
    }

    // Catches suspend signal
    else if(signo == SIGTSTP){
        int success = kill(process, SIGTSTP);
        resuspended = 1;
        suspended_process[last_suspended+1] = process;
        last_suspended++;
    }
}

这是 fork-exec 代码中等待进程或继续运行的部分。

  else if(pid > 0){ //Parent
    current_pid = pid;

    // Waits if background flag not activated.
    if(BG == 0){
      // WUNTRACED used to stop waiting when suspended
      waitpid(current_pid, &status, WUNTRACED);

        if(WIFEXITED(status)){
          setExitcode(WEXITSTATUS(status));
        }
        else if(WIFSIGNALED(status)){
          printf("Process received SIGNAL %d\n", WTERMSIG(status));
        }
    }
  }

如果我事先暂停一个进程,也会发生这种情况。例如,我运行firefox然后按ctrl-z暂停它。然后我运行gedit并按下ctrl-c关闭它。之后,如果我按fg恢复暂停firefox,它会立即关闭。

我找不到只将 SIGINT 信号发送到前台进程的方法,它总是将信号发送给除父进程之外的所有子进程,无论它们是在后台还是挂起。

以防万一,这是初始化信号处理程序的函数:

void init_handler(){
    struct sigaction sa;

    sa.sa_handler = signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;

    // If conditions for signal handling.
    // Also creates 2 signal handlers in memory for the SIGINT and SIGTSTP
    if(sigaction(SIGINT, &sa, NULL) == -1)
        printf("Couldn't catch SIGINT - Interrupt Signal\n");
    if(sigaction(SIGTSTP, &sa, NULL) == -1)
        printf("Couldn't catch SIGTSTP - Suspension Signal\n");
}
4

2 回答 2

4

这相当简单,但不是用信号完成的。相反,您必须使用称为进程组的功能。每个单独的作业(可执行文件或管道等)都将是一个单独的进程组setpgid您可以使用(或在某些系统上使用)创建进程组setpgrp。您可以简单地设置子进程的进程组 afterfork但 before exec,然后将这个作业的进程组 id 存储到作业表中。

现在,前台的进程组被设置为终端(/dev/ttyshell 的)的活动进程组tcsetpgrp- 这是将接收CTRL+的进程组C。属于同一会话但不属于设置为前台的组的那些进程组tcsetpgrp将完全忽略CTRL+ C

于 2018-05-10T20:11:44.763 回答
0

在 Antti 的帮助下,我设法找到了问题所在。我在 fork-exec 代码中添加了一行:

  else if(pid > 0){ //Parent
    current_pid = pid;

    if(setpgid(pid, pid) == 0) perror("setpid");

    // Waits if background flag not activated.
    if(BG == 0){
      // WUNTRACED used to stop waiting when suspended
      waitpid(current_pid, &status, WUNTRACED);

        if(WIFEXITED(status)){
          setExitcode(WEXITSTATUS(status));
        }
        else if(WIFSIGNALED(status)){
          printf("Process received SIGNAL %d\n", WTERMSIG(status));
        }
    }
  }

if(setpgid(pid, pid) == 0) perror("setpid");

据我所知,setpgid设置进程的进程组 ID。这意味着在上面的行中,我将带有 pid 的进程的 pgidpid设置为pid.

我可能是错的,我仍然不完全理解这个过程,但它起作用的原因是SIGINT信号只发送到带有pgid pid. 之前的意思是,由于我没有设置pgid每个进程的 ,它们都具有相同的pgid,因此它们都会收到信号。但是,一旦我pgid为每个进程设置了,如果我CTRL-C在前台进程的中间按下,它只会退出那个正在运行的进程。

至少这是我能收集到的。我仍然不完全理解tcsetpgrp,尤其是我可以设置为第一个参数,即文件描述符。在之后添加此行setpgid

tcsetpgrp(STDIN_FILENO, pid)

exec每当我发出命令时,只需在后台启动整个程序。firefox我没有运行它并显示它,而是运行了firefox整个程序stopped (至少根据终端所说的)。我不知道为什么会这样。

不过,感谢安蒂!

于 2018-05-10T20:56:36.563 回答