Home / Indice sezione
 www.icosaedro.it 

 Ripasso Unix - Lezione 2 - Processi

Indice

1. Eseguibili (o programmi) e processi
2. Scheduling dei processi
3. Struttura dati di un processo
4. Forking di processi
5. Exec
6. Visualizzare i processi
7. Segmentazione e memoria protetta
8. Eseguibili binari
9. Eseguibili script
10. Comunicare con i processi

1. Eseguibili (o programmi) e processi

Un eseguibile è un file che possiede il permesso di esecuzione per qualcuno e contiene il codice di un programma, completo delle informazioni necessarie per la sua esecuzione (librerie esterne necessarie, spazio di memoria necessario, informazioni per il debugging, ecc.).

Un processo è l'insieme delle informazioni di gestione, dei dati e del codice caricati in memoria e necessari per la sua esecuzione: tutta questa memoria occupata dal processo si chiama immagine del processo.

Ci possono essere anche parecchi processi che eseguono uno stesso programma: per esempio ci possono essere diversi utenti che eseguono /bin/bash e che stanno usando /bin/ls.

2. Scheduling dei processi

Il kernel esegue a turno tutti i processi che si trovano nello stato di "ready". Ogni processo viene eseguito tipicamente per 0.01 secondi, poi il kernel lo sospende, aggiorna lo scheduler e quindi invoca il processo che risulta averne diritto.

Stati di un processo. Su di un sistema a singolo processore, un solo processo alla volta si trova nello stato Running; scaduto il tempo ad esso concesso, il kernel lo interrompe e lo pone nello stato di Ready. I processi in attesa di eventi esterni, per esempio in attesa di dati dalla tastiera o in attesa di connessioni di rete, vengono posti nello stato di Waiting e risvegliati dal kernel solo quando l'evento arriva. In realtà esistono vari altri stati del processo che qui non abbiamo esaminato, ma si tratta di aspetti non fondamentali per la comprensione degli argomenti di questo corso.

3. Struttura dati di un processo

Le principali informazioni che il kernel mantiene sullo stato di un processo sono:

4. Forking di processi

Un processo (parent) crea un altro processo (child) invocando la funzione del kernel fork(). Il kernel crea allora una copia esatta del parent, incluso i dati, lo stack, l'heap, il program counter, ecc. Naturalmente, il processo child ha un PID diverso, e il suo PPID è quello del padre. Tutti i dati del processo padre visti nel paragrafo precedente che sono ereditari, vengono duplicati nel child. Dunque il child ha la stessa identità, la stessa dir. di lavoro, la stessa maschera dei permessi e le stesse variabili di ambiente.

L'unica vera differenza è che la funzione fork() ritorna al padre il PID del figlio, mentre al figlio ritorna zero. L'esecuzione del codice dei due processi prosegue quindi dal punto in cui è avvenuto il forking.

La replicazione è l'unico modo in Unix per creare un nuovo processo, intendendo per nuovo un processo con un altro PID.

Esempio di frammento di programma in liguaggio in prosa che esegue la formattazione e la stampa di un documento. In questo caso l'utente deve aspettare che il programma abbia terminato il suo lavoro di stampa prima di poter proseguire l'editing del documento (pensiamo, ad esempio, che si tratti di un word processor):

procedura Stampa()
{
    Formatta
    Invia alla stampante
    Ritorna
}
In questo secondo esempio, lo stesso programma demanda il lavoro di formattazione e stampa a un processo child:

procedura Stampa()
{
    pid = fork()

    se pid > 0
        io sono il padre:
        ritorna immediatamente

    altrimenti
        io sono il figlio:
        Formatta
        Invia alla stampante
        fine del programma e morte del processo
}
Altro esempio tipico di forking: i daemon process che gestiscono servizi di rete: il padre aspetta la richiesta di connessione e, quando ne arriva una, crea un figlio al quale demanda il lavoro, mentre il padre si rimette subito in ascolto di nuove richieste di connessione (casi tipici: inetd, xinetd, sshd, Apache). In ambiente Unix questa è la strategia seguita da molti programmi server per poter rispondere a interrogazioni multiple contemporanee.

Siccome il forking può richiedere un certo tempo per la duplicazione del processo, alcuni server di rete eseguono il pre-forking in base al carico di lavoro (come fa Apache): il processo padre esegue preventivamente il forking di un certo numero di processi figli, che sono pertanto già pronti a rispondere ad eventuali interrogazioni via rete.

5. Exec

