【问题标题】:Figuring out what startpar.c (sysvinit) is doing弄清楚 startpar.c (sysvinit) 在做什么
【发布时间】:2014-05-15 10:37:24
【问题描述】:

好的,这是一个很长的,振作起来! :)

最近我尝试在启动过程中启动一个用 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 的任何内容重定向到 console 的阶段?为什么这样做?为什么选择 ptmx?这是一个错误吗?

【问题讨论】:

    标签: c linux debian sysv


    【解决方案1】:

    TL;DR

    这绝对不是 startpar 的错误,它的作用与 promises to in the first place 完全相同。

    每个脚本的输出在脚本退出时被缓冲和写入,因此不同脚本的输出行不会混在一起。您可以通过设置超时来修改此行为。


    代码详情

    startpar.c 中的run() 函数内,

    1. 第 422 行:获取主伪终端的句柄(本例中为 /dev/ptmx

      p-&gt;fd = getpt();

    2. 第429行:获取对应从伪终端的路径

      else if ((m = ptsname(p-&gt;fd)) == 0 || grantpt(p-&gt;fd) || unlockpt(p-&gt;fd))

    3. 第 438 行:分叉子进程

      if ((p-&gt;pid = fork()) == (pid_t)-1)

    4. 第 475 行:使默认值无效stdout

      TEMP_FAILURE_RETRY(close(1));

    5. 第 476 行:获取从伪终端的句柄。现在,这是 1,即 child 的 stdout 现在重定向到从伪终端(并被主伪终端节点接收)。

      if (open(m, O_RDWR) != 1)

    6. 第 481 行:同时捕获 stderr,方法是使用 salve 伪终端 fd 复制它。

      TEMP_FAILURE_RETRY(dup2(1, 2));

    7. 第 561 行:在做一些簿记工作后,启动感兴趣的可执行文件(作为子进程)

      execlp(p-&gt;name, p-&gt;arg0, (char *)0);

    8. 然后,父进程可以稍后通过读取缓冲的主伪终端来捕获这个新启动进程的所有输出/错误日志,并将其记录到实际的标准输出(即在这种情况下为 /dev/console)。


    如何防止系统上出现悬空的startpar -f ... 进程?

    方法一:将要启动的可执行文件定义为交互式。

    显式标记可执行交互告诉startpar 跳过psedoterminal 主/从技巧以缓冲终端I/O,因为启动的交互式可执行文件的任何输出都需要立即显示在屏幕上而不是缓冲。

    这会修改几个地方的执行流程。主要在第 1171 行,其中startpar 不会为交互式可执行文件调用run() 函数。

    这已经过测试和描述here

    方法二:丢弃要启动的可执行文件的stdoutstderr

    使用构造 "&gt;/dev/null 2&gt;&amp;1 &amp;" 丢弃要启动的可执行文件的 stdout/stderr。如果它们都显式设置为 NULL,即 startpar 不会像通常那样无限期地缓冲它们。

    方法3:为startpar设置显式超时

    startpar.c中配置timo

    使用 -t 选项设置的超时用作缓冲区超时。如果脚本的输出缓冲区不为空,并且最后一次输出在几秒前超时,startpar 将刷新缓冲区。

    gtimostartpar.c

    -T 选项超时更适用于全局。如果超过 global_timeout 秒没有输出输出,startpar 将用最旧的输出刷新脚本的缓冲区。之后它只会打印这个脚本的输出,直到它完成。

    【讨论】:

    • 你自己说得最好:“很遗憾,这一切都没有很好的记录,花了一个多小时来挖掘源代码并正确理解。”
    • 我也同意!感谢 TheCodeArtist 的分析,干得好!
    【解决方案2】:

    你快到了,我可以看到你的调查是如何脱轨的。

    文件描述符 0 指的是 stdin(标准输入)。这只是它在 Linux 内核某处的表格中的数字。您的聪明调查追踪到/dev/ptmx 的原因是因为这是Linux 内核用来处理键盘输入的pseudo-terminal

    同样,文件描述符 1 指的是 stdout。出于类似的原因,您现在可能会明白为什么它会与您的控制台挂钩。

    所以,是的:这会从文件描述符 0 重定向到文件描述符 1,也就是说,从 stdin 重定向到 stdout。虽然起初这对我来说似乎很无害,但显然之前有 have been problems with it,其他人 (e.g.) 就像你一样解决了它(通过重定向 stdinstdoutstderr 到 @987654334 @)。 AFAICT,该错误报告已关闭/解决,但由于您似乎使用的是最新版本的 Debian,我不确定您为什么没有修复。因为我个人使用的是 Arch Linux,所以我在这里有点不了解,它甚至不再使用 sysvinit!

    编辑:这是 2009 年的另一个 bug report,涉及由 startpar -f 启动的 rsyslog,并导致一个额外的进程挂在(惊喜!)打开的文件描述符。解决方案是相同的(全部关闭)。

    【讨论】:

    • 链接的错误报告没有提及“关闭”标准 fd 以解决问题! (或者我在这里遗漏了什么?)
    • 不,你没有错过任何东西。我链接了错误报告,因为它似乎有软件修复。 “大多数人”只是[citation needed],因为在我发现实际错误之前,我在几个论坛上搜索时看到了相同的方法作为成功的解决方法。再说一次,我从远处看这个,因为 Arch Linux hasn't supported initscripts 自 2012 年以来。
    • 我并不是要反对这个措辞。链接的错误报告似乎没有提及有关重定向 stdin/stdout/stderr 修复问题的任何内容。因此,我仍然不相信这个答案中提出的论点。没有冒犯的意思。就是想。我也在尝试理解startpar的递归代码流。
    • 未拍摄。我自己并不完全理解它,但鉴于几个月前其他人似乎有同样的问题(和解决方法),并且有一个封闭的错误报告,我想这可能真的是一回事。可能这只是部分答案,并不能完全解决 OP 的谜团。编辑了我的答案,在 Debian 论坛上添加了一个用于解决类似问题的解决方法的示例。
    • 绝对不是 startpar 的错误。详解here。可惜这一切都没有很好的记录,花了一个多小时才挖掘源代码并正确理解。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多