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.