Свободная проблема дочерней памяти процесса с execvp - PullRequest
1 голос
/ 14 марта 2020

Следующий код взят из книги «Операционные системы: три простых пьесы». Код сбивает меня с толку. Я знаю, execvp никогда не возвращается, когда работает хорошо.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <assert.h>
#include <sys/wait.h>

int
main(int argc, char *argv[])
{
    int rc = fork();
    if (rc < 0) {
        // fork failed; exit
        fprintf(stderr, "fork failed\n");
        exit(1);
    } else if (rc == 0) {
        // child: redirect standard output to a file
        close(STDOUT_FILENO); 
        open("./p4.output", O_CREAT|O_WRONLY|O_TRUNC, S_IRWXU);

        // now exec "wc"...
        char *myargs[3];
        myargs[0] = strdup("wc");   // program: "wc" (word count)
        myargs[1] = strdup("p4.c"); // argument: file to count
        myargs[2] = NULL;           // marks end of array
        execvp(myargs[0], myargs);  // runs word count
    } else {
        // parent goes down this path (original process)
        int wc = wait(NULL);
    assert(wc >= 0);
    }
    return 0;
}

Я использую Valgrind для проверки утечек памяти. Приведенный выше код не утечки памяти. Когда я удаляю строку execvp, она определенно обнаружит потерю: 8 байтов в 2 блоках. Почему это?

Команда Valgrind:

valgrind --leak-check=full ./a.out

при использовании команды valgrind --trace-children = yes --leak-check = full ./p4

==15091== Memcheck, a memory error detector
==15091== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==15091== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==15091== Command: ./p4
==15091==
==15092== Memcheck, a memory error detector
==15092== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==15092== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==15092== Command: /usr/bin/wc p4.c
==15092==
==15092==
==15092== HEAP SUMMARY:
==15092==     in use at exit: 0 bytes in 0 blocks
==15092==   total heap usage: 36 allocs, 36 frees, 8,809 bytes allocated
==15092==
==15092== All heap blocks were freed -- no leaks are possible
==15092==
==15092== For counts of detected and suppressed errors, rerun with: -v
==15092== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
==15091==
==15091== HEAP SUMMARY:
==15091==     in use at exit: 0 bytes in 0 blocks
==15091==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==15091==
==15091== All heap blocks were freed -- no leaks are possible
==15091==
==15091== For counts of detected and suppressed errors, rerun with: -v
==15091== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
[root cpu-api]#

no Независимо от того, сколько байтов я малу c, в сводке кучи всегда говорится: == 15092 == общее использование кучи: 36 выделений, 36 освобождений, 8,809 выделенных байтов

Ответы [ 2 ]

4 голосов
/ 14 марта 2020

Что такое «определенно потерянная» память?

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

Это означает, что такая программа :

int main(void) {
    char *buf = malloc(10);
    // ...
    exit(0);
}

вызовет без ошибок от Valgrind, в то время как программа, подобная этой:

void func(void) {
    char *buf = malloc(10);
    // ...
} // memory is definitely lost here

int main(void) {
    func();
    exit(0);
}

, вызовет "определенно потерянную" ошибку.

Почему первая версия подходит для Valgrind? Это потому, что память всегда освобождается системой при выходе из программы. Если вы продолжаете использовать выделенный кусок памяти до конца вашей программы, на самом деле нет необходимости явно вызывать free() для него, и это можно рассматривать как просто трата времени. По этой причине, если вы не освобождаете какой-то выделенный блок, сохраняя при этом ссылку на него, Valgrind предполагает, что вы сделали это, чтобы избежать «бесполезного» * ​​1018 *, потому что вы умны и знаете, что ОС позаботится об этом. об этом все равно.

Если, однако, вы забыли что-то free() и потеряли все ссылки на него, то Valgrind предупредит вас, потому что у вас должно быть free() d памяти. Если вы этого не сделаете, и программа продолжит работу, то каждый раз, когда вводится ошибочный блок, происходит то же самое, и вы теряете память. Это то, что называется «утечка памяти». Очень простым примером является следующий:

void func(void) {
    char *buf = malloc(10);
    // ...
} // memory is definitely lost here

int main(void) {
    while (1) {
        func();
    }
    exit(0);
}

