Неверный результат от getpid () для внука с vfork () и -lpthread - PullRequest
3 голосов
/ 31 января 2020

В одном из особых случаев, показанных ниже, getpid() для внука, созданного с помощью vfork(), возвращает PID родительского процесса.

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

int main() {
  if(vfork()) { /* parent */
    printf("parent pid = %d\n", getpid());
    exit(0);
  } else {
    if(vfork()) { /* child */
      printf("child pid = %d\n", getpid());
      exit(0);
    } else { /* grandchild */
      printf("grandchild pid = %d\n", getpid());
      exit(0);
    }
  }
}

Скомпилировано как gcc main.c, это работает как ожидалось :

grandchild pid = 12241
child  pid = 12240
parent pid = 12239

Скомпилировано как gcc main.c -lpthread, неверный PID внука:

grandchild pid = 12431
child pid = 12432
parent pid = 12431

Есть какие-нибудь подсказки, почему? Это один из неопределенных случаев поведения?

С ps и strace я могу видеть правильный PID. Кстати, тот же пример кода хорошо работает с fork(), то есть правильно getpid() с или без -lpthread.

Ответы [ 3 ]

4 голосов
/ 31 января 2020

getpid не является одной из двух операций, которые вам разрешено выполнять после vfork в дочернем элементе; только два execve и _exit. Бывает, что glib c кэширует pid процесса в пользовательском пространстве и не обновляет этот кэш на vfork (так как это изменило бы кэшированное значение родителя, и так как оно не нужно, так как действительный код не может наблюдать результат); это механизм поведения, которое вы видите. Поведение кэширования немного отличается при использовании -lpthread. Но основная причина в том, что ваш код неверен.

В значительной степени, не используйте vfork. Вы ничего не можете с этим поделать.

2 голосов
/ 31 января 2020

С страница руководства для vfork():

Функция vfork() имеет тот же эффект, что и fork(2), за исключением того, что поведение не определено , если процесс, созданный с помощью vfork(), либо изменяет любые данные, кроме переменной типа pid_t, используемой для хранения возвращаемого значения из vfork(), либо возвращает функцию, в которой был вызван vfork(), или вызывает любая другая функция перед успешным вызовом _exit(2) или одной из функций семейства exec(3).

Это не очень красиво сформулировано, но это говорит о том, что единственное, что может сделать дочерний процесс сделать после vfork():

  • Проверить возвращаемое значение.
  • Вызвать одну из семейства функций exec*().
  • Вызов _exit().

Это потому, что:

vfork() является частным случаем clone(2). Он используется для создания новых процессов без копирования таблиц страниц родительского процесса . Это может быть полезно в чувствительных к производительности приложениях, где создается дочерний элемент, который затем немедленно выдает execve(2).

Другими словами, предполагаемое использование vfork() предназначено только для создания дочерних элементов, которые будут выполняйте другие программы через exec*(), делая это быстрее, чем обычный fork(), потому что таблица страниц родительского элемента не дублируется в дочернем элементе (так как она все равно будет заменена на exec*()). Даже в этом случае vfork() имеет реальное преимущество только в том случае, если этот вид операции необходимо выполнять несколько раз. Поскольку родительская память не копируется, доступ к ней любым способом является неопределенным поведением.

0 голосов
/ 01 февраля 2020

вот требования для vfork()

   #include <sys/types.h>
   #include <unistd.h>

   pid_t vfork(void);

Обратите внимание, что в опубликованном коде OPs не содержатся необходимые заголовочные файлы.

...