Когда вызывается vfork, действительно ли родительский процесс действительно приостановлен?

3

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

  • Когда порождается дочерний процесс, приостанавливается ли родительский процесс?
  • Если да, то почему?
  • Они могут работать параллельно (как потоки)? В конце концов, и потоки, и процессы вызывают одну и ту же clone()функцию.

После небольшого исследования и поиска в Google, я обнаружил, что родительский процесс на самом деле не приостановлен, но вызывающий поток приостановлен. Даже если это тот случай, когда дочерний процесс выполняет exit()или exec(), как родительский процесс узнает, что дочерний процесс завершился? И что будет, если мы вернемся из дочернего процесса?

Аникет Тхакур
источник

Ответы:

2

Ваш вопрос частично основан на соглашении о плохом именовании. «Поток управления» в языке, говорящем на ядре, - это процесс, говорящий на языке пользователя. Поэтому, когда вы читаете, что vfork «вызывающий поток приостановлен», подумайте «процесс» (или «тяжеловесный поток», если хотите), а не «поток», как в «многопоточном процессе».

  • Так что да, родительский процесс приостановлен.

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

  • Почему? Потому что fork / exec был обычным, дорогим и расточительным

Учитывая более точное определение «потока управления ядра», ответ на вопрос, могут ли они работать параллельно, однозначно

  • Нет, родитель будет заблокирован ядром, пока ребенок не выйдет или не выполнит

Как родитель узнает, что ребенок вышел?

  • Это не так, ядро ​​знает и не дает родителю вообще получить какой-либо процессор, пока ребенок не уйдет.

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

MSW
источник
ты уверен в этом? Я написал довольно простую программу (но она слишком длинна для вставки), которая создает 40 pthreads, выполняет vfork в каждом из них и выполняет exec, если pid = 0 в каждом дочернем процессе, выполняя 2x sleep (1) между vfork и exec, и регистрация каждого шага. Я вижу в журналах, что все vforks объединены, затем идут все "промежуточные шаги" и затем все execs. Единственное объяснение, которое я вижу этому, заключается в том, что другие потоки не останавливаются при вызове vfork.
Qbolec
Да, это правильно. Ваш тестовый код содержит действия, которые вы не полностью понимаете, в частности, как sleep()это реализовано.
MSW
1

Я не программист Linux, но сталкиваясь с тем же вопросом сегодня, я сделал следующий тест:

#include<unistd.h>
#include<signal.h>
#include<errno.h>
#include<fcntl.h>
#include<cassert>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<iostream>
#include<fstream>
#include<sstream>
#include<list>
using namespace std;
int single_talk(int thread_id){
  fprintf(stderr,"thread %d before fork @%d\n",thread_id,time(0));
  int pid=vfork();
  if(-1==pid){
    cerr << "failed to fork: " << strerror(errno) << endl;
    _exit(-3);//serious problem, can not proceed
  }
  sleep(1);
  fprintf(stderr,"thread %d fork returned %d @%d\n",thread_id,pid,time(0));
  if(pid){//"CPP"
    fprintf(stderr,"thread %d in parent\n",thread_id);
  }else{//"PHP"
    sleep(1);
    fprintf(stderr,"thread %d in child @%d\n",thread_id,time(0));
    if(-1 == execlp("/bin/ls","ls",(char*)NULL)){
      cerr << "failed to execl php : " << strerror(errno) << endl;
      _exit(-4);//serious problem, can not proceed
    }
  }
}
void * talker(void * id){
  single_talk(*(int*)id);
  return NULL;
}
int main(){
  signal(SIGPIPE,SIG_IGN);
  signal(SIGCHLD,SIG_IGN);
  const int thread_count = 44;
  pthread_t thread[thread_count];
  int thread_id[thread_count];
  int err;
  for(size_t i=0;i<thread_count;++i){
    thread_id[i]=i;
    if((err = pthread_create(thread+i,NULL,talker,thread_id+i))){
      cerr << "failed to create pthread: " << strerror(err) << endl;
      exit(-7);
    }
  }
  for(size_t i=0;i<thread_count;++i){
    if((err = pthread_join(thread[i],NULL))){
      cerr << "failed to join pthread: " << strerror(err) << endl;
      exit(-17);
    }
  }
}

Я собрал это с g++ -pthread -o repro repro.cppи бегу с ./repro. Что я вижу в выводе, так это то, что все происходит одновременно в раундах: сначала все pthreads запускают vfork, затем все ждут секунду, затем "просыпаются" в реальности дочернего процесса, затем все дочерние элементы запускают exec (), а затем, наконец, все родители просыпаются вверх.

Для меня это доказывает, что если один pthread вызывает vfork, то он не приостанавливает другие pthread - если он это сделал, то они не смогут вызывать vfork () до тех пор, пока не будет вызван exec ().

qbolec
источник
Это демонстрирует, что если вы записываете в родительское пространство данных до vforking, тогда все потоки будут иметь одинаковое значение для этой переменной. Это также показывает, что вы не понимаете, как sleep()это реализовано. Наконец, это должен быть собственный вопрос, потому что это вопрос, а не ответ.
MSS
1
Какую переменную вы имеете в виду в частности? Не могли бы вы запустить этот код и объяснить мне, почему все эти pthreads выполняют действия, в то время как они должны быть приостановлены? Это потому, что сон их не поддерживает? Я не думаю, что использование сна крайне важно для этой демонстрации, потому что мы видим это поведение в производстве, в то время как мы не делаем сон там: /
qbolec
1
Пожалуйста, представьте это как новый вопрос, и я буду рад. Я не пытаюсь быть противным, но это правила StackOverflow (и они имеют смысл для сайта, который хочет быть чистым Q & A)
msw
@msw спасибо, вот вопрос: unix.stackexchange.com/questions/163947/pthreads-and-vfork
qbolec
0

Когда дочерний процесс порожден, родительский процесс приостановлен?

Да.

Если да, то почему?

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

Они могут работать параллельно (как потоки)? Ведь оба потока и процесс вызывают одну и ту же функцию clone ().

На самом деле. Только вызывающая нить приостановлена. Смотри выше.

Как родительский процесс узнает, что ребенок вышел?

Это звонки wait4(). Но, скорее, вы спрашиваете, как родитель знает, чтобы продолжить. Это не так. Ядро снова запускает его, когда происходит одно из двух определенных событий.

И что будет, если мы вернемся из дочернего процесса?

vfork()возвращается в освобожденный фрейм стека, а следующий return;- быстрый путь к неопределенному поведению.

Джошуа
источник
0

Что будет, если мы вернемся из дочернего процесса?

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

Я не понимаю это буквально. Байты в области стека буквально не исчезают после каждой инструкции POP (или RETURN). Однако если вы вернетесь, продолжите работу и выполните инструкцию PUSH (или CALL), она заменит предыдущее значение в стеке.

Это всего лишь один яркий пример странных вещей, которые произойдут в общем, если вы позвоните, vfork()а затем сделаете что-нибудь, чтобы изменить какую-либо память вашего процесса.

[Технически я предполагаю то же поведение, что и в Linux. Другие поведения vfork () были технически разрешены POSIX. Я не знаю, нашел ли кто-нибудь применение для технической гибкости POSIX, кроме как для обеспечения vfork (), который идентичен fork ()].

sourcejedi
источник