弄清楚startpar.c(sysvinit)正在做什么

时间:2014-04-03 14:12:10

标签: c linux debian sysv

好的,这是一个很长的,支撑自己! :)

最近我尝试在启动时启动用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;

好的,让我们看看 1 变量 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 ?这是一个错误吗?

2 个答案:

答案 0 :(得分:3)

TL; DR

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

  

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


代码详情

startpar.c中的run()函数

  1. 第422行:获取主假终端的句柄(在这种情况下为/dev/ptmx

    p->fd = getpt();

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

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

  3. 第438行:分叉子进程

    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行:通过使用salve pseudoterminal fd复制它来捕获 stderr

    TEMP_FAILURE_RETRY(dup2(1, 2));

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

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

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


  9. 如何防止系统中的悬空startpar -f ...进程?

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

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

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

    这已经过测试和描述here

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

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

    方法3:为startpar

    设置显式超时

    timo

    中配置 startpar.c
      

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

    {p>或gtimo

    中的 startpar.c
      

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

答案 1 :(得分:1)

你几乎就在那里,我可以看出你的调查是如何脱轨的。

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

同样,文件描述符1引用 stdout 。出于类似的原因,您现在可以看到为什么它会挂钩到您的控制台中。

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

修改:这是2009年的另一个bug report,其中rsyslogstartpar -f启动并导致额外的进程与(惊奇!)打开文件描述。解决方案是相同的(关闭所有)。