Il concetto di processo
 Un sistema operativo può eseguire una grande varietà di
attività diverse:
 sistemi batch – jobs;
 sistemi time-shared – programmi utente o task.
 Molti libri di testo utilizzano i termini job e processo in
modo quasi interscambiabile.
 Processo – un programma in esecuzione; l’esecuzione
di un processo deve progredire in modo sequenziale.
 Un processo comprende:





codice;
stack (es: variabili temporanee, parametri di subroutine);
sezione dati (es: variabili globali);
program counter;
un identificatore di processo univoco (PID).
1.1
Il concetto di processo
1.2
Modelli di esecuzione dei processi
 Multiprogrammazione di quattro programmi.
 Modello concettuale di 4 processi indipendenti e sequenziali.
 Un solo programma è attivo in ogni istante.
1.3
Creazione e terminazione di un processo

Eventi principali che comportano la creazione di un processo:
1.
2.
3.
4.
boot del sistema;
esecuzione di una chiamata di sistema per la creazione di un
processo;
l’utente richiede la creazione di un nuovo processo;
inizio di un job batch.

Condizioni per la terminazione di un processo:
1.
normale uscita, esecuzione dell’ultima istruzione del codice
(volontaria);
chiamata esplicita di un’apposita system call (volontaria);
fatal error, tentativi di operazioni illegali (involontaria);
interruzione causata dalla ricezione di segnali (involontaria).
2.
3.
4.
1.4
Stato del processo
 Mentre un processo è in esecuzione, subisce un
cambiamento di stato:
 new: il processo viene creato;
 running: le istruzioni vengono eseguite;
 waiting:
il processo è in attesa che accada qualche
evento;
 ready: il processo è in attesa di essere assegnato a un
processore;
 terminated: il processo ha terminato l’esecuzione.
1.5
Diagramma degli stati di un processo
1.6
Processi UNIX
 UNIX e Linux sono sistemi multitasking, pertanto ci
possono
essere
più
contemporaneamente.
processi
in
esecuzione
 Come si fa a visualizzare la lista dei processi in
esecuzione?
 $ ps -A
1.7
ps utility
 ps è un’abbreviazione di Process Status. Il comando può
essere usato per mostrare i processi attualmente in
esecuzione su sistemi Unix/Linux.
$ ps
PID TTY
24467 pts/0
24504 pts/0
TIME CMD
00:00:00 bash
00:00:00 ps
$ ps -u
USER
PID %CPU %MEM
dellungo 24467 0.0 0.7
dellungo 24510 0.0 0.3
VSZ RSS TTY
2740 1632 pts/0
2504 688 pts/0
STAT START
S
11:02
R
11:39
TIME COMMAND
0:00 -bash
0:00 ps –u
$ ps -l
F S
100 S
000 R
UID
PID PPID
501 24467 24466
501 24511 24467
C PRI
0 75
0 76
NI ADDR SZ WCHAN
0
- 685 wait4
0
- 746 -
TTY
pts/0
pts/0
TIME CMD
00:00:00 bash
00:00:00 ps
 Digitando ps --help viene mostrato un sommario delle
opzioni e dell’utilizzo in generale del comando ps.
1.8
Eseguire processi in background
 Se a un singolo comando, a una pipeline, o a un gruppo di comandi
viene fatto seguire il metacarattere &, viene creata una sottoshell per
eseguire i comandi come processo in background. Il processo in
background viene eseguito concorrentemente con la shell padre, non
è controllato da tastiera e non pregiudica il controllo della tastiera.
 Eseguire processi in background è molto utile per eseguire task in
parallelo che non richiedono controllo da tastiera.
$ xemacs &
[1] 25330
$ ps
PID TTY
25298 pts/1
25330 pts/1
25336 pts/1
TIME
00:00:00
00:00:00
00:00:00
CMD
bash
xemacs
ps
1.9
Eseguire processi in background
 Per sospendere il processo foreground: Ctrl+Z
 Per inviare il processo sospeso in:
 foreground: fg
 background: bg
$ xemacs
Ctrl+Z
[2]+ Stopped
$ ps -l
F S
TTY
000 S
501
000 T
501
000 R
501
$ bg %2
[2]+ xemacs &
$ ps -l
F S
TTY
000 S 501
000 S 501
000 R 501
xemacs
TIME
00:00:00
00:00:00
00:00:00
CMD
bash
xemacs
ps
TIME
00:00:00
00:00:00
00:00:00
CMD
bash
xemacs
ps
1.10
Terminazione di un processo
 È possibile terminare un processo digitando: Ctrl+C.
 Se vogliamo terminare un processo background prima che si
