在 mini-shell 中使用 setpgid 会中断交互式命令
Using setpgid in a mini-shell breaks interactive commands
我正在尝试使用 this template 在 C 中编写 mini-shell。但是每当我尝试使用 less
和 vi
等交互式命令时,shell 就会卡在 waitpid 上(WUNTRACED
立即启用这些命令 return 因为它们由 ps
) 指示的作业控制信号停止。其他不需要输入的命令,如 ls
都可以。根本原因是 setpgid
,它似乎将分叉的 child 进程(例如 less
和 vi
)放入不再共享终端的不同进程组中。 child 进程因此被作业控制信号停止。删除 setpgid
将使 mini-shell 再次工作,但不能将其删除,因为 mini-shell 需要将其前台进程作为一个组来控制(例如,如果前台进程 P 分叉其他进程P1 和 P2,即 shell,在收到用户的 SIGTSTP
后,应该停止 P、P1 和 P2。如果 P、P1、P2 在同一个进程组中,这可以很方便地完成pgid和P的pid是一样的,我们直接给整个进程组发送SIGTSTP即可)。
我曾尝试使用 tcsetpgrp
修复我的 shell。虽然它会让 vi
等命令再次起作用,但是 mini-shell 会在分叉的 child 完成后自动退出,大概是因为 parent shell 错误地将分叉 child 的完成视为 mini-shell.
的完成
是否有修复程序可以让我保留 setpgid?
// full code is in the provided link
if (!builtin_command(argv)) {
if ((pid = Fork()) == 0) { /* Child runs user job */
if (execve(argv[0], argv, environ) < 0) {
execvp(argv[0], argv);
printf("%s: Command not found.\n", argv[0]);
exit(0);
}
}
// call wrapper function for error handling
// set process group id of child to the pid of child
Setpgid(pid, 0);
if (!bg) {
// foreground process, should wait for completion
// tcsetpgrp does make vi and less work,
// but their completion also terminates the mini-shell
// tcsetpgrp(STDERR_FILENO, pid);
int status;
if (waitpid(pid, &status, 0) < 0) {
unix_error("waitfg: waitpid error");
}
} else {
// background process
printf("%d %s", pid, cmdline);
}
}
解决方案是使用 tcsetpgrp
将 tty 的控制权交给其他进程组,当子进程完成后,再次使用 tcsetpgrp
收回对 tty 的控制权。请注意,tcsetpgrp
将 SIGTTOU
发送给其调用者 if the calling process belongs to a background process group,因此必须阻止 SIGTTOU
和 SIGTTIN
。
// error handling is omitted for brevity
// these two signal functions can be anywhere before tcsetpgrp
// alternatively, they can be masked during tcsetpgrp
signal(SIGTTOU, SIG_IGN);
signal(SIGTTIN, SIG_IGN);
if (!builtin_command(argv)) {
if ((pid = Fork()) == 0) {
if (execve(argv[0], argv, environ) < 0) {
execvp(argv[0], argv);
printf("%s: Command not found.\n", argv[0]);
exit(0);
}
}
if (!bg) {
setpgid(pid, 0);
tcsetpgrp(STDERR_FILENO, pid);
int status;
if (waitpid(pid, &status, 0) < 0) {
unix_error("waitfg: waitpid error");
}
tcsetpgrp(STDERR_FILENO, getpgrp());
} else {
printf("%d %s", pid, cmdline);
}
}
这是一个相当粗糙的实现。请参阅 Craig 对这个问题的评论,了解在哪里可以找到 bash
的实现。
我正在尝试使用 this template 在 C 中编写 mini-shell。但是每当我尝试使用 less
和 vi
等交互式命令时,shell 就会卡在 waitpid 上(WUNTRACED
立即启用这些命令 return 因为它们由 ps
) 指示的作业控制信号停止。其他不需要输入的命令,如 ls
都可以。根本原因是 setpgid
,它似乎将分叉的 child 进程(例如 less
和 vi
)放入不再共享终端的不同进程组中。 child 进程因此被作业控制信号停止。删除 setpgid
将使 mini-shell 再次工作,但不能将其删除,因为 mini-shell 需要将其前台进程作为一个组来控制(例如,如果前台进程 P 分叉其他进程P1 和 P2,即 shell,在收到用户的 SIGTSTP
后,应该停止 P、P1 和 P2。如果 P、P1、P2 在同一个进程组中,这可以很方便地完成pgid和P的pid是一样的,我们直接给整个进程组发送SIGTSTP即可)。
我曾尝试使用 tcsetpgrp
修复我的 shell。虽然它会让 vi
等命令再次起作用,但是 mini-shell 会在分叉的 child 完成后自动退出,大概是因为 parent shell 错误地将分叉 child 的完成视为 mini-shell.
是否有修复程序可以让我保留 setpgid?
// full code is in the provided link
if (!builtin_command(argv)) {
if ((pid = Fork()) == 0) { /* Child runs user job */
if (execve(argv[0], argv, environ) < 0) {
execvp(argv[0], argv);
printf("%s: Command not found.\n", argv[0]);
exit(0);
}
}
// call wrapper function for error handling
// set process group id of child to the pid of child
Setpgid(pid, 0);
if (!bg) {
// foreground process, should wait for completion
// tcsetpgrp does make vi and less work,
// but their completion also terminates the mini-shell
// tcsetpgrp(STDERR_FILENO, pid);
int status;
if (waitpid(pid, &status, 0) < 0) {
unix_error("waitfg: waitpid error");
}
} else {
// background process
printf("%d %s", pid, cmdline);
}
}
解决方案是使用 tcsetpgrp
将 tty 的控制权交给其他进程组,当子进程完成后,再次使用 tcsetpgrp
收回对 tty 的控制权。请注意,tcsetpgrp
将 SIGTTOU
发送给其调用者 if the calling process belongs to a background process group,因此必须阻止 SIGTTOU
和 SIGTTIN
。
// error handling is omitted for brevity
// these two signal functions can be anywhere before tcsetpgrp
// alternatively, they can be masked during tcsetpgrp
signal(SIGTTOU, SIG_IGN);
signal(SIGTTIN, SIG_IGN);
if (!builtin_command(argv)) {
if ((pid = Fork()) == 0) {
if (execve(argv[0], argv, environ) < 0) {
execvp(argv[0], argv);
printf("%s: Command not found.\n", argv[0]);
exit(0);
}
}
if (!bg) {
setpgid(pid, 0);
tcsetpgrp(STDERR_FILENO, pid);
int status;
if (waitpid(pid, &status, 0) < 0) {
unix_error("waitfg: waitpid error");
}
tcsetpgrp(STDERR_FILENO, getpgrp());
} else {
printf("%d %s", pid, cmdline);
}
}
这是一个相当粗糙的实现。请参阅 Craig 对这个问题的评论,了解在哪里可以找到 bash
的实现。