7.4 I processi

Un processo (o task) è l’istanza dell’esecuzione di un determinato programma: quando si lancia un file eseguibile viene creato un processo in memoria che esegue le istruzioni indicate dal programma. In genere l’esecuzione di un programma genera un processo, ma programmi più complessi (per esempio quelli che gestiscono comunicazioni client-server) possono generare più processi.

Quando viene lanciato in esecuzione un programma, il kernel crea un processo in memoria assegnandogli un numero intero che lo identifica univocamente all’interno della macchina: il PID (Process IDentifier). In genere il PID è sequenziale, ovvero ad ogni processo viene assegnato il PID del processo precedente incrementato di 1 ed al primo processo lanciato dal sistema (init) viene assegnato il PID uguale ad 1. Infatti, quando viene avviato il sistema, il kernel crea il processo init lanciando in esecuzione il comando /sbin/init (man page init(8))22 e questo è l’unico processo creato dal kernel. Qualunque altro processo viene sempre creato a partire da una richiesta effettuata da un processo già esistente. In questo modo si viene a creare una gerarchia di processi (process tree o albero dei processi) alla cui radice c’è init. Un processo A che crea un processo B è detto processo genitore (o processo padre) del processo B ed il processo B è detto processo figlio del processo A. Poiché ogni processo è identificato dal PID, è una convenzione diffusa riferirsi al PID del processo genitore con il termine PPID (Parent PID).

La struttura dati relativa ad un processo è riportata di seguito (sorgente C)

struct task_struct {
/* these are hardcoded - don't touch */
volatile long        state;          /* -1 unrunnable, 0 runnable, >0 stopped */
long                 counter;
long                 priority;
unsigned             long signal;
unsigned             long blocked;   /* bitmap of masked signals */
unsigned             long flags;     /* per process flags, defined below */
int errno;
long                 debugreg[8];    /* Hardware debugging registers */
struct exec_domain   *exec_domain;
/* various fields */
struct linux_binfmt  *binfmt;
struct task_struct   *next_task, *prev_task;
struct task_struct   *next_run,  *prev_run;
unsigned long        saved_kernel_stack;
unsigned long        kernel_stack_page;
int                  exit_code, exit_signal;
/* ??? */
unsigned long        personality;
int                  dumpable:1;
int                  did_exec:1;
int                  pid;
int                  pgrp;
int                  tty_old_pgrp;
int                  session;
                                                                        
                                                                        
/* boolean value for session group leader */
int                  leader;
int                  groups[NGROUPS];
/*
* pointers to (original) parent process, youngest child, younger sibling,
* older sibling, respectively.  (p->father can be replaced with
* p->p_pptr->pid)
*/
struct task_struct   *p_opptr, *p_pptr, *p_cptr,
                  *p_ysptr, *p_osptr;
struct wait_queue    *wait_chldexit;
unsigned short       uid,euid,suid,fsuid;
unsigned short       gid,egid,sgid,fsgid;
unsigned long        timeout, policy, rt_priority;
unsigned long        it_real_value, it_prof_value, it_virt_value;
unsigned long        it_real_incr, it_prof_incr, it_virt_incr;
struct timer_list    real_timer;
long                 utime, stime, cutime, cstime, start_time;
/* mm fault and swap info: this can arguably be seen as either
mm-specific or thread-specific */
unsigned long        min_flt, maj_flt, nswap, cmin_flt, cmaj_flt, cnswap;
int swappable:1;
unsigned long        swap_address;
unsigned long        old_maj_flt;    /* old value of maj_flt */
unsigned long        dec_flt;        /* page fault count of the last time */
unsigned long        swap_cnt;       /* number of pages to swap on next pass */
/* limits */
struct rlimit        rlim[RLIM_NLIMITS];
unsigned short       used_math;
char                 comm[16];
/* file system info */
int                  link_count;
struct tty_struct    *tty;           /* NULL if no tty */
/* ipc stuff */
struct sem_undo      *semundo;
struct sem_queue     *semsleeping;
/* ldt for this task - used by Wine.  If NULL, default_ldt is used */
struct desc_struct *ldt;
/* tss for this task */
struct thread_struct tss;
/* filesystem information */
struct fs_struct     *fs;
/* open file information */
struct files_struct  *files;
/* memory management info */
struct mm_struct     *mm;
/* signal handlers */
struct signal_struct *sig;
#ifdef __SMP__
int                  processor;
int                  last_processor;
int                  lock_depth;     /* Lock depth.
                                   We can context switch in and out
                                   of holding a syscall kernel lock... */
#endif
};
Ogni processo è caratterizzato quindi da alcune proprietà ed in particolare gli vengono assegnati determinati privilegi sul sistema, in funzione (generalmente) dell’utente che lo ha lanciato. Tra le proprietà di un processo ci sono le seguenti:

