Переместите указатель stderr fd для запуска

Я пишу оболочку, которая разветвляет процесс execv(). Выходные данные дочернего элемента фиксируются в stderr. Когда waitpid() выйдет, я смогу прочитать содержимое stderr и сообщить об этом.

В моем случае я хочу динамически выделить буфер и записать в этот буфер stderr.

Для размера буфера я могу realloc(), но это не очень эффективно, и я обнаружил, что это имеет тенденцию к раздуванию использования пула памяти. Скорее, я хотел бы знать размер, не меняя указатель.

Рассмотрим следующее и обратите внимание, что MyStderr — это просто заполнитель для stderr:

int size=0;
int ch=0;
// int MyStderr is the stderr fd
FILE *nCountFD = fdopen(MyStderr, "r");
while ((ch = getc(nCountFD)!=EOF)
{
    ++size;
}
printf("Size is %ld\n", size);

Вот и узнаю размер. Однако теперь указатель файла для MyStderr находится в конце его буфера.

Я пытался использовать lseek.

lseek() терпит неудачу против stderr, поэтому я не могу использовать его здесь. По крайней мере, на это указывает мое тестирование и поиск в стеке.

So ...

  1. Есть ли способ получить размер, не перемещая MyStderr в eof?

or

  1. Есть ли метод lseek, который будет работать с MyStderr?

Примечание. Вот единственное решение, которое я могу придумать, используя realloc..

char *buf=(char*)malloc(80);
char *NewBuf=NULL;
int n=80;
while ((ch = getc(nCountFD)!=EOF)
{
    buf[i]=ch;
    ++size;
    if (size>n)
    {
       n=n+80;
       NewBuf= realloc(buf, n);
       // some code to make sure it works here //
       buf=NewBuf;
    }
}
printf("Size is %ld\n", size);

А теперь обновление

Вместо того, чтобы создавать функциональность для обхода того факта, что stderr не буферизован, я решил сделать начальный malloc моего буфера результатов достаточно большим, чтобы в большинстве случаев использование realloc() было маловероятным. И если происходит realloc(), размер исходного распределения удваивается для каждого realloc(), как было предложено.

При тестировании (100 000 итераций) это работает очень хорошо, без утечек или заметного вздутия.

Я многим обязан сообществу Stack Overflow. Спасибо вам всем.

Приведенный ниже код не будет работать автономно. Он размещен здесь, чтобы проиллюстрировать то, что я сделал.

.. после всего кода, который анализирует командную строку, разветвляется, выполняет execv и очищает...

while (waitpid(nPID, &status, 0) != nPID)
    ;
i = 0;
nFD = fdopen(nErrFD, "r");
if (!nFD) {
    snprintf(cErrMsg, 80, "Cannot open fd[%i].  Failed to spaw process",
            nErrFD);
    cbuf = strcpy(cbuf, cErrMsg);
    goto NECerror;
}

close(nErrFD);


cbuf = calloc(nBufSz, sizeof(char));
memset(cbuf, 0x00, nBufSz);
i = 0;
while ((ch = getc(nFD)) != EOF) {
    cbuf[i] = (char) ch;
    ++size;
    ++i;
    if (size > nBufSz) {
        nBufSz = nBufSz + nBaseBufSz;
        NewBuf = realloc(cbuf, nBufSz);
        if (NewBuf == NULL) {
            snprintf(cErrMsg, 80,
                    "Internal error:cannot allocate [%i] bytes", nBufSz);
            cbuf = strcpy(cbuf, cErrMsg);
            fclose(nFD);
            goto NECerror;
        }
        cbuf = NewBuf;
        free(NewBuf);
    }

}
fclose(nFD);

person Mark Richards    schedule 08.02.2012    source источник
comment
Фрагментация памяти из-за realloc()ating памяти может быть уменьшена, если вы используете экспоненциальное увеличение, но линейное. Поэтому не просто добавляйте постоянное количество символов, а удваивайте объем памяти, выделенной от перераспределения к перераспределению. В конце вы можете поместить окончательную realloc()ation, которая затем сожмет выделенную память до действительно необходимого размера.   -  person alk    schedule 08.02.2012


Ответы (4)


Каналы недоступны для поиска — если вы читаете данные из канала, предоставленного другим процессом, вы не можете искать его поток данных. Вы должны хранить данные по мере их чтения.

Причина, по которой вы, вероятно, видите много перегрузок памяти, связана с тем, как вы расширяете свой буфер - вы расширяете его только на постоянную величину каждый раз, когда он превышен. Вы добьетесь гораздо большего успеха, если будете удваивать его размер, когда он превышен: это хорошо известный прием для динамические массивы.

Например:

// Error checking omitted for expository purposes
size_t size = 4096;
char *buf = malloc(size);
char *bufPtr = buf;
ssize_t n;
while((n = read(MyStderr, bufPtr, 4096) > 0)
{
    bufPtr += n;

    // Make sure we always have at least 4096 bytes of free space in the
    // buffer for the next read
    if(size - (bufPtr - buf) < 4096)
    {
        size *= 2;  // Watch out for overflow!
        buf = realloc(buf, size);
    }
}

// The (bufPtr - buf) bytes of buf now hold the entirety of the process's stderr
person Adam Rosenfield    schedule 08.02.2012
comment
Возможно, количество добавления должно быть ограничено соответственно. коэффициент уменьшился - в какой-то момент, может быть, около 10..100 МБ или около того, удвоение размера буфера может быть преувеличено. - person glglgl; 08.02.2012
comment
Возможно, но к этому моменту у вас возникнет серьезная проблема с дизайном: вы не должны буферизировать сотни МБ, вы должны обрабатывать их как поток по мере их поступления. - person Adam Rosenfield; 08.02.2012
comment
@AdamRosenfield: у тебя хорошее предложение. В сочетании с glglgl кажется, что лучше всего увеличить realloc как buf=realloc(buf,size+original_size); где original_size также установлен на 4096. - person Mark Richards; 09.02.2012

Можете ли вы записать вывод из stderr во временный файл? Затем вы можете иметь возможность случайного поиска по всему файлу, проверки размера, использования mmap() и т. д. Вы даже можете делать это, пока вы все еще получаете данные.

person mrb    schedule 08.02.2012
comment
Я думал об этом, но это значительно замедлит работу. - person Mark Richards; 09.02.2012

Как уже говорили другие, трубы не доступны для поиска. Однако все это означает, что вам нужно убедиться, что stderr не является каналом. В обертке перед execv делаем

close( STDERR_FILENO );
open( error_log_path, O_RDWR | O_TRUNC ); // opens into STDERR_FILENO

Вызов open гарантированно устанавливает стандартный поток ошибок, потому что всегда назначается самый низкий свободный дескриптор файла.

Затем fork должен дублировать дескриптор файла, чтобы родительский процесс находился на нулевом байте, а дочерний процесс начинал запись с самого начала. Нет нужды искать.

Это предполагает, что вы также не пытаетесь передать вывод ошибки куда-то еще одновременно. В этом случае дочерний процесс должен будет дублировать вывод ошибки во внешне видимый канал и родительский файл буферизации, или вам придется выполнять потоковую передачу через родительский процесс вместо использования waitpid, с дочерним stderr и родительским stderr двумя < em>разные трубы.

person Potatoswatter    schedule 08.02.2012
comment
Интересно, сработает ли этот метод, когда другие приложения, использующие ту же функцию, откроют error_log_path? - person Mark Richards; 09.02.2012
comment
Абсолютно верно - перенаправление определенно вариант в этом сценарии. - person paulsm4; 09.02.2012
comment
@MarkRichards Только один процесс может записать файл, и читатели, очевидно, не должны его обрезать. Это обычный файловый ввод-вывод. Что вас беспокоит? - person Potatoswatter; 09.02.2012
comment
@Potatoswatter Что он будет уничтожен или вызовет тупик. Также я хотел бы избежать записи в файл/устройство (проблемы встроенной системы/памяти и процессора). - person Mark Richards; 09.02.2012
comment
@MarkRichards: Во встроенной системе с проблемами синхронизации и памяти я настоятельно рекомендую делать все правильно и избегать waitpid. Нет причин вообще блокировать или буферизировать неопределенный объем вывода. Файловый ввод-вывод никогда не вызывает взаимоблокировок, если вы не добавите межпроцессную блокировку. Простое чтение файла никогда не затирает его. Запись в файл обычно безопаснее, чем запись в ОЗУ, с точки зрения нехватки памяти, особенно в системе с виртуальной памятью. - person Potatoswatter; 09.02.2012

Нет — stderr не буферизован. Ваш единственный выбор - буферизовать ввод самостоятельно. Я бы рекомендовал реализовать циклический буфер (если вы можете позволить себе потерять самый старый вывод) или просто создать фиксированный буфер некоторого «максимального размера». ИМХО...

person paulsm4    schedule 08.02.2012