Appunti di Sistema Operativi, Processi e Segnali
Appunti di Sistemi Operativi, Processi e Segnali.
1 Sistemi Operativi
L'evoluzione continua degli elaboratori in termini di velocità, capacità di calcolo e di interconnessione non è
accompagnata da un analogo incremento dei sistemi operativi che li gestiscono. L' evoluzione di questi ultimi è stata
molto più lenta ed è praticamente ferma ai concetti informatici sviluppati negli anni 80. In questo capitolo vengono
affrontati i concetti fondamentali sviluppati negli ultimi 50 anni riguardanti i sistemi operativi.
1.1 − Che cosa è un sistema operativo ?
In termini molto semplicistici si può affermare che
Un sistema operativo è l'insieme del software che gestisce l'hardware di un
elaboratore.
Dove per software si intende i programmi realizzati per essere eseguiti su un dato elaboratore dotato di una o più CPU
che usa un insieme definito di istruzioni macchina; mentre il termine hardware identifica l'insieme delle parti elettriche,
elettroniche, meccaniche e ottiche che costituiscono la parte fisica dell'elaboratore (la ferraglia). Il termine software
deve essere ulteriormente specificato, se riferito al sistema operativo, in quanto da esso deve essere escluso quello che
viene identificato con il termine firmware, che identifica la parte di software residente nei dispositivi hardware che
costituiscono dell'elaboratore (dispositivi di pilotaggio unità disco, dispositivi per la gestione video, dispositivi di
comunicazione quali modem, schede Ethernet ..), come pure la parte di software comunemente detta BIOS (Basic I/O
System) che inizializza le funzionalità di un elaboratore alla sua partenza. Approfondendo la definizione del sistema
operativo dal punto di vista dell' utente, esso è
L'insieme del software che rende semplice e conveniente la gestione di un
elaboratore ed utilizza l'hardware secondo modalità trasparenti all'utente
in modo di ottenere le massime prestazioni complessive.
In altri termini si dice anche che il compito del sistema operativo è quello di gestire le risorse di un elaboratore, dove con
il termine risorse vengono identificate principalmente i dispositivi hardware quali :
1.
Processori
1.
Memoria primaria (RAM e memoria virtuale)
2.
Memoria secondaria (dischi, nastri,...)
3.
Dispositivi di ingresso−uscita (terminali video, tastiera, hardware speciale di I/O)
4.
Dispositivi di comunicazione (porte seriali e parallele, modem, dispositivi di rete)
Prof. Augusto Costantini – Itis “Enrico Fermi” – Frascati
1
Appunti di Sistema Operativi, Processi e Segnali
Inoltre il sistema operativo deve disporre delle seguenti funzionalità :
1.
Implementazione dell'interfaccia utente (tipo testo, tipo grafico)
2.
Condivisione dell'hardware con altri utenti
3.
Condivisione delle informazioni (dati) tra diversi utenti
4.
Semplificazione delle operazioni di ingresso−uscita
5.
Recupero da errori
6.
Gestione degli utenti (account)
7.
Semplificazione delle operazioni parallele (per sistemi multi−CPU)
8.
Organizzazione interna dei dati per un accesso rapido e sicuro (File System)
9.
Gestione della comunicazione e sincronizzazione dei programmi in esecuzione
10. Gestione delle comunicazioni (interconnessione tra elaboratori)
1.2 − Evoluzione storica
I sistemi operativi si sono evoluti in un lasso di tempo che mediamente coincide con una decade (10 anni), detta anche
generazione a partire dal 1950 anno in cui nacque il primo sistema operativo. Tale scansione temporale è venuta meno
dal 1990 fino ai nostri giorni in cui sono stati effettuati solo miglioramenti dell'interfaccia utente passata da testo a
grafica e delle capacità di interconnessione come reti LAN e WAN (Internet), dell'incremento delle capacita della
memoria primaria e secondaria e della velocità di elaborazione.
1.2.1 Dal 1940−1950
In questa decade gli elaboratori non avevano un sistema operativo. Ogni operazione veniva effettuata manualmente,
programmando l'elaboratore mediante codici binari attraverso interruttori ed eventualmente perforatori di schede. Il
linguaggio di programmazione utilizzato era l'assembler.
1.2.2 La prima generazione 1950−1960
Il primo sistema operativo fu sviluppato dal General Motors Research Laboratories per l'elaboratore IBM
701. Questo primo sistema operativo consentiva di eseguire programmi in modo strettamente sequenziale con un piccolo
ritardo tra loro in modo di ottimizzare il tempo di utilizzo dell'elaboratore. Tale tipo di sistema operativo venne
identificato come sistema operativo con elaborazione a lotti (single−stream batch processing system).Con il termine
lotto si intende un insieme di programmi soggetti al processamento da parte dell'elaboratore.
1.2.3 La seconda generazione 1960−1970
In questo periodo i sistemi operativi erano ancora con elaborazione a lotti su elaboratori a singola CPU. Su essi si
potevano eseguire contemporaneamente più programmi, ogni programma caricato in memoria in esecuzione o meno
viene identificato con il termine lavoro (job). Si era visto che, in genere un programma in esecuzione, non usava tutte le
risorse disponibili nell'elaboratore e che mentre esso utilizzava una periferica di I/O, molto più lenta dell'unità di calcolo
centrale CPU, quest'ultima era praticamente inutilizzata . Quindi era possibile utilizzare questi tempi morti per far
avanzare un altro lavoro presente in memoria. Fu quindi sviluppato il concetto di multi−programmazione così definito:
Capacità di un sistema operativo di eseguire più lavori contemporaneamente
utilizzando i tempi morti dell'unità centrale quando un lavoro utilizza una
periferica di I/O.
In figura 1 viene schematizzata la struttura delle partizioni di memoria utilizzate da un sistema operativo
multi−programmato. Piu lavori vengono caricati ed il sistema operativo li esegue in modo da ottimizzare il tempo della
unità centrale CPU .
Prof. Augusto Costantini – Itis “Enrico Fermi” – Frascati
2
Appunti di Sistema Operativi, Processi e Segnali
Figura 1- Sistema Operativo multi-programmato.
Gli utenti, con questo tipo di sistemi operativi, non avevano nessuna interazione diretta con l'elaboratore; i lavori
venivano caricati in memoria da lettori di schede perforate o da nastri magnetici ed eseguiti sotto il controllo di un
'operatore. Ogni lavoro prevedeva la compilazione del programma (linguaggi utilizzati erano l'assembler, il Fortran) ed
eventualmente la sua esecuzione e stampa dei risultati su tabulali in carta, ogni errore che si verificava a tempo di
esecuzione provocava l'espulsione immediata del lavoro dal lotto. Il tempo medio di elaborazione per un programma era
dell'ordine di giorni.
Nel 1964 venne introdotto il sistema operativo OS/360 per gli elaboratori IBM 360 e i successivi 370 con la nascita dei
primi CED (Centri di Elaborazione Dati) nelle grandi aziende e istituzioni pubbliche.
Ulteriori miglioramenti vennero fatte per migliorare l'interazione diretta tra l'utente e l'elaboratore realizzando la
gestione ad utenti interattivi. In questo caso più utenti comunicano con l'elaboratore attraverso terminali collegati
direttamente con l'elaboratore ed il sistema operativo deve gestire tali interazione minimizzando i tempi di risposta ai
comandi inviati di quest'ultimi in modo che sembri che l'intero elaboratore sia disponibile. Nasce quindi l'elaborazione a
condivisione di tempo (time−sharing) che consiste nella :
Capacità di un sistema operativo di interagire direttamente con più utenti,
rispondendo
in
tempi
trascurabili
ai
comandi
inviati,
e
fornendo
l'impressione che tutte la capacità dell'elaboratore siano per essi
disponibili.
Diversi sistemi operativi furono sviluppati con le caratteristiche della multi−programmazione e della condivisione di
tempo, quali il sistema CTSS del MIT (Massachusses Institute of Technology) ed il TSS dell'IBM. Successivamente
venne prodotto il sistema operativo Multics come miglioramento del CTSS da parte del MIT e il sistema operativo
CP/CMS dell'IBM, che sfocierà verso il sistema operativo VM/370. Particolare attenzione occorre porre sul Multics
(1960) che fu il primo sistema operativo sviluppato con un linguaggio ad alto livello ( gli altri erano scritti in linguaggio
Assembler) e che fu preso a riferimento nello sviluppo del sistema operativo UNIX con il linguaggio C intorno agli anni
70 da parte di Ken Thompson e Kernigham Ritchie. In questi sistemi il tempo medio di elaborazione dei programmi
dell'utente sono ridotti a pochi minuti.
Prof. Augusto Costantini – Itis “Enrico Fermi” – Frascati
3
Appunti di Sistema Operativi, Processi e Segnali
In questi ultimi sistemi operativi venne introdotto una ulteriore caratteristica derivata dal concetto di memoria virtuale
definita come:
Capacità di un sistema operativo di eseguire un programma
utilizzando la memoria primaria in termini di indirizzi virtuali
utilizzando per la sua esecuzione una memoria RAM di dimensione
inferiore a quella da esso necessaria usando come estensione di
questa, quella presente su supporti di memoria secondaria (disco).
Per elaboratori destinati ad operare per la gestione degli impianti industriali , in questi anni, vennero sviluppati sistemi
operativi in tempo reale (real−time) in cui l'obiettivo principale del sistema operativo era di rispondere in tempi
trascurabili ad eventi esterni che provenivano da sensori hardware esterni.
1.2.4 La terza generazione 1970−1980
Nasce nel 1970 il sistema operativo UNIX sviluppato su elaboratore PDP−7 della Digital presso la Bell Laboratories da
Ken Thompson e Kernigham Ritchie mediante il linguaggio C, sistema operativo che sarà preso ad esempio di molti altri
sistemi successivamente sviluppati negli anni 80 basati su Personal Computer ( es. sistema operativo DOS) . I sistemi
operativi di questi anni sono sistemi operativi multi−modo, cioè operano in multi−programmazione, a condivisione di
tempo e in tempo reale. Nasce in questo periodo le prime comunicazioni su reti geografiche WAN (Wide Area Network)
con lo sviluppo del protocollo TCP/IP e su reti locali LAN (Local Area Network) con lo standard Ethernet sviluppato
dalla Xerox .
1.2.5 La quarta generazione 1980−1990
L'evoluzione tecnologica dei dispositivi elettronici digitali a larga scala di integrazione LSI consente di ridurre le unità
centrali di un elaboratore ad un solo chip. Nasce il microprocessore e con esso il primo Personal Computer (PC IBM con
processore INTEL 8086 anno 1980) che consentirà l'enorme sviluppo dell'informatica, rimasta confinata negli anni 70
nei soli Centri di Calcolo (CED), per ogni attività produttiva fino a permetterne l'uso a livello personale (Home
Computing). Nello stesso tempo nascono elaboratori destinati all'uso professionale nella ricerca scientifica e per
applicazioni industriali basate su processori RISC (Reduced Instruction Set Computing) dette comunemente stazioni di
lavoro (Workstation) . La diffusione dei Personal Computer e delle Workstation necessitano di sistemi operativi di facile
utilizzo ed elevata interattiìvità. Nasce il sistema operativo DOS per i PC, mentre viene utilizzato il sistema
operativo UNIX per le Workstation. L'aumento vertiginoso del numero di macchine di calcolo pone il
problema della loro interconnessione, cosi che nella meta degli anni 80, nascono i primi sistemi operativi di
rete :
Sistema operativo che attraverso connessioni rete condivide le sue risorse
(files, periferiche esterne di I/O).
In questi anni inizia la diffusione dei servizi basati sul protocollo TCP/IP quali Telnet (login remota), FTP (File
Transport Protocol), la posta elettronica (Mail) su reti geografiche; nasce Internet.
Prof. Augusto Costantini – Itis “Enrico Fermi” – Frascati
4
Appunti di Sistema Operativi, Processi e Segnali
1.2.6 Dal 1990 ai giorni nostri
Questa decade vede un miglioramento continuo delle prestazioni dei PC e delle Workstation in termini di velocità,
capacita di memoria primaria e secondaria. I sistemi operativi evolvono verso l'interazione grafica tra utente e macchina.
Nasce Windows e le sue versioni successive quali Windows 95 (anno 1995), Windows 98 (anno 1998), Windows 2000
(anno 2000), Windows XP (anno 2001) come pure la versione UNIX chiamata Linux (dal suo ideatore Linus Thorwald)
per i Personal Computer. Nasce il concetto di elaborazione distribuita definita come:
Capacità di un sistema operativo di gestire un insieme di
elaboratori connessi in rete in modo da utilizzare tutte le loro
risorse (CPU, memoria secondaria, periferiche) per l'esecuzione di
applicazioni
in
modo
coordinato
con
l'obiettivo
di
creare
l'illusione che esista un unico sistema di elaborazione operante in
time−sharing .
Un sistema operativo che implementa in parte tale concetto è NFS (Network File System) sviluppato dalla Sun
Microsystem's per le sue Workstation UNIX. Attualmente esso è implementato all'interno del sistema operativo UNIX .
Prof. Augusto Costantini – Itis “Enrico Fermi” – Frascati
5
Appunti di Sistema Operativi, Processi e Segnali
1.3 − Struttura dei sistemi operativi
1.3.1 Sistemi monolitici
Questi tipi di sistemi operativi, di cui un esempio tipico è il DOS (Disk Operative System), presentano una struttura
monolitica (Figura 2) in cui non esiste una struttura modulare per la gestione delle risorse hardware. Il sistema operativo
è costituito da una collezione di procedure dette chiamate di sistema (system call) che gestiscono le funzionalità
dell'hardware. Esse vengono identificate e chiamate tramite un numero che è l'indice di un elemento presente in una
tabella di smistamento il quale contiene l'indirizzo di memoria della procedura di servizio stessa. Il sistema operativo
DOS per i PC basati su processori INTEL 8086 e successivi realizza l'operazione di chiamata di procedure di sistema
attraverso l'istruzione assembler interruzione software INT n dove per il DOS n vale 21h (esadecimale) e il registro AL
viene precedentemente inizializzato con l'identificatore numerico della procedura da chiamare.
Figura 2- Sistema Operativo Monolitico
Riassumendo in sintesi le caratteristiche dei sistemi monolitici essi avranno:
1.
Un programma in esecuzione che richiede una procedura di servizio.
2.
Un insieme di procedure di servizio che realizzano le chiamate di sistema.
3.
Un insieme di procedure di utilità che aiutano le procedure di servizio a svolgere il proprio compito.
Prof. Augusto Costantini – Itis “Enrico Fermi” – Frascati
6
Appunti di Sistema Operativi, Processi e Segnali
1.3.2 Sistemi stratificati
Questi sistemi operativi presentano una struttura a strati (layer) che permettono l'interazione solo tra strati adiacenti. In
figura 3 è rappresentato un sistema operativo a 3 strati escludendo quello relativo all'hardware; il nucleo del sistema
operativo operante in una modalità detta kernel, comunica con l'hardware attraverso gestori specifici e con lo strato
superiore attraverso le chiamate di sistema con i programmi di utilità di gestione del sistema e la shell (testo o grafica)
che a loro volta comunica con i programmi utente quelli per lo sviluppo di applicazioni. ed i vari programmi di utilità
generale tutti operanti in modalità utente.
Figura 3- Sistema Operativo Stratificato
Analoga struttura, questa volta rappresentata in forma concentrica (figura 4) è quella usata per rappresentare la
stratificazione del sistema operativo UNIX
Figura 4- Sistema Operativo a Shell
La presenza di due modalità quella di esecuzione utente e kernel (privilegiata) serve a impedire l'uso di istruzioni
privilegiate (Assembler) da parte dei programmi realizzati dall'utente che potrebbero interferire con la gestione
dell'hardware da parte del nucleo del sistema operativo
Prof. Augusto Costantini – Itis “Enrico Fermi” – Frascati
7
Appunti di Sistema Operativi, Processi e Segnali
1.3.2 Sistemi virtuali
I sistemi operativi virtuali sono sistemi operativi emulati (imitazione) via software da parte di un sottosistema di sistema
operativo ospite. Un esempio di sistema virtuale è stato il VM/370 dell'IBM (anno 1970) che consentiva di creare
macchine multiple virtuali che emulavano tutte le funzionalità hardware utilizzando le risorse di un unico elaboratore
fisico . Un'altro esempio più attuale è dato dal sottosistema DOS implementato nel sistema operativo Windows In
quest'ultimo è possibile creare macchine virtuali a 16 bit che hanno tutte le funzioni di un Personal Computer con
sistema operativo DOS con una propria memoria RAM (640 kbyte dimensione standard) e che condividono il processore
con il sistema operativo ospitante (Windows) ed inoltre gestiscono l'hardware attraverso chiamate di sistema
DOS,emulate da quest'ultimo, in modo di mantenere la massima compatibilità con gli applicativi DOS pre−esistenti.
1.3.3 Modello cliente−servente (client−server)
Una delle funzionalità più evolute di un sistema operativo è costituita dalla possibilità di implementare in esso attraverso
scambio di dati tra processi operanti nella stessa macchina e tra processi operanti su macchine diverse ma dotate di
risorse di rete. Queste capacità sono basate sul modello cliente−servente (client−server) sul quale viene implementato il
meccanismo di comunicazione tra processi. Un processo è detto cliente (client) se chiede un servizio, attraverso scambi
di messaggi e di dati, ad un altro processo detto servente (server) che li fornisce. Il processo servente è un processo
specializzato a fornire determinati servizi nella figura 1.3.3.1 è schematizzato un sistema operativo che implementa
diversi server specializzati, quello per la gestione della memoria, dei file, dei processi e dei servizi WEB (server delle
pagine Web locali); i processi client chiedono attraverso opportune chiamate di sistema la connessione a un dato server
con il quale scambia messaggi (comandi) e dati secondo un insieme di regole (protocollo) noto ad entrambi.
Figura 5-Modello Client-Server
Figura 6-Modello Client-Server distribuito
Le stesse interazioni cliente−servente, vedi figura 1.3.3.2, possono essere effettuate tra diversi elaboratori che abbiano
disponibili risorse di rete e che comunichino attraverso un protocollo specifico (es. TCP/IP, UDP o altri).
Prof. Augusto Costantini – Itis “Enrico Fermi” – Frascati
8
Appunti di Sistema Operativi, Processi e Segnali
2 I Processi
Le prestazioni di un elaboratore dotato di un sistema operativo risiede principalmente nell'ottimizzazione dell'uso delle sue
risorse. La risorsa fondamentale è il tempo di CPU utilizzato nell'esecuzione di programmi; l'ottimizzazione dell'uso di tale
risorsa richiede al sistema operativo la gestione dell'esecuzione di programmi come entità che hanno una loro evoluzione
temporale in quanto esse vengono create, avanzate, bloccate e terminate. Se il sistema operativo è mono−programmato
(mono−task) un solo programma è in esecuzione mentre nei sistemi multi−programati (multi−task) più programmi sono
"contemporaneamente" in esecuzione attraverso il meccanismo di condivisione del tempo ( time−sharing) del tempo della
CPU.
2.1 − Definizioni di processo
Con il sistema operativo Multics (1960) nasce il termine processo per identificare un programma in esecuzione. Tale termine
ha anche ulteriori definizioni, intercambiabili tra loro :
•
Attività asincrona
•
L'entità a cui è stato assegnato un processore
•
Lo "spirito animato" di una procedura
2.2 − Lo stato di un processo in un sistema operativo multi−programmato a
condivisione di tempo
Un processo, in quanto entità che si evolve nel tempo, ha una sua evoluzione che viene identificata attraverso gli stati di un
processo rappresentati dai blocchi circolari del diagramma degli stati di figura 7 . Un programma che chiede di essere eseguito
diventa un processo mediante una azione di creazione (fork) da parte del sistema operativo. Tale azione ha successo se è
disponibile la memoria primaria necessaria per la sua esecuzione; al processo gli viene assegnato un descrittore numerico di
processo dettto PID (Process IDentificator), che identifica un'area informativa detta PCB (Process Control Block) in cui sono
contenute tutte le informazioni necessarie al sistema operativo per la gestione del processo. Tutti i processi vengono gestiti dal
sistema operativo prelevando le loro informazioni attraverso una tabella dei processi (process table).
Figura 7-Diagramma dello stato di un processo
Prof. Augusto Costantini – Itis “Enrico Fermi” – Frascati
9
Appunti di Sistema Operativi, Processi e Segnali
Gli stati fondamentali di un processo sono tre:
1.
Stato di pronto Stato in cui il processo ha disponibile la risorsa memoria ed è in attesa in una coda (coda dei pronti) della
risorsa CPU. Una modulo del sistema operativo chiamato smistatore (dispatcher) mediante l'azione smista (dispatch)
preleva in testa alla coda dei pronti un processo e lo assegna al processore modificando lo stato del processo da pronto ad
esecuzione.
2.
Stato di esecuzione Il processo in questo stato utilizza la risorsa CPU ed avanza nell'esecuzione delle sue istruzioni, se
esso non richiede operazioni di I/O dopo un tempo predefinito detto quanto di tempo rilascia la risorsa processore mediante
l'azione esaurito quanto (time−out) che salva tutti i registri del processore ed inserisce il processo in coda alla coda dei
pronti. Il processo nello stato di esecuzione, qualora richieda al sistema operativo l'azione di terminazione (exit), gli
vengono tolte tutte le risorse da esso utilizzate. Se il processo invece richiede l'uso di un operazione di I/O questa gli può
essere concessa o meno che quella risorsa sia disponibile. Nel caso di indisponibilità della risorsa di I/O (perché utilizzata
da un altro processo) il sistema operativo mediante l'azione richiesta di I/O (I/O) lo pone nello stato di bloccato su una data
risorsa di I/O (coda di attesa su una richiesta di I/O).
3.
Stato di bloccato Quando la risorsa di I/O per quel processo si rende disponibile, il sistema operativo mediante l'azione di
risveglio (wake−up) pone il processo nello stato di pronto inserendolo in coda alla coda dei pronti.
Il quanto di tempo, assegnato ad un processo nello stato di esecuzione, è un fattore costante definito nell'implementazione del
sistema operativo ed è dell'ordine dei milli−secondi.
Prof. Augusto Costantini – Itis “Enrico Fermi” – Frascati
10
Appunti di Sistema Operativi, Processi e Segnali
2.3 − La commutazione di contesto
Il contesto di un processo identifica tutte le informazioni necessarie al suo avanzamento. Queste informazioni coincidono
essenzialmente con i valori che assumono tutti i registri del processore ad un dato istante. Quando un processo lascia lo stato di
esecuzione avviene la commutazione di contesto con la quale si effettua il salvataggio del suo contesto e il caricamento del
contesto del nuovo processo che và nello stato di esecuzione. La commutazione di contesto viene forzata una volta esaurito il
quanto di tempo oppure quando, non ancora esaurito il quanto, un processo và nello stato di bloccato a causa di una richiesta di
I/O che non può essere servita dal sistema operativo.
2.4 − Informazioni relative un processo
Nel paragrafo precedente si è detto che il sistema operativo identifica un processo attraverso il PID, unico nell'ambito dei
processi gestiti dal sistema operativo, che individua un'area informativa detta PCB (Process Control Block). Questa area,
diversa, a seconda del tipo di sistema operativo, contiene informazioni fondamentali necessarie alla gestione dei processi;
queste verranno ulteriormente dettagliate nei paragrafi seguenti per il sistema operativo UNIX.
Le informazioni principali presenti nel PCB sono :
1.
PID del processo
2.
Stato del processo
3.
Puntatore al processo padre
4.
Puntatore/i dei processo/i figlio/i
5.
Puntatore per localizzare la memoria del processo
6.
Priorità del processo
7.
Puntatore alle risorse del processo
8.
Puntatore all'area di salvataggio dei registri del processore
2.5 − La gestione dei processi
Le operazioni generali per la gestione dei processi sono :
1.
Creazione di un processo
2.
Distruzione di un processo (terminazione forzata)
3.
Sospensione di un processo
4.
Ripristino di un processo
5.
Cambio della priorità
6.
Smistare (dispatch) un processo
7.
Blocco di un processo
8.
Risveglio di un processo
9.
Terminazione di un processo
Alcune di queste operazioni sono fatte internamente dal gestore dei processi del sistema operativo (3,4,8,7,8,9) mentre altre
possono essere fatte da programmi di utilità (1,2,5). L'operazione di creazione di un processo può essere realizzata secondo due
modalità fondamentali :
1.
Un processo ha sempre un processo padre e ha la possibilità di creare processi figlio. Struttura dei processi di tipo
gerarchico.
Prof. Augusto Costantini – Itis “Enrico Fermi” – Frascati
11
Appunti di Sistema Operativi, Processi e Segnali
2.
Un processo ha la possibilità di creare processi figlio che si evolvono in maniera indipendente da esso. Struttura dei
processi isolata.
La modalità 1 è quella più usata nei moderni sistemi operativi e prevede che se un processo termina, anche i processi da esso
creati (l'intera gerarchia), vengono forzati a terminare. Nel caso 2 invece ogni processo ha una vita indipendente dagli altri, se
termina il processo padre questo non ha nessuna influenza diretta sui processi figlio.
2.6 − Informazioni dei processi nel sistema operativo Unix
Entrando nei dettagli operativi della creazione e gestione dei processi nell'ambito del sistema operativo UNIX occorre
ulteriormente specificare le informazioni relative ad un processo presenti nella tabella dei processi.
I campi presenti in tale tabella sono:
1.
Campo di stato che identifica lo stato del processo
2.
Informazioni che permettono al kernel di allocare il processo e la sua area u (user)
3.
Identificatore dell'utente UID
4.
Identificatore del processo PID
5.
Parametri di schedulazione
6.
Campo enumeratore dei segnali inviati al processo ma non ancora gestiti.
7.
Contatori vari per il tempo di esecuzione e del tempo di utilizzazione delle risorse
L'area u (user) è accessibile solo in modalità kernel ed è composta dai seguenti campi :
1.
Puntatore al processo attualmente in esecuzione
2.
I parametri relativi alla chiamata di sistema corrente ed i valori di ritorno e relativi codici di errore.
3.
I descrittori di tutti i file aperti dal processo
4.
Parametri interni di I/O
5.
La directory e la radice (punto di montaggio) corrente utilizzata dal processo
6.
Nome del processo e limiti della dimensione dei file (quota)
Prof. Augusto Costantini – Itis “Enrico Fermi” – Frascati
12
Appunti di Sistema Operativi, Processi e Segnali
3 La gestione dei processi nel sistema operativo UNIX
I concetti teorici visti nel capitolo precedente trovano, nel sistema operativo UNIX, una chiara e semplice implementazione.
Attraverso esempi di programmi, scritti in linguaggio C sviluppati per la piattaforma Linux, vengono affrontate le operazioni
fondamentali sui processi.
3.1 − Creazione
La creazione dei processi nel sistema operativo UNIX avviene secondo diverse modalità; in particolare attraverso la chiamate
di sistema system , fork ed exec. Un programma viene eseguito (diventa un processo) scrivendo nella linea di comando della
shell il nome del file che ha attributi di esecuzione; la shell usa la chiamata di sistema system. Un programma viene eseguito
secondo due diverse modalità:
1.
Modalità diretta (foreground) In questa modalità i processo creato dalla shell (processo padre) interagisce con l'utente
attraverso i tre standard di ingresso,uscita ed errore da essa ereditati. La shell riprende il controllo dei tre standard di I/O
solo alla terminazione del processo.
[augusto@linux−v73 corso]$ pid
2.
Modalità nascosta (background) In questa modalità ancora gli standard di uscita e di errore vengono ereditati dal processo
figlio.Il processo viene creato posponendo il carattere & al nome del file eseguibile, la shell crea il processo figlio e
riprende il controllo immediato dei tre standard di I/O visualizzando il PID del processo creato.Gli standard di I/O
vengono condivisi tra i due processi (shell e processo creato).
[augusto@linux−v73 corso]$ pid &
[1] 12187
[augusto@linux−v73 corso]
Il processo creato secondo una delle due modalità viene identificato attraverso il suo PID (Process Identificator) un numero
intero che lo identifica in modo univoco nell'ambito del sistema operativo. Il sistema operativo UNIX gestisce i processi
secondo la struttura gerarchica per cui un processo ha sempre un processo padre definito dal PPID (Parent Process
Identificator) . Il programma pid.c compilato attraverso il comando :
[augusto@linux−v73 corso]$ cc −o pid pid.c
ed eseguito visualizza le informazioni (PID e PPID)
/* pid.c
Visualizza l'identificatore del processo corrente PID
ed l'identificatore del processo padre PPID */
#include <stdio.h>
#include <unistd.h>
int main ()
{
/* stampa il pid del processo */
printf("L'identificatore di processo ID e' %d\n", (int) getpid ());
/* stampa il ppid del processo padre*/
printf("L'identificatore del processo padre PPID e' %d\n", getppid ());
return 0;
}
Programma pid.c. Identificatori di processo.
Prof. Augusto Costantini – Itis “Enrico Fermi” – Frascati
13
Appunti di Sistema Operativi, Processi e Segnali
3.1.1 Creazione di un processo attraverso la chiamata di sistema system
Un processo può essere creato in diversi modi; uno di questi è quello dell'utilizzo della chiamata di sistema system che esegue
in una nuova shell (/bin/sh) un comando Unix. e ritorna dopo la sua esecuzione.
La funzione è definita nel file di inclusione stdlib.h, mentre il prototipo di tale funzione è:
int system(const char *string);
dove string contiene la stringa ASCIIZ del comando da eseguire.
La funzione ritorna −1 se c'è un errore nell'esecuzione del comando, nel caso contrario ritorna lo stato del comando eseguito;
mentre ritorna 127 se la shell (/bin/sh) non può essere eseguita.
Il programma riportato in seguito esamina le caratteristiche di tale chiamata.
/* system.c
Creazione di un processo attraverso la chiamata di sistema
int system(char * command);
*/
#include <stdlib.h>
int main ()
{
int retval;
/* visualizza il contenuto della directory radice */
retval = system("ls −l /");
/* La chiamata di sistema system ritorna:
retval = 127 se la shell non viene eseguita
retval = valore di ritorno del comando invocato
retval = −1 altro tipo di errore
*/
return retval;
}
Programma system.c. Uso della funzione system.
3.1.2 Creazione di un processo attraverso la chiamata di sistema fork
Il metodo fondamentale che il sistema operativo UNIX usa per la generazione di un processo figlio è realizzato attraverso
l'uso della chiamata di sistema fork .
Il prototipo della funzione e le costanti simboliche necessarie per la sua utilizzazione nella programmazione C sono definite
nei file di inclusione :
#include <sys/types.h>
#include <unistd.h>
Il prototipo è:
pid_t fork(void);
La funzione ritorna al processo padre il PID del processo figlio creato, mentre ritorna 0 al processo figlio La funzione fork
crea una copia identica dell'immagine del programma in esecuzione ereditando tutte le variabili e i descrittori dei file aperti. Il
codice per i due processi padre−figlio viene eseguito utilizzando il valore di ritorno della fork che è diverso per i due processi
attraverso l'uso dell'istruzione di selezione if.
Prof. Augusto Costantini – Itis “Enrico Fermi” – Frascati
14
Appunti di Sistema Operativi, Processi e Segnali
Il programma seguente, fork.c esamina in dettaglio tali concetti.
/* fork.c : creazione processo figlio */
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main ()
{
pid_t parent_pid;
parent_pid = getpid();
printf ("Il PID del processo principale e' %d\n",(int)parent_pid);
/* (1) crea un duplicato: processo figlio */
child_pid = fork ();
/* (2) Separa i due processi padre e figlio.
Se il valore di ritorno della fork() e' 0 viene eseguita la sezione codice per
il processo figlio in caso contrario quella del padre */
if (child_pid == 0)
{
/* (3) sezione codice del processo figlio */
printf ("Sezione del processo figli con PID %d\n",(int) getpid ());
printf ("Il PID del processo padre e' %d\n", (int) parent_pid);
}
else
{
/* (4) sezione codice del processo padre */
printf ("Sezione del processo padre con PID %d\n",(int) getpid ());
}
/* I due processi eseguono entrambi questa sezione di codice in modo autonomo */
return 0;
}
Programma fork.c . Uso della funzione fork .
3.1.2 Sincronizzazione tra processi attraverso la funzione wait
Per sincronizzare due processi il sistema operativo Unix utilizza la chiamata di sistema wait() che sospende
(blocca) il processo che realizza la chiamata (normalmente il processo padre).
Il processo padre riprende l'esecuzione quando il processo figlio termina.
La funzione di sistema wait() ha il seguente definizione:
#include <sys/types.h> /* file di inclusione */
#include <sys/wait.h>
pid_t wait(int *status); /* prototipo della funzione */
Prof. Augusto Costantini – Itis “Enrico Fermi” – Frascati
15
Appunti di Sistema Operativi, Processi e Segnali
L'argomento status, puntatore ad un intero, memorizza lo stato di uscita del processo figlio La funzione ritorna 0 su successo
o −1 su errore
Il programma seguente implementa la sincronizzazione tra due processi attraverso l'uso delle funzioni fork() e wait()
/* fork_wait.c*/
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main ()
{
pid_t parent_pid;
parent_pid = getpid();
printf ("Il PID del processo principale e' %d\n",(int)parent_pid);
/* (1) crea un duplicato: processo figlio */
child_pid = fork ();
/* (2) Separa i due processi padre e figlio.
Se il valore di ritorno della fork() e' 0 viene eseguita
la sezione codice per il processo figlio in caso contrario quella del padre */
if (child_pid == 0)
{
/* (3) sezione codice del processo figlio */
printf ("Sezione del processo figlio con PID %d\n",(int) getpid ());
printf ("Il PID del processo padre e' %d\n", (int) parent_pid);
}
else
{
/* (4) sezione codice del processo padre */
printf ("Sezione del processo padre con PID %d\n",(int) getpid ());
/* Aspetta per il completamento del processo figlio*/
wait(&child_status);
/* Determinazione dello stato di uscita del processo figlio*/
if (WIFEXITED (child_status)) /*figlio esce normalmente ? */
/* Estrazione dello stato del processo figlio */
printf("Il processo figlio termina normalmente con codice di uscita %d\n",
WEXITSTATUS (child_status));
else
printf ("Il processo figlio termina in modo anormale\n");
}
/* (5)I due processi (padre e figlio) eseguono entrambi questa
sezione di codice in modo autonomo */
return 0;
}
Programma fork_wait.c. Sincronizzazione tra processi.
Prof. Augusto Costantini – Itis “Enrico Fermi” – Frascati
16
Appunti di Sistema Operativi, Processi e Segnali
3.2 − Terminazione
Un processo termina in modo normale quando esso chiama esplicitamente la funzione di sistema exit() oppure
quando la funzione main() ritorna un valore 0 (corretta esecuzione).
Un processo può terminare in modo anormale attraverso segnali interni lanciati dal kernel per errori di esecuzione
quali:
1.
SIGBUS: errore del bus (errori su periferiche di I/O).
2.
SIGSEGV: violazione della segmentazione della memoria (indirizzamento di variabili al di fuori dell'area dati).
3.
SIGFPE: errore di float−point (errore del co−processore matematico: esempio divisione per zero)
oppure per segnalazione dell'utente mediante uso di CRTL−C (SIGINT: segnale di interruzione)) attraverso
la standard input (tastiera).
L'utente può inoltre utilizzare comandi esterni quali kill pid che invia il segnale SIGTERM (segnale di
terminazione che può essere catturato da programma) oppure dal comando kill −KILL pid che invia il
segnale SIGKILL (segnale che non può essere catturato).
A tempo di esecuzione un processo può abortire attraverso la chiamata di sistema:
#include <stdlib.h>
void abort(void);
che invia al processo stesso il segnale SIGABRT e che produce un file di nome core il quale può essere
utilizzato successivamente per la diagnosi dell'errore attraverso l'uso di un debugger simbolico.
Un' altra modalità utilizzata per la terminazione di processi è l'utilizzo della segnalazione attraverso l'uso
della funzione di sistema :
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid int signal);
in cui signal assume il valore SIGTERM.
3.4 − Priorità
Un processo viene schedulato da parte del kernel assegnando ad esso una data priorità , il cui valore di default è zero. Un
valore di priorità alto indica che il processo prende più tempo di CPU rispetto agli altri processi del sistema. La priorità può
essere cambiata attraverso l'uso del comando:
nice −n priorità comando
dove il valore numerico di priorità va da −20 (priorità più alta, i valori negativi possono essere assegnati solo dal super user) a
+19 (priorità più bassa).
Il comando nice senza argomenti fà vedere il valore corrente della priorità ereditata dal processo di shell.
Per esaminare il funzionamento del comando nice viene lanciata una nuova shell (bash) ed eseguito nuovamente il comando
nice viene visualizzata il valore della priorità, per tornare alla shell precedente usare il comando exit .
[augusto@linux−v73 corso]$ nice −n 10 bash
[augusto@linux−v73 corso]$ nice
10
[augusto@linux−v73 corso]$
Esempio di uso del comando nice.
Prof. Augusto Costantini – Itis “Enrico Fermi” – Frascati
17
Appunti di Sistema Operativi, Processi e Segnali
4 Comunicazione tra processi
Il sistema operativo Unix dispone di una serie di tecniche di comunicazioni tra processi che permettono lo scambio di
informazioni tra di essi. In questo capitolo vengono affrontati, attraverso esempi, i meccanismi di comunicazione che
utilizzano i segnali, le pipe e la memoria condivisa.
4.1 − Comunicazione con i segnali
I segnali sono utilizzati dal kennel per comunicare ai processi il verificarsi di eventi asincroni esterni. I segnali possono essere
utilizzati anche per la comunicazione tra processi attraverso la chiamata di sistema kill() vista nel capitolo precedente
I segnali vengono classificati il classi:
1.
Segnali relativi alla terminazione di un processo
2.
Segnali dovuti ad eccezioni indotte da un processo
3.
Segnali riguardanti a condizioni non recuperabili prodotte durante le chiamate di sistema
4.
Segnali provocati da una condizione di errore
5.
Segnali che hanno origine da un programma in modalità utente
6.
Segnali connessi ad interazione con terminali.
L'elenco dei segnali gestiti dal sistema operativo Unix è :
Segnale
Valore
Azione
Commento
SIGHUP
1
A
La linea sul terminale controllante è stata
agganciata (hangup) o il processo
controllante è morto
SIGINT
2
A
Interrupt da tastiera (CTRL−C)
SIGQUIT
3
A
Segnale d'uscita (quit) della testiera
SIGILL
4
A
Istruzione illegale
SIGTRAP
5
CG
Trappola per traccia/breakpoint
SIGABRT
6
C
Segnale d'abbandono della chiamata di
sistema abort()
SIGFPE
8
C
Eccezione di virgola mobile
SIGKILL
9
AEF
Uccide (kill) il processo
SIGSEGV
11
C
Riferimento di memoria non valido
Prof. Augusto Costantini – Itis “Enrico Fermi” – Frascati
18
Appunti di Sistema Operativi, Processi e Segnali
SIGPIPE
13
A
Pipe non valida: scrittura su una pipe priva di
lettori
SIGALRM
14
A
Allarme prodotto dalla chiamata di sistema
alarm()
SIGTERM
15
A
Segnale di termine
SIGUSR1
30,10,16
A
Primo segnale definito dall'utente
SIGUSR2
31,12,17
A
Secondo segnale definito dall'utente
SIGCHLD
20,17,18
B
Processo figlio fermato o terminato
SIGCONT
19,18,25
SIGSTOP
17,19,23
DEF
Ferma (stop) il processo
SIGTSTP
18,20,24
D
Stop digitato sul tty
SIGTTIN
21,21,26
D
Input da tty per un processo in background
SIGTTOU
22,22,27
D
Output da tty per un processo in background
Processo continua se fermato
Tabella 1- Tabella dei segnali
Tabella dei segnali Il significato delle lettere nella colonna «azione» è:
Azione
Significato
A
L'azione di default è di terminare il processo
B
L'azione di default è di ignorare il segnale
C
L'azione di default è "to dump core"
D
L'azione di default è di fermare il processo
E
Il segnale non può essere bloccato
F
Il segnale non può essere ignorato
G
Il segnale non è conforme allo standard POSIX.1
Tabella 2- Tabella delle azioni prodotte dai segnali.
Prof. Augusto Costantini – Itis “Enrico Fermi” – Frascati
19
Appunti di Sistema Operativi, Processi e Segnali
4.1.1 Gestione dei segnali
Il kernel gestisce i segnali nel contesto del processo che li riceve (deve essere in esecuzione). Il processo che li riceve esegue
le azioni di default della tablella di Fig . I segnali possono essere intercettati attraverso l'uso della chiamata di sistema signal()
che imposta una funzione specifica (signal handler) per la loro gestione. Un esempio di programma che fà vedere l'uso della
funzione signal() è riportato in Fig. 4.3; il programma lanciato in background termina solo dopo aver ricevuto 3 volte il
segnale SIGUSR1 inviato tramite la shell usando il comando:
kill −s SIGUSR1 pid
La chiamata di sistema utilizzata è definita da:
#include <signal.h>
typedef void (*sighandler_t)(int);
/* include file */
/* definizione del tipo sighandler_t come */
/*puntatore ad una funzione con argomento intero */
sighandler_t signal(int signum, sighandler_t handler); /* prototipo */
/* signal.c
lanciare il programma in background
ed esegure 3 volte (da shell) il comando kill −s SIGUSR1 pid */
#include <signal.h> /* definizione dei nomi dei segnali */
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
/* il tipo sig_atomic_t garantisce che l'istruzione di assegnamento
non puo' essere interrotta */
sig_atomic_t sigusr1_count = 0;
/* Signal−handler risponde al segnale SIGUSR1.
L'esecuzione del programma riprende dopo la gestione del segnale
*/
void handler (int signal_number)
{
/* conteggia il numero di segnali SIGUSR1 ricevuti */
++sigusr1_count;
}
int main ()
{
/* Indica al processo la gestione del segnale SIGUSR1 */
signal (SIGUSR1, handler);
/* ciclo di attesa su 3 segnalazioni */
while(sigusr1_count < 3);
/* ... */
printf("Il segnale SIGUSR1 e' stato lanciato %d volte.\n",sigusr1_count);
return 0;
}
Programma signal.c. Uso dei segnali.
Prof. Augusto Costantini – Itis “Enrico Fermi” – Frascati
20
Appunti di Sistema Operativi, Processi e Segnali
4.2 − Comunicazione con pipe
La pipe è uno dei metodi di comunicazione tra processi che utilizza le caratteristiche del file system Unix. La pipe realizza un
sistema di comunicazione bi−direzionale di tipo FIFO attraverso l'uso di due descrittori di file, uno per la lettura ed uno per la
scrittura. Vi sono due tipi di pipe: le pipe senza nome associato e le pipe con nome associato, in questi appunti si esaminerà
solo il primo tipo. In entrambi i casi la comunicazione tra i processi avviene attraverso l'uso delle funzioni classiche per la
gestione dei file quali la open, read , write e close. Nel caso delle pipe senza nome associato le funzioni open e close non
vengono utilizzate.
Figura 8 Pipe per la sincronizzazione tra processi.
La funzione di sistema per la gestione delle pipe senza nome associato è definita nel file di inclusione :
#include <unistd.h>
ed ha il seguente prototipo:
int pipe(int fd[2]);
Questa funzione restituisce nell'array fd[2] due descrittori di file rispettivamente :
fd[0] per la lettura
fd[1] per la scrittura
Il valore di ritorno della funzione è 0 se la chiamata ha successo, −1 se si verifica un errore.
Per verificare il comportamento e l'uso della pipe senza nome, è stato realizzato il seguente programma C di esempio
denominato eq2_pipe.c, che calcola le soluzioni di una equazione di 2° grado attraverso l'uso di due processi; il processo
figlio che calcola il discriminante e lo comunica tramite la pipe al processo padre che, aspetta la terminazione del processo
figlio e successivamente calcola e stampa le radici del polinomio.
/* eq2_pipe.c
uso di pipe per lo scambio di dati tra processi
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <unistd.h>
#include <math.h>
#include <stdlib.h>
int main (int argc, char *argv[])
{
pid_t child_pid;
int status;
int pipeDesc[2];
double a = 0.0;
double b = 0.0;
double c = 0.0;
Prof. Augusto Costantini – Itis “Enrico Fermi” – Frascati
21
Appunti di Sistema Operativi, Processi e Segnali
double discr=0.0;
double Re1,Re2,Im1,Im2;
if (argc < 4){
printf("Uso: eq2_pipe a b c\n");
exit(−1);
}
/* legge i coefficenti del polinomio dagli argomenti di linea */
a = atof(argv[1]);
b = atof(argv[2]);
c = atof(argv[3]);
/* crea la pipe */
if (pipe(pipeDesc) == −1){
printf("Impossibile creare pipe\n");
exit(−1);
}
printf ("Il PID del processo principale e' %d\n",(int) getpid ());
/* (1) crea un duplicato: processo figlio */
child_pid = fork ();
/* (2) Separa i due processi padre e figlio.
Se il valore di ritorno della fork() e' 0 viene eseguita la sezione
codice per il processo figlio
in caso contrario quella del padre
*/
if (child_pid != 0)
{
/* (3) sezione codice del processo padre */
printf ("\nSezione del processo padre con PID %d calcola le radici\n",(int) getpid ());
/* il processo padre attende la terminazione del figlio */
wait(&status);
/* il processo padre legge un dato dalla pipe */
if (read(pipeDesc[0],&discr,sizeof(discr)) != sizeof(discr))
perror("Errore: Impossibile leggere dalla pipe");
if (discr >= 0.0){
Re1 = (−b + discr)/(2 * a);
Re2 = (−b − discr)/(2 * a);
Im1 =Im2 = 0.0;
}
else{
Re1 =Re2 = −b/(2 * a);
Im1 = abs(discr)/(2 * a);
Im2 = −abs(discr)/(2 * a);
}
Prof. Augusto Costantini – Itis “Enrico Fermi” – Frascati
22
Appunti di Sistema Operativi, Processi e Segnali
printf("Il processo padre stampa le radici\n");
printf("Radice: x1=%f",Re1);
if (Im1 >=0.0)printf("+j%f\n",Im1);
else
printf("−j%f\n",Im1);
printf("Radice: x2=%f",Re2);
if (Im2 >=0.0)printf("+j%f\n",Im2);
else
printf("−j%f\n",Im2);
printf("Il processo padre termina\n");
}
else
{
/* (4) sezione codice del processo figlio */
printf("Sezione del processo figlio con PID %d calcola discriminante\n",(int) getpid ());
discr =(b * b) − 4.0 * a * c;
if (discr >= 0.0) discr = sqrt(discr);
else
discr =−sqrt(abs(discr)) ;
/* il processo figlio scrive un dato nella pipe */
if (write(pipeDesc[1],&discr,sizeof(discr)) != sizeof(discr))
perror("Errore: Impossibile scrivere sulla pipe");
printf("Il processo figlio termina\n");
}
/* I due processi eseguono entrambi questa sezione di codice in modo autonomo */
return 0;
}
Programma Eq2_pipe.c
Prof. Augusto Costantini – Itis “Enrico Fermi” – Frascati
23
Appunti di Sistema Operativi, Processi e Segnali
4.3 − Comunicazione con messaggi
La comunicazione tra due o più processi mediante messaggi viene realizzata utilizzando una coda di messaggi comune gestita
da un modulo di controllo del kernel del sistema operativo Unix (modulo message−passsing) Figura 8. Il messaggio inviato da
un processo nella coda dei messaggi può essere letto da un altro selezionandolo attraverso un identificatore di messaggio tra
tutti quelli presenti nella coda. Questo meccanismo di gestione, diversamente dalla pipe (utilizzata per far comunicare solo
due processi), consente la comunicazioni tra due o più processi permettendo la realizzazione di programmi client−server. I
processi devono collegarsi alla coda dei messaggi attraverso l'uso di una chiave numerica che la identifica in modo univoco e
avere i permessi opportuni per accedere ad essa.
Figura 8- Scambio di messaggi tra due processi.
Un messaggio è definito da una struttura C del tipo:
#define MSGSZ 256
typedef struct msgbuf {
long mtype;
char mtext[MSGSZ];
} message_buf;
Definizione della struttura C di un messaggio
Il campo mtype (sempre presente) definisce l'identificatore del messaggio, mentre il campo mtext è un array di caratteri di
dimensione definita dalla costante simbolica MSGSZ (in questo esempio un array di 256 caratteri) che può essere cambiata a
secondo della lunghezza del messaggio. La coda dei messaggi è una classica struttura FIFO nella quale oltre alle regole
classiche di inserimento in coda ed estrazione in testa, è possibile estrarre il messaggio all'interno della coda identificandolo
mediante un intero positivo maggiore di zero (identificatore di messaggio, vedere specifiche della funzione msgrcv() ).
Prima che un processo possa inviare o ricevere un messaggio, la coda deve essere inizializzata (operazione effettuata dal
processo server) ed identificata (attraverso la chiave numerica di identificazione) dai processi client. Queste operazioni
vengono effettuate attraverso la chiamata di sistema msgget(). Per inviare o ricevere i dati sono utilizzate le funzioni
msgsnd() e msgrcv() le quali operano in due modalità :
Prof. Augusto Costantini – Itis “Enrico Fermi” – Frascati
24
Appunti di Sistema Operativi, Processi e Segnali
1.
1. Bloccato In questa modalità (messaggio sincrono) il processo viene sospeso (bloccato) finché l'intero messaggio non è
stato ricevuto o inviato. Il processo rimane sospeso fino a quando non si verificano le seguenti condizioni:
a. Le chiamate di sistema msgsnd() o msgrcv() hanno successo.
b. Il processo riceve un segnale.
c. La coda dei messaggi è stata rimossa..
2.
2. Non bloccato In questa modalità (messaggio asincrono) il processo non viene sospeso durante l'operazione di invio o
ricezione del messaggio.
4.3.1 Inizializzazione della coda dei messaggi
La funzione di sistema che permette l'inizializzazione della coda dei messaggi è la msgget() che è definita in :
#include <sys/ipc.h>;
#include <sys/msg.h>;
ed ha il seguente prototipo :
int msgget(key_t key, int msgflg)
gli argomenti della funzione hanno il seguente significato:
•
key_t key
chiave di identificazione della coda oppure IPC_PRIVATE (chiave privata relativamente al processo corrente)
•
int msgflg
flags costituiti da OR tra costanti simboliche e permessi di accesso in ottale
Le costanti possono essere :
•
IPC_CREAT
crea la coda dei messaggi
•
IPC_EXCL
specifica che la coda deve essere creata in modo esclusivo, la chiamata di sistema fallisce la coda è stata in precedenza
creata (nel caso che più processi server provino a inizializzare la stessa coda
I permessi sono definiti il ottale nella forma 0ooo dove i campi o sono definiti dai diritti di accesso in formato binario r−w−x
per il proprietario, gruppo, gli altri.
La funzione ritorna con successo un intero maggiore di zero che identifica la coda dei messaggi, − 1 in caso di errore, vedi
esempio seguente.
int msgid;
key_t key = 123
int msgflg = IPC_CREAT | IPC_EXL |
0777;
if ((msqid = msgget(key, msgflg)) == −1)
{ perror("msgget: msgget ha fallito"); exit(1);
} else fprintf(stderr, "msgget ha avuto successo");
Esempio di chiamata a msgget().
Prof. Augusto Costantini – Itis “Enrico Fermi” – Frascati
25
Appunti di Sistema Operativi, Processi e Segnali
Qualora si voglia creare una chiave univoca è possibile utilizzare la funzione ftok() che crea una chiave in base ad una
specifica stringa in questo caso "/tmp":
msqid = msgget(ftok("/tmp",key), (IPC_CREAT | IPC_EXCL | 0777));
4.3.2 Invio e ricezione di messaggi
Le chiamate di sistema che permettono l'invio e la ricezione sono realizzate dalle due funzioni msgsnd()e msgrcv() i cui
prototipi sono:
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
/* invia un messaggio */
int msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
/* riceve un messaggio*/
Per msgsnd() e msgrcv():
l'argomento msqid deve essere un identificatore a una coda esistente. L'argomento msgp è il puntatore ad
una struttura che contiene il tipo di messaggio ed il messaggio stesso, la strutture seguente è un esempio di
messaggio:
struct mymsg {
long mtype;
char mtext[MSGSZ];
/* tipo di messaggio */
/* testo del messaggio di lunghezza MSGSZ */
}
Per msgsnd()e msgrcv():
L'argomento msgsz specifica la lunghezza in byte del solo testo del messaggio.
Per msgrcv():
L'argomento msgtype è il tipo di messaggio che può assumere i seguenti valori:
msgtyp
Significato
0
Il messaggio è letto dalla test della coda
>0
Il primo messaggio con tipo pari a msgtyp oppure, se nei flags è
presente la costante MSG_EXCEPT viene prelevato il primo
messaggio con tipo diverso da msgtyp.
<0
Viene letto il primo messaggio con tipo inferiore al valore assoluto
di msgtyp.
Prof. Augusto Costantini – Itis “Enrico Fermi” – Frascati
26
Appunti di Sistema Operativi, Processi e Segnali
Per msgsnd() e msgrcv():
L'argomento msgflg è costituito dall' OR logico tra le seguenti costanti :
msgflag
Significato
IPC_NOWAIT
Ritorna immediatamente se non esiste un messaggio di un
dato tipo. La variabile globale errno assume il valore
ENOMSG (Errore nessun messaggio).
MSG_NOERROR
Usato per troncare un messaggio il cui campo testo è
maggiore msgsz bytes.
4.3.3 Controllo della coda dei messaggi
La funzione msgctl() modifica i permessi e le altre caratteristiche di una coda di messaggi. Ogni processo cha possiede i
diritti di accesso alla coda può modificare il suo comportamento attraverso questa funzione.
La funzione msgctl() ha il seguente prototipo:
int msgctl(int msqid, int cmd, struct msqid_ds *buf )
L'argomento msqid è l'identificatore di una coda esitente.
I valori che può assumere l'argomento cmd sono definiti nella seguente tabella:
cmd
Significato
IPC_STAT
Preleva lo stato della coda memorizzandolo nella struttura puntata da buf
IPC_SET
Imposta l'ID del proprietario e del gruppo, i permessi e la dimensione (in
bytes) della coda definiti nella struttura struct msqid_ds
IPC_RMID
Rimuove la coda definita dal parametro msqid
La funzione msgctl() ritorna 0 su successo e −1 se si verifica qualche errore.
Prof. Augusto Costantini – Itis “Enrico Fermi” – Frascati
27