Un processo non è altro che un'istanza in esecuzione di un programma. È anche definito come un programma in azione.
Il concetto di processo è il concetto fondamentale di un sistema Linux. I processi possono generare altri processi, uccidere altri processi, comunicare con altri processi e molto altro.
In questo tutorial, discuteremo del ciclo di vita di un processo e analizzeremo vari aspetti che un processo attraversa nel suo ciclo di vita.
1. Codice vs programma vs processo
Per prima cosa comprendiamo la differenza tra codice, programma e processo.
Codice: Di seguito è riportato un esempio di codice:
#include <stdio.h> #include <unistd.h> int main(void) { printf("\n Hello World\n"); sleep(10); return 0; }
Salviamo la parte di codice sopra in un file chiamato helloWorld.c. Quindi questo file diventa codice.
Programma: Ora, quando il codice viene compilato, produce un file eseguibile. Ecco come viene compilato il codice sopra:
$ gcc -Wall helloWorld.c -o helloWorld
Ciò produrrebbe un eseguibile chiamato helloWorld. Questo eseguibile è noto come programma.
Processo: Ora, eseguiamo questo eseguibile:
$ ./helloWorld Hello World
Una volta eseguito, viene creato un processo corrispondente a questo eseguibile (o programma). Questo processo eseguirà tutto il codice macchina che era presente nel programma. Questo è il motivo per cui un processo è noto come istanza in esecuzione di un programma.
Per controllare i dettagli del processo appena creato, esegui il comando ps nel modo seguente:
$ ps -aef | grep hello* 1000 6163 3017 0 18:15 pts/0 00:00:00 ./helloWorld
Per comprendere l'output del comando ps, leggi il nostro articolo su 7 esempi di comandi ps.
2. Processo genitore e figlio
Ogni processo ha un processo padre e può avere o meno processi figlio. Prendiamo questo uno per uno. Considera l'output del comando ps sulla mia macchina Ubuntu:
1000 3008 1 0 12:50 ? 00:00:23 gnome-terminal 1000 3016 3008 0 12:50 ? 00:00:00 gnome-pty-helper 1000 3017 3008 0 12:50 pts/0 00:00:00 bash 1000 3079 3008 0 12:58 pts/1 00:00:00 bash 1000 3321 1 0 14:29 ? 00:00:12 gedit root 5143 2 0 17:20 ? 00:00:04 [kworker/1:1] root 5600 2 0 17:39 ? 00:00:00 [migration/1] root 5642 2 0 17:39 ? 00:00:00 [kworker/u:69] root 5643 2 0 17:39 ? 00:00:00 [kworker/u:70] root 5677 2 0 17:39 ? 00:00:00 [kworker/0:2] root 5680 2 0 17:39 ? 00:00:00 [hci0] root 5956 916 0 17:39 ? 00:00:00 /sbin/dhclient -d -sf /usr/lib/NetworkManager/nm-dhcp-client.action -pf /run/sendsigs. root 6181 2 0 18:35 ? 00:00:00 [kworker/1:0] root 6190 2 0 18:40 ? 00:00:00 [kworker/1:2] 1000 6191 3079 0 18:43 pts/1 00:00:00 ps -aef
I numeri interi nella seconda e terza colonna dell'output sopra rappresentano l'ID processo e l'ID processo padre. Osservare le cifre evidenziate in grassetto. Quando ho eseguito il comando "ps -aef", è stato creato un processo, il suo ID processo è 6191. Ora, guarda l'ID del processo padre, è 3079. Se guardi verso l'inizio dell'output vedrai quell'ID 3079 è l'ID del processo bash. Questo conferma che bash shell è il genitore per qualsiasi comando che esegui attraverso di essa.
Allo stesso modo, anche per i processi che non vengono creati tramite la shell, esiste un processo padre. Basta eseguire il comando "ps -aef" sulla tua macchina Linux e osservare la colonna PPID (ID processo padre). Non vedrai alcuna voce vuota al suo interno. Ciò conferma che ogni processo ha un processo padre.
Ora, veniamo ai processi figlio. Ogni volta che un processo crea un altro processo, il primo è chiamato genitore mentre il secondo è chiamato processo figlio. Tecnicamente, un processo figlio viene creato chiamando la funzione fork() dall'interno del codice. Di solito quando si esegue un comando dalla shell, fork() è seguito dalla serie di funzioni exec().
Abbiamo discusso del fatto che ogni processo ha un processo genitore, questo può portare a una domanda che cosa accadrà a un processo figlio il cui processo genitore viene ucciso? Bene, questa è una buona domanda, ma torniamo sull'argomento qualche tempo dopo.
3. Il processo di inizializzazione
Quando il sistema Linux viene avviato, la prima cosa che viene caricata in memoria è vmlinuz. È l'eseguibile compresso del kernel Linux. Ciò si traduce nella creazione del processo init. Questo è il primo processo che viene creato. Il processo Init ha un PID pari a uno ed è il super genitore di tutti i processi in una sessione Linux. Se consideri la struttura del processo Linux come un albero, init è il nodo iniziale di quell'albero.
Per confermare che init è il primo processo, puoi eseguire il comando pstree sulla tua macchina Linux. Questo comando mostra l'albero dei processi per una sessione Linux.
Ecco un esempio di output:
init-+-NetworkManager-+-dhclient | |-dnsmasq | `-3*[{NetworkManager}] |-accounts-daemon---2*[{accounts-daemon}] |-acpid |-at-spi-bus-laun-+-dbus-daemon | `-3*[{at-spi-bus-laun}] |-at-spi2-registr---{at-spi2-registr} |-avahi-daemon---avahi-daemon |-bamfdaemon---3*[{bamfdaemon}] |-bluetoothd |-colord---{colord} |-console-kit-dae---64*[{console-kit-dae}] |-cron |-cups-browsed |-cupsd |-2*[dbus-daemon] |-dbus-launch |-dconf-service---2*[{dconf-service}] |-evince---3*[{evince}] |-evinced---{evinced} |-evolution-sourc---2*[{evolution-sourc}] |-firefox-+-plugin-containe---16*[{plugin-containe}] | `-36*[{firefox}] |-gconfd-2 |-gedit---3*[{gedit}] |-6*[getty] |-gnome-keyring-d---7*[{gnome-keyring-d}] |-gnome-terminal-+-bash | |-bash-+-less | | `-pstree | |-gnome-pty-helpe | `-3*[{gnome-terminal}] |-gvfs-afc-volume---2*[{gvfs-afc-volume}] |-gvfs-gphoto2-vo---{gvfs-gphoto2-vo} |-gvfs-mtp-volume---{gvfs-mtp-volume} |-gvfs-udisks2-vo---{gvfs-udisks2-vo} |-gvfsd---{gvfsd} |-gvfsd-burn---2*[{gvfsd-burn}] |-gvfsd-fuse---4*[{gvfsd-fuse}] ... ... ...
L'output conferma che init è in cima all'albero dei processi. Inoltre, se osservi il testo in grassetto, vedrai la relazione genitore-figlio completa del processo pstree. Leggi di più su pstree nel nostro articolo su tree e pstree.
Ora, torniamo alla domanda (lasciata aperta nell'ultima sezione) sulle conseguenze quando il processo genitore viene ucciso mentre il bambino è ancora vivo. Ebbene in questo caso, il bambino diventa ovviamente orfano ma viene adottato dal processo init. Quindi, init process diventa il nuovo genitore di quei processi figlio i cui genitori sono terminati.
4. Ciclo di vita del processo
In questa sezione, discuteremo il ciclo di vita delle normali coperture di un processo Linux prima che venga terminato e rimosso dalla tabella dei processi del kernel.
- Come già discusso, un nuovo processo viene creato tramite fork() e se deve essere eseguito un nuovo eseguibile, la famiglia di funzioni exec() viene chiamata dopo fork(). Non appena questo nuovo processo viene creato, viene messo in coda nella coda dei processi pronti per l'esecuzione.
- Se è stato chiamato solo fork(), è molto probabile che il nuovo processo venga eseguito in modalità utente, ma se viene chiamato exec(), il nuovo processo verrà eseguito in modalità kernel finché non verrà creato un nuovo spazio di indirizzi del processo.
- Mentre il processo è in esecuzione, un processo con priorità più alta può anticiparlo tramite un'interruzione. In questo caso, il processo annullato va nuovamente nella coda dei processi pronti per l'esecuzione. Questo processo viene ripreso dallo scheduler in una fase successiva.
- Un processo può entrare in modalità kernel durante l'esecuzione. Questo è possibile quando è necessario accedere ad alcune risorse come file di testo che sono conservati sul disco rigido. Poiché le operazioni che coinvolgono l'accesso all'hardware possono richiedere tempo, è molto probabile che il processo vada in modalità di sospensione e si riattivi solo quando i dati richiesti sono disponibili. Quando il processo viene attivato, non significa che inizierà immediatamente l'esecuzione, si accoderà nuovamente e verrà prelevato per l'esecuzione dallo scheduler al momento opportuno.
- Un processo può essere interrotto in molti modi. Può chiamare la funzione exit() per uscire o elaborare segnali Linux per uscire. Inoltre, alcuni segnali non possono essere rilevati e provocano l'interruzione immediata del processo.
- Esistono diversi tipi di processi Linux. Una volta terminato, il processo non viene completamente eliminato. Una voce contenente alcune informazioni ad essa correlate viene conservata nella tabella degli indirizzi del processo del kernel fino a quando il processo padre non chiama esplicitamente le funzioni wait() o waitpid() per ottenere lo stato di uscita del processo figlio. Fino a quando il processo padre non esegue questa operazione, il processo terminato è noto come processo zombie.