微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

在 mini-shell 中使用 setpgid 会破坏交互式命令

如何解决在 mini-shell 中使用 setpgid 会破坏交互式命令

我正在尝试使用 this template 在 C 中编写一个迷你外壳。但是每当我尝试使用诸如 lessvi 之类的交互式命令时,shell 都会卡在 waitpid 上(启用 WUNTRACED 这些命令会立即返回,因为它们被作业控制信号停止由 ps) 表示。其他不需要输入的命令(例如 ls)都可以。根本原因是 setpgid,它似乎将分叉的子进程(例如 lessvi)放入不再共享终端的不同进程组中。因此子进程被作业控制信号停止。删除 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 会在 fork 子进程完成后自动退出,大概是因为父 shell 错误地将 fork 子进程的完成也视为 mini 进程的完成-壳。

是否有修复程序仍然允许我保留 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 向其调用方 if the calling process belongs to a background process group 发送 SIGTTOU,因此必须阻止 SIGTTOUSIGTTIN

// 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 的实现。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。