функции fork (), wait () и exit () в процессах - PullRequest
3 голосов
/ 06 марта 2020

У меня есть C исходный код, как показано ниже.

#include<stdio.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<unistd.h>
#include<sys/types.h>

int main(void) {

   pid_t process_id;
   int status;

   if (fork() == 0) 
   {
       if (fork() == 0)
       {
           printf("A");
       } else {
           process_id = wait(&status);
           printf("B");
       }
   } else {
       if (fork() == 0)
       {
           printf("C");
           exit(0);
       }
       printf("D");
   }
   printf("0");
   return 0;   
}

когда я выполнил его в терминале, у меня появилось несколько выходов, показанных на этом изображении:

some outputs for the source code written above

Я на самом деле запутался в том, как генерируются эти выходные данные. Например, как генерируется D0A0 ~ $ B0 C.

Может кто-нибудь объяснить, что как эти выходные данные генерируются, а также функциональные возможности выхода (0) в этом коде?

Ответы [ 2 ]

7 голосов
/ 06 марта 2020

В общем, если у вас есть такой код

if (fork() == 0) {
  printf("I'm a child\n");
} else {
  printf("I'm a parent\n");
}

printf("This part is common\n");

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

                                       fork()
                                      /    \
             ------- parent ----------      ---------- child -----------
             |                                                         |
             |                                                         |
   printf("I'm a parent\n");                          printf("I'm a child\n");
   printf("This part is common\n");                   printf("This part is common\n");

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

                                    fork()
                                   /    \
         --------- parent ---------      ---------- child -------------
         |                                                            |
         |                                                            |

      if (fork() == 0)                                 if (fork() == 0)
      {                                                {
        printf("C");                                      printf("A");
        exit(0);                                       } else {
      }                                                   process_id = wait(&status);
      printf("D");                                        printf("B");
                                                       }

      // Common code                                  // Common code
      printf("0");                                    printf("0");                
      return 0;                                       return 0;

После того, как будут выполнены следующие разветвления в родительском и дочернем элементах, мы получим следующую древовидную структуру:

                                    fork()
                                   /    \
                ----  parent ------      ------ child ------
                |                                          |
              fork()                                     fork()
              /    \                                     /    \
--- parent ---      --- child ---          --- parent ---      --- child ----
|                               |          |                                 |
|                               |          |                                 |
printf("D");           printf("C");      process_id = wait(&status);      printf("A");
                       exit(0);          printf("B");
                       printf("D");

printf("0");           printf("0");      printf("0");                     printf("0");
return 0;              return 0;         return 0;                        return 0;

Обратите внимание, что printf ("D"); появляется как в родительских, так и в родительских дочерних ветвях, поскольку на самом деле это общий код в этих двух ветвях после if(fork()==0){}.

. В этот момент все 4 процесса выполняются асинхронно.

  • родительско-родительский процесс печатает «D», затем «0», затем выходит

  • родительско-дочерний процесс печатает «C «затем выходит

  • дочерний родительский процесс ожидает завершения своего дочернего процесса, затем печатает« B », затем« 0 »и выходит

  • дочерний процесс выводит «A», затем «0», затем завершается

Как вы можете видеть, результат этих процессов может быть почти произвольно чередован, единственная гарантия состоит в том, что перед «B0» будет напечатан дочерним процессом, «A0» будет напечатан дочерним процессом. Оболочка, которая использовалась для запуска программы, вернет управление после того, как основной процесс завершится sh (то есть родительский процесс). Однако при возврате элемента управления в оболочку все еще может выполняться другой процесс, поэтому выходные данные какого-либо процесса могут появиться после того, как оболочка выведет командную строку. Например, возможна следующая цепочка событий:

  • parent-parent получает элемент управления. Он печатает «D0» и выходит, элемент управления возвращается обратно в оболочку.

  • дочерний родительский процесс получает элемент управления. Он начинает ждать (блокирует) дочерний процесс.

  • дочерний процесс получает контроль. Он печатает «A0» и завершает работу.

  • , в то время как процесс оболочки получает командную строку управления и печатает «~ $»

  • child- родительский процесс получает контроль. Поскольку дочерний процесс завершен, он разблокируется, печатает «B0» и завершается.

  • родительский процесс получает управление, печатает «C» и завершается.

Комбинированный выход "D0A0 ~ $ B0 C". Это объясняет последнюю строку в вашем примере.

0 голосов
/ 08 марта 2020

Здесь участвуют четыре процесса, назовем их a, b, c и d (d является родителем b и c, а b - родительский элемент a):

shell
    `-d
      +-b
      | `-a
      `-c
  • d - это родительский процесс, который выполняет первый вызов fork(2) (процесс создания b). Поскольку он является родителем, он будет go в оператор else этого первого if и снова будет fork(2) (создание процесса c), а затем печатает в конце строку D0 (оба символа) записываются всегда вместе в одном вызове write(2), поскольку printf(3) буферизует данные, см. ниже причину)
  • первый ответвление d производит процесс b, который после выполнения второго fork(2) (процесс получения a) ожидает его завершения sh, а затем печатает B0.
  • второй fork() из b (ну, b создается после первый разветвляющийся список и выполняет только один разветвление (но это второй разветвленный список), создает процесс a, который печатает A0, а затем завершается, что делает процесс b способным продолжаться после wait(2) вызовите и напечатайте B0, это заставит порядок всегда быть A0 до B0.
  • третий fork() производит процесс c, задачей которого является печать C и exit(3). (из-за этого C0 не выводится, а просто C)
  • , поскольку d не ждет ни одного из двух его дочерних элементов, как только напечатано D0, оно exit(3) s , заставляя оболочку выводить подсказку ~$ и ждать новой команды. Это вынуждает порядок D0 перед приглашением оболочки.
  • * ^C - это Control- C, который вы нажали, думая, что программа все еще работает, он заставляет оболочку выдавать второе приглашение в новой строке.

Поскольку единственными процессами, которые ожидают своих потомков, являются оболочка (заставляющая выводить запрос после D0) и процесс b, ожидающий процесс a (заставляя всегда A0 печататься до B0) разрешается любая другая последовательность, в зависимости от того, как система планирует процессы. и это включает в себя подсказку. Подумайте, что сообщения всегда печатаются в конце выполнения всех задействованных процессов.

Возможные заказы - это перестановки A0, B0, D0, C и приглашение оболочки, но из этого числа половина имеет порядок A0 и B0, а из этой половины половина имеет порядок оболочки и D0 обменены ... так что число возможности должны быть 5!/4 == 30 возможностей. Посмотри, сможешь ли ты получить их все !!! :)

объяснение D0A0~$ B0C

Возможное планирование, которое приводит к выводу выше:

  • оболочка запускает процесс d и ожидает до фини sh.
  • процесс d дважды разветвляется, создавая процессы b и c. Печатает D0 и завершает работу.
  • process b разветвляется, создает процесс a и ожидает от a до fini sh.
  • process a печатает A0 и выходит.
  • оболочка пробуждается от wait() и печатает приглашение ~$.
  • process b пробуждается от wait() для a и печатает B0.
  • process c печатает C и завершается.

printf() буферизация.

в режиме терминала (когда вывод на терминал) stdout буферизация в режиме линии. Если у вас есть, скажем, 512-байтовый буфер, он начинает заполнять его до тех пор, пока не увидит символ \n, или пока буфер не заполнится полностью, тогда он сбрасывает все содержимое буфера в стандартный вывод одним вызовом write(2). Это делает последовательность:

printf("D");
...
printf("0");

заставляет оба символа накапливаться в буфере и фактически выводиться вместе в конце процесса, когда exit(3) вызывает подпрограмму, с которой пакет stdio устанавливается atexit(3) вызов.

Возможные выходы:

все возможные комбинации показаны ниже:

D0A0B0C~$, D0A0B0~$ C, D0A0CB0~$, D0A0C~$ B0, D0A0~$ CB0, D0A0~$ B0C, D0CA0B0~$, D0CA0~$ B0, D0C~$ A0B0, D0~$ CA0B0, D0~$ A0CB0, D0~$ A0B0C, A0D0B0C~$, A0D0B0~$ C, A0D0CB0~$, A0D0C~$ B0, A0D0~$ CB0, A0D0~$ B0C, A0B0D0C~$, A0B0D0~$ C, A0B0CD0~$, A0CB0D0~$, A0CD0B0~$, A0CD0~$ B0, CA0B0D0~$, CA0D0B0~$, CA0D0~$ B0, CD0A0B0~$, CD0A0~$ B0, CD0~$ A0B0.

...