Finita fork, inizio terminazione e wait
[gapil.git] / prochand.tex
index 22da5c0194b660c71df60ba5a39403221ef5c9c3..69bdb16e4224a89cfe216fca14896f760804029b 100644 (file)
@@ -116,7 +116,7 @@ non ritorna mai (in quanto con essa viene eseguito un altro programma).
 In questa sezione tratteremo le funzioni per la gestione dei processi, a
 partire dalle funzioni elementari che permettono di leggerne gli
 identificatori, alle varie funzioni di manipolazione dei processi, che
-riguardano la lore creazione, terminazione, e la messa in esecuzione di altri
+riguardano la loro creazione, terminazione, e la messa in esecuzione di altri
 programmi.
 
 
@@ -151,7 +151,7 @@ Entrambe le funzioni non riportano condizioni di errore.
 \end{functions}
 
 Il fatto che il \acr{pid} sia un numero univoco per il sistema lo rende il
-candidato ideale per generare ultieriori indicatori associati al processo di
+candidato ideale per generare ulteriori indicatori associati al processo di
 cui diventa possibile garantire l'unicità: ad esempio la funzione
 \func{tmpname} (si veda \secref{sec:file_temp_file}) usa il \acr{pid} per
 generare un pathname univoco, che non potrà essere replicato da un'altro
@@ -170,15 +170,13 @@ identificativi associati ad un processo relativi al controllo di sessione.
 
 La funzione \func{fork} è la funzione fondamentale della gestione dei processi
 in unix; come si è detto l'unico modo di creare un nuovo processo è attraverso
-l'uso di questa funzione, che è quindi la base per il multitasking; il protipo
+l'uso di questa funzione, che è quindi la base per il multitasking. Il prototipo
 della funzione è:
 
 \begin{functions}
   \headdecl{sys/types.h} 
   \headdecl{unistd.h} 
-  
   \funcdecl{pid\_t fork(void)} 
-  
   Restituisce zero al padre e il \acr{pid} al figlio in caso di successo,
   ritorna -1 al padre (senza creare il figlio) in caso di errore;
   \texttt{errno} può assumere i valori:
@@ -197,7 +195,7 @@ figlio continuano ad essere eseguiti normalmente alla istruzione seguente la
 dei segmenti di testo, stack e dati (vedi \secref{sec:proc_mem_layout}), ed
 esegue esattamente lo stesso codice del padre, ma la memoria è copiata, non
 condivisa\footnote{In generale il segmento di testo, che è identico, è
-  condiviso e tenuto in read-only, linux poi utilizza la tecnica del
+  condiviso e tenuto in read-only, Linux poi utilizza la tecnica del
   \textit{copy-on-write}, per cui la memoria degli altri segmenti viene
   copiata dal kernel per il nuovo processo solo in caso di scrittura, rendendo
   molto più efficiente il meccanismo} pertanto padre e figlio vedono variabili
@@ -207,6 +205,18 @@ La differenza che si ha nei due processi 
 ritorno della funzione fork è il \acr{pid} del processo figlio, mentre nel
 figlio è zero; in questo modo il programma può identificare se viene eseguito
 dal padre o dal figlio.
+Si noti come la funzione \func{fork} ritorni \textbf{due} volte: una nel padre
+e una nel figlio. La sola differenza che si ha nei due processi è il valore di
+ritorno restituito dalla funzione, che nel padre è il \acr{pid} del figlio
+mentre nel figlio è zero; in questo modo il programma può identificare se
+viene eseguito dal padre o dal figlio. 
+
+La scelta di questi valori non è casuale, un processo infatti può avere più
+figli, ed il valore di ritorno di \func{fork} è l'unico modo che permette di
+identificare quello appena creato; al contrario un figlio ha sempre un solo
+padre (il cui \acr{pid} può sempre essere ottenuto con \func{getppid}, vista
+in \secref{sec:proc_pid}) e si usa il valore nullo, che non può essere il
+\acr{pid} di nessun processo.
 
 \begin{figure}[!htb]
   \footnotesize
@@ -225,9 +235,10 @@ int main(int argc, char *argv[])
 /* 
  * Variables definition  
  */
-    int i;
-    int nchild;
+    int nchild, i;
     pid_t pid;
+    int wait_child=0;
+    int wait_parent=0;
 
     ...        /* handling options */
 
