进一步阅读

进一步阅读

后台进程本身可以吗?

这个的 Perl 和/或 C 实现是什么?

答案1

我只想在这里回答字面上的问题:可以吗?过程背景本身与 fork 本身相反,在子进程中继续执行并退出,以便等待它的进程可以恢复此处其他进程已经涵盖的执行。

这里首先对术语进行注释。

后台通常是指交互式 shell 中的作业控制。

就像您运行带有附加命令的命令一样&。或者按Ctrl+然后Z运行bg

工作没有进程进程组。当你跑步时:

$ ps -ej | grep -w "$$" & echo "$!"
35131
  19152   19152   19152 pts/0    00:00:00 zsh
  35130   35130   19152 pts/0    00:00:00 ps
  35131   35130   19152 pts/0    00:00:00 grep

这是ps | grep 工作(这里通过 35130 进程组实现,其领导者是正在运行的进程ps,但也包含正在运行的进程grep),该进程被置于后台。

背景这里的意思是:

  1. shell 不会等待该作业的终止。您将返回到提示符,并且能够在该作业仍在运行时输入更多命令。
  2. 终端设备驱动程序被告知该进程组不在前台。因此,该进程组中的进程无权从终端读取数据(如果组中的任何进程尝试从终端读取数据,则该组中的所有进程都会被中断),并且不会受到^C//^Z的影响^\, ETC。
  3. shell 将作业输入到其内部作业表中,并将其记录为当前处于后台。

现在背景有时在终端作业控制之外使用措辞。

当你这样做时:

cmd1 | cmd2 & pid=$!
somecommand
wait

在脚本中,没有作业控制。如果该脚本是通过交互式 shell 在终端中启动的,则它本身将被置于前台或后台,具体取决于脚本的启动方式,就像任何其他命令一样。

cmd1 | cmd2脚本中的内容不会被解释脚本的 shell 置于后台,因为它不是交互式 shell。

如果您按 ^C,cmd1cmd2与运行脚本的 shell 一样会被杀死,并在您按 ^Z 时暂停。与其说cmd1 | cmd2是在后台启动,不如说是它们是异步运行的

与在后台开始的工作相比交互的外壳,仅1已经完成了。没有创建进程组来运行该管道。

现在,澄清了这一点,进程可以将自己置于后台吗?

就像它一样工作并不是流程放在背景中,该过程的问题将是:

  • 我是否处于连接到终端的会话中? (工作控制是否有意义?)
  • 我的流程组中只有我一个人还是还有其他流程?
  • 我的进程组已经在前台还是后台?
  • 我的进程组最终是否由交互式 shell 等待?

如果我们对所有这些问题都能回答“是”,也就是说,如果我们从终端中的交互式 shell 作为一个简单命令(而不是作为管道或复合命令的一部分)被调用,那么我们需要 (1) 告诉等待的进程shell 停止等待我们,(2) 告诉终端其进程组不再是前台进程组,(3) 告诉 shell 更新其作业表以记录我们现在处于后台的事实。

除了终止或挂起之外,您无法真正告诉另一个进程停止等待您,在这种情况下,该进程将收到信号SIGCHLDwait*()调用它当前正在做的事情将会返回。

但是,您可以通过向自己发送 SIGTSTP 信号(与按 时发送的信号相同^Z)或 SIGSTOP(无法拦截)来暂停自己,在这种情况下,所有 (1)、(2) 和 (3) 都会自动发生,除了工作状态将是暂停代替在后台运行

现在,由于您已被暂停,因此您不再运行,也无法恢复运行。

然而,您可以分叉一个子进程,该子进程将在暂停自己之前的一段时间内自行恢复(通过向您的 pid 发送 SIGCONT)。

当您恢复执行时,您的 shell 将再次收到 SIGCHLD,并且 (3) shell 意识到您现在处于跑步当它开始处理该信号时,就会在后台发生。

举个例子,实现它sh

$ sh -c 'echo running in foreground; sleep 1
         (sleep 1; echo resuming my parent; kill -s CONT "$$") &
         echo stopping; kill -s STOP "$$"
         echo resumed
         sleep 30
         echo finished'; echo "$?"
running in foreground
stopping
147
zsh: suspended (signal)  sh -c
$ resuming my parent
resumed
$ jobs
[1]  + running    sh -c
$ finished
[1]  + done       sh -c

也可以使用kill(0, SIGSTOP)( kill -s STOP 0in sh) 暂停整个作业,但是进程这样做是否正确,会影响尚未启动且不知道的进程的运行流程?