Эта программа заставит вашу машину исчерпать память и в конечном итоге может привести к уничтожению или зависанию вашей системы (предупреждение: не проверяйте это, если не хотите рискнуть заморозить свой P C). Если вместо этого вы правильно вызовете free(buf) до конца func, то программа продолжит работать без проблем.

Что происходит в вашей программе

Теперь давайте посмотрим, где вы размещаете память и где переменные, содержащие ссылки, объявлены. Единственная часть программы, которая выделяет память, находится внутри блока if (rc == 0), до strdup, здесь:

char *myargs[3];
myargs[0] = strdup("wc");   // program: "wc" (word count)
myargs[1] = strdup("p4.c"); // argument: file to count

Два вызова strdup() дублируют строку и выделяют новую память для выполнения так. Затем вы сохраняете ссылку на вновь выделенную память в массиве myargs, который объявлен внутри блока if . Если ваша программа выходит из блока без освобождения выделенной памяти, эти ссылки будут потеряны, и ваша программа не сможет освободить память.

С execvp(): ваш дочерний процесс заменяется новым процессом (wc p4.c), а пространство памяти родительского процесса выбрасывается операционной системой (для Valgrind это точно так же, как завершение программы). Эта память не считается потерянной Valgrind, потому что ссылки на выделенную память все еще присутствуют при вызове execvp(). ПРИМЕЧАНИЕ: это , а не , потому что вы передаете указатели на выделенную память для execvp(), это потому, что исходная программа эффективно завершается и память сохраняется ОС.

Без execvp(): ваш дочерний процесс продолжает выполнение и сразу после выхода из блока кода, в котором определено myargs, он теряет любую ссылку на выделенную память (так как myargs[0] и myargs[1] были единственные ссылки). Затем Valgrind правильно сообщает об этом как о «определенно потерянных», 8 байтов (3 для "wc" и 5 для "p4.c") в 2 блоках (2 выделения). То же самое происходит, если по какой-либо причине не удается вызвать execvp().

Дополнительные соображения

Если быть честным, в программе, которую вы показываете, нет необходимости вызывать strdup(). Это не так, как эти строки должны быть скопированы, потому что они используются где-то еще (или что-то в этом роде). Код мог бы быть просто:

myargs[0] = "wc";   // program: "wc" (word count)
myargs[1] = "p4.c"; // argument: file to count

В любом случае, при использовании семейства функций exec*() рекомендуется ставить exit() непосредственно после него, чтобы программа не продолжала работать в случае сбоя exec*(). Примерно так:

execvp(myargs[0], myargs); 
perror("execvp failed");
exit(1);
1 голос
/ 14 марта 2020

Семейство функций exec() заменяет текущий образ процесса новым образом процесса. Это означает, что программа, которая в данный момент выполняется вызывающим процессом, должна быть заменена новой программой с вновь инициализированными сегментами стека, кучи и (инициализированными и неинициализированными).

В дочернем процессе, когда вы вызывают execvp()

execvp(myargs[0], myargs);

дочерний процесс заменен новым процессом (при условии, что execvp() успешен) и память, выделенная в дочернем процессе

    myargs[0] = strdup("wc");   // program: "wc" (word count)
    myargs[1] = strdup("p4.c"); // argument: file to count

, будет эффективно восстановлена по новому процессу. Таким образом, если у вас execvp() в дочернем процессе, valgrind не сообщает об утечке памяти.

When I delete the execvp line, it will detect definitely lost: 8 bytes in 2 blocks. Why is this?

С strdup () : [выделение добавлено]

char * strdup (const char * str1); (dynamici c memory TR)

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

Таким образом, когда в вашей программе нет вызова execvp(), дочерний процесс утечка памяти, выделенной на strdup(). Чтобы решить эту проблему, освободите память, выделенную strdup(), возможно, после execvp(), так что, если execvp() случайно завершится неудачно или вы явно удалите вызов execvp() из вашей программы, он не должен пропускать память:

    myargs[0] = strdup("wc");   // program: "wc" (word count)
    myargs[1] = strdup("p4.c"); // argument: file to count
    myargs[2] = NULL;           // marks end of array
    execvp(myargs[0], myargs);  // runs word count
    printf ("execvp failed\n"); // You may want to print the errno as well
    free (myargs[0]);
    free (myargs[1]);

Обратите внимание, что нет необходимости выделять память и копировать строковые литералы в эту память. Вы можете напрямую присвоить строковые литералы myargs, например:

    myargs[0] = "wc";
    myargs[1] = "p4.c";
...