何时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() 不返回。