Generalmente il PGID di un processo coincide con il suo PID, tranne nel caso in cui il processo venga lanciato in esecuzione come parte di un comando più complesso, che ne lancia altri in cascata. Come sarà accennatto nella sez. 7.6 ed illustrato nella sez. 3.4.13, è possibile lanciare in esecuzione una serie di comandi correlati tra loro: si può far eseguire, ad esempio, un comando A, indicando al sistema di fornire il risultato dello stesso ad un altro comando B. Si consideri la seguente riga di comando

 
$ ls -l | less  
La riga di comando precedente è in realtà formata da due comandi distinti ls -l (comando A) e less (comando B). In tal caso il sistema non fa altro che eseguire il comando ls con l’opzione -l, che produce in uscita il contenuto della working directory e quindi lo passa al comando less che permette uno scorrimento più agevole dello stesso. Quindi, il comando ls genererà un processo con il proprio PID e con PGID = PID, mentre less ne genererà un altro con il proprio PID ma con lo stesso PGID del processo generato da ls, poiché entrambi facenti parti dello stesso process group, cioè i due comandi fanno parte della stessa riga di comando. Il processo generato da less attenderà, per mezzo di una pipe23, il risultato del processo generato da ls e quindi produrrà il suo output.

In genere il real UID coincide con l’effective UID (ed analogamente il real GID coincide con l’effective GID), ma se un file ha impostato il bit suid (v. cap. 4), le proprietà di tipo real si riferiscono all’utente che ha lanciato l’esecuzione del file, mentre quelle di tipo effective si riferiscono all’utente proprietario del file stesso.

I sistemi Unix-like che si attengono alle specifiche POSIX, utilizzano anche le proprietà saved UID e saved GID che sono una copia dei valori delle relative proprietà di tipo effective e servono per tener traccia di tali valori qualora le proprietà di tipo effective varino.

Le proprietà di tipo effective caratterizzano il processo, ovvero determinano i privilegi che esso ha sul sistema.

Le proprietà di tipo filesystem sono utilizzate per l’accesso ai file, ovvero determinano i permessi del processo sul filesystem: il processo apre i file come se fosse l’utente identificato dall’effective UID (appartenente al gruppo effective GID). In particolare, quando un file viene creato, il proprietario è l’utente identificato dall’effective UID. Esse sono state introdotte da GNU/Linux per rendere l’accesso al NFS24 più sicuro. In genere tali valori coincidono con quelle di tipo effective poiché una qualunque variazione di esse si riflette su quest’ultime, ma c’è un caso in cui le proprietà di tipo filesystem sono diverse dalle relative di tipo effective: quando il server NFS deve impostare i privilegi di accesso al NFS (in questo modo il server NFS concede agli utenti gli opportuni privilegi soltanto per l’accesso al NFS, ma non per la comunicazione con il server NFS stesso).

Il kernel di GNU/Linux permette l’accesso ai dati contenuti nella struttura dei processi per mezzo di un filesystem virtuale montato nella directory /proc. Tali dati sono utilizzati dalla maggior parte dei programmi per la gestione dei processi.

Per ogni processo il kernel riserva uno specifico spazio di memoria: in questo modo due processi che girano “contemporaneamente” sul sistema non si accorgono l’uno dell’altro e il malfunzionamento dell’uno non causa problemi all’altro. Inoltre, un processo non può accedere alla memoria assegnata ad un altro processo se non attraverso l’utilizzo di particolari meccanismi gestiti e controllati dal kernel.


  7.4.1 Multitasking e multithreading
  7.4.2 I diritti di un processo
  7.4.3 Creazione di un processo
  7.4.4 Gestione della memoria
  7.4.5 Terminazione di un processo