completi, possiamo usare il comando kill. Naturalmente, abbiamo i
permessi per terminare esclusivamente i nostri processi. La sintassi
per il comando kill è:
 kill [-signalId] PID.
Esempio: $ ps
PID TTY
TIME CMD
25298 pts/1
00:00:00 bash
25330 pts/1
00:00:00 xemacs
25353 pts/1
00:00:00 ps
$ kill 25330
[1]+ Terminated xemacs
 kill invia la signal con relativo codice signalId alla lista dei
processi elencati. Di default, kill invia una signal TERM (numero
15). Per ottenere la lista delle signals, si può usare kill –l. Alcuni
processi resistono ad essere terminati facilmente, per essi si può
usare l’opzione "-9" per forzare l’eliminazione del processo.
1.11
Creazione dei processi
 Il solo modo di creare un nuovo processo in UNIX e
Linux è quello di duplicare un processo esistente.
Processi padri creano processi figli, che, a loro volta,
creano altri processi, dando vita così a un albero di
processi.
1.12
Creazione dei processi
 Nel momento in cui un processo viene duplicato (fork()), i processi
padre e figlio sono totalmente identici, eccetto che per il loro PID;
codice, dati e stack del figlio sono una copia di quelli del padre,
pertanto i due processi continuano ad eseguire lo stesso codice
dall’istruzione che segue la fork. Tuttavia, un processo figlio può
sostituire (exec()) il suo codice con un altro file eseguibile,
differenziandosi in tal modo dal proprio padre.
 Condivisione di risorse
 Padre e figlio condividono tutte le risorse.
 Il figlio condivide un sottoinsieme delle risorse del padre.
 Padre e figlio non condividono alcuna risorsa.
 Esecuzione
 Padre e figlio sono in esecuzione concorrente.
 Il padre attende che il figlio termini per riprendere l’esecuzione.
1.13
Terminazione di un processo
 Il processo esegue l’ultima istruzione e chiede al sistema
operativo di essere cancellato (exit()).
 Possone essere restituiti dai dati (output) al padre da parte di un
figlio che viene terminato (via wait()).
 Tutte le risorse del processo vengono deallocate dal sistema
operativo.
 Il padre può terminare l’esecuzione dei processi figli
(abort()).
 Il figlio ha ecceduto nell’uso di alcune delle risorse che gli sono
state allocate.
 Il compito assegnato al figlio non è più necessario.
 Il padre termina.
1.14
Esempio
Parent Process PID 34
Running shell
Duplicate: fork ()
Child Process PID 35
Running shell
Parent Process PID 34
Running shell
Waiting for child
Differentiate: exec ()
Child Process PID 35
Running utility
Wait for child: wait ()
Terminate: exit ()
Parent Process PID 34
Running shell
awakens
signal
1.15
Child Process PID 35
Terminates
Creare un nuovo processo: fork
 System call: int fork() causa la duplicazione di un
processo. Il processo figlio condivide il codice
con il padre ed eredita una copia delle aree dati
globali e dello stack, ma riceve un differente PID.
 Se fork() ha successo, restituisce il PID del figlio
al processo padre, e restituisce 0 al processo
figlio. Se fallisce, restituisce –1 al processo padre
e non viene creato alcun figlio.
 Entrambi i processi continuano ad eseguire il
