3

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

最近我尝试在启动期间启动一个用 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?这是一个错误吗?

4

2 回答 2

3

TL;博士

这绝对不是一个错误startpar,它正在做它首先承诺的事情。

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


代码详情

run()函数中startpar.c

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

    p->fd = getpt();

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

    else if ((m = ptsname(p->fd)) == 0 || grantpt(p->fd) || unlockpt(p->fd))

  3. 第 438 行:fork 一个子进程

    if ((p->pid = fork()) == (pid_t)-1)

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

    TEMP_FAILURE_RETRY(close(1));

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

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

  6. 第 481 行:也stderr通过使用 salve 伪终端 fd 复制它来捕获。

    TEMP_FAILURE_RETRY(dup2(1, 2));

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

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

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


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

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

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

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

此处已对此进行了测试和描述。

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

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

方法 3:为startpar

要么配置timostartpar.c

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

gtimostartpar.c

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

于 2014-04-05T18:59:39.097 回答
1

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

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

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

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

编辑:这是 2009 年的另一个错误报告,涉及rsyslog启动startpar -f并导致一个额外的进程与(惊喜!)打开的文件描述符一起闲逛。解决方案是相同的(全部关闭)。

于 2014-04-05T16:29:53.490 回答