Architettura dei sistemi operativi della famiglia win32

Transcript

Architettura dei sistemi operativi della famiglia win32
© M.Badella, G. Malnati, L. Tessitore 2002-14
LEZIONE 33
Come i servizi concorrenti e le altre funzionalità del SO sono implementate in Win32?
Architettura dei sistemi
operativi della famiglia win32
A.A. 2013-14
Programmazione di Sistema
Obiettivi
Spiegare la struttura dei sistemi operativi win32
• Descrivere come avviene la creazione e la
terminazione di un processo
• Spiegare il ruolo svolto dagli oggetti kernel per
la condivisione di informazioni tra processi
• Conoscere i meccanismi che regolano l'accesso
al file system
© M.Badella, G. Malnati, L. Tessitore 2002-14
•
Programmazione di Sistema
2
Sistema Operativo (1)
© M.Badella, G. Malnati, L. Tessitore 2002-14
•
Permette l’esecuzione concorrente di più
programmi
◦ fornisce un contesto per l’esecuzione
◦ ripartisce le risorse (CPU, dispositivi di I/O, file, ...) tra tutti
i programmi che le richiedono
◦ offre un meccanismo permanente di memorizzazione (file
system, registry, ...)
•
Cerca di impedire che il malfunzionamento di un
programma influisca sull’esecuzione degli altri
◦ fornisce ad ogni programma in esecuzione l’illusione di
controllare l’intera macchina
Programmazione di Sistema
3
Siccome eseguire I/O controllato è fondamentale, il SO riconosce
che in certe occasioni è possibile elevare il privilegio. Per fare questo si utilizza la modalità supervisore presente sulle CPU.
Per passare da user mode a supervisor mode, Windows utilizza delle system call la cui attivazione dipende dal SO. Con l'avvento del
Pentium 2 sono state introdotte una copia di istruzioni particolari
(sysenter e sysexit) che permettono il passaggio.
Passare da user mode a supervisor mode è un'operazione onerosa
in termini di tempo, principalmente perchè il passaggio avviene in
una modalità tale da evitare "impersonamenti". Questo perchè:
a) tutti i registri vengono sostituiti con altri registri che abilitano la
modalità privilegiata
b) facendo accesso al vecchio SP si copiano i parametri associati
nel nuovo stack
c) si validano i parametri passati
Al ritorno si fa lo stesso passaggio al contrario.
Sistema Operativo (2)
© M.Badella, G. Malnati, L. Tessitore 2002-14
•
Si basa sulla disponibilità di un supporto hardware
per distinguere due modalità di esecuzione
◦ modalità utente – esecuzione di un sottoinsieme delle
istruzioni offerte dalla CPU
◦ modalità supervisore – accesso illimitato a tutte le
funzionalità del sistema
•
•
Offre un insieme controllato e predefinito di punti
di accesso per eseguire, in modalità supervisore,
specifiche funzioni a supporto dell’esecuzione
(system call)
Il passaggio da una modalità all’altra comporta un
costo non trascurabile in termini di tempo di
esecuzione
Programmazione di Sistema
Di base il sistema operativo offre uno strumento per l'esecuzione
concorrfferti una serie di servizi aggiuntivi come la memorizzazione
permanente: il filesystem e il registry (database gerarchico chiave-valore) in cui le applicazioni possono salvare dati di configurazione.
Nel suo agire, l'SO è guidato da un principio fondamentale: impedire che il malfunzionamento di un programma abbia implicazioni sulla vita degli altri programmi in esecuzione. L'esecuzione di un programma è dunque confinata all'interno di una sandbox, sostanzialmente costituita da uno spazio di indirizzamento virtuale (ciascun
processo è convinto di avere a disposizione l'intera macchina, ma
non è così). Oltre a non uscire dallo spazio di indirizzamento, il programma utente non può fare ad esempio I/O in maniera diretta.
4
Come si può far questo senza impazzire? Ogni SO definisce i suoi
meccanismi e Windows fa nel seguente modo.
Invocazione del sistema
Applications
© M.Badella, G. Malnati, L. Tessitore 2002-14
Kernel32.dll
Gdi32.dll
User32.dll
Gli entry pointer del sistema operativo sono nascosti all'interno di un
insieme di librerie di tipo DLL presenti nel sistema operativo, che si
chiamano Kernel32.dll, Gdi32.dll, User32.dll. Queste librerie contengono una grande quantità di metodi dichiarati direttamente o indirettamente in Windows.h e dentro di sè prendono i parametri con la
signature opportuna rimappandoli in funzioni non documentate pubblicamente in NtDll.dll, la vera responsabile dell'ingresso in modalità
kernel.
In modalità kernel c'è un mucchio di moduli esposti in una maniera
un po' stramba: questo perchè sono funzioni che provvedono, oltre
che fare quello che richiesto, a propagare quello che è successo ad
altre parti. Questo fa sì che non ci sia un singolo scheduler nel sistema operativo.
User
Mode
NtDll.dll
Configuration
Managerd
Manag
Virtual
Mmenory
ry
Processes
& Threads
Local
Procedure Call
File
System
m
Driver
Secutiry
Monitor
Plug&Play
Manag
Manager
Object
Manag
Manager
I/O
Mngr
File System
Cache
System Service Dispatcher & Callable interfaces
Kernel
User
/GDI
Kernel
Mode
Graph
Graphics
Syste
System
Driver
Hardware Abstraction Layer
Programmazione di Sistema
10
Gestione dell’interazione
© M.Badella, G. Malnati, L. Tessitore 2002-14
•
Le funzionalità offerte dall’API win32 sono
accessibili attraverso le corrispettive funzioni
esposte dalle DLL di sistema
◦ I cui prototipi sono esposti, per lo più, nel file
<windows.h>
•
Spesso, le interazioni tra un programma applicativo
ed il sistema operativo non si esauriscono
nell’ambito di una singola chiamata a funzione…
Il problema di fondo è che spesso quando interagiamo col SO non
riusciamo a chiudere l'interazione nell'ambito di un'unica richiesta.
Scrivere su un file ad esempio richiede più operazioni. Sono presenti una serie di sessioni di lavoro in cui un programma applicativo chiede al SO di fare una sequenza di operazioni. Come facciamo a far
capire al SO che la cosa che stiamo chiedendo è la continuazione
di quella precedente? Win32 utilizza il concetto di "oggetto".
◦ …ma si estendono nel tempo e possono coinvolgere
numerose fasi
•
Occorre un meccanismo per denotare lo stato
corrispondente alle singole sessioni di lavoro
◦ Win32 utilizza il concetto di “oggetto”
Programmazione di Sistema
11
Oggetti e Handle
•
Un oggetto è una struttura dati che rappresenta
una risorsa del sistema, con il relativo stato
•
Un’applicazione non può accedere direttamente alla
risorsa di sistema che l’oggetto rappresenta, ma
utilizza una handle opaca che ne regola e protegge
l’accesso
© M.Badella, G. Malnati, L. Tessitore 2002-14
◦ Ad esempio un file, un thread o un’immagine
◦ Per garantire l’incapsulamento
• Fintanto che l’interfaccia di un oggetto viene mantenuta,
l’implementazione può essere modificata in modo trasparente
◦ Per motivi di sicurezza
Nella terminologia Win32, "oggetto" è una qualunque struttura dati
interna al SO che descrive una qualche entità (file, thread, immagine...). Noi non accediamo mai agli oggetti, perchè vivono in una zona di memoria inaccessibile all'utente.
Dato che non possiamo accedere agli oggetti, a livello utente si usa
l'identità degli oggetti ovvero degli handle (= numero a 32 bit che indica l'esistenza di un oggetto). Questo crea tutta una serie di vantaggi, in primis di sicurezza. Inoltre, fintanto che l'interfaccia rimane la
stessa, l'implementazione del SO può essere perfezionata e il codice utente rimane lo stesso.
Associate agli oggetti ci sono tutta una serie di condizioni (ACL: Access Control List) che specifica le azioni che un processo può effettuare sull'oggetto.
• Ciascun oggetto è dotato di una propria access control list (ACL)
che specifica le azioni che un processo può effettuare sull’oggetto
Programmazione di Sistema
12
Il concetto di oggetto dentro Windows si è in qualche modo specializzato in tre direzioni. Due di queste sono meno legate al SO, mentre la terza è quella di nostro interesse.
- oggetti utente: tipicamente quelli che hanno a che fare con la gestione delle finestre (bottoni, finestre...). Questo tipo di oggetti è globale nel sistema, quindi un'applicazione può conoscere la handle
di un'altra finestra e spedirgli dei messaggi.
- oggetti grafici: implementati dal motore GDI. Penne, brush, region
(porzioni di schermo), bitmap... Non c'è particolare livello di privilegio.
- oggetti kernel: modellano la parte più interna dell'esecuzione (gestione della memoria, esecuzione dei processi, identità thread, meccanismi di sincronizzazione, parti di comunicazione tra processi).
Tipologie di oggetti
•
Windows fornisce tre tipologie di oggetti
◦ Oggetti user
© M.Badella, G. Malnati, L. Tessitore 2002-14
Le funzionalità hanno a che fare con la gestione dell'I/O, caching,
oggetti (mutex, semafori, eventi...) e tante altre cose. A meno che
non si ha l'esigenza di scrivere un driver per una periferica, questo
insieme di caratteristiche non è visto direttamente, ma è riflesso
dalle funzionalità che le API espongono verso l'alto.
---------------------------------------------------------------------------------------------Come già detto, tutte le funzioni del SO sono esposte in windows.h,
da includere quando creo un'applicazione Win32. Ai tempi di Win 7
c'è stato un refactoring e qualcosa è andato fuori, poca roba.
• Per la gestione delle finestre
◦ Oggetti grafici (GDI)
• Per la gestione delle primitive grafiche
◦ Oggetti kernel
• Per la gestione della memoria, dell’esecuzione di processi, per
la sincronizzazione e la comunicazione fra processi (IPC)
Programmazione di Sistema
13
Questi oggetti esistono in ciascun processo che ne ha una sua copia
privata. Un processo può fare riferimento a un oggetto kernel di un
altro processo, chiedendo al SO di ottenere una copia della handle
dell'altro.
Devo però avere alcune indicazioni. Gli oggetti kernel, quando vengono creati, possono ricevere un nome. Se io conosco il nome di un
oggetto posso chiedere di collegarmi. Ovviamente non basta, in
quanto devo rientrare nella ACL (se chi ha creato l'oggetto è l'unico
a poterci accedere non ho la possibilità di andarlo a pescare). Alcuni nomi sono in qualche modo predefiniti, mentre altri sono scelti liberamente dal programmatore nell'ambito di un certo spazio.
La possibilità di scegliere queste cose permette di creare applicazioni fate di moduli distinti che mantengono una forma di comunicazione.
Oggetti Kernel
© M.Badella, G. Malnati, L. Tessitore 2002-14
•
Le handle ad oggetti kernel sono specifiche di
ciascun processo
◦ Qualsiasi processo può creare una nuova handle ad
un oggetto esistente purché
• Ne conosca il nome
• Ed abbia i diritti per accedervi
• Un’applicazione specifica i diritti di accesso all’atto della creazione
dell’oggetto o quando ne ottiene una handle
•
Tipi di oggetti kernel
◦ AccessToken, Event, File, FileMapping, Job, Mailslot,
Mutex, Pipe, Process, Semaphore, Socket, Thread, …
Programmazione di Sistema
14
Handle
•
© M.Badella, G. Malnati, L. Tessitore 2002-14
•
Permettono di fare riferimento, in modo indiretto ad un
dato oggetto
Ogni oggetto kernel mantiene al proprio interno un
contatore di utilizzo
◦ quando un processo inizia ad usare un dato oggetto il contatore
viene incrementato
◦ quando un processo rilascia l’oggetto, il contatore viene
decrementato: se torna a 0, l’oggetto viene distrutto
•
•
Un processo rilascia un oggetto kernel chiamando la
funzione
int CloseHandle(HObject)
Quando un processo termina, tutte le handle ancora
aperte vengono chiuse
(risentire su videolezione se poco chiaro)
15
In generale, all'interno di ogni processo c'è una handleTable e la
handle non è nient'altro che un indice nella tabella. Nelle righe ho:
- puntatore all'oggetto kernel
- insieme di regole che indicano che tipo di accesso è consentito
- flag ulteriori
Handle Table
Elenca gli oggetti kernel in uso in un dato
processo
• La handle costituisce l’indice in tale tabella
© M.Badella, G. Malnati, L. Tessitore 2002-14
•
Windows usa le informazioni all'interno per governare le cose che
succedono. Con duplicateHandle possiamo creare un secondo riferimento a un oggetto handle esistente che ha diritti di accesso differenti ("limitatamente alla scrittura, faccio una copia che fa un uso
particolare", o cose simili)
◦ non è esportabile ad altri processi
•
Per ogni voce, sono specificati
◦ puntatore all’oggetto
◦ tipo d’accesso consentito
◦ vari flag
•
Quando un nuovo oggetto entra in uso, viene
inserito nella prima riga libera della tabella
Programmazione di Sistema
16
Tutte le volte che noi invochiamo funzioni di sistema (anche indirettamente, con la _beginthreadex) del tipo CreateXXXX(...) viene
restituito l'handle dell'oggetto creato.
Tutte queste funzioni prendono tra i parametri il riferimento a un oggetto di tipo lpsecurity. Tipicamente se noi mettiamo NULL diciamo
che vogliamo ottenere il livello di sicurezza dell'utente attualmente
loggato, consentendo l'accesso pieno all'amministratore di sistema.
In caso contrario dobbiamo creare un oggetto di tipo security e poi
passare il puntatore.
Creazione di oggetti kernel (1)
•
Insieme di funzioni del tipo CreateXxx(…)
•
Per ogni oggetto è possibile specificare gli
attributi di sicurezza associati
© M.Badella, G. Malnati, L. Tessitore 2002-14
◦ restituiscono la handle dell’oggetto creato
◦ NULL indica il comportamento di default (oggetto
accessibile al creatore ed all’amministratore del
sistema)
•
Quale che sia il tipo di oggetto kernel che andiamo a considerare, ci
riferiamo a questo oggetto tramite handle. La handle non è nient'altro che un numero a 32 bit ed è un indice in una tabella. Quando
Windows crea un processo, in una zona non accessibile a livello
user mette gli oggetti kernel riempiendo l'array in ordine. La handle
è l'indice all'interno dell'array. Quando un oggetto kernel non viene
più usato, Windows può liberare una locazione nella tabella: conoscere un handle non dà nessuna indicazione ad altri processi di
un dato oggetto kernel.
Quando io chiedo di avere accesso all'oggetto kernel di un altro processo, Windows verifica se la cosa è fattibile, fa una copia e dà una
handle che non ha nulla a che fare con quella originale (anche se
ovviamente c'è un legame). Siccome l'oggetto kernel consuma risorse, Windows tiene un contatore dei riferimenti per capire quanti conoscono l'oggetto che viene rilasciato quando il contatore raggiunge
0. Il contatore diminuisce di 1 chiamando la CloseHandle. Il contatore all'inizio è settato a 2.
Se debitamente configurate, alcune handle
possono essere ereditate dai sottoprocessi
◦ Che le ritrovano già presenti nella propria
HandleTable all’atto del proprio avvio
Programmazione di Sistema
17
Mentre in UNIX è molto forte il concetto processo padre - processi
figli (con figli zombie e menate varie), in Windows tale relazione è
più debole. L'unico punto in comune è nella ereditarietà della handle:
di base, le handle non sono ereditabili MA in certe situazioni posso
creare handle, marcarle come ereditarie e poi creare processi figli.
A quel punto, il processo figlio si trova nella lista dei suoi handle un
riferimento all'oggetto ereditato. Ovviamente deve saperlo, altrimenti se ne fa poco. Non tutte le handle sono ereditabili.
(spiegazione delle tre slide)
Quando all'interno di un processo eseguiamo una CreateXxxx(...)
succede che nella parte protetta viene allocato un oggetto kernel,
l'indirizzo viene posto nella HandleTable e la posizione viene ritornata come handle. Se abbiamo associato un nome è possibile che
quello stesso processo (o un altro che ne conosca il nome) è possibile invocare OpenXxxx(...) passando il nome. A quel punto viene
creata una seconda handle che fa riferimento al medesimo oggetto
kernel. Questo è particolarmente utile tra processi differenti.
Creazione di oggetti kernel (2)
© M.Badella, G. Malnati, L. Tessitore 2002-14
Processo
Oggetto
kernel
CreateXXX
Spazio di
indirizzamento virtuale
Crea un oggetto kernel
con la funzione CreateXXX
Programmazione di Sistema
18
Creazione di oggetti kernel (3)
© M.Badella, G. Malnati, L. Tessitore 2002-14
Processo
Oggetto
kernel
Handle1
Spazio di
indirizzamento virtuale
La funzione CreateXXX
restituisce all’applicazione una
handle all’oggetto
Programmazione di Sistema
19
Gestione di handle multiple
© M.Badella, G. Malnati, L. Tessitore 2002-14
Processo
Oggetto
kernel
Handle1
OpenXXX
Handle2
Spazio di
indirizzamento virtuale
L’applicazione ottiene
un’ulteriore handle all’oggetto
utilizzando OpenXXX
Programmazione
ne di
d Sistema
20
Tutte le volte che invochiamo una funzione di sistema sappiamo che
è possibile andare incontro a fallimenti. Questo perchè magari ci
mancano i privilegi, perchè abbiamo sbagliato i parametri, perchè il
sistema non può eseguire l'azione.
Gestione degli errori (1)
•
Ogni richiesta al sistema operativo può fallire
© M.Badella, G. Malnati, L. Tessitore 2002-14
◦ Le cause possono essere legate alla richiesta (parametri
errati, privilegi insufficienti) o alla carenza di risorse da
parte del sistema
•
Fondamentale da capire: quando chiamiamo una funzione di sistema attraversiamo la barriera del SO (e quindi viene congelato lo
stack, vengono sostituiti i registri della CPU e quant'altro) e quindi
non è possibile utilizzare il meccanismo delle eccezioni. Serve dunque un meccanismo diverso. Purtroppo ogni funzione di sistema,
per motivi storici, si è inventata un modo diverso per dire che non è
riuscita a fare quello che è richiesto. Quindi tocca leggere la guida.
Normalmente si può guardare il tipo ritornato. Già con la handle
abbiamo qualche ambiguità. Solo in caso di LONG/DWORD tocca
documentarsi benissimissimo.
Esempio tipico: waitForSingleObject.
Ogni funzione ha un proprio modo di segnalare il
fallimento, in funzione del tipo di dato ritornato
Tipo ritornato
Condizione di errore
BOOL
FALSE
PVOID
NULL
HANDLE
NULL o
INVALID_HANDLE_VALUE
LONG/DWORD
Occorre leggere la
documentazione
Programmazione di Sistema
21
Per tutte le funzioni che non tornano il codice d'errore in modo esplicito, occorre invocare la funzione GetLastError(). Notare che questo
va fatto il prima possibile. Se facesi altre azioni che comportano
interazione col sistema, queste altre azioni resettano il valore della
variabile d'errore e quindi non capisco più cosa è andato storto.
Una volta che chiamo GetLastError() me ne viene poco. Per capirlo
devo usarla in combinato con FormatMessage() che assomiglia un
po' alla printf e stampa un messaggio human readable.
Gestione degli errori (2)
© M.Badella, G. Malnati, L. Tessitore 2002-14
•
La funzione GetLastError() restituisce il codice
dell’ultimo errore verificatosi nel thread
corrente
◦ Occorre chiamarla non appena si rileva la presenza di
un errore, altrimenti c’è il rischio che venga
sovrascritta
•
La funzione FormatMessage(…) permette di
tradurre il codice di errore in un messaggio
localizzato nella lingua dell’utente del sistema
operativo
Programmazione di Sistema
22
Processi.
Tutte le volte che noi abbiamo un programma in esecuzione abbiamo un processo. In Windows un processo rappresenta il contesto
di esecuzione dei nostri programmi. Creare un processo significa
creare uno spazio di indirizzamento virtuale all'interno del quale
venga creato un thread che inizia l'esecuzione a partire da un entry
point specificato. Lo spazio di esecuzione virtuale è un insieme di
tabelle che indicano come la memoria virtuale corrisponda alla memoria fisica: questa corrispondenza è governata dalla MMU (negli
X86) e fondamentalmente vede un meccanismo di accesso con
doppia indicizzazione (vedi appunti di ASE a livello di GDT e memoria virtuale). Le singole pagine a cui si fa riferimento sono descritte mediante struttura dati che indica che tipo di informazione c'è nella pagina (codice, stack, dati) e altri permessi.
Cos’è un Processo
In modo informale, un processo è un programma in
esecuzione
Più formalmente, un processo costituisce il contesto di
esecuzione di un programma
•
© M.Badella, G. Malnati, L. Tessitore 2002-14
•
◦ corrispondenza tra memoria virtuale e fisica
◦ utilizzo dello spazio di indirizzamento virtuale:
• codice
• stack dei thread
• heap
◦ Per ogni processo, viene mantenuto un apposito descrittore a
livello kernel (kernel object)
•
•
•
•
Descrizione del mapping
Attributi di sicurezza
Contatore di utilizzo
…
Programmazione di Sistema
23
Ogni volta che creiamo un processo (= oggetto kernel di tipo Process) l'oggetto contiene il thread principale. Quando questo parte
può creare altri thread.
Occhio: le librerie di Windows permettono anche di fare operazioni
un po' strane. C'è un API che consente di creare un thread in un altro processo. A quel thread possiamo passare un parametro di una
DLL da mappare. Questo serve a iniettare virus :) e probabilmente
ha anche usi più consoni (tipo l'upgrade di sistema). Va detto che
all'inizio era una breccia aperta, ora è un po' più complicato ma comunque fattibile.
Processi e Thread
© M.Badella, G. Malnati, L. Tessitore 2002-14
•
Ad ogni processo è associato almeno un thread
(primary thread)
◦ il thread è l’unità di base a cui il sistema operativo
assegna la CPU
◦ è il thread che esegue il codice associato al processo
e non il processo stesso che di per sé è inerte!
Un thread può creare altri thread all’interno
dello stesso processo
• Il S.O. stabilisce l’ordine d’esecuzione dei thread
(scheduling)
•
© M.Badella, G. Malnati, L. Tessitore 2002-14
Programmazione di Sistema
24
In ciascun processo quello che noi vediamo è un insieme di cose:
- un po' di oggetti kernel
- uno spazio di indirizzamento in cui c'è
* codice eseguibile
* strutture dati (unico heap, tanti stack quandi thread)
* DLL
Processo m + ...
Address Space
Processo m + 2
Address Space
Processo m + 1
Dynamic Memory
Address Space
Processo m
Allocation
Spazio di Dynamic Memory
Allocation
(Heap,
Thread Stacks)
indirizzamento
Dynamic Memory
Allocation
Kernel(Heap, Thread Stacks)
DLL
(Heap, Thread Stacks) Executable
Kernel Object
Code
Executable
KernelObject
Heap,
Code
Object Thread Stacks
Executable
Oggetti
Kernel
Programmazione di Sistema
Quindi posso avere più di un thread perchè se lo crea lui o perchè
glielo fa creare qualcun altro.
Code
Executable
Code
25
Per questo motivo, a livello user si può accedere solo ai 2GB inferiori
(e nemmeno tutti, in modo da fare detection dei null pointer). Tutti gli
indirizzi con bit più significativo = 1 sono marcati RPL = 0 (solo il
kernel può fare riferimento a quegli oggetti). Normalmente, tutti
i 2GB superiori sono condivisi tra tutti i processi in modo che Windows ha sempre i pezzi al suo posto. Su certi vincoli di applicazioni
può essere un vincolo grande: inizialmente non c'era soluzione, ma
col passaggio a 64 bit è andato tutto bene. Tra parentesi quadre sono indicati gli spazi utente e kernel a 64 bit. Sbilanciatissimo per
Windows, ma con 8192GB ci navigo.
La quantità enorme di spazio che mantiene il SO è alla base dei
meccanismi di sicurezza, evitando lo scanning: anche se riuscissi
ad accedere allo spazio kernel, le informazioni sono sparpagliate
su uno spazio enorme.
Organizzazione della
Memoria Virtuale (32 bit)
© M.Badella, G. Malnati, L. Tessitore 2002-14
0xFFFFFFFF
Oggetti e codice kernel
2 GByte
RPL=0
[18 EB]
0x7FFFFFFF
0x7FFEFFFF
Guardia superiore
Spazio privato di ogni
applicazione win32
Dll condivise, file mappati
in memoria
2 GByte
RPL=3
[8192 GB]
RPL=0
0x0000FFFF
0x00000000
Guardia per puntatori nulli
Programmazione
Sistema
64diKByte
26
Quando si crea un processo? Quando clicchiamo su un'icona viene
invocata la CreateProcess, responsabile della attivazione di un nuovo processo e del relativo thread principale.
Quando un processo termina? Ad esempio quando il thread principale ritorna al suo chiamante (questo perchè la libreria di startup
include nella funzione che invoca il metodo main la chiamata finale
a exit process). Di fatto un processo può terminare anche prima se
uno qualunque dei thread invoca exit process. Un processo può terminare anche se un thread di un altro processo invoca la terminate
process (a patto di avere i diritti per farlo). Può terminare anche se
tutti i suoi thread terminano.
Ciclo di vita di un processo
•
Creazione
•
Terminazione
© M.Badella, G. Malnati, L. Tessitore 2002-14
◦ CreateProcess
◦ il main del primary thread ritorna al loader del S.O.
◦ un thread del processo esegue ExitProcess
◦ un thread di un altro processo esegue una
TerminateProcess
◦ tutti i thread del processo terminano la loro esecuzione
•
Valori d’uscita del processo
Un processo termina ritornando un exit code. Tipicamente è il valore ritornato da main, ma volendo possiamo specificare un valore di
ritorno. Per sapere il valore restituito da un processo terminato possiamo usare GetExitCodeProcess.
◦ sono assegnati tramite ExitProcess o TerminateProcess
◦ sono consultabili tramite GetExitCodeProcess
Programmazione di Sistema
30
Informazioni relative al
processo (1)
© M.Badella, G. Malnati, L. Tessitore 2002-14
•
Quando creiamo un processo dobbiamo passare una serie di parametri equivalenti a quelli della createThread. Queste informazioni
sono reperibili con getProcessinfo.
Sicurezza
◦ proprietario e gruppo d’esecuzione del processo
◦ lista d’accesso (DACL) di utenti e gruppi a cui è
permesso o negato l’accesso al processo
Risorse utilizzate (memoria, tempo CPU)
Priorità di default dei thread del processo
• Variabili d’ambiente
•
•
Programmazione di Sistema
31
Possiamo sapere se il processo è in una relazione di child-parent
con qualcun altro o se si stanno condividendo delle cose.
Informazioni relative al
processo (2)
© M.Badella, G. Malnati, L. Tessitore 2002-14
•
Relazioni del processo rispetto ad altri processi
◦ un child process è un processo creato da un altro
processo (parent)
◦ un child process può condividere variabili d’ambiente
ed handle a file, semafori, pipe …
◦ … ma non può condividere handle a thread, processi,
dll, GDI e regioni di memoria
•
Dati sull’uso della macchina utili alla tariffazione
•
Tabella degli oggetti kernel
◦ GetProcessIoCounters (accountability)
Programmazione di Sistema
32
I processi Windows includono al proprio interno riferimenti a oggetti
kernel, che servono anche per cooperare con altri processi (es.: ho
bisogno di far dialogare il compilatore e il linker). Tipicamente affinchè due processi possano cooperare devono avere uno spazio di
comunicazione. Siccome però i processi hanno spazi di indirizzamento separati, questa comunicazione di natura sua non è possibile (grossa differenza con Linux: usando fork() padre e figlio vedono
una mappa di memoria uguale, in Windows invece si usa uno spazio di indirizzamento vergine).
Condivisione di oggetti kernel -1
© M.Badella, G. Malnati, L. Tessitore 2002-14
•
Possono esserci molte situazioni in cui due
processi devono cooperare per il
raggiungimento di un obiettivo comune
◦ la cooperazione implica la necessità di comunicare
◦ la comunicazione, a sua volta, impone l’esistenza di un
canale sulla cui identità i processi devono accordarsi
•
Bisogna dunque costruire meccanismi diversi. Questo meccanismo
è basato su un qualche canale di comunicazione: si usano oggetti
kernel condivisi.
Un oggetto kernel condiviso è un modo per
accordarsi su un dato canale di comunicazione
Programmazione di Sistema
33
Una possibilità è quella per cui il figlio erediti delle handle. Il figlio
userà le handle ereditate per accedere ad alcune informazioni (come ad esempio gli stream). Ovviamente solo alcuni tipi di oggetti
kernel sono ereditabili.
Oppure, il processo padre potrebbe avere costruito un oggetto kernel di qualche tipo e avergli dato un nome. Se il figlio conosce il nome può provare ad aprirlo e se tutto va bene può utilizzarlo. Buono
per creare ad esempio una shared memory. Meccanismo molto
semplice, che va bene fintantochè i nomi non sono ambigui.
Un altro modo con cui condividere oggetti kernel è dire che un processo padre prepara un qualche oggetto kernel, poi crea il figlio e
poi duplica una propria handle dentro il figlio. Nota che
DuplicateHandle() è uno di quegli strumenti maledetti usati dai virus.
In realtà può essere usata non solo da padre a figlio, ma anche da
un processo terzo.
Condivisione di oggetti kernel -2
•
Ereditarietà di Handle
© M.Badella, G. Malnati, L. Tessitore 2002-14
◦ tra processo padre e processo figlio
◦ limitatamente per le voci ereditabili
•
Oggetti condivisi tramite nome
◦ agli oggetti kernel può essere associato un nome in fase di
creazione (stringa)
◦ un secondo processo può utilizzare tale nome per connettersi
allo stesso oggetto
•
Duplicazione di Handle
◦ DuplicateHandle(…) crea una copia della voce relativa ad una
data handle presente in un processo all’interno di un secondo
processo
◦ richiede che chi invoca DuplicateHandle() conosca entrambi i
processi e disponga dei necessari privilegi
Programmazione di Sistema
34
Qui c'è un processo 1 che dispone di una handle a un qualche oggetto kernel. E' possibile che qualcun altro apra questa handle
conoscendone il nome: se in due hanno aperto la handle, l'oggetto
rimane allocato fin quando tutti e due non chiamano close.
Condivisione di oggetti kernel -3
© M.Badella, G. Malnati, L. Tessitore 2002-14
Processo 1
CreateXXX
Handle1
Oggetto
kernel
Processo
2
OpenXXX
XX
Spazio di
indirizzamento virtuale
Un’applicazione crea un oggetto
kernel
Programmazione dii Sistema
Sistem
Sistem
stema
35
Condivisione di oggetti kernel -4
© M.Badella, G. Malnati, L. Tessitore 2002-14
Processo 1
Handle1
Oggetto
kernel
Handle2
Spazio di
indirizzamento virtuale
Processo
2
XX
OpenXXX
OpenXXX
Un altro processo può ottenere
una handle all’oggetto kernel
utilizzando OpenXXX
Programmazione dii Sistema
Sistem
Sistem
stema
36
Condivisione di oggetti kernel -5
© M.Badella, G. Malnati, L. Tessitore 2002-14
Processo 1
Handle1
Oggetto
kernel
Handle2
Spazio di
indirizzamento virtuale
Processo
2
XX
OpenXXX
L’oggetto kernel rimane in
memoria fino a quando esiste una
handle
Programmazione dii Sistema
Sistem
Sistem
stema
37
Condivisione di oggetti kernel -6
© M.Badella, G. Malnati, L. Tessitore 2002-14
Processo 1
CloseHandle
Handle1
Oggetto
kernel
Handle2
Spazio di
indirizzamento virtuale
Processo
2
XX
OpenXXX
Programmazione di Sistema
38
Condivisione di oggetti kernel -7
© M.Badella, G. Malnati, L. Tessitore 2002-14
Processo 1
Oggetto
kernel
Processo
2
XX
OpenXXX
CloseHandle
Handle2
Spazio di
indirizzamento virtuale
Programmazione di Sistema
39
Condivisione di oggetti kernel -8
© M.Badella, G. Malnati, L. Tessitore 2002-14
Processo 1
Oggetto
kernel
Processo
2
OpenXXX
XX
Spazio di
indirizzamento virtuale
L’oggetto kernel
viene distrutto
Programmazione
ne di
di Sistema
Si
40
File e file system
•
Un file costituisce la forma più semplice attraverso cui un processo
può importare/esportare informazioni all’esterno
© M.Badella, G. Malnati, L. Tessitore 2002-14
◦ I dati contenuti in un file hanno una durata che va oltre quella del
processo che li ha generati
◦ Ad ogni file è associato un identificativo univoco a livello di sistema
costituito dal suo percorso
•
Dal punto di vista logico, un file è ospitato all’interno di una cartella
di un dato filesystem
◦ Un filesystem è un componente software del sistema operativo (driver)
che offre un’interfaccia applicativa uniforme per creare, rimuovere,
leggere e scrivere file/cartelle su un determinato supporto fisico
◦ La stessa interfaccia può essere implementata in modo alquanto diversi
dando origine a meccanismi funzionalmente equivalente ma dalle
caratteristiche non funzionali completamente distinte (sicurezza,
affidabilità, prestazioni, …)
Programmazione di Sistema
42
Il concetto di file in Win32 è abbastanza esteso, che vede diversi oggetti separati. Anche le cartelle, ad esempio, sono visibili come file.
Elenco completo qui a fianco. Tutte cose che posso leggere o scrivere in modalità streaming, quindi posso:
- aprirli con CreateFile
- smettere di leggerli con CloseHandle
- leggerci con ReadFile o ReadFileEx
- scriverci con WriteFile o WriteFileEx
Poi ce ne sono alcune più specifiche solo per alcuni tipi.
Accedere ad un file
© M.Badella, G. Malnati, L. Tessitore 2002-14
•
La piattaforma win32 offre un meccanismo omogeneo
per accedere ad un insieme di oggetti kernel che
condividono l’astrazione del modello di file
◦ File, cartelle, dischi fisici, partizioni, console, porte I/O, pipe,
mailslot
•
Un caso particolare di oggetto kernel estremamente comune è quello. Il file serve per rendere persistenti le informazioni.
Tipicamente ogni file ha un nome e questo nome è costituito da
una porzione individuale che lo identifica nella cartella e poi tutto il
path. Quest'organizzazione gerarchica è un'astrazione aggiunta dal
SO che fa credere quello che vuole (è possibile creare link simbolici che permettono di far apparire un file come se fosse memorizzato altrove). E' il filesystem driver che fa la traduzione da nomi simbolici a indirizzi fisici sul disco di dove viene rappresentato il file.
Un filesystem mette a disposizione le classiche operazioni di gestione sui file. Queste operazioni possono essere implementate in molti modi differenti.
Tale meccanismo è costituito dall’insieme di funzioni
◦ CreateFile, ReadFile(Ex), WriteFile(Ex), CloseHandle
◦ A tali funzioni se ne affiancano altre che specializzano un insieme
di comportamenti
Programmazione di Sistema
43
Un file deve avere un nome univoco all'interno della cartella che lo
ospita. Le caratteristiche del nome dipendono dal file system.
Nomi dei file
Ogni file ha un nome univoco all’interno della
cartella che lo ospita
• Le caratteristiche di tale nome dipendono dal
tipo specifico di filesystem adottato
• Nel caso di NTFS
© M.Badella, G. Malnati, L. Tessitore 2002-14
•
◦ Il nome è costituito da caratteri unicode stampabili (e
diversi da ‘\’, ‘/’, ‘<‘, ‘>’, ‘"’, ‘:’, ‘?’, ‘*’)
◦ Il numero massimo di caratteri è 255 se si lavora in
modalità ASCII, 32767 in modalità UNICODE
◦ I nomi “.” e “..” sono riservati
Programmazione di Sistema
44
Accedere ad un file
© M.Badella, G. Malnati, L. Tessitore 2002-14
•
Si utilizza la funzione
CreateFile(…)
◦ Sia per costruire un nuovo file
che per accedere ad un file
esistente
•
Restituisce un oggetto di tipo
HANDLE
◦ Occorre controllare che non
sia invalido
•
In caso di errore
◦ GetLastError() restituisce il
codice dell’errore
•
Si chiude il file con
CloseHandle()
Programmazione di Sistema
#include <windows.h>
HANDLE hFile;
hFile = CreateFile(
_T(“FileName.txt”), //nome
GENERIC_WRITE,
//scrittura
NULL,
//condivisione
CREATE_ALWAYS,
//sovrascrive
FILE_ATTRIBUTE_NORMAL, //attributo
NULL
//template
);
if (hFile == INVALID_HANDLE_VALUE) {
DWORD dwErr = GetLastError();
//…
}
//…
CloseHandle(hFile);
45
Per accedere a basso livello a un file si usa CreateFile, che vuole
una fraccata di parametri:
- nome (li passiamo con la macro _T in modo che il tutto venga ridefinito come wchar)
- GENERIC_WRITE, GENERIC_READ (sola lettura, sola scrittura
o se li metto in OR ho accesso con entrambe le modalità)
- condivisione: mettendo NULL diciamo che il file non è accessibile
mentre l'abbiamo aperto
- devo creare sempre il file oppure l'apertura deve avere successo
solo se il file esiste già?
- devo crare un file normale o uno con particolari privilegi?
- puntatore a un'eventuale struttura di tipo FileTemplate in cui andiamo a dire che caratteristiche deve avere quel file.
CreateFile restituisce una handle. Se questa handle è invalida vuol
dire che non è stato possibile accedere al file, altrimenti l'oggetto
contiene una handle valida e possiamo usarla per farci delle operazioni. Ricordiamoci di chiuderla alla fine altrimenti perdiamo delle
risorse.
Pezzo interessante: dwShareMode. Se mettiamo NULL o 0 vuol dire che nessun altro può aprire il file quando ce l'ho io, altrimenti ci
sono tutta serie di altre modalità (FILE_SHARE_READ, FILE_SHARE_WRITE e così via). Si noti che è possibile creare file normali, di
sistema o nascosti.
Parametri di CreateFile
•
HANDLE CreateFile(
LPCTSTR lpFileName,
// Puntatore alla stringa: attenzione se si sta lavorando in modalità UNICODE o ASCII
DWORD dwDesiredAccess,
Volendo un file può anche essere aperto in esecuzione.
© M.Badella, G. Malnati, L. Tessitore 2002-14
// Una combinazione di GENERIC_READ, GENERIC_WRITE o GENERIC_EXECUTE
DWORD dwShareMode,
// Una combinazione di FILE_SHARE_READ e FILE_SHARE_WRITE
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
// Puntatore ad una struttura di tipo SECURITY_ATTRIBUTES che permette di indicare
// se la HANDLE ritornata sia ereditabile o meno e eventuali liste di accesso
DWORD dwCreationDisposition,
// Come comportarsi se il file non esiste (CREATE_NEW,OPEN_ALWAYS,..)
DWORD dwFlagsAndAttributes,
// un campo di bit che specifica gli attributi del file (NORMAL, SYSTEM, HIDDEN,…)
HANDLE hTemplateFile
// Una handle ad un template file con accesso di tipo GENERIC_READ
// Se il file attuale non esiste, si utilizzano gli attributi del template file per la creazione
// Di solito si indica NULL
);
Programmazione di Sistema
46
Leggere e scrivere
•
Le funzioni ReadFile e WriteFile permettono
l’accesso al contenuto del file
© M.Badella, G. Malnati, L. Tessitore 2002-14
◦ Possono essere operazioni sincrone (il chiamante
rimane bloccato fino a che il sistema operativo non ha
terminato l’operazione) o asincronte (la chiamata
torna subito, ma i dati sono disponibili in un secondo
tempo)
◦ Se il file non è stato aperto in modalità diretta
(FILE_FLAG_NO_BUFFERING), le operazioni fanno
accesso ad un buffer interno, mantenuto dal sistema
operativo
Programmazione di Sistema
Windows offre la possibilità di creare file temporanei, con file inventati dal SO per non fare casino. GetTempFileName(...) crea file temporanei in C:\Windows\Temp di dimensione 0 byte e ci restituisce il
nome. In questo modo qui la nostra applicazione sa che può usare
quel file per fare delle cose. Il file è utilizzabile anche come meccanismo di coordinamento. Possiamo sapere la cartella in cui il file è
creato con GetTempPath()
© M.Badella, G. Malnati, L. Tessitore 2002-14
La funzione GetTempFileName(…) crea un file
temporaneo dal nome univoco, lo chiude e ne
restituisce il percorso
◦ Tale funzione richiede il nome della cartella in cui il
file deve essere creato
◦ Si può ottenere il riferimento alla cartella temporanea
di sistema con la funzione GetTempPath(…)
Programmazione di Sistema
48
Possiamo utilizzare funzioni di alto livello per manipolare i file.
- copyFile permette di copiare un file su un altro
- replaceFile permette di rimpiazzare un file esistente con un altro,
eventualmente salvandosi quello vecchio
- moveFile permette di muovere un file da una cartella all'altra (possiamo anche impostare un flag con la versione Ex al fine di muovere file al prossimo riavvio)
- deleteFile permette di cancellare un file dal filesystem
Manipolare i file
•
© M.Badella, G. Malnati, L. Tessitore 2002-14
I file possono essere aperti in modalità grezza (senza buffering) che
va bene quando opero sui dispositivi, ma in modo pessimo quando
eseguo delle computazioni perchè ogni volta faccio una system call.
47
File temporanei
•
Per leggere e scrivere da file, le operazioni di basso livello sono
read e write. Quando noi facciamo fread o fwrite in C, internamente
vengono chiamate ReadFile e WriteFile. Queste due operazioni sono sempre sincrone. La versione Ex permette di fare le cose asincronicamente, questo perchè le operazioni sui file sono molto lente.
Il problema grosso naturalmente è sapere quando sono fatte prima
di proseguire.
Alcune funzioni permettono di copiare, sostituire,
muovere e cancellare i file nel loro complesso, senza
dover operare direttamente sui dati contenuti
◦ CopyFile(…), CopyFileEx(…) copiano un file
◦ ReplaceFile(…) sostituisce un file con un altro, permettendo
anche di ottenere un backup del file originale
◦ MoveFile(…), MoveFileEx(…) spostano un file da una cartella ad
un’altra
• MoveFileEx permette di effettuare lo spostamento al successivo
reboot del sistema
◦ DeleteFile(…) elimina un file dal filesystem
Programmazione di Sistema
49
Versioni specializzate per le cartelle. Se vogliamo enumerare i file
presenti in una cartella possiamo usare le funzioni Find...
Manipolare le cartelle
Le funzioni CreateDirectory(…) e
CreateDirectoryEx(…) permettono di creare
una nuova cartella, specificandone i permessi
• RemoveDirectory(…) elimina una cartella
• FindFirstFile(…), FindNextFile(…) e
FindClose(…) permettono di enumerare i file
presenti in una data cartella
© M.Badella, G. Malnati, L. Tessitore 2002-14
•
Programmazione di Sistema
Manipolare i dischi
© M.Badella, G. Malnati, L. Tessitore 2002-14
•
La funzione CreateFile
permtte di accedere anche ai
dischi fisici
◦ Si utilizza il nome particolare
\\.\PhysicalDrive<N>
◦ Occorre disporre dei diritti di
amministratore del sistema
◦ La handle ottenuta può essere
usata per ottenere
informazioni sul disco
•
Un’operazione errata
comporta la perdita di tutti i
dati del disco!
Programmazione di Sistema
50
Le stesse funzioni che ci permettono di manipolare i file ci permettono di manipolare i dischi, utilizzando nomi corretti. Dobbiamo avere i diritti di amministratore del sistema.
Otteniamo una handle che ci permette di ottenere informazioni sul
disco. Occhio perchè se facciamo cagate perdiamo dati utili.