medesimo
codice
in
concorrenza,
ma
mantengono uno spazio di indirizzamento dei dati
completamente separato.
1.16
Esempio con fork
#include<stdio.h>
main()
{
int pid;
printf("I am the original process with PID %d and
PPID %d \n",getpid(),getppid());
pid=fork();
if (pid!=0)
{printf("I am the parent process with PID %d and
PPID %d\n",getpid(),getppid());
printf("My child's PID is %d\n",pid);}
else
{printf("I am the child process with PID %d and
PPID %d\n",getpid(),getppid());}
printf("PID %d terminates \n",getpid());
}
1.17
Esempio con fork
$ gcc fork1.c -o fork1.out
$ ls -l fork1.*
-rw-r--r-- 1 lferrari lferrari 421 apr 28 12:35 fork1.c
-rwxrwxr-x 1 lferrari lferrari 12043 apr 28 12:36 fork1.out*
$ ./fork1.out
I am the original process with PID 24629 and PPID 24467
I am the parent process with PID 24629 and PPID 24467
I am the child process with PID 24630 and PPID 24629
PID 24630 terminates
My child's PID is 24630
PID 24629 terminates
1.18
Processi orfani
 Cosa accade se un padre termina senza attendere la
morte del figlio?
 Ad esempio, si potrebbe inserire un istruzione di sleep nel
codice del figlio del programma precedente:
else{ sleep (5);
printf(“I am the child process …); }
 In tal modo, mandando in esecuzione orphan.out (il
programma nuovo) otteniamo:
$ ./orphan.out
I am the original process with PID 24638 and PPID 24467
I am the parent process with PID 24638 and PPID 24467
My child's PID is 24639
PID 24638 terminates
I am the child process with PID 24639 and PPID 1
PID 24639 terminates
1.19
Processi orfani
 Ciò significa che se un padre muore prima del proprio figlio,
il figlio viene automaticamente adottato dal processo
originario init (PID 1).
 Il kernel assicura che tutti i figli di un processo in
terminazione vengono adottati da init impostando il loro
PPID a 1.
init
Parent Process
dies first
Adopt child
Child Process
Survives the parent
1.20
Terminare un processo: exit
 System call: int exit (int status) chiude tutti i descrittori del
processo, provvede a deallocare il suo codice, i suoi dati e
il suo stack, e termina il processo.
 Quando un processo figlio termina, invia al proprio padre
una signal SIGCHLD e rimane in attesa che venga
accettato il proprio valore d’uscita status.
 Un processo in attesa che il proprio padre raccolga le
informazioni sul suo stato viene chiamato un processo
zombie. Un padre raccoglie le informazioni relative alla
terminazione di un figlio eseguendo la system call wait().
Se il processo padre è vivo ma non esegue mai una wait(),
il codice di uscita del figlio non verrà mai accettato e il
processo rimane uno zombie.
1.21
Esempio con exit
$ cat exit.c
#include<stdio.h>
main()
{printf (“I am going to exit with return
code 42”);
exit(42);}
$ exit.out
I am going to exit with return code 42
$ echo $?
42
1.22
Processo zombie
$ cat zombie.c
#include<stdio.h>
main()
{ int pid;
pid=fork();
if (pid!=0) {while(1) sleep(1000);}
else {exit(42);}
}
$ ./zombie.out &
[1] 25363
$ ps
PID TTY
TIME CMD
24467 pts/0
00:00:00 bash
25363 pts/0
00:00:00 zombie.out
25364 pts/0
00:00:00 zombie.out <defunct>
25365 pts/0
00:00:00 ps
$ kill 25363
[1]+ Terminated
./zombie.out
1.23
Attendere un figlio: wait
 System call: int wait (int status) fa sì che un processo
venga sospeso finchè uno dei suoi figli termina.
 Una chiamata wait() che ha successo restituisce il PID del
figlio che è stato terminato e memorizza nella variabile
status lo stato di terminazione del figlio nel modo
seguente:
 se il byte meno significativo (più a destra) di status è zero, la
terminazione del figlio è avvenuta volontariamente e nel byte più
significativo (più a sinistra) è memorizzato lo stato di terminazione
(il valore eventualmente passato dalla exit() al figlio);
 se il byte meno significativo è diverso da zero, la terminazione è
avvenuta involontariamente e il suo valore è uguale al numero
della signal che ha provocato la terminazione del figlio.
 se un processo esegue una wait() e non ha figli, wait() restituisce
immediatamente –1.
1.24
Esempio con wait
#include<stdio.h>
main()
{
int pid, status, childPID;
printf(“I am the original process with PID %d
getpid(),getppid());
pid=fork();
if (pid!=0)
{printf("I am the parent process with PID %d and
PPID %d\n",getpid(),getppid());
childPID = wait(&status);
printf(“A child with PID %d terminated with exit
code %d \n",childPID, status>>8);}
else
{printf("I am the child process with PID %d and
PPID %d\n",getpid(),getppid());
exit(42);}
printf("PID %d terminates \n",getpid());
}
1.25
\n”,
Esempio con wait
$ gcc wait.c -o wait.out
$ ls -l wait.*
-rw-r--r-- 1 lferrari lferrari 421 apr 28 12:35 wait.c
-rwxrwxr-x 1 lferrari lferrari 12043 apr 28 12:36 wait.out*
$ ./wait.out
I am the parent process with PID 24629
I am the parent process with PID 24629 and PPID 24467
I am the child process with PID 24630 and PPID 24629
A child with PID 24630 terminated with exit code 42
PID 24629 terminates
1.26
Esempio con wait
#include<stdio.h>
main()
{
int pid, status, childPID;
printf(“I am the original process with PID %d
getpid(),getppid());
pid=fork();
if (pid!=0)
{printf("I am the parent process with PID %d and
PPID %d\n",getpid(),getppid());
childPID = wait(&status);
printf(“A child with PID %d terminated with exit
code %d \n",childPID, status);}
else
{printf("I am the child process with PID %d and
PPID %d\n",getpid(),getppid());
sleep(10000);
exit(42);}
printf("PID %d terminates \n",getpid());
1.27
}
\n”,
Esempio con wait
$ xterm &
(con xterm apriamo una nuova finestra)
$ ./waitsleep.out
I am the parent process with PID 2000
I am the parent process with PID 2000 and PPID 1806
I am the child process with PID 2001 and PPID 2000
Nel nuovo terminale aperto digitiamo
$
ps –a
PID
1999
2000
2001
2002
TTY
pst/1
pst/1
pst/1
pst/2
CDM
xterm.real
wait.out
wait.out
ps
$ kill –kill 2001
Nel finestra originaria si vedrà
A child with PID 2001 terminated with exit code 9
PID 2001 terminates
1.28
Signal
 I processi a volte si trovano ad avere a che fare con eventi
imprevisti (un errore di floating point, un’interruzione
dell’alimentazione elettrica, la morte di un processo figlio,
una richiesta di terminazione da parte di un utente (cioè
Ctrl-C) …). Tali eventi vengono detti interrupts; essi
devono interrompere il regolare flusso del processo per
essere opportunamente presi in consierazione.
 Quando Linux riconosce che un tale evento si è verificato,
manda una signal al processo corrispondente. C’è
un’unica signal, opportunamente numerata con interi
positivi da 1 a 63, per ogni possibile evento.
 Ad esempio, se un processo causa un errore di floating
point, il kernel manda al processo in questione la signal
SIGFPE (vale a dire, la signal numero 8).
 Il kernel non è l’unico che può mandare signal. Ogni
processo può inviare ad ogni altro processo una signal, se
ha il permesso.
1.29
La lista predefinita delle signal
 $ kill -l
1) SIGHUP
2) SIGINT
3) SIGQUIT
4) SIGILL
5) SIGTRAP
6) SIGABRT
7) SIGBUS
8) SIGFPE
9) SIGKILL
10) SIGUSR1
11) SIGSEGV
12) SIGUSR2
13) SIGPIPE
14) SIGALRM
15) SIGTERM
17) SIGCHLD
18) SIGCONT
19) SIGSTOP
20) SIGTSTP
21) SIGTTIN
22) SIGTTOU
23) SIGURG
24) SIGXCPU
25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF
28) SIGWINCH 29) SIGIO
30) SIGPWR
31) SIGSYS
32) SIGRTMIN 33) SIGRTMIN+1
34) SIGRTMIN+2 35) SIGRTMIN+3 36) SIGRTMIN+4 37) SIGRTMIN+5
38) SIGRTMIN+6 39) SIGRTMIN+7 40) SIGRTMIN+8 41) SIGRTMIN+9
42) SIGRTMIN+10 43) SIGRTMIN+11 44) SIGRTMIN+12 45) SIGRTMIN+13
46) SIGRTMIN+14 47) SIGRTMIN+15 48) SIGRTMAX-15 49) SIGRTMAX-14
50) SIGRTMAX-13 51) SIGRTMAX-12 52) SIGRTMAX-11 53) SIGRTMAX-10
54) SIGRTMAX-9 55) SIGRTMAX-8 56) SIGRTMAX-7 57) SIGRTMAX-6
58) SIGRTMAX-5 59) SIGRTMAX-4 60) SIGRTMAX-3 61) SIGRTMAX-2
62) SIGRTMAX-1 63) SIGRTMAX
 man signal mostra un riassunto della descrizione della signal.
