【问题标题】:monitor bytes in stdin, stdout, and stderr in C在 C 中监视 stdin、stdout 和 stderr 中的字节
【发布时间】:2011-05-26 21:24:20
【问题描述】:

我需要计算通过标准输入向子进程发送了多少字节,以及子进程向标准输出和标准错误写入了多少字节。子进程调用 execvp,因此我无法从进程本身监控这些统计信息。我目前的策略涉及创建 3 个额外的子进程,每个子进程通过管道监视每个 std 流(或者在 stdin 的情况下,仅从 stdin 读取)。

这种策略充其量似乎真的很脆弱,而且我正在做一些奇怪的事情,这使得监控 stdout/err 的进程无法从它们各自的管道末端读取(并使它们无限期挂起)。代码如下。

这会创建三个辅助子进程,并且应该允许它们统计统计信息:

void controles(struct fds *des)
{
    int ex[2];
    int err[2];

    int n_in = 0;
    int c_in;

    int n_ex = 0;
    int c_ex;

    int n_err = 0;
    int c_err;

    pipe(ex);
    pipe(err);

    /*has two fields, for the write end of the stdout pipe and the stderr pipe. */
    des->err = err[1];
    des->ex = ex[1];
    switch (fork()) {
    case 0:     /*stdin */
        while (read(0, &c_in, 1) == 1)
            n_in++;
        if (n_in > 0)
            printf("%d bytes to stdin\n", n_in);
        exit(n_in);
    default:
        break;
    }

    switch (fork()) {
    case 0:     /*stdout */
        close(ex[1]);
        /*pretty sure this is wrong */
        while (read(ex[0], &c_ex, 1) == 1) {
            n_ex++;
            write(1, &c_ex, 1);
        }
        if (n_ex > 0)
            printf("%d bytes to stdout\n", n_ex);
        close(ex[0]);
        exit(n_ex);
    default:
        close(ex[0]);
    }
    switch (fork()) {
    case 0:     /*error */
        close(err[1]);
        /*also probably have a problem here */
        while (read(err[0], &c_err, 1) == 1) {
            n_err++;
            write(2, &c_err, 1);
        }
        if (n_err > 0)
            printf("%d bytes to stderr\n", n_err);
        close(err[0]);
        exit(n_err);
    default:
        close(err[0]);
    }
}

这是一个代码片段(在子进程中),它从 fds 结构中设置两个 fd,以便子进程应该写入管道而不是 stdin/stderr。

    dup2(des.ex, 1);
dup2(des.err, 2);
close(des.ex); close(des.err); /*Is this right?*/
execvp(opts->exec, opts->options); /*sure this is working fine*/

我迷路了,任何帮助将不胜感激。

【问题讨论】:

  • 1.您可以从一个进程为所有三个管道提供服务(它甚至可以是一个 shell 脚本,这取决于您希望通过的结果如何) 2. 如果进程挂起,这可能是在工作中缓冲;确保管道没有缓冲(至少对于输入管道)

标签: c linux system


【解决方案1】:

我认为你的代码可以通过稍微分解一下来改进;会计和复制程序基本上都是同一个任务,如果您选择继续使用多个进程,可以简单地编写:

void handle_fd_pair(char *name, int in, int out) {
    char buf[1024];
    int count = 0, n;
    char fn[PATH_MAX];
    snprintf(fn, PATH_MAX - 1, "/tmp/%s_count", name);
    fn[PATH_MAX-1] = '\0';
    FILE *output = fopen(fn, "w");
    /* handle error */

    while((n = read(in, buf, 1024)) > 0) {
        count+=n;
        writen(out, buf, n); /* see below */
    }

    fprintf(output, "%s copied %d bytes\n", name, count);
    fclose(output);
}

我们可以使用Advanced Programming in the Unix Environment 源代码中的writen() 函数处理部分写入,而不是一次一个字符,这对于中等数量的数据效率低下:

ssize_t             /* Write "n" bytes to a descriptor  */
writen(int fd, const void *ptr, size_t n)
{
  size_t      nleft;
  ssize_t     nwritten;

  nleft = n;
  while (nleft > 0) {
      if ((nwritten = write(fd, ptr, nleft)) < 0) {
          if (nleft == n)
              return(-1); /* error, return -1 */
          else
              break;      /* error, return amount written so far */
      } else if (nwritten == 0) {
          break;
      }
      nleft -= nwritten;
      ptr   += nwritten;
  }
  return(n - nleft);      /* return >= 0 */
}