@@ -243,12 +254,14 @@ int main(int argc, char *argv[])
             printf("Error on %d child creation, %s\n", i, strerror(errno));
         }
         if (pid == 0) {   /* child */
-            printf("Child %d successfully executing\n", i++);
-            sleep(2);
+            printf("Child %d successfully executing\n", ++i);
+            if (wait_child) sleep(wait_child);
             printf("Child %d exiting\n", i);
             exit(0);
         } else {          /* parent */
-            printf("Spawned %d child, pid %d \n", i, pid);
+            printf("Spawned %d child, pid %d \n", i+1, pid);
+            if (wait_parent) sleep(wait_parent);
+            printf("Go to next child \n");
         }
     }
     /* normal exit */
@@ -259,86 +272,300 @@ int main(int argc, char *argv[])
   \label{fig:proc_fork_code}
 \end{figure}
 
-Si noti come la funzione \func{fork} ritorni \textbf{due} volte: una nel padre
-e una nel figlio. La sola differenza che si ha nei due processi è il valore di
-ritorno restituito dalla funzione, che nel padre è il \acr{pid} del figlio
-mentre nel figlio è zero; in questo modo il programma può identificare se
-viene eseguito dal padre o dal figlio. 
-
-La scelta di questi valori comunque non è casuale, un processo infatti può
-avere più figli, ed il valore di ritorno di \func{fork} è l'unico modo che
-permette di identificare quello appena creato; al contrario un figlio ha
-sempre un solo padre (il cui \acr{pid} può sempre essere ottenuto con
-\func{getppid}, vista in \secref{sec:proc_pid}) e si usa il valore nullo, che
-non può essere il \acr{pid} di nessun processo.
-
-In \curfig\ si è riportato il corpo del codice dell'esempio \cmd{forktest},
-che ci permette di illustrare l'uso della funzione \func{fork}, creando un
-numero di figli specificato a linea di comando; il codice completo, compresa
-la parte che gestisce le opzioni a riga di comando, è disponibile nel file
+Normalmente la chiamata a \func{fork} può fallire solo per due ragioni, o ci
+sono già troppi processi nel sistema (il che di solito è sintomo che
+qualcos'altro non sta andando per il verso giusto) o si è ecceduto il limite
+sul numero totale di processi permessi all'utente (il valore della costante
+\macro{CHILD\_MAX} definito in \file{limits.h}, che fa riferimento ai processo
+con lo stesso \textit{real user id}).
+
+L'uso di \func{fork} avviene secondo due modalità principali; la prima è
+quella in cui all'interno di un programma si creano processi figli per
+affidargli l'esecuzione di una certa sezione di codice, mentre il processo
+padre ne esegue un'altra. È il caso tipico dei server di rete in cui il padre
+riceve ed accetta le richieste da parte dei client, per ciascuna delle quali
+pone in esecuzione un figlio che è incaricato di fornire il servizio.
+
+La seconda modalità è quella in cui il processo vuole eseguire un altro
+programma; questo è ad esempio il caso della shell. In questo caso il processo
+crea un figlio la cui unica operazione è quella fare una \func{exec} (di cui
+parleremo in \secref{sec:proc_exec}) subito dopo la \func{fork}.
+
+Alcuni sistemi operativi (il VMS ad esempio) combinano le operazioni di questa
+seconda modalità (una \func{fork} seguita da una \func{exec}) in un'unica
+operazione che viene chiamata \textit{spawn}. Nei sistemi unix-like è stato
+scelto di mantenere questa separazione, dato che, come visto per la prima
+modalità d'uso, esistono numerosi scenari in cui si può usare una \func{fork}
+senza bisogno di una \func{exec}. Inoltre anche nel caso della seconda
+modalità di operazioni, avere le due funzioni separate permette al figlio di
+cambiare gli attributi del processo (maschera dei segnali, redirezione
+dell'output, \textit{user id}) prima della \func{exec}, rendendo molto più
+flessibile la possibilità di modificare gli attributi del nuovo processo.
+
+In \curfig\ si è riportato il corpo del codice del programma di esempio
+\cmd{forktest}, che ci permette di illustrare l'uso della funzione
+\func{fork}. Il programma permette di creare un numero di figli specificato a
+linea di comando, e prende anche due opzioni \cmd{-p} e \cmd{-c} per indicare
+degli eventuali tempi di attesa (in secondi, eseguiti tramite la funzione
+\func{sleep}) per il padre ed il figlio; il codice completo, compresa la parte
+che gestisce le opzioni a riga di comando, è disponibile nel file
 \file{ForkTest.c}.
 
 Decifrato il numero di figli da creare, il ciclo principale del programma
 (\texttt{\small 28--40}) esegue in successione la creazione dei processi figli
 controllando il successo della chiamata a \func{fork} (\texttt{\small
   29--31}); ciascun figlio (\texttt{\small 29--31}) si limita a stampare il
-suo numero di successione, attendere 3 secondi e scrivere un messaggio prima
-di uscire. Il processo padre invece (\texttt{\small 29--31}) stampa un
-messaggio di creazione e procede nell'esecuzione del ciclo. Se eseguiamo il
-comando otterremo come output sul terminale:
+suo numero di successione, eventualmente attendere il numero di secondi
+specificato e scrivere un messaggio prima di uscire. Il processo padre invece
+(\texttt{\small 29--31}) stampa un messaggio di creazione, eventualmente
+attende il numero di secondi specificato, e procede nell'esecuzione del ciclo.
+Se eseguiamo il comando senza specificare attese (il default è non attendere),
+otterremo come output sul terminale:
 \begin{verbatim}
-[piccardi@selidor sources]$ ./forktest 5
-Test for forking 5 child
-Spawned 1 child, pid 840 
+[piccardi@selidor sources]$ ./forktest 3
+Test for forking 3 child
+Spawned 1 child, pid 2038 
 Child 1 successfully executing
+Child 1 exiting
+Go to next child 
+Spawned 2 child, pid 2039 
 Child 2 successfully executing
-Spawned 2 child, pid 841 
-Spawned 3 child, pid 842 
+Child 2 exiting
+Go to next child 
 Child 3 successfully executing
-Spawned 4 child, pid 843 
-Child 4 successfully executing
-Child 5 successfully executing
-Spawned 5 child, pid 844 
-[piccardi@selidor sources]$ Child 2 exiting
-Child 1 exiting
-Child 4 exiting
 Child 3 exiting
-Child 5 exiting
-\end{verbatim}
-
-Come si vede non si può dire quale processo fra il padre ed il figlio venga
-eseguito per primo\footnote{anche se nel kernel 2.4.x era stato introdotto un
-  meccanismo che metteva in esecuzione sempre il xxx per primo (TODO
-  recuperare le informazioni esatte)} dopo la chiamata a \func{fork}, nel caso
-mostrato sopra ad esempio si può notare come dopo la creazione il secondo ed
-il quinto figlio sia stato stati eseguiti per primi, mantre per gli altri
-figli è stato eseguito per primo il padre. 
+Spawned 3 child, pid 2040 
+Go to next child 
+\end{verbatim} %$
+
+Esaminiamo questo risultato: una prima conclusione che si può trarre è non si
+può dire quale processo fra il padre ed il figlio venga eseguito per
+primo\footnote{anche se nel kernel 2.4.x era stato introdotto un meccanismo
+  che metteva in esecuzione sempre il xxx per primo (TODO recuperare le
+  informazioni esatte)} dopo la chiamata a \func{fork}; dall'esempio si può
+notare infatti come nei primi due cicli sia stato eseguito per primo il padre
+(con la stampa del \acr{pid} del nuovo processo) per poi passare
+all'esecuzione del figlio (completata con i due avvisi di esecuzione ed
+uscita), e tornare all'esecuzione del padre (con la stampa del passaggio al
+ciclo successivo), mentre la terza volta è stato prima eseguito il figlio
+(fino alla conclusione) e poi il padre.
 
 In generale l'ordine di esecuzione dipenderà, oltre che dall'algoritmo di
 scheduling usato dal kernel, dalla particolare situazione in si trova la
 macchina al momento della chiamata, risultando del tutto impredicibile.