Invocando la funzione del kernel exec(programma, parametri) un processo viene sostituito dal programma indicato: il codice del nuovo programma viene caricato in memoria ed eseguito partendo dall'inizio. Ovviamente ciò comporta la cessazione del programma originario.

Se un programma (come bash) vuole avviare un processo (come ls) senza però morire nell'intento, allora deve eseguire un forking seguito da un exec:

procedura EseguiLS()
{
    pid = fork()

    se pid > 0
        io sono il padre:
        1) aspetta la terminazione del figlio, oppure
        2) ritorna immediatamente se il processo è stato
           avviato con &

    altrimenti
        io sono il figlio:
        exec(/bin/ls, /)
}

Dunque, quando da linea di comando diamo il comando ls, lo shell esegue le seguenti operazioni: genera un processo figlio con fork(); se il PID del processo ritornato dalla funzione fork() è positivo, io sono il padre (cioè lo shell dal quale abbiamo lanciato il comando) e quindi rimango in attesa della terminazione del figlio oppure ritorno immediatamente (se il comando è stato dato con l'& finale); se invece il PID è zero, io sono il figlio, e quindi eseguo con exec() il comando ls.

6. Visualizzare i processi

Ecco i processi attivi sulla mia macchina:
$ ps ax
  PID TTY      STAT   TIME COMMAND
    1 ?        S      0:04 init [3] 
    2 ?        SW     0:00 [keventd]
    3 ?        SW     0:00 [kapmd]
    4 ?        SWN    0:00 [ksoftirqd_CPU0]
    5 ?        SW     0:00 [kswapd]
    6 ?        SW     0:00 [bdflush]
    7 ?        SW     0:00 [kupdated]
    8 ?        SW     0:00 [mdrecoveryd]
   82 ?        SW     0:00 [khubd]
  591 ?        SW     0:00 [eth0]
  666 ?        S      0:00 syslogd -m 0
  671 ?        S      0:00 klogd -x
  783 ?        S      0:00 /usr/sbin/apmd -p 10 -w 5 -W -P /etc/sysconfig/apm-sc
  838 ?        S      0:00 /usr/sbin/sshd
  871 ?        S      0:00 xinetd -stayalive -reuse -pidfile /var/run/xinetd.pid
  897 ?        S      0:00 lpd Waiting  
  927 ?        S      0:00 sendmail: accepting connections
  951 ?        S      0:00 /usr/sbin/httpd
  952 ?        S      0:00 /usr/sbin/httpd
  970 ?        S      0:00 gpm -t imps2 -m /dev/mouse
  988 ?        S      0:00 crond
 1042 ?        S      0:00 xfs -droppriv -daemon
 1078 ?        S      0:00 /usr/sbin/atd
 1092 ?        S      0:00 login -- salsi     
 1093 tty2     S      0:00 /sbin/mingetty tty2
 1094 tty3     S      0:00 /sbin/mingetty tty3
 1095 tty4     S      0:00 /sbin/mingetty tty4
 1096 tty5     S      0:00 /sbin/mingetty tty5
 1097 tty6     S      0:00 /sbin/mingetty tty6
 1100 tty1     S      0:00 -bash
 1149 tty1     S      0:00 /bin/sh /usr/X11R6/bin/startx
 1161 tty1     S      0:00 xinit /home/salsi/.xinitrc --
 1162 ?        S      0:09 X :0
 1166 tty1     S      0:00 fvwm2
 1174 tty1     S      0:00 /usr/X11R6/lib/X11/fvwm2/FvwmButtons 7 4 none 0 8
 1176 tty1     S      0:00 netmon -i ppp0 -smooth
 1177 tty1     S      0:00 wmmon
 1178 tty1     S      0:00 xclock
 1323 tty1     S      0:02 gnome-terminal 
 1330 tty1     S      0:00 gnome-pty-helper
 1331 pts/1    S      0:00 bash
 1386 pts/1    S      0:01 vim index.html
 2494 tty1     S      0:00 gnome-terminal 
 2501 tty1     S      0:00 gnome-pty-helper
 2502 pts/2    S      0:00 bash
 4512 tty1     S      0:00 /bin/sh /usr/bin/opera
 4513 tty1     S      0:04 /usr/lib/opera/6.02-20020701.3/opera
 7081 pts/1    S      0:00 /bin/bash -c (ps ax) < /tmp/salsi/trash/v64790/6 >/tm
 7088 pts/1    R      0:00 ps ax
Il comando pstree mette in evidenza l'albero genealogico dei processi, dal quale possimo dedurre chi ha avviato chi:
$ pstree -a -l -n -p -u
init,1)  
  |-(keventd,2)
  |-(kapmd,3)
  |-(ksoftirqd_CPU0,4)
  |-(kswapd,5)
  |-(bdflush,6)
  |-(kupdated,7)
  |-(mdrecoveryd,8)
  |-(khubd,82)
  |-(eth0,591)
  |-syslogd,666) -m 0 
  |-klogd,671) -x 
  |-apmd,783) -p 10 -w 5 -W -P /etc/sysconfig/apm-scripts/apmscript 
  |-sshd,838) 
  |-xinetd,871) -stayalive -reuse -pidfile /var/run/xinetd.pid 
  |   |-in.ftpd,8302)
  |   `-ipop3d,8416) 
  |-lpd,897,lp)   
  |-sendmail,927)
  |-httpd,951)
  |   `-httpd,952,apache)
  |-gpm,970) -t imps2 -m /dev/mouse 
  |-crond,988) 
  |-xfs,1042,xfs) -droppriv -daemon 
  |-atd,1078,daemon) 
  |-login,1092)      
  |   `-bash,1100,salsi) 
  |       `-startx,1149) /usr/X11R6/bin/startx 
  |           `-xinit,1161) /home/salsi/.xinitrc -- 
  |               |-X,1162,root) :0 
  |               `-fvwm2,1166) 
  |                   |-FvwmButtons,1174) 7 4 none 0 8 
  |                   |-netmon,1176) -i ppp0 -smooth 
  |                   |-wmmon,1177) 
  |                   |-xclock,1178)
  |                   |-gnome-terminal,1323)  
  |                   |   |-gnome-pty-helpe,1330) 
  |                   |   `-bash,1331) 
  |                   |       `-vim,1386) index.html 
  |                   |           `-bash,7231) -c ...
  |                   |               `-pstree,7238) -a -l -n -p -u 
  |                   |-gnome-terminal,2494)  
  |                   |   |-gnome-pty-helpe,2501) 
  |                   |   `-bash,2502) 
  |                   |       `-less,7193) /usr/local/bin/mypstree 
  |                   `-opera,4512) /usr/bin/opera 
  |                       `-opera,4513) 
  |-mingetty,1093) tty2 
  |-mingetty,1094) tty3 
  |-mingetty,1095) tty4 
  |-mingetty,1096) tty5 
  `-mingetty,1097) tty6 
Il comando top fornisce il quadro del carico della macchina, e mostra in cima ("top") i processi più onerosi:
$ top
 11:07am  up  3:26,  4 users,  load average: 0.00, 0.00, 0.00
53 processes: 51 sleeping, 2 running, 0 zombie, 0 stopped
CPU states:  0.6% user,  0.6% system,  0.0% nice, 98.8% idle
Mem:   255908K av,  241824K used,   14084K free,       0K shrd,    2684K buff
Swap:  136512K av,       0K used,  136512K free                  102548K cached

  PID USER     PRI  NI  SIZE  RSS SHARE STAT %CPU %MEM   TIME COMMAND
 1164 root      15   0  266M  10M  3684 S     0.8  4.2   0:44 X
    1 root      15   0   480  480   420 S     0.0  0.1   0:04 init
    2 root      15   0     0    0     0 SW    0.0  0.0   0:00 keventd
    3 root      15   0     0    0     0 SW    0.0  0.0   0:00 kapmd
    4 root      34  19     0    0     0 SWN   0.0  0.0   0:00 ksoftirqd_CPU0
    5 root      15   0     0    0     0 SW    0.0  0.0   0:00 kswapd
    6 root      25   0     0    0     0 SW    0.0  0.0   0:00 bdflush
    7 root      15   0     0    0     0 SW    0.0  0.0   0:00 kupdated
    8 root      25   0     0    0     0 SW    0.0  0.0   0:00 mdrecoveryd
   82 root      15   0     0    0     0 SW    0.0  0.0   0:00 khubd
  593 root      15   0     0    0     0 SW    0.0  0.0   0:00 eth0
  668 root      15   0   560  560   472 S     0.0  0.2   0:00 syslogd
  673 root      15   0   444  444   384 S     0.0  0.1   0:00 klogd
  785 root      15   0   480  480   424 S     0.0  0.1   0:00 apmd
  840 root      20   0  1228 1228  1020 S     0.0  0.4   0:00 sshd
  873 root      18   0   916  916   724 S     0.0  0.3   0:00 xinetd
  899 lp        15   0  1132 1132   976 S     0.0  0.4   0:00 lpd
  929 root      15   0  1816 1816  1300 S     0.0  0.7   0:00 sendmail
  953 root      15   0  1504 1504  1392 S     0.0  0.5   0:00 httpd
  954 apache    18   0  1572 1572  1444 S     0.0  0.6   0:00 httpd
  972 root      15   0   452  452   396 S     0.0  0.1   0:00 gpm
  990 root      15   0   616  616   540 S     0.0  0.2   0:00 crond
 1044 xfs       15   0  4376 4376   980 S     0.0  1.7   0:01 xfs
 1080 daemon    15   0   524  524   460 S     0.0  0.2   0:00 atd
 1094 root      15   0  1184 1184   960 S     0.0  0.4   0:00 login
 1095 root      15   0   400  400   344 S     0.0  0.1   0:00 mingetty
 1096 root      15   0   400  400   344 S     0.0  0.1   0:00 mingetty
 1097 root      16   0   400  400   344 S     0.0  0.1   0:00 mingetty
 1098 root      16   0   400  400   344 S     0.0  0.1   0:00 mingetty
 1099 root      16   0   400  400   344 S     0.0  0.1   0:00 mingetty
 1102 salsi     15   0  1272 1272  1000 S     0.0  0.4   0:00 bash
 1144 salsi     16   0  1024 1024   872 S     0.0  0.4   0:00 x
 1151 salsi     17   0  1020 1020   868 S     0.0  0.3   0:00 startx
 1152 salsi     15   0   520  520   456 S     0.0  0.2   0:00 tee
 1163 salsi     15   0   612  612   540 S     0.0  0.2   0:00 xinit
 1168 salsi     15   0  2228 2228  1384 S     0.0  0.8   0:00 fvwm2
 1176 salsi     15   0  1176 1176  1008 S     0.0  0.4   0:00 FvwmButtons
 1177 salsi     15   0  2320 2320  1880 S     0.0  0.9   0:00 xterm
 1178 salsi     15   0   656  656   580 S     0.0  0.2   0:00 netmon

7. Segmentazione e memoria protetta

I processore Intel 386 e seguenti, i processori Motorola PowerPC, i processori MIPS e altri ancora supportano il concetto di memoria protetta, di segmentazione, e di istruzioni privilegiate.

La memoria protetta è una soluzione hardware adottata dai processori per impedire ai processi di interferire tra di loro andando a scrivere su indirizzi di memoria arbitrari, con conseguenze fatali per il sistema.

La segmentazione è la tecnica hardware/software tipicamente adottata per mantenere traccia dello spazio di indirizzamento riservato a un processo. Per ogni processo, il sistema operativo mantiene una tabella delle aree di memoria cui esso ha accesso; per ogni istruzione eseguita dal processo, il processore verifica che l'accesso alla memoria rispetti la tabella dei segmenti assegnati. Se il processo, per errata programmazione o per deliberata intenzione del programmatore, tenta di accedere ad aree vietate della memoria, il processore interrompe il processo e genera un errore di segmentazione (SEGMENTATION FAULT).

Inoltre, il processore si può trovare di volta in volta in vari stati di esecuzione nei quali determinate istruzioni privilegiate possono o meno essere eseguite dal processo. I processori come l'i386 supportano fino a 4 modalità di privilegio, ma nei sistemi operativi come Unix, Linux e Windows ne vengono utilizzate solo due: la modalità kernel space e la modalità user space:

8. Eseguibili binari

Sono i file che hanno almeno un bit "x" attivo e sono in formato ELF. Sono prodotti dalla compilazione di qualche linguaggio ad alto livello e contengono codice macchina e informazioni varie per il collegamento dinamico alle librerie. La libreria più importante si chiama libc ed è l'interfaccia tra i programmi che girano nello spazio utente e il kernel.

Esempio di come si può produrre un eseguibile binario a partire da un sorgente C:

salsi@orso, ~ $ echo 'main() { printf("Ciao, mondo!"); }' > ciao.c

salsi@orso, ~ $ cat ciao.c 
main() { printf("Ciao, mondo!"); }

salsi@orso, ~ $ cc ciao.c -o ciao

salsi@orso, ~ $ ls -l ciao*
-rwxr-xr-x    1 salsi    users       13507 Jul 29 08:50 ciao
-rw-r--r--    1 salsi    users          35 Jul 29 08:49 ciao.c

salsi@orso, ~ $ ./ciao
Ciao, mondo!
salsi@orso, ~ $ file ciao*
ciao:   ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV),
	dynamically linked (uses shared libs), not stripped
ciao.c: ASCII text

salsi@orso, ~ $ ldd ciao
	libc.so.6 => /lib/i686/libc.so.6 (0x42000000)
	/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)

salsi@orso, ~ $

9. Eseguibili script

Sono i file che hanno almeno un bit "x" attivo e sono in formato testo; inoltre, il primi due byte del file devono essere obbligatoriamente la sequenza magica "#!" cui deve seguire il pathfile assoluto dell'interprete, terminato con il carattere di a-capo "Line Feed" (codice ASCII 10) come previsto dalle convenzioni sui file di testo Unix.

Esempio di come si può produrre uno script che visualizza il tradizionale saluto (Bash):

salsi@orso, ~ $ echo "#! /bin/bash" > hello

salsi@orso, ~ $ echo 'echo Hello, world!' >> hello

salsi@orso, ~ $ cat hello
#! /bin/bash
echo Ciao, mondo!

salsi@orso, ~ $ chmod +x hello

salsi@orso, ~ $ ls -l hello
-rwxr-xr-x    1 salsi    users          31 Jul 29 08:58 hello

salsi@orso, ~ $ ./hello
Hello, world!

Esempio di script PHP che fa la stessa cosa:

salsi@orso, ~ $ cat bye
#! /usr/bin/php
<? echo "Bye, bye..." ?>

salsi@orso, ~ $ chmod +x bye

salsi@orso, ~ $ ls -l bye
-rwxr-xr-x    1 salsi    users          41 Jul 31 08:19 bye

salsi@orso, ~ $ ./bye
X-Powered-By: PHP/4.1.2
Content-type: text/html

Bye, bye...
NOTA: 1 in quest'ultimo esempio in PHP, quando si esegue il programma l'interprete PHP genera in output anche l'header MIME per il protocollo HTTP, siccome per default il PHP suppone di stare rispondendo ad una richiesta CGI. Per evitare la generazione di queste righe basta aggiungere l'opzione -f dopo il nome dell'interprete.

NOTA 2: è possibile scrivere i propri script anche su altri sistemi operativi e poi inviare lo script al sistema Unix, impostare i flag x e infine eseguirlo. Oltre che molto lento e scomodo, questo modo di operare richiede di fare attenzione alla diversa convenzione degli a-capo tra i diversi sistemi operativi:

Unix: LF (10)
DOS, Windows: CR LF (10 13)
Macintosh pre-X: CR
Cosa succede se si avvia uno script scritto in un file di testo realizzato su di un sistema "straniero" e che usa la convenzione di a-capo di quel sistema?

10. Comunicare con i processi

Esistono diverse vie, alcune delle quali le abbiamo già viste:
Segnale  Valore Azione   Commento
-------  ------ ------   -----------------------------------
SIGHUP      1     t      Chiusura del terminale o morte del
                            processo controllante
SIGINT      2     t      Interruzione da tastiera (CTRL-C)
SIGQUIT     3     c      Quit da tastiera (CTRL-\)
SIGILL      4     c      Istruzione illegale 
SIGKILL     9     K      Kill signal
SIGSEGV    11     c      Violazione della segmentazione
SIGTERM    15     t      Richiesta di terminazione
                            (default signal per kill)
SIGUSR1    10     t      User-defined signal 1
SIGUSR2    12     t      User-defined signal 2
SIGCHLD    17     i      Child fermato o ucciso
SIGCONT    18            Continua se stoppato
SIGSTOP    19     S      Stoppa il processo
SIGTSTP    20     s      Stop da tastiera (CTRL-S)
SIGBUS      7     c      Bus error (bad memory access)
SIGWINCH   28     i      Window resize signal
Le lettere nella colonna "Azione" hanno il seguente significato (le lettere maiuscole indicano che il processo non può intercettare il segnale, e l'azione indicata viene perciò eseguita d'ufficio dal kernel):
t      Default action is to terminate the process.

i      Default action is to ignore the signal.

c      Default action is to terminate the process and dump
       core.

s      Default action is to stop the process.

S      Stop the process. Restart with signal SIGCONT.

K      Kill the process.

Umberto Salsi
Commenti
Contatto
Mappa
Home / Indice sezione
Still no comments to this page. Use the Comments link above to add your contribute.