Nella lezione precedente si è visto con un certo dettaglio quale sia la sequenza di boot eseguita dal sistema dall'istante dell'accensione del PC fino al momento in cui il sistema è pronto ad accettare i login degli utenti. La configurazione finale si sviluppa a vari livelli, partendo dal riconiscemento dei diversi dispositivi hardware di cui è fornito il sistema fino alla creazione dei servizi che permettono al sistema di essere effettivamente utilizzato.
Fra questi, il meccanismo di montaggio dei file-system è uno dei più importanti perchè consente una strutturazione delle informazioni accessibili del sistema arbitrariamente complessa. Inoltre è possibile mantenere una configurazione aperta, nel senso che il sistema riconosce il nuovo insieme di dati solo nel momento in cui il supporto fisico, su cui tali dati sono realmente registrati, è effettivamente presente (introduzione di floppy disk, cdrom, flash memory, ecc...).
La versatilità di tale meccanismo si basa sull'idea di poter sempre associare un dispositivo ad una directory di un file-system specificato, detto mounting point. Naturalmente si deve specificare, implicitamente od esplicitamente, qual'è la struttura effettiva delle informazioni contenute nel dispositivo, il cosiddetto file-system type in modo da poter associare, nel punto di montaggio l'opportuna interfaccia di conversione delle informazioni.
In questo modo è possibile navigare all'interno delle informazioni contenute nei vari dispositivi (che possono essere anche partizioni di dischi locali e remoti) in modo completamente uniforme, come se si stesse esplorando le sottodirectory e i file di una qualunque directory. L'eleganza della soluzione è tale da essere implementata in tutti i sistemi.
Il cuore dei Sistemi Operativi è sempre costituito dal suo nucleo, o kernel, il cui compito è quello di fornire alla sottostante piattaforma hardware quell'insieme di funzionalità che rendono l'elaboratore praticamente utilizzabile dall'utente finale.
Si parla allora di servizi che sono realizzati sia come programmi esterni che come chiamate di procedure interne al kernel. In entrambi i casi si utilizzano le chiamate di sistema le quali si basano sulla momentanea sospensione dell'attività corrente, ripresa dopo la fornitura del servizio.
Senza entrare nel dettaglio si può dire che un sistema operativo deve fornire un'interfaccia utente, che si basa su quello che costituisce lo scheletro del nucleo, ossia, la
al quale si aggiunge normalmente la gestione del file-system e della memoria virtuale. Inoltre, un sistema operativo deve provvedere alla gestione di tutti i dispositivi periferici (porte seriali e parallele, i bus USB, IDE o SCSI, schede grafiche, di rete, ecc...) che possono essere connessi all'elaboratore, compresa la connessione con altri elaboratori.
Ogni sistema operativo utilizza un proprio particolare metodo per generare tali funzionalità che possono ricondursi alla creazione di un certo numero di processi. Nel sistema operativo Unix tale soluzione è particolarmente evidente grazie alla manipolazione esplicita delle entità che rappresentano i programmi in esecuzione, detti perciò processi.
Ad esempio, con il comando
§ ps ax
il sistema Linux fornisce la seguente lista di informazioni
PID TTY STAT TIME COMMAND 1 ? S 0:04 init [5] 2 ? SW 0:00 [keventd] 3 ? SW 0:00 [kapm-idled] 4 ? SW 0:00 [kswapd] 5 ? SW 0:00 [kreclaimd] 6 ? SW 0:00 [bdflush] 7 ? SW 0:00 [kupdated] 8 ? SW< 0:00 [mdrecoveryd] 67 ? SW 0:00 [khubd] 394 ? S 0:00 syslogd -m 0 399 ? S 0:00 klogd -2 413 ? S 0:00 portmap 428 ? S 0:00 rpc.statd 480 ? S 0:00 /sbin/cardmgr 533 ? S 0:00 /usr/sbin/apmd -p 10 -w 5 -W -P /etc/sysconfig/apm-sc 582 ? S 0:00 /usr/sbin/automount --timeout 60 /misc file /etc/auto 594 ? S 0:00 /usr/sbin/atd 644 ? S 0:00 sendmail: accepting connections 657 ? S 0:00 gpm -t ps/2 -m /dev/mouse 669 ? S 0:00 crond 705 ? S 0:01 xfs -droppriv -daemon 736 tty1 S 0:00 /sbin/mingetty tty1 737 tty2 S 0:00 /sbin/mingetty tty2 738 tty3 S 0:00 /sbin/mingetty tty3 739 tty4 S 0:00 /sbin/mingetty tty4 740 tty5 S 0:00 /sbin/mingetty tty5 741 tty6 S 0:00 /sbin/mingetty tty6 742 ? S 0:00 /usr/bin/kdm -nodaemon 752 ? S 77:35 /etc/X11/X -auth /etc/X11/xdm/authdir/authdir/authfil 756 ? S 0:00 -:0 771 ? S 0:00 ksmserver --restore 850 ? S 0:00 kdeinit: dcopserver --nosid 852 ? S 0:00 kdeinit: klauncher 854 ? S 0:00 kdeinit: kded 857 ? S 0:00 artsd -F 10 -S 4096 862 ? S 0:00 kdeinit: kxmlrpcd 874 ? S 0:00 kdeinit: Running... 876 ? S 0:00 knotify 877 ? S 0:09 kdeinit: kwin 879 ? S 0:01 kdeinit: kdesktop 883 ? S 0:03 kdeinit: kicker 886 ? S 0:05 kdeinit: klipper -icon klipper -miniicon klipper 888 ? S 0:00 kdeinit: khotkeys 890 ? S 0:00 kdeinit: kwrited |
in cui ciascuna riga rappresenta uno dei processi sequenziali che, nel loro insieme, caratterizzano il funzionamento del sistema.
Init è il padre di tutti i processi. Il suo compito principale è quello di creare processi da uno script immagazzinato nel file /etc/inittab. Questo file ha, di solito, delle voci che fanno sì che init avvii getty su ogni riga nella quale possono loggarsi gli utenti. Controlla anche processi autonomi richiesti da qualsiasi sistema.
Un runlevel è una configurazione software del sistema che permette l'esistenza solo di un gruppo selezionato di processi. I processi avviati da init per ognuno di questi runlevel sono definiti nel file /etc/inittab. Init può trovarsi in uno degli otto runlevel, da 0 a 6 e S o s. Il runlevel è cambiato da un utente privilegiato lanciando /sbin/telinit, il quale invia un segnale appropriato a Init, indicandogli a quale runlevel passare.
I runlevel 0, 1 e 6 sono riservati. Il runlevel 0 è usato per fermare (halt) il sistema, il runlevel 6 è usato per riavviare (reboot) il sistema e il runlevel 1 è usato per portare il sistema nel modo single user. Il runlevel S non è realmente pensato per essere direttamente usato, ma piuttosto per gli script che sono eseguiti quando si entra nel runlevel 1. Per maggiori informazioni si consultino le pagine di manuale relative ai comandi shutdown e inittab.
Sono validi anche i runlevel 7-9, sebbene non realmente documentati. Ciò perchè le varianti di Unix "tradizionali" non li usano. Si tenga presente che i runlevel S e s sono di fatto lo stesso perchè internamente sono un alias per lo stesso runlevel. Si tratta, in effetti, di ciè che rimane quando fu scritto per la la prima volta sysvinit.
Dopo che init è stato lanciato come ultimo passo dell'avvio (boot) del kernel, cerca se nel file /etc/inittab c'è una voce del tipo initdefault (si veda inittab) con la quale viene specificato il runlevel iniziale del sistema. Se non c'è tale voce o non c'è il file /etc/inittab, è necessario immettere un runlevel dalla console del sistema.
Il runlevel S o s porta il sistema in modalità utente singolo (single user mode) e non necessita del file /etc/inittab. In modalità single user, viene lanciato /bin/sh sul dispositivo /dev/console.
Quando si entra in single user mode, init legge lo stato ioctl della console da /etc/ioctl.save. Se questo file non esiste, init inizializza la linea di collegamento a 9600 baud e con le impostazioni CLOCAL. Quando init lascia il single user mode, salva le impostazioni ioctl della console in questo file cosicchè possa riusarli nella successiva sessione in single-user.
Quando si entra per la prima volta in modalità multiutente, init esegue le voci boot e bootwait che permettono al filesystem di essere montato prima che un utente possa loggarsi. Poi sono eseguite tutte le voci corrispondenti al runlevel.
Quando avvia un nuovo processo, init per prima cosa guarda se esiste il file /etc/initscript. Se c'è, usa questo script per far partire il processo. Ogni volta che un processo figlio termina, init salva questa informazione e la ragione per la quale è morto nei file /var/run/utmp e /var/log/wtmp (se esistono).
Dopo aver avviato tutti i processi specificati, init aspetta che uno dei suoi processi figlio muoia, un segnale di powerfail, o fino a che non riceve un segnale da /sbin/telinit per cambiare il runlevel del sistema. Quando avviene una delle tre condizioni suddette, riesamina il file /etc/inittab. A questo file possono essere aggiunte nuove voci quando si vuole. Comunque, init aspetta sempre che avvenga una delle tre condizioni suddette. Per ottenere una risposta istantanea, si possono usare i comandi Q o q per risvegliare init e fargli riesaminare il file /etc/inittab.
Se init non è in single user mode e riceve un segnale di powerfail, saranno invocate apposite voci per il powerfail. Quando a init è richiesto di cambiare il runlevel, invia il segnale di avviso SIGTERM a tutti i processi che non sono definiti nel nuovo runlevel. Poi aspetta 5 secondi prima di terminare forzatamente questi processi tramite il segnale di kill SIGKILL. Si noti che init assume che tutti questi processi (e i loro discendenti) rimangano nello stesso gruppo di processi nel quale init li ha creati in origine. Se un qualsiasi processo cambia il suo process group non riceverà questi segnali. Questi processi devono essere terminati a parte.
/sbin/telinit è linkato a /sbin/init. Accetta un carattere singolo come argomento e segnala a init di effettuare le azioni appropriate. I seguenti argomenti servono come direttive a /sbin/telinit:
Il file inittab descrive quali processi sono avviati all'avvio e durante le normali operazioni. Init distingue tra diversi runlevel, ognuno dei quali può avere il suo insieme di processi da avviare. I runlevel validi sono quelli indicati in precedenza e compaiono una per riga secondo il seguente formato:
Le righe che iniziano con `#' sono ignorate.
Le azioni valide sono:
Il campo runlevel può contenere più caratteri per i diversi runlevel, p.es. &123 se il processo dovesse essere avviato nei runlevel 1, 2 e 3. Le voci ondemand possono contenere un A, B o C. I campi runlevel delle voci sysinit, boot e bootwait sono ignorati.
Quando si cambia runlevel, qualsiasi processo in esecuzione che non è specificato nel nuovo runlevel verrà terminato, prima con SIGTERM, e poi con SIGKILL.
Il primo esempio di tabella inittab è basato sulle originarie tabelle delle prime distribuzioni di Linux. In essa si vede che l'ultima fase del boot è determinata dall'esecuzione dei comandi contenuti in /etc/rc per poi lanciare getty sui terminali alfanumerici tty1-tty4.
# inittab per Linux id:1:initdefault: rc::bootwait:/etc/rc 1:1:respawn:/etc/getty 9600 tty1 2:1:respawn:/etc/getty 9600 tty2 3:1:respawn:/etc/getty 9600 tty3 4:1:respawn:/etc/getty 9600 tty4 |
|
Il secondo esempio proposto presenta una tabella inittab molto più elaborata in cui compaiono diversi runlevel (si vedano i commenti all'interno) nello stile delle tabelle inittab delle attuali distribuzioni di Linux.
# Livello in cui si entra id:2:initdefault: # Inizializzazione del sistema prima di ogni altra cosa. si::sysinit:/etc/rc.d/bcheckrc # I runlevel 0,6 sono halt e reboot, 1 e' il modo per la # manutenzione l0:0:wait:/etc/rc.d/rc.halt l1:1:wait:/etc/rc.d/rc.single l2:2345:wait:/etc/rc.d/rc.multi l6:6:wait:/etc/rc.d/rc.reboot # Cosa fare quando viene premuta la combinazione di tasti CTRL+ALT+DEL. ca::ctrlaltdel:/sbin/shutdown -t5 -rf now # Runlevel 2&3: getty sulla console, il livello 3 fa # anche getty sulla porta del modem. 1:23:respawn:/sbin/getty tty1 VC linux 2:23:respawn:/sbin/getty tty2 VC linux 3:23:respawn:/sbin/getty tty3 VC linux 4:23:respawn:/sbin/getty tty4 VC linux S:23:respawn:/sbin/uugetty ttyS2 M19200 |
Come si è già accennato ad inizio di lezione, un aspetto molto importante della configurazione iniziale del sistema e realizzata durante l'ultima fase del boot, è quello che si chiama il montaggio dei file-system. Come'è noto il sistema operativo UNIX mette a disposizione dell'utente un certo numero di file-system che possono risiedere localmente sulla propria stazione di lavoro oppure remotamente su server NFS.
Il comando mount, previa consultazione del file /etc/fstab esegue il cosidetto montaggio dei file-system. La tabella seguente ne è un esempio standard per le distribuzioni di Linux.
/dev/hda2 / ext2 defaults 1 1 /dev/hda5 /usr ext2 defaults 1 2 /dev/hda6 /home ext2 defaults 1 2 /dev/hda7 swap swap defaults 0 0 /dev/hda8 /var ext2 defaults 1 2 /dev/hda1 /mnt/dos fat32 noauto 0 0 /dev/fd0 /mnt/floppy auto noauto,owner 0 0 /dev/cdrom /mnt/cdrom iso9660 noauto,owner,ro 0 0 none /proc proc defaults 0 0 none /dev/pts devpts gid=5,mode=620 0 0 |
Come si può notare ciscuna riga specifica un dispositivo, a cui rimane associato un driver ed una partizione (per esempio, hda per il disco fisso con 5 per indicare una possibile partizione), la radice del file-system, tipicamente strutturata ad albero (per esempio, /usr), un tipo (per esempio, ext2) ed alcune informazioni supplementari.
Il Sistema Operativo UNIX mette a disposizione dell'utente un certo numero di linguaggi di comandi, ciascuno dei quali caratterizzato da un diverso livello di flessibilità e da differenti standard di riferimento. Fra questi i più noti sono la Bourne shell e la C shell, la prima essendo uniforme rispetto ai diversi dialetti di Unix (bsd4.2, SunOS, Solaris, Ultrix, Linux, Aix, SystemV, ecc...), la seconda potendo differire anche sostanzialmente fra piattaforme e versioni.
Caratteristica fondamentale di tutti questi linguaggi è la struttura generale dei comandi
a cui si aggiunge un elevata flessibilità nella gestione dei file, attraverso l'uso delle pipe e la ridirezione dei flussi di input e di output. A seconda delle necessità i dati possono essere fatti fluire da un comando all'altro, essere prelevati dall'input o da qualunque variabile, prodotti in output o concatenati ad altri file, ecc... Inoltre è disponibile un manuale in linea in grado di fornire con sufficiente livello di dettaglio informazioni su tutti i comandi disponibili relativamente alla piattaforma in uso.
Eseguendo i comandi
man sh man cshè possibile ottenere abbastanza informazioni per utilizzare correttamente la Bourne shell e la C shell, rispettivamente. Per quanto riguarda l'impiego dei comandi e l'accesso all'input/output valgono le seguenti osservazioni
Per quanto riguarda i costrutti di controllo questi sono diversi a seconda che si tratti di Bourne shell o C shell. La tabella che segue ne da la lista mettendo in evidenza analogie e differenze.
COSTRUTTO | BOURNE SHELL | C-LIKE SHELL |
Ciclo For |
for name in word do .... done |
foreach var (wordlist) .... end |
Alternativa-1 |
if command; then .... ; | if (expr) command |
Alternativa-2 |
if command; then .... ; elif command; then .... ; else .... ; fi |
if (expr) then .... else if (expr2) then .... else .... endif |
Assegnazione |
var=value var[n]=word |
set var = value set var[n] = word |
Selezione |
case word in pattern) .... ;; pattern) .... ;; .... esac |
switch (string) case label: .... breaksw endsw |
Ciclo While |
while list do .... done |
while (expr) .... end |
Si tenga, comunque, presente che nel caso della Bourne shell non esistono espressioni booleane in senso stretto da valutare, ma viene utilizzato il codice di ritorno associato all'esecuzione di ciascun comando o lista di comandi per ottenere il corretto valore true o false. Il comando che esegue i "test" nella Bourne shell può essere indifferentemente test oppure [ ... ].
Negli esempi che seguono sono riportati alcune caratteristiche salienti sia della Bourne shell che della C shell. In essi compaiono alcuni comandi di utilità generale il cui impiego e spiegazione dettagliata possono ottenersi consultando il manuale in linea con man <nomecomando>.
Il primo esempio proposto consente di determinare l'identificatore di processo associato all'esecuzione di un dato programma.
# fpid <progname> # fpid : find the process identifier associated # to a given running program # if [ "$#" -eq 0 ]; then echo "Usage: fpid <processname>" exit 1 fi # A=`ps -e | grep -e "\b$1\b" | grep -v grep` ; if [ "$A" == "" ] then echo "no such a process" ; exit 2 ; fi K=1 P=1 for ITEM in $A ; do let "M = $K % 4" ; if [ "$M" -eq 1 ]; then echo "<$P> : $ITEM" ; let "P = $P + 1" ; fi let "K = $K + 1" ; done exit 0 |
|
#! /bin/sh # # where cmd : where is (the path of) the cmd command # if [ "$#" -eq 0 ]; then echo "Usage: where <commandname>" exit 1 fi # opath=$PATH PATH=/bin:/usr/bin case $# in 0) echo 'Usage: where command' 1>&2; exit 2 esac for i in `echo $opath | sed 's/::/.:/ s/::/:.:/g s/:$/:./ s/:/ /g'` do if test -f $i/$1 then echo $i/$1 exit 0 fi done exit 1 |
|