当 Bash 作为 PID 1 运行时,为什么前台作业会忽略作业控制信号?

当 Bash 作为 PID 1 运行时,为什么前台作业会忽略作业控制信号?

何时bash被调用为进程号1直接通过内核选项init=/bin/bash --login,它会在提示之前发出类似这样的信息:

bash: cannot set terminal process group (-1): Inappropriate ioctl for device
bash: no job control in this shell

键盘生成的信号(例如 ^Z、^C、^)不起作用

为了解决这个问题,我编写了一个简单的程序init1.c(如简化的sulogin),如下所示:

/* init1.c */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>

int main(int argc, char **argv)
{
  char *options[] = {"--login", NULL};
  int tty_fd = -1;

  printf("\n----- Bash Init 1 -----\n\n");

  /* Make bash as session leader. */
  if (setsid() == -1)
    {
      fprintf(stderr, "%s : %d : %s\n", "setsid()", __LINE__, strerror(errno));
      exit(EXIT_FAILURE);
    }

  /* Make /dev/tty1 as controlling terminal of Bash. */
  tty_fd = open("/dev/tty1", O_RDWR);
  if (tty_fd == -1)
    {
      fprintf(stderr, "%s : %d : %s\n", "open()", __LINE__, strerror(errno));
      exit(EXIT_FAILURE);
    }

  /* Re-connect stdin, stdout, stderr to the new tty. */
  dup2(tty_fd, STDIN_FILENO);
  dup2(tty_fd, STDOUT_FILENO);
  dup2(tty_fd, STDERR_FILENO);
  close(tty_fd);
  
  execv("/bin/bash", options);
}

将其编译为init1,然后将其调用为进程号1(即 Bash 运行为进程号1),前面的错误消息消失,并且某些信号(例如 Ctrl-C、Ctrl-\)起作用,但作业控制信号(例如 Ctrl-Z)仍然不起作用(意外)。

因此,为了使作业控制信号起作用,我将上面的代码修改为init2.c(只是fork()):

/* init2.c */
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>

int main(int argc, char **argv)
{
  char *options[] = {"--login", NULL};
  pid_t pid = -1;
  int tty_fd = -1;
  
  printf("\n----- Bash Init 2 -----\n\n");
  
  pid = fork();
  if (pid < 0)
    {
      fprintf(stderr, "%s : %d : %s\n", "fork()", __LINE__, strerror(errno));
      exit(EXIT_FAILURE);
    }
    
  /* Parent. */
  if (pid > 0)
   {
     /* Wait for its child, otherwise all processes would be killed ! */
     while (wait(NULL) > 0)
       ;
     exit(EXIT_SUCCESS);
   }
      
  /* Child. */
  if (setsid() == -1)
    {
      fprintf(stderr, "%s : %d : %s\n", "setsid()", __LINE__, strerror(errno));
      exit(EXIT_FAILURE);
    }        
  
  /* Make /dev/tty1 as controlling terminal of Bash. */
  tty_fd = open("/dev/tty1", O_RDWR);
  if (tty_fd == -1)
    {
      fprintf(stderr, "%s : %d : %s\n", "open()", __LINE__, strerror(errno));
      exit(EXIT_FAILURE);
    }

  /* Re-connect stdin, stdout, stderr to the new tty. */
  dup2(tty_fd, STDIN_FILENO);
  dup2(tty_fd, STDOUT_FILENO);
  dup2(tty_fd, STDERR_FILENO);
  close(tty_fd);
  
  execv("/bin/bash", options);
}

将其编译为init2并调用为进程号1(即最终 Bash 以除 1 之外的任意 PID 运行),这一次,作业控制信号起作用了!

但我不明白为什么作业控制信号在init2(Bash 不是进程号1)但不是init1(Bash 是进程号1),为什么当 Bash 作为 PID 1 运行时,前台作业会忽略作业控制信号?似乎有什么特别之处进程号1

更新:

我找到了一个非常简单的shell米什在github中也实现了作业控制,只有949行!当我用init1和init2运行它时,这个shell也有同样的问题! (多亏了它,我不必阅读复杂的 bash 源代码来解决我的问题。Orz)问题在于 waitpid() ,当 SIGTSTP(^Z) 到达时,它不会立即返回。所以这个问题不仅与bash有关,还与实现作业控制的shell有关。但是,我不明白为什么当 shell 作为 PID 1 运行时如果 SIGTSTP 到达, waitpid() 不返回。

相关内容