Settima lezione

UNIX e sue peculiarità

Esaminiamo ora brevemente alcune caratteristiche del sistema operativo installato sui PC del laboratorio, e che plausibilmente servirà per svolgere le future attività di tesi e di ricerca.

UNIX è un sistema operativo multiprocesso, multiutente, stratificato, a memoria virtuale e con supporto nativo di rete. Non ci interessa approfondire il significato di queste caratteristiche, che sono spiegate un po' meglio in questa nota. Per il momento ci interessa solo sapere che questa lunga espressione afferma in pratica che UNIX è un sistema operativo di uso generale adatto per molteplici applicazioni nella ricerca (dall'acquisizione di dati on-line alla loro analisi off-line, alla pura computazione). Il sistema garantisce inoltre una robusta protezione contro i misfatti degli utenti, dimostrandosi così un ottimo terreno di apprendimento.

Abbiamo già brevemente accennato al fatto che UNIX può contare su un'ulteriore importante caratteristica progettuale: la possibilità di gestire tutte le operazioni di accesso al sistema attraverso il filesystem. Dallo studio del Fortran abbiamo già appreso il comportamento dei file "normali" (sequenze di byte rese permanenti perché scritte su qualche sistema di memoria di massa). Vediamo ora qualche esempio significativo di file "anormali".

/dev e /proc

Ecco qualche file interessante che si può trovare nella directory /dev di un PC con Linux:

brw-rw----    1 root     floppy     2,   0 Apr 26  1999 /dev/fd0
lrwxrwxrwx    1 root     root            8 Feb 24  1999 /dev/floppy -> /dev/fd0
Questo è il device (a cui si accede attraverso le normali operazioni valide per i file, come open, seek, read, write, ioctl, close) che comunica direttamente col primo lettore di dischi floppy. Ovviamente a questo livello non esiste la nozione di filesystem: il dischetto è visto come una sequenza di blocchi (da qui la b), ciascuno di 512 byte.
brw-rw----    1 root     disk       3,   0 May 28  1997 /dev/hda
brw-rw----    1 root     disk       3,   1 May 28  1997 /dev/hda1
brw-rw----    1 root     disk       3,   2 May 28  1997 /dev/hda2
brw-rw----    1 root     disk       3,   3 May 28  1997 /dev/hda3
(eccetera...)
Attraverso questi device si accede invece al primo disco IDE installato sul PC, e rispettivamente alla prima, seconda, terza,... partizione presente sul disco.
crw-rw----    1 root     lp         6,   0 Apr 26  1999 /dev/lp0
crw-rw----    1 root     lp         6,   1 Apr 26  1999 /dev/lp1
Questi device permettono la comunicazione con la prima e la seconda porta parallela del PC (alla quale spesso può essere collegata una stampante).
crw-rw----    1 root     dialout    4,  64 Aug  1 23:45 /dev/ttyS0
crw-rw----    1 root     dialout    4,  65 Apr 26  1999 /dev/ttyS1
lrwxrwxrwx    1 root     root            5 Dec  3  1997 /dev/mouse -> ttyS0
Idem per le porte seriali. Vediamo che, in questo caso, sembra che ci sia un mouse collegato alla prima porta seriale. I mouse PS/2 usano invece /dev/psaux.
crw-rw-rw-    1 root     root       5,   0 Apr 26  1999 /dev/tty
Questo device invece non c'entra nulla con i precedenti, e dimostra come i device siano una porta di comunicazione diretta col sistema operativo. /dev/tty è collegato automaticamente al terminale (tastiera + dispositivo di output) che controlla il processo corrente. Che cosa provoca il comando echo "Hello" >/dev/tty ?
cr--r--r--    1 root     sys        1,   8 May 28  1997 /dev/random
cr--r--r--    1 root     sys        1,   9 Aug  1 23:45 /dev/urandom
Questi sono altri device interessanti, e forniscono numeri casuali. La sorgente della casualità è una serie di eventi asincroni interni al sistema, che vengono raccolti nel tempo. /dev/random fornisce un numero limitato di numeri casuali (basati sul numero limitato di eventi asincroni disponibili), mentre /dev/urandom fornisce un numero illimitato di numeri "pseudo"-random, nel senso che sono costruiti a partire dal seme costituito dal numero limitato di eventi asincroni disponibili.
crw-rw-rw-    1 root     sys        1,   5 May 28  1997 /dev/zero
Un device spesso utile (anche se a prima vista non sembrerebbe): fornisce una sequenza infinita di zeri.
crw-rw----    1 root     audio     14,   4 Apr 26  1999 /dev/audio
Per chi vuole un po' di intrattenimento, questo device riceve dati audio con compressione logaritmica (U-law) e li riproduce sulle periferiche audio del sistema, se presenti.
lrwxrwxrwx    1 root     root           13 Dec  3  1997 /dev/fd -> /proc/self/fd
lrwxrwxrwx    1 root     root            4 Dec  3  1997 /dev/stderr -> fd/2
lrwxrwxrwx    1 root     root            4 Dec  3  1997 /dev/stdin -> fd/0
lrwxrwxrwx    1 root     root            4 Dec  3  1997 /dev/stdout -> fd/1
Sempre più interessante: possiamo qui accedere a standard input, standard output e standard error del processo corrente. Ma che cos'è mai /proc/self/fd ?