-Eseguendo più volte il programma di prova, si sono ottenute situazioni
-completamente diverse, compreso caso in cui il processo padre ha eseguito più
-di una \func{fork} prima che uno dei figli venisse messo in
-esecuzione. 
+Eseguendo più volte il programma di prova e producendo un numero diverso di
+figli, si sono ottenute situazioni completamente diverse, compreso il caso in
+cui il processo padre ha eseguito più di una \func{fork} prima che uno dei
+figli venisse messo in esecuzione.
 
 Pertanto non si può fare nessuna assunzione sulla sequenza di esecuzione delle
-istruzioni del codice fra padre e figli, e se è necessaria una qualche forma
-di precedenza occorrerà provvedere ad espliciti meccanismi di
-sincronizzazione, pena il rischio di incorrere nelle cosiddette \textit{race
-  conditions}.
-
-Si ricordi inoltre che come accennato, essendo i segmenti di memoria
-utilizzati dai singoli processi completamente separati, le modifiche delle
-variabili nei processi figli (come l'incremento di \var{i} in \texttt{\small
-  33}) saranno effettive solo per essi, e non hanno alcun effetto sul valore
-che le stesse variabili hanno nel processo padre.
+istruzioni del codice fra padre e figli, nè sull'ordine in cui questi potranno
+essere messi in esecuzione, e se è necessaria una qualche forma di precedenza
+occorrerà provvedere ad espliciti meccanismi di sincronizzazione, pena il
+rischio di incorrere nelle cosiddette \textit{race conditions}.
+
+Si noti inoltre che, come accennato, essendo i segmenti di memoria utilizzati
+dai singoli processi completamente separati, le modifiche delle variabili nei
+processi figli (come l'incremento di \var{i} in \texttt{\small 33}) sono
+visibili solo al loro interno, e non hanno alcun effetto sul valore che le
+stesse variabili hanno nel processo padre (ed in eventuali altri processi
+figli che eseguano lo stesso codice).
+
+Un secondo aspetto molto importante nella creazione dei processi figli è
+quello dell'interazione dei vari processi con i file; per illustrarlo meglio
+proviamo a redirigere su un file l'output del nostro programma di test, quello
+che otterremo è:
+\begin{verbatim}
+[piccardi@selidor sources]$ ./forktest 3 > output
+[piccardi@selidor sources]$ cat output
+Test for forking 3 child
+Child 1 successfully executing
+Child 1 exiting
+Test for forking 3 child
+Spawned 1 child, pid 836 
+Go to next child 
+Child 2 successfully executing
+Child 2 exiting
+Test for forking 3 child
+Spawned 1 child, pid 836 
+Go to next child 
+Spawned 2 child, pid 837 
+Go to next child 
+Child 3 successfully executing
+Child 3 exiting
+Test for forking 3 child
+Spawned 1 child, pid 836 
+Go to next child 
+Spawned 2 child, pid 837 
+Go to next child 
+Spawned 3 child, pid 838 
+Go to next child 
+\end{verbatim}
+che come si vede è completamente diverso da quanto ottenevamo sul terminale.
+
+Il comportamento delle varie funzioni di interfaccia con i file è analizzato
+in gran dettaglio in \capref{cha:file_unix_interface} e in
+\secref{cha:files_std_interface}. Qui basta accennare che si sono usate le
+funzioni standard della libreria del C che prevedono l'output bufferizzato; e
+questa bufferizzazione varia a seconda che si tratti di un file su disco (in
+cui il buffer viene scaricato su disco solo quando necessario) o di un
+terminale (nel qual caso il buffer viene scaricato ad ogni a capo).
+
+Nel primo esempio allora avevamo che ad ogni chiamata a \func{printf} il
+buffer veniva scaricato, e le singole righe erano stampate a video subito dopo
+l'esecuzione della \func{printf}. Ma con la redirezione su file la scrittura
+non avviene più alla fine di ogni riga e l'output resta nel buffer, per questo
+motivo, dato che ogni figlio riceve una copia della memoria del padre, esso
+riceverà anche quanto c'è nel buffer delle funzioni di I/O, comprese le linee
+scritte dal padre fino allora. Così quando all'uscita del figlio il buffer
+viene scritto su disco, troveremo nel file anche tutto quello che il processo
+padre aveva scritto prima della sua creazione.  E alla fine del file, dato che
+in questo caso il padre esce per ultimo, troviamo anche l'output del padre.
+
+Ma l'esempio ci mostra un'altro aspetto fondamentale dell'interazione con i
+file, che era valido anche per l'esempio precedente, ma meno evidente; il
+fatto cioè che non solo processi diversi possono scrivere in contemporanea
+sullo stesso file (l'argomento della condivisione dei file in unix è trattato
+in dettaglio in \secref{sec:file_sharing}), ma anche che, a differenza di
+quanto avviene per le variabili, la posizione corrente sul file è condivisa 
+fra il padre e tutti i processi figli. 
+
+Quello che succede è che quando lo standard output del padre viene rediretto,
+lo stesso avviene anche per tutti i figli; la funzione \func{fork} infatti ha
+la caratteristica di duplicare (allo stesso modo in cui lo fa la funzione
+\func{dup}, trattata in \secref{sec:file_dup}) nei figli tutti i file
+descriptor aperti nel padre, il che comporta che padre e figli condividono
+le stesse voci della file table (per la spiegazione di questi termini si veda
+\secref{sec:file_sharing} e referenza a figura da fare) e quindi anche
+l'offset corrente nel file.
+
+In questo modo se un processo scrive sul file aggiornerà l'offset sulla file
+table, e tutti gli altri processi che condividono la file table vedranno il
+nuovo valore; in questo modo si evita, in casi come quello appena mostrato in
+cui diversi processi scrivono sullo stesso file, che l'output successivo di un
+processo vada a sovrapporsi a quello dei precedenti (l'output potrà risultare
+mescolato, ma non ci saranno parti perdute per via di una sovrascrittura).
+
+Questo tipo di comportamento è essenziale in tutti quei casi in cui il padre
+crea un figlio ed attende la sua conclusione per proseguire, ed entrambi
+scrivono sullo stesso file, ad esempio lo standard output (un caso tipico è la
+shell). Se l'output viene rediretto con questo comportamento avremo che il
+padre potrà continuare a scrivere automaticamente in coda a quanto scritto dal
+figlio; se così non fosse ottenere questo comportamento sarebbe estremamente
+complesso necessitando di una qualche forma di comunicazione fra i due
+processi.
+
+In generale comunque non è buona norma far scrivere più processi sullo stesso
+file senza una qualche forma di sincronizzazione in quanto, come visto con il
+nostro esempio, le varie scritture risulteranno mescolate fra loro in una
+sequenza impredicibile. Le modalità con cui in genere si usano i file dopo una
+\func{fork} sono sostanzialmente due:
+\begin{enumerate}
+\item Il processo padre aspetta la conclusione del figlio. In questo caso non
+  è necessaria nessuna azione riguardo ai file, in quanto la sincronizzazione
+  degli offset dopo eventuali operazioni di lettura e scrittura effettuate dal
+  figlio è automatica.
+\item L'esecuzione di padre e figlio procede indipendentemente. In questo caso
+  ciascuno dei due deve chiudere i file che non gli servono una volta che la
+  \func{fork} è stata eseguita, per evitare ogni forma di interferenza.
+\end{enumerate}
+
+Oltre ai file aperti i processi figli ereditano dal padre una serie di altre
+proprietà comuni; in dettaglio avremo che dopo l'esecuzione di una \func{fork}
+padre e figlio avranno in comune:
+\begin{itemize}
+\item i file aperti (e gli eventuali flag di \textit{close-on-exec} se
+  settati).
+\item gli identificatori per il controllo di accesso: il \textit{real user
+    id}, il \textit{real group id}, l'\textit{effective user id},
+  l'\textit{effective group id} e i \textit{supplementary group id} (vedi
+  \secref{tab:proc_uid_gid}).
+\item gli identificatori per il controllo di sessione: il \textit{process
+    group id} e il \textit{session id} e il terminale di controllo.
+\item i flag \acr{suid} e \acr{suid} (vedi \secref{sec:file_suid_sgid}).
+\item la directory di lavoro e la directory radice (vedi
+  \secref{sec:file_work_dir}).
+\item la maschera dei permessi di creazione (vedi \secref{sec:file_umask}).
+\item la maschera dei segnali.
+\item i segmenti di memoria condivisa agganciati al processo. 
+\item i limiti sulle risorse
+\item le variabili di ambiente (vedi \secref{sec:proc_environ}).
+\end{itemize}
+le differenze invece sono:
+\begin{itemize}
+\item il valore di ritorno di \func{fork}.
+\item il \textit{process id}. 
+\item il \textit{parent process id} (quello del figlio viene settato al
+  \acr{pid} del padre).
+\item i valori dei tempi di esecuzione (\var{tms\_utime}, \var{tms\_stime},
+  \var{tms\_cutime}, \var{tms\_uetime}) che nel figlio sono posti a zero.
+\item i \textit{file lock}, che non vengono ereditati dal figlio.
+\item gli allarmi pendenti, che per il figlio vengono cancellati.
+\end{itemize}
+
+
+\subsection{La funzione \func{vfork}}
+\label{sec:proc_vfork}
+
+La funzione \func{vfork} è esattamente identica a \func{fork} ed ha la stessa
+semantica e gli stessi errori; la sola differenza è che non viene creata la
+tabella delle pagine né la struttura dei task per il nuovo processo. Il
+processo padre è posto in attesa fintanto che il figlio non ha eseguito una
+\func{execve} o non è uscito con una \func{\_exit}. Il figlio condivide la
+memoria del padre (e modifiche possono avere effetti imprevedibili) e non deve
+ritornare o uscire con \func{exit} ma usare esplicitamente \func{\_exit}.
+
+Questa funzione è un rimasuglio dei vecchi tempi in cui eseguire una
+\func{fork} comportava anche la copia completa del segmento dati del processo
+padre, che costituiva un inutile appesantimento in tutti quei casi in cui la
+\func{fork} veniva fatto solo per poi eseguire una \func{exec}. La funzione
+venne introdotta in BSD per migliorare le prestazioni.
+
+Dato che Linux supporta il \textit{copy on write} la perdita di prestazioni è
+assolutamente trascurabile, e l'uso di questa funzione (che resta un caso
+speciale della funzione \func{clone}), è deprecato, per questo eviteremo di
+trattarla ulteriormente.
+
+
+\subsection{La conclusione di un processo.}
+\label{sec:proc_termination}
+
+In \secref{sec:proc_conclusion} abbiamo già affrontato le tre modalità con cui
+si conclude un programma in maniera normale: la chiamata di \func{exit} (che
+esegue le funzioni registrate e chiude gli stream), il ritorno dalla funzione
+\func{main} (equivalente alla chiamata di \func{exit}), e la chiamata ad
+\func{\_exit} (che esegue direttamente la terminazione del processo).
+
+Ma oltre alla conclusione normale abbiamo accennato che esistono anche delle
+modalità di conclusione anomala; queste sono in sostanza due: il programma può
+chiamare la funzione \func{abort} per invocare una chiusura anomala, o essere
+terminato da un segnale.  In realtà anche la prima modalità si riconduce alla
+seconda, dato che \func{abort} si limita a generare il segnale
+\macro{SIGABRT}.
+
+Qualunque sia la modalità di conclusione di un processo, il kernel esegue
+comunque una serie di operazioni: chiude tutti i file aperti, rilascia la
+memoria che stava usando, e così via. Ma per ciascuna delle varie modalità
+di chiusura al padre deve essere riportato come il figlio è terminato. 
+
+Nel caso di conclusione normale per riportare lo stato di uscita del processo
+viene usato l'\textit{exit status} specificato dal valore passato alle
+funzioni \func{exit} o \func{\_exit} (o dal valore di ritorno per
+\func{main}).  Ma se il processo viene concluso in maniera anomala è il kernel
+che deve generare un \textit{termination status} per indicare le ragioni della
+conclusione anomala. Si noti che si è distinto fra \textit{exit status} e
+\textit{termination status} in quanto anche in caso di conclusione normale, il
+kernel usa il primo per produrre il secondo.
+
+In ogni caso il valore dello stato di conclusione del processo può essere
+letto attraverso le funzioni \func{wait} o \func{waitpid}.
 
 
 \subsection{Le funzioni \texttt{wait} e  \texttt{waitpid}}
 \label{sec:proc_wait}
 
 
+
 \subsection{Le funzioni \texttt{exec}}
 \label{sec:proc_exec}
 
@@ -359,7 +586,7 @@ funzioni per la loro manipolazione diretta.
 
 Abbiamo già accennato in \secref{sec:intro_multiuser} ad ogni utente ed gruppo
 sono associati due identificatori univoci, lo \acr{uid} e il \acr{gid} che li
-contraddistinguono nei confonti del kernel. Questi identificatori stanno alla
+contraddistinguono nei confronti del kernel. Questi identificatori stanno alla
 base del sistema di permessi e protezioni di un sistema unix, e vengono usati
 anche nella gestione dei privilegi di accesso dei processi.