好吧,这很长,请做好准备! :)

最近,我尝试在启动过程中启动用bash编写的看门狗脚本。因此,我在 rc.local 中添加了以下内容:

su someuser -c "/home/someuser/watchdog.sh &"

watchdog.sh看起来像这样:
#!/bin/bash
until /home/someuser/eventMonitoring.py
do
    sleep 1
done

一切都很好,一切都很好,脚本也已开始。但是,新流程将出现在流程列表中,并一直保留在该列表中:
UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
root      3048     1  0  1024   620   1 20:04 ?        00:00:00 startpar -f -- rc.local

现在,我的脚本(watchdog.sh)已启动并成功分离,因为它的PPID也为1。我当时的任务是找出该过程是什么。 Startpar是 sysvinit 引导系统(http://savannah.nongnu.org/projects/sysvinit)的一部分。我目前在使用该系统的Debian Wheezy 7.4.0上。现在 man startpar 说:
startpar is used to run multiple run-level scripts in parallel.

通过反复试验的方法,我基本上想出了如何在引导过程中正确启动脚本,而不使 startpar 挂起。进程的所有文件描述符都需要重定向到文件或/dev/null 或一起关闭。当您考虑时应该做的是理性的事情。我终于做到了:
su someuser -c "some_script.sh >/dev/null 2>&1 &"

那解决了问题。但是仍然让我怀疑为什么会这样。为什么 startpar 的行为类似于它。它是错误还是功能。

因此,我深入研究了代码(http://svn.savannah.nongnu.org/viewvc/startpar/trunk/startpar.c?root=sysvinit&view=markup),然后从头开始:

首先,我找到 startpar -f的位置-进行rc.local 调用:
第741行:
execlp(myname, myname, "-f", "--", p->name, NULL);

好的,这样实际上将启动一个新的 startpar 进程,它将替换当前正在运行的实例。基本上,这是对自身的递归调用。让我们看看 -f 参数的作用:

第866行:
case 'f':
      forw = 1;
      break;

好的,让我们看看将 forw 变量设置为 1 是什么...
第900行:
if (forw)
    do_forward();

最后,让我们看看该函数的功能:

第615行:
void do_forward(void)
{
  char buf[4096], *b;
  ssize_t r, rr;
  setsid();
  while ((r = read(0, buf, sizeof(buf))))
    {
      if (r < 0)
    {
      if (errno == EINTR)
        continue;
#if defined(DEBUG) && (DEBUG > 0)
      perror("\n\rstartpar: forward read");
#endif
      break;
    }
      b = buf;
      while (r > 0)
    {
      rr = write(1, b, r);
      if (rr < 0)
        {
          if (errno == EINTR)
        continue;
          perror("\n\rstartpar: forward write");
          rr = r;
        }
      r -= rr;
      b += rr;
    }
    }
  _exit(0);
}

据我了解。这会将来自文件描述符0的所有内容重定向到文件描述符1。现在,让我们看一下与这些文件描述符真正相关的内容:
root@server:~# ls -al /proc/3048/fd
total 0
dr-x------ 2 root root  0 Apr  2 21:13 .
dr-xr-xr-x 8 root root  0 Apr  2 21:13 ..
lrwx------ 1 root root 64 Apr  2 21:13 0 -> /dev/ptmx
lrwx------ 1 root root 64 Apr  2 21:13 1 -> /dev/console
lrwx------ 1 root root 64 Apr  2 21:13 2 -> /dev/console

嗯,很有趣...所以ptmx是根据人说的:
The file /dev/ptmx is a character file with major number 5
and minor number 2, usually of mode 0666 and owner.group of root.root.
It is used to create a pseudoterminal master and slave pair.

和控制台:
The current console is also addressed by
/dev/console or /dev/tty0, the character device with major number 4
and minor number 0.

那时我来到了stackoverflow。现在,有人可以告诉我这是怎么回事吗?我是否正确,startpar处于不断将 ptmx 所涉及的任何内容重定向到控制台的阶段?为什么这样做呢?为什么是 ptmx ?这是一个错误吗?

最佳答案

TL; DR
绝对不是 startpar 的错误,它正在执行promises to in the first place的功能。


代码详细信息
run() startpar.c函数中,

  • 第422行:获取主伪终端的句柄(在这种情况下为/dev/ptmx)p->fd = getpt();
  • 第429行:获取相应从属伪终端的路径else if ((m = ptsname(p->fd)) == 0 || grantpt(p->fd) || unlockpt(p->fd))
  • 第438行: fork 子进程if ((p->pid = fork()) == (pid_t)-1)
  • 第475行:使默认无效stdout TEMP_FAILURE_RETRY(close(1));
  • 第476行:获取从属伪终端的句柄。现在,这是 1 ,即子代的stdout现在重定向到从属伪终端(并由主伪终端节点接收)。if (open(m, O_RDWR) != 1)
  • 第481行:还通过与salve伪终端fd复制来捕获 stderr TEMP_FAILURE_RETRY(dup2(1, 2));
  • 第561行:完成一些簿记工作后,启动感兴趣的可执行文件(作为子进程)execlp(p->name, p->arg0, (char *)0);
  • 然后,父进程可以通过读取缓冲的主伪终端并将其记录到实际的标准输出(在这种情况下为/dev/console)来捕获该新启动进程的所有输出/错误日志。

  • 如何防止系统上悬挂startpar -f ...进程?
    方法1:定义要以交互方式启动的可执行文件。
    显式标记可执行的交互式可执行文件会告诉startpar跳过psedoterminal主/从欺骗来缓冲终端I/O,因为启动的交互式可执行文件的任何输出都需要立即显示在屏幕上而不是进行缓冲。
    这样可以在几个地方修改执行流程。主要在第1171行,其中startpar不会为交互式可执行文件调用run()函数。
    已经测试并描述了here
    方法2:丢弃要启动的可执行文件的stdoutstderr
    使用结构">/dev/null 2>&1 &"丢弃要启动的可执行文件的stdout/stderr。如果它们都显式设置为NULL,即startpar不会像通常那样无限期地缓冲它们。
    方法3:为startpar设置一个明确的超时
    timo中配置 startpar.c

    gtimo中的 startpar.c

    08-05 10:28