/proc è una directory, presente solo su alcuni sistemi Unix (fra cui Linux), che permette di accedere attraverso il filesystem allo spazio di memoria virtuale utilizzato dai processi, ai file che i processi stessi hanno aperti, ed anche a tante altre informazioni relative al funzionanento immediato del sistema. Inutile dire che i file presenti nella directory proc non sono file "normali", ma sono molto utili. Ad esempio:

> cat /proc/meminfo
        total:    used:    free:  shared: buffers:  cached:
Mem:  64708608 62943232  1765376 39866368  1323008 14729216
Swap: 267771904 29421568 238350336
MemTotal:     63192 kB
MemFree:       1724 kB
MemShared:    38932 kB
Buffers:       1292 kB
Cached:       14384 kB
SwapTotal:   261496 kB
SwapFree:    232764 kB

oppure:

cat /proc/devices
Character devices:
 1 mem
 2 pty
 3 ttyp
 4 ttyS
 5 cua
 7 vcs
10 misc
14 sound
36 netlink

Block devices:
 1 ramdisk
 2 fd
 3 ide0
 8 sd
 9 md

Pipe

Un altro meccanismo che interessa l'accesso ai file ed è tipico di Unix è quello delle pipe, cioè la comunicazione di dati fra processi diversi, attraverso le normali funzioni di I/O, senza che i dati vengano fisicamente immagazzinati in un file nella memoria di massa.

Sappiamo già che ad ogni processo, non appena viene creato, sono collegati almeno tre canali di comunicazione, rappresentati dai file descriptor 0, 1 e 2:

Che succede allora, quando i processi vengono concatenati?

Processi e sottoprocessi

Un altro meccanismo la cui efficienza è stata fra le principali considerazioni progettali di UNIX è quello della creazione di sottoprocessi. Tutte le attività che avvengono nel sistema al di fuori del kernel sono infatti inserite in una gerarchia di processi, tutti generati all'origine da un unico processo "padre", init, contrassegnato dal numero 1.
Quando su utilizza il sistema attraverso la shell, vengono normalmente creati uno o più sottoprocessi per ogni comando che viene eseguito.

La creazione di un sottoprocesso è regolata da due chiamate di sistema, ovviamente accessibili dalla libreria C:

In questo viene dettagliato il meccanismo utilizzato ad esempio dalla shell per eseguire il comando cat /proc/meminfo.

argc, argv[]

Finalmente siamo in grado di capire cosa sono argc e argv[], che abbiamo sempre incluso nella definizione delle nostre funzioni main. Ogniqualvolta il sistema esegue un exec, trasferisce all'eseguibile che viene chiamato una serie di argomenti (trasferisce anche una serie di parametri, il cosiddetto environment, ma questa è un'altra storia...). Questi parametri sono sempre una serie di stringhe di caratteri, e possono essere utilizzati all'interno del programma. Proviamo ad esempio ad utilizzare queste informazioni modificando uno dei nostri ultimi esempi:

La possibilità di specificare gli argomenti di un programma sulla linea di comando facilita la composizione di comandi nella shell. I singoli eseguibili, la cui funzionalità individuale può essere molto semplice e modulare, vengono collegati da pipe. Nei sistemi UNIX sono già disponibili molti eseguibili semplici che si prestano a questo gioco di costruzione (ad esempio grep, sort, more, sed, eccetera).

Esercizio: Provare a collegare via pipe con utility di sistema qualcuno dei programmi già sviluppati.