有了助手,我认为其余的工作会更容易。叉一个 每个流的新孩子,并给出in[0] read-end,out[1]err[1]子管道的写端。

每个孩子的所有close() 电话都很丑陋,但试图 围绕所有 fd 的数组编写一个小包装器,并免除 作为参数传入的那些,似乎也很麻烦。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

#ifndef PATH_MAX
#define PATH_MAX 128
#endif

void handle_fd_pair(char *name, int in, int out) {
    char buf[1024];
    int count = 0, n;
    char fn[PATH_MAX];
    snprintf(fn, PATH_MAX - 1, "/tmp/%s_count", name);
    fn[PATH_MAX-1] = '\0';
    FILE *output = fopen(fn, "w");
    /* handle error */

    while((n = read(in, buf, 1024)) > 0) {
        count+=n;
        writen(out, buf, n); /* see below */
    }

    fprintf(output, "%s copied %d bytes\n", name, count);
    fclose(output);
}

int main(int argc, char* argv[]) {
    int in[2], out[2], err[2];
    pid_t c1, c2, c3;

    pipe(in);
    pipe(out);
    pipe(err);

    if ((c1 = fork()) < 0) {
        perror("can't fork first child");
        exit(1);
    } else if (c1 == 0) {
        close(in[0]);
        close(out[0]);
        close(out[1]);
        close(err[0]);
        close(err[1]);
        handle_fd_pair("stdin", 0, in[1]);
        exit(0);
    }

    if ((c2 = fork()) < 0) {
        perror("can't fork second child");
        exit(1);
    } else if (c2 == 0) {
        close(in[0]);
        close(in[1]);
        close(out[1]);
        close(err[0]);
        close(err[1]);
        handle_fd_pair("stdout", out[0], 1);
        exit(0);
    }

    if ((c3 = fork()) < 0) {
        perror("can't fork third child");
        exit(1);
    } else if (c3 == 0) {
        close(in[0]);
        close(in[1]);
        close(out[0]);
        close(out[1]);
        close(err[1]);
        handle_fd_pair("stderr", err[0], 2);
        exit(0);
    }

    /* parent falls through to here, no children */

   close(in[1]);
   close(out[0]);
   close(err[0]);
   close(0);
   close(1);
   close(2);
   dup2(in[0], 0);
   dup2(out[1], 1);
   dup2(err[1], 2);
   system(argv[1]);
   exit(1); /* can't reach */
}

无论如何它似乎都适用于玩具应用程序:)

$ ./dup cat
hello
hello
$ ls -l *count
-rw-r--r-- 1 sarnold sarnold 22 2011-05-26 17:41 stderr_count
-rw-r--r-- 1 sarnold sarnold 21 2011-05-26 17:41 stdin_count
-rw-r--r-- 1 sarnold sarnold 22 2011-05-26 17:41 stdout_count
$ cat *count
stderr copied 0 bytes
stdin copied 6 bytes
stdout copied 6 bytes

我认为值得指出的是,你可以实现这个 只有一个进程的程序,并使用select(2)来确定哪个 文件描述符需要读写。

【讨论】:

  • 谢谢。我的主要错误是我没有正确关闭所有文件描述符(导致子进程挂起),但是您帮助我组织并更好地理解了我的代码。
【解决方案2】:

总的来说,我认为你的方向是正确的。

一个问题是,在您的 stderr 和 stdout 处理程序中,应该从管道中提取字节并写入真正的 stderr/stdout,您正在回写到同一个管道。

了解如何启动子进程也会很有帮助。您提供了一个代码片段来关闭真正的 stderr,然后将管道 fd dup2 回 stderr 的 fd,但您可能希望在父进程中(在 fork 和 exec 之前)这样做,这样您就不需要修改源代码子进程。通常,您应该能够从父级执行此操作。

【讨论】:

  • 嗯,我应该更清楚。我遇到的问题是在处理程序中,无限期地读取块的调用,我不知道为什么。我不应该进行一般调用,因为我只想监视子进程正在写入 stdout/err 的内容,而不是父进程。
猜你喜欢
  • 2011-05-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-13
  • 2017-07-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多