sh -c 'echo running in foreground
       perl -MPOSIX -le "setpgid 0,0; # leave the process group before it is suspended
                         sleep 2;
                         print q(resuming the process group of my parent);
                         kill q(CONT), - shift@ARGV
                        " "$(ps -o pgid= -p "$$")" &
       sleep 1
       echo stopping my process group; kill -s STOP 0
       echo process group resumed
       sleep 30
       echo finished' | cat

答案2

只是有时。

  • 一个过程就是在后台运行具有与当前进程组 ID 不同的进程组 ID前台进程组ID在其控制终端中。
  • 这是不要混淆一个过程是作为守护进程运行,它正在耗尽所有登录会话,其中前台和后台的概念根本不适用因为一开始就没有控制终端。

安赫尔的回答中的建议已经过时而且很糟糕。正如所谓的“守护进程”,它实际上并不能在登录会话中发挥作用。系统为了建立登录会话而经过太多的单向活板门; Ángel 的答案不仅忽略了 OpenBSD setlogin()、AIX 的受保护环境(请参阅 参考资料)以及 Linux 的安全上下文和控制组等内容,而且几乎所有 C 库中的setsenv旧库函数也是如此。daemon()

自 20 世纪 80 年代以来,这种做法就不再奏效了。因为 20 世纪 90 年代出现了许多此类单向活板门。 “恶魔化”是一个谬论,不幸的是,这么多年过去了,它仍然被公认的智慧和民间传说所推动。

这甚至不是守护进程应该做。 20 世纪 80 年代末(例如 AT&T Unix 服务访问设施)和 90 年代(例如 IBM 的系统资源控制器)开始的服务管理子系统调用守护进程已经在守护进程上下文中。它们首先不是在登录会话上下文中启动的。

此外,执行 fork-and-exit-parent 与服务管理子系统 30 多年来一直用于守护进程的控制语义相冲突;关闭文件描述符与服务管理子系统为守护进程设置的日志记录机制相冲突。

  • 关闭文件描述符与 TTY 信号无关(这是我之前提到的民间传说的一个示例),并且破坏了从 daemontools 到 systemd 到 runit 的服务管理子系统如何设置标准输出和标准错误以发送到日志。服务管理子系统不会首先打开任意文件描述符来启动守护进程;它们以可重复且一致且通常最少(除非另有明确配置)的打开文件描述符集来启动它们。
  • 分叉和退出父进程是 20 世纪 80 年代的事情,当时系统管理员通过将守护进程添加到/etc/rc或shell 脚本中,甚至从超级用户登录会话中交互地启动守护进程,并且违背了服务管理子系统使用从ing/etc/rc.local获得的进程 ID 的事实fork()服务流程以跟踪和控制服务。 (正如您从进一步阅读中看到的那样,自从 IBM 的系统资源控制器存在以来,IBM 就一直建议不要这样做。)服务管理子系统在会话中调用守护进程,已经有无控制终端。

此外,“守护进程”并不是人们为了获得在登录会话的后台。 “作业控制”shell 通过调用tcsetpgrp()函数来改变控制终端的当前状态,从而将事物置于登录会话的前台和后台前台进程组ID价值。这实际上fork()chdir()关闭文件描述符或(内核)会话无关。

shell的子进程调用,但请注意 (a)如果是,tcsetpgrp()则会发送一个信号SIGTTOU已经(b) 找到另一个要切换到的进程组 ID 并不简单,因为 shell 可能并非在所有情况下都是直接父进程。

  • 如果您想在后台启动进程,请使用 shell 的&机制来执行此操作。
  • 如果您想以交互方式将前台进程组切换到后台,请从终端发送“stop”特殊字符。
  • 如果您想将某些东西作为守护进程运行,与前台和后台无关,那么无论您的系统的服务管理子系统是什么,都可以创建一个服务定义。

进一步阅读

答案3

是的。背景工作的基本方式是其fork()自身并完成父级,同时继续在子级中工作。这通常还包括将工作目录更改为/(以便分离的进程不会阻止卸载文件系统)、关闭文件描述符(以避免 TTY 信号)以及创建新的进程会话(请参阅设置 ID(2)

有类似的库函数守护进程这可以简化所有这些操作:

#define _DEFAULT_SOURCE // glibc >= 2.19
#define _BSD_SOURCE // glibc <= 2.19
#include <unistd.h>

int main() {
    daemon(0, 0);
    ....
}

相关内容