контролировать байты в stdin, stdout и stderr в C - PullRequest
3 голосов
/ 27 мая 2011

Мне нужно подсчитать, сколько байтов отправляется дочернему процессу через stdin, и сколько байтов дочерний процесс записывает в stdout и stderr. Дочерний процесс вызывает 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]);
    }
}

и это фрагмент кода (внутри дочернего процесса), который устанавливает два fd из структуры fds, чтобы дочерний процесс записывал в канал вместо 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*/

Я потерян, любая помощь будет признательна.

Ответы [ 2 ]

4 голосов
/ 27 мая 2011

Я думаю, что ваш код можно улучшить, немного разбив вещи; все процедуры учета и копирования - в основном одна и та же задача, и если вы решите продолжить работу с несколькими процессами, можно написать просто:

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);
}

Вместо одного символа за раз, который неэффективен для умеренных объемов данных, мы можем обрабатывать частичные записи с помощью функции writen() из Advanced Programming в источнике Unix Environment код:

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() звонки у каждого ребенка довольно уродливы, но пытаются написать небольшую оболочку вокруг массива всех FDS, и освобождая те, что передаются в качестве аргументов, тоже кажутся неприятностями.

#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), чтобы определить, какой файловые дескрипторы требуют чтения и записи.

0 голосов
/ 27 мая 2011

В целом, я думаю, что вы на правильном пути.

Одна проблема заключается в том, что в ваших обработчиках stderr и stdout, которые должны отбирать байты из канала и записывать в настоящий stderr / stdout, вы записываете обратно в тот же канал.

Также было бы полезно посмотреть, как вы запускаете дочерние процессы. Вы дали фрагмент кода, чтобы закрыть реальный stderr, а затем дублировать fd канала обратно в fd stderr, но вы, вероятно, захотите это в родительском процессе (после fork и перед exec), чтобы вам не нужно было изменять исходный код дочерний процесс. Вы должны быть в состоянии сделать это в целом все от родителя.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...