1.30
Sospensione e ripresa dei processi
#include<stdio.h>
#include<signal.h>
main()
{ int pid1, pid2;
pid1=fork();
if (pid ==0) /* first child */
{while(1) {printf(“pid1 is alive\n”); sleep(1);}}
pid2=fork();
if (pid ==0) /* second child */
{while(1) {printf(“pid2 is alive\n”); sleep(1);}}
sleep(3);
kill(pid1,SIGSTOP); /* suspend first child */
sleep(3);
kill(pid1,SIGCONT); /* resume first child */
sleep(3);
kill(pid1,SIGINT); /* kill first child */
kill(pid1,SIGINT); /* kill second child */
1.31
}
Esempio con kill()
$ ./susres.out
pid1
pid2
pid1
pid2
pid1
pid2
pid2
pid2
pid2
pid1
pid2
pid1
pid2
pid1
pid2
$
is
is
is
is
is
is
is
is
is
is
is
is
is
is
is
alive
alive
alive
alive
alive
alive
alive
alive
alive
alive
alive
alive
alive
alive
alive
… entrambi in esecuzione nei primi tre secondi
… ora è in esecuzione soltanto il secondo figlio
… il primo figlio viene ripreso
1.32
Sostituzione di codice: exec
 La system call:
int execl (char path, char arg0, …, char argn, NULL)
sostituisce codice, dati globali e stack del processo
chiamante prendendo i nuovi dal file con pathname path.
 Se l’eseguibile non viene trovato, viene restituito -1;
altrimenti, il processo chiamante sostituisce i propri codice,
dati e stack con quelli dell’eseguibile e comincia ad
eseguire il nuovo codice. Una chiamata exec() eseguita
con successo è “senza ritorno”, in quanto il processo
chiamante perde ogni riferimento al vecchio codice.
 C’è un’intera famiglia di system call di tipo exec() in Unix
per la sostituzione del codice: execv(), execlp(), execvp()
…
1.33
Esempio con execl
$ cat exec.c
#include<stdio.h>
main()
{
printf(“I am process with PID %d and I am about to
exec an ls –l \n”, getpid());
execl (“/bin/ls”,“ls”, “-l”, NULL);
printf(“this line should never be executed \n”);
}
$./exec.out
I am process with PID 2031 and I am about to exec ls -l
-rw-r--r-- 1 lferrari lferrari 421 apr 28 12:35 exec.c
-rwxrwxr-x 1 lferrari lferrari 12043 apr 28 12:36 exec.out*
1.34
Una shell semplificata
#include<stdio.h>
main()
{
while (1) {
type_prompt( );
read_command (path, command, parameters)
pid=fork();
if (pid < 0) { printf(“Unable to fork”);}
else
if (pid != 0)
{wait(&status);}
else
{execl (path, command, parameters, NULL);}
}
1.35
Gestione dei processi della shell
Parent Process PID 501
Running shell
Duplicate: fork ()
Child Process PID 748
Running shell
Parent Process PID 501
Running shell
Waiting for child
Differentiate: exec ()
Child Process PID 748
Running utility
Wait for child: wait ()
Terminate: exit ()
Parent Process PID 501
Running shell
awakens
signal
1.36
Child Process PID 748
Terminates
Passi effettuati durante l’esecuzione del comando
di shell ls
1.37
Scheduling
 Con questo termine si indica l’attività di allocare il tempo
della CPU a compiti diversi all’interno di un sistema
operativo.
 Sebbene si pensi normalmente allo scheduling come
all’esecuzione e interruzione dei processi, in Linux lo
scheduling comprende anche l’esecuzione dei vari task
del kernel.
 L’esecuzione dei task del kernel comprende sia i task
che sono richiesti da un processo in esecuzione sia i
task che vengono eseguiti internamente per conto dei
driver delle periferiche.
1.38
La sincronizzazione del kernel
 Una richiesta di esecuzione in modalità kernel può
avvenire in due modi distinti:
 un programma in esecuzione può richiedere un servizio del
sistema operativo, o esplicitamente tramite una system call, o
implicitamente, ad esempio, quando si verifica un page fault;
 un driver di periferica può scatenare un interrupt hardware che
provoca l’esecuzione da parte della CPU dell’handler definito dal
kernel per quel particolare interrupt.
 La sincronizzazione del kernel richiede una struttura che
consenta l’esecuzione di una sezione critica del kernel,
senza violare l’integrità dei dati condivisi.
1.39
Scheduler di UNIX
Lo scheduler di UNIX è basato su una struttura a code multiple.
1.40
Scarica

La gestione dei processi