Tworzenie i zakończenie procesu

Do tej pory wiemy, że za każdym razem, gdy wykonujemy program, tworzony jest proces, który zostanie zakończony po zakończeniu wykonywania. A co, jeśli potrzebujemy stworzyć proces w programie i możemy chcieć zaplanować dla niego inne zadanie. Czy można to osiągnąć? Tak, oczywiście poprzez tworzenie procesów. Oczywiście po wykonaniu zadania zostanie ono automatycznie zakończone lub możesz je zakończyć w razie potrzeby.

Tworzenie procesu odbywa się poprzez fork() system call. Nowo utworzony proces nazywany jest procesem potomnym, a proces, który go zainicjował (lub proces rozpoczynający wykonywanie), nazywany jest procesem nadrzędnym. Po wywołaniu systemowym fork () mamy teraz dwa procesy - nadrzędny i potomny. Jak je rozróżnić? Bardzo proste, dzieje się tak poprzez ich wartości zwracane.

Po utworzeniu procesu potomnego zobaczmy szczegóły wywołania systemowego fork ().

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

pid_t fork(void);

Tworzy proces potomny. Po tym wywołaniu istnieją dwa procesy, istniejący nazywany jest procesem nadrzędnym, a nowo utworzony nazywany jest procesem potomnym.

Wywołanie systemowe fork () zwraca jedną z trzech wartości -

  • Wartość ujemna wskazująca na błąd, tj. Niepowodzenie w tworzeniu procesu potomnego.

  • Zwraca zero dla procesu potomnego.

  • Zwraca wartość dodatnią dla procesu nadrzędnego. Ta wartość to identyfikator procesu nowo utworzonego procesu potomnego.

Rozważmy prosty program.

File name: basicfork.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main() {
   fork();
   printf("Called fork() system call\n");
   return 0;
}

Kroki wykonania

Kompilacja

gcc basicfork.c -o basicfork

Wykonanie / wyjście

Called fork() system call
Called fork() system call

Note- Zwykle po wywołaniu fork () proces potomny i proces nadrzędny wykonywałyby różne zadania. Jeśli to samo zadanie ma zostać uruchomione, to dla każdego wywołania funkcji fork () uruchomi się 2 potęgi n razy, gdzien to liczba wywołań funkcji fork ().

W powyższym przypadku fork () jest wywoływana raz, stąd wynik jest drukowany dwukrotnie (2 potęga 1). Jeśli funkcja fork () zostanie wywołana, powiedzmy 3 razy, wynik zostanie wydrukowany 8 razy (2 potęgi 3). Jeśli zostanie wywołany 5 razy, wydrukuje 32 razy i tak dalej, i tak dalej.

Widząc, jak fork () tworzy proces potomny, nadszedł czas, aby zobaczyć szczegóły procesu rodzica i procesu potomnego.

Nazwa pliku: pids_after_fork.c

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

int main() {
   pid_t pid, mypid, myppid;
   pid = getpid();
   printf("Before fork: Process id is %d\n", pid);
   pid = fork();

   if (pid < 0) {
      perror("fork() failure\n");
      return 1;
   }

   // Child process
   if (pid == 0) {
      printf("This is child process\n");
      mypid = getpid();
      myppid = getppid();
      printf("Process id is %d and PPID is %d\n", mypid, myppid);
   } else { // Parent process 
      sleep(2);
      printf("This is parent process\n");
      mypid = getpid();
      myppid = getppid();
      printf("Process id is %d and PPID is %d\n", mypid, myppid);
      printf("Newly created process id or child pid is %d\n", pid);
   }
   return 0;
}

Kroki kompilacji i wykonywania

Before fork: Process id is 166629
This is child process
Process id is 166630 and PPID is 166629
Before fork: Process id is 166629
This is parent process
Process id is 166629 and PPID is 166628
Newly created process id or child pid is 166630

Proces może zakończyć się na dwa sposoby -

  • Nieprawidłowo występuje po dostarczeniu pewnych sygnałów, na przykład sygnału zakończenia.

  • Zwykle za pomocą funkcji systemowej _exit () (lub funkcji systemowej _Exit ()) lub funkcji bibliotecznej exit ().

Różnica między _exit () i exit () polega głównie na czyszczeniu. Plikexit() wykonuje pewne porządki przed zwróceniem kontroli z powrotem do jądra, podczas gdy _exit() (lub _Exit ()) natychmiast zwróci sterowanie z powrotem do jądra.

Rozważmy następujący przykładowy program z exit ().

Nazwa pliku: atexit_sample.c

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

void exitfunc() {
   printf("Called cleanup function - exitfunc()\n");
   return;
}

int main() {
   atexit(exitfunc);
   printf("Hello, World!\n");
   exit (0);
}

Kroki kompilacji i wykonywania

Hello, World!
Called cleanup function - exitfunc()

Rozważmy następujący przykładowy program z funkcją _exit ().

Nazwa pliku: at_exit_sample.c

#include <stdio.h>
#include <unistd.h>

void exitfunc() {
   printf("Called cleanup function - exitfunc()\n");
   return;
}

int main() {
   atexit(exitfunc);
   printf("Hello, World!\n");
   _exit (0);
}

Kroki kompilacji i wykonywania

Hello, World!