Articolo di Santosh Sivaraj, originariamente pubblicato sul fossix.org
Introduzione
Il sistema informatico sta diventando una bestia complessa che non può essere addomesticato facilmente. Nel nuovo mondo i sistemi operativi sono diventati troppo grandi e complessi per imparare tutto in profondità. La maggior parte degli aspiranti nuovi programmatori di sistema non hanno una foto di ciò che sta accadendo in un sistema quando si digita ./a.out. Questo articolo è un tentativo di fornire l’immagine e anche i dettagli necessari per un nuovo arrivato in Linux da cogliere, in modo che lui/lei possa fare riferimento a libri più dettagliati per un apprendimento ulteriore. Questo articolo/carta è solo l’inizio in modo che quando i nuovi arrivati inizino con grandi libri non vengono sopraffatti senza conoscere il flusso naturale del sistema Linux.
Questo documento cerca di spiegare la vita di un processo con tutti i dettagli di basso livello. Si tratta di un tentativo di spiegare ai nuovi utenti di Linux e per me capire come sia la vita completa di un processo che copra sia il kernel che gli aspetti user-space. Le spiegazioni ed i dettagli sono basati solo sui sistemi operativi Linux, altri sistemi potrebbero seguire diversi meccanismi di cui potrei non essere a conoscenza o non ho intenzione di discutere di tali sistemi.
La nascita di un processo
Un processo nasce quando un programma viene eseguito. Cerchiamo quindi di tornare indietro un po’ e cominciamo dalla nascita del programma. Il programma nasce quando ve ne è la necessità per il programmatore. Così ora ho bisogno di scrivere un programma in modo che possa creare un processo da esso e iniziare a spiegare ciò che accade lungo la strada. Di seguito viene riportato il codice di esempio utilizzato per spiegare diversi concetti nel resto dell’articolo.
#include <stdio.h> #include <math.h> int main() { float d; d = cos(20); printf("%f\n", d); }
Questo sorgente è banale, trova solo il coseno di un numero e lo stampa. Ora la compilazione del programma dovrebbe darci un eseguibile da cui inizierà il viaggio per tracciare il processo.
# cc sample-source.c -lm
che dovrebbe darci a.out. Si noti che lo stiamo collegando con la libreria math. Ora, l’esecuzione di questo deve creare un processo, su cui il nostro studio si baserà.
Il programma e la Shell
# ./a.out
Quando si digita ./a.out
nella shell, la shell crea innanzitutto un processo autonomamente utilizzando la chimata di sistema fork ()
fork () creerà un nuovo processo. Questo nuovo processo si sovrapporrà con l’immagine eseguibile dato attraverso la serie di chiamate di sistema execv ()
. Andremo più inprofondità per ognuna di queste chiamate di sistema nelle sezioni seguenti. Grosso modo quello che la shell fa è :
#include <unistd.h> #include <stdio.h> int shell_exec (char *command) { pid_t pid; pid = fork(); if (pid == 0) { execlp(command, command, NULL); } /* parent process continues running */ return 0; } int main (int count, char **command) { if (count < 2) printf("Need a command to execute\n"); return shell_exec(command[1]); }
Il codice sopra è una notevole semplificazione di ciò che fa la shell, che gestisce le pipe, le autorizzazioni, controllo dei job ed altro ancora.
La chiamata di sistema Fork
Come sappiamo la chiamata di sistema ci porterà dall’user-land al kernel-land. Come accennato in precedenza questo articolo descrive anche i dettagli molto evidenti, ma perdendo i dettagli più “cruenti”. Le diverse cose che avvengono nel kernel durante l’avvio di un processo è quello che discuteremo in questa sezione.
Come si vede nel precedente listato di codice , la shell fa un fork ()
e chiama la famiglia di chiamate di sistema exec
per sovrapporre l’immagine del comando sullo spazio di indirizzamento del processo figlio appena creato. Una volta che la chiamata di sistema fork ()
viene invocata , il kernel crea una copia del processo di esecuzione, durante il quale succede questo:
fork()
crea una nuovo stack, e copia le risorse condivise come file descriptor aperti.- il kernel controlla il limite delle risorse del processo chiamante. I limiti delle risorse, come se il numero di processo creato ha superato il limite impostato per il sistema di un utente (ulimit)
- Azzera le statistiche del processo come ad esempio il tempo di esecuzione
- Al processo viene assegnato un nuovo ID di processo e inizia l’esecuzione del processo appena creato.
In questo contesto vi è una politica di copy-on-write. Idealmente il processo figlio e il processo padre (che aveva chiamato fork ()
) dovrebbe avere diverse aree di dati. Ma Linux efficienza non crea una nuova area dati per il figlio, ma utilizza la stessa area di quello del genitore fino a quando uno dei processi iniziare a scrivere su di essa. Dal momento che questo documento non è un approfondimento del kernel, ho volutamente lasciato fuori alcune funzioni che sono chiamate internamente dal kernel. Si prega di consultare la bibliografia per ulteriori approfondimenti.
Il fork da un valore di ritorno due volte, una nel parent con valore di ritorno del PID del processo figlio e una volta il child con un valore di ritorno pari a zero.
Il processo appena creato è univocamente identificato dal processo ID (PID). Questo processo appartiene allo stesso process group del genitore. L’ID gruppo viene utilizzata per il controllo lavoro nella shell. C’è anche un altro tipo di ID chiamato l’ID di sessione. Tutti i processi dello stesso gruppo, generalmente sono nella stesso ID di sessione a meno che il processo utilizzi la chiamata di sistema setsid ()
. L’ID del processo in corso e l’id del suo processo genitore può essere trovato con il comando ps.
ps -e $ ps -f UID PID PPID C STIME TTY TIME CMD santosh 3939 15592 0 Apr17 pts/3 00:00:03 bash santosh 25841 3939 0 07:17 pts/3 00:00:00 ps -f
Il processo padre di tutti i comandi eseguiti in una shell è la shell stessa. Finora, il nostro futuro processo, il codice scritto sopra, non è ancora entrato nel nostro quadro di insieme.
Popular Posts:
- None Found