【发布时间】:2021-06-12 02:50:29
【问题描述】:
我正在尝试使用this template 在 C 中编写一个迷你 shell。但是每当我尝试使用诸如less 和vi 之类的交互式命令时,shell 就会卡在waitpid 上(启用WUNTRACED 这些命令会立即返回,因为它们被ps 指示的作业控制信号停止) .其他不需要输入的命令,例如ls 也可以。根本原因是setpgid,这似乎将分叉的子进程(例如less 和vi)放到了不再共享终端的不同进程组中。因此,子进程被作业控制信号停止。删除setpgid 将使mini-shell 再次工作,但它不能被删除,因为mini-shell 需要将其前台进程作为一个组来控制(例如,如果前台进程P 派生额外的进程P1 和P2,则shell , 在收到用户的SIGTSTP 后, 应该停止 P, P1, P2. 如果 P, P1, P2 在同一个进程组, pgid 与 P 的 pid 相同, 这可以很方便地完成. 我们可以发送SIGTSTP 到整个进程组)。
我尝试使用tcsetpgrp 来修复我的shell。虽然它会使vi 等命令再次起作用,但迷你shell 会在分叉子进程完成后自动退出,可能是因为父shell 错误地将分叉子进程的完成视为迷你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);
}
}
【问题讨论】:
-
其他 shell 如何解决这些问题?其中有几个是开源的,您可以研究它们。
-
删除 setpgid 将使 mini-shell 再次工作,但无法删除,因为 shell 需要分组控制分叉进程。 确切地说如何 你想控制 进程分组吗?也就是说,它对你有什么作用?进程[复数]和组[复数]是什么意思?而且,究竟什么是“冻结”?当“shell”是你的 shell 时会发生什么(即你分叉然后子进程调用它自己的
main)? -
@CraigEstey 我编辑了这个问题,因为我的措辞不清楚。之所以需要 setpgid,是因为“如果前台进程 P 派生出额外的进程 P1 和 P2,shell 在收到用户的 SIGTSTP 后,应该停止 P、P1 和 P2。如果 P、P1、P2 在同一进程组,其 pgid 与 P 的 pid 相同。我们可以向整个进程组发送 SIGTSTP"。外壳“冻结”,因为它卡在 waitpid 上。启用 WUNTRACED 后,waitpid 立即退出,因为分叉的进程“被作业控制信号停止”。在这两种情况下,vi 都不会出现
-
我会看看 [如果你还没有这样做的话]:
man 2 setsid和man credentials以获取有关建立新的“控制 TTY”的更多信息。另外,我会将源代码拉到bash并查看它的作用。当然,如果没有那个 TTY,vi就会出现问题。同样,设置您的 shell,使其可以fork并自行运行(通过main或./myshell)。我会从另一个窗口运行gdb,然后将attach运行到子shell。或者,将更多的调试printf发送到(例如)/tmp/mylog/log.<pid>您的子shell 应该与另一个程序有相同的问题,但更容易调试。 -
在
bash,查看jobs.c。这有点复杂,但是在执行setpgid之后,它调用give_terminal_to,它调用sigprocmask和tcsetpgrp。其他 shell 可能有更简单的实现。