2.04 Lezione 4 - follen99/ArchitetturaDeiCalcolatori GitHub Wiki
Esiste una grande varietà di programmi di sistema che permettono di gestire i file, come copia, Rename, stampa ecc. Per quanto riguarda le informazioni di stato, esistono dei servizi che permettono di accedervi, e che quindi permetto di accedere ad informazioni come data, ora ecc.
Questi programmi di sistema stampano l'output del sistema sul terminale o altri Devices, per permettere all'utente di consultarli.
Alcuni sistemi implementano un registry, tipico dei sistemi Windows odierni.
L'idea era quella di usare un deposito unico in cui conservare tutte le informazioni di configurazione di sistema, in modo da avere sempre pronte queste informazioni. Il problema fuoriesce nel momento in cui vogliamo disinstallare un programma che utilizza il registry, perché ci
Nei sistemi Unix, invece di avere un unico file dove vengono conservate tutte le informazioni, esiste una directory apposita, chiamata etc, in cui, racchiuse all'interno di diverse directory, sono presenti tutte le informazioni di sistema. Tutti questi file sono sotto forma di file di testo, quindi completamente analizzabili dall'utente.
Alcuni sistemi operativi forniscono degli strumenti per sviluppatori, come compilatori, assemblato, sistemi di debuggano ecc.
Oltre ai programmi che vengono lanciati dall'utente, qualsiasi SO lancia dei servizi in background, ovvero dei programmi che restano in attesa di essere utilizzati. Nel mondo UNIX questo tipo di programma è chiamato Deamon. Nel mondo Windows invece sono chiamati Services.
- Un Linker è un software che unisce tutti i file oggetto a disposizione, in maniera da ottenere un eseguibile binario completo.
- Un Loader è in grado di prelevare un eseguibile dal disco e gli assegna un indirizzo di memoria per essere eseguito.
Il comando MAN su UNIX è suddiviso in sezioni. La sezione 1 è quella destinata ai programmi di sistema, e non alle syscalls. Per cui se si volesse accedere alla syscall dovrebbe digitare: man -S 2 open
.
Inoltre, per "uccidere" un processo su unix basta digitare ps
, controllare il il PID del processo ed usarlo per chiudere il processo con kill -9 2793
.
Posso prendere un determinato programma scritto per un determinato processore, su un determinato sistema operativo, ed eseguirlo sulla stessa macchina ma con SO diverso? Ovviamente la risposta è NO.
Posso trasferire una libreria, in formato già compilato, ed utilizzarla sulla stessa macchina con SO diverso? Anche in questo caso la risposta è NO.
- Compatibilità del formato dell'eseguibile: il formato con cui viene creato il binario, è diverso da SO a SO.
- System calls diverse: anche le System calls sono diverse.
- Parametri delle Syscalls diversi: anche i parametri, da passare con le chiamate di sistema, variano da SO a SO.
Non è possibile, quindi, far eseguire lo stesso programma, anche sulla stessa macchina, con due Sistemi operativi diversi.
É possibile eseguire, ad esempio, un programma windows su sistema Linux. Si può fare ciò attraverso l'uso degli emulatori. Un Emulatore (ai tempi veniva usato Wine-Windows emulator) che al momento dell'esecuzione del programma, intercettava tutte le syscalls del programma, trasformandole in syscalls di linux. Il problema è che microsoft (che aveva scritto il programma da emulare) aveva usato delle syscalls non note, e di conseguenza non era possibile effettuare la conversione delle syscalls.
Il problema viene in realtà risolto con l'emulazione, ovvero eseguendo, all'interno di Linux, una sessione di Windows, dove verrà eseguito il programma nitidamente.
Esistono dei criteri di progetto che rendono il processo "semplice"? Risposta veloce: No.
Fondamentalmente la struttura interna di un SO può variare, ed anche di molto, da SO a SO. Anche nello stesso mondo Unix, la struttura interna cambia moltissimo.
Quando si sviluppa un sistema, ci sono due obbiettivi principali:
- Obbiettivi di Utente : Il sistema dovrebbe essere di facile utilizzo, facile da comprendere, affidabile sicuro e veloce.
- Obbiettivi di Sistema : Il sistema dovrebbe essere semplice da progettare, implementare e sopratutto efficiente.
Come si nota, gli obbiettivi non coincidono completamente, infatti l'utente finale è poco interessato a sapere che il SO sia efficiente, ma il suo interesse è quello che esso sia veloce ed affidabile.
Nel progetto di una qualsiasi cosa, bisognerebbe distinguere "ciò che voglio fare" e "qual è il modo per farlo". Dovrei ragionare sulla politica, ovvero il risultato che voglio ottenere, senza farmi influenzare fin dall'inizio dalla modalità con cui implementerò.
In poche parole, questo concetto ci permette di progettare il nostro obbiettivo, senza farci influenzare dalle problematiche del "come realizzarlo". In questo modo, gli obbiettivi, e quindi le funzioni, rimarranno gli stessi nel tempo, anche se l'implementazione potrà cambiare.
- Decidere cosa voglio che il mio software faccia
- Impazzire, solo dopo, per implementarlo
🏁 00:34 05-06
I primi SO venivano implementati rigorosamente in assembly, dopodiché sono stati utilizzati dei linguaggi come l'Algol, che permettevano di scrivere SO.
Infine, usato ancora oggi, venne impiegato il C, C++. In effetti, il C venne proprio creato per lo sviluppo di sistemi operativi, per via del bisogno di avere un linguaggio che fosse a basso livello, ma che allo stesso momento permettesse di eseguire operazioni complesse.
I SO, in verità, sono composti da un mix di linguaggi. Infatti i livelli più bassi del SO vengono scritti in linguaggio macchina, e quindi in assembly, per via della manipolazione dei registri, il corpo principale viene scritto in C, per le motivazioni già spiegate precedentemente, ed infine i programmi di sistema possono essere scritti, ancora una volta, in vari linguaggi, come il C, C++, linguaggi di scripting come Python e Shell.
Fondamentalmente i sistemi più antichi, programmati in modo tale da occupare una piccolissima quantità di memoria, avevano una struttura molto semplice; era il caso di MS-DOS. Altri sistemi sono invece più complessi, come UNIX.
In questi sistemi è presente una struttura di vari moduli software, ma il kernel è unico. Ciò significa che non solo il kernel è un'unico pezzo, ma che esso viene eseguito in modalità supervisore.
Negli anni è stato suggerito di strutturare il SO a livelli:
Questa è una struttura molto "comoda" sulla carta, ma nella pratica non funziona molto bene, perchè tra un'operazione e l'altra c'è il bisogno di effettuare dei "salti" tra uno strato e l'altro.
Per questo motivo non esiste un SO che adotti questa struttura.
Il microkernel è "l'avversario" della struttura monolitica. Questo perchè nel kernel resta solo il minimo indispensabile come:
- Scheduling della CPU: perché serve all'esecuzione di processi
- Gestione della memoria
- Comunicazione tra processi
Tutto il resto, vengono eseguiti come Programmi di sistema, in User mode. L'idea è la seguente: "cerchiamo di levare quanta più roba possibile dal kernel, facendolo eseguire come programma di sistema". Il vantaggio ottenuto, oltre al fatto di avere un kernel molto compatto, è sopratutto il fatto che molte attività vengono eseguite in modalità utente; questo significa che se è presente un guasto o malfunzionamento, non è possibile fare "danni". In questi casi basta semplicemente rilanciare il processo, ed il sistema continua a funzionare senza problemi.
Con questo schema, come già detto, nel kernel è posto solo il minimo indispensabile, il resto invece viene fatto mediante l'invio di "messaggi", ovvero i programmi di sistema comunicano tra loro grazie all'interprocess communication, situato nel kernel.
Il primo sistema strutturato su microkernel era il sistema Mach, che nella sua prima versione era molto lento. Questo perchè scambiare dei messaggi non è veloce come cambiare una funzione all'interno del kernel stesso, e quindi, per i processori di una volta, avere un sistema a Microkernel, voleva dire rallentare l'esecuzione.
Linux è stato strutturato in modo "monolitico", nonostante inizialmente si era insistito per utilizzare un approccio a microkernel, che però non venne ben visto dai primi sviluppatori. Anche Windows è basato su una struttura Monolitica. La differenza principale tra i due sistemi è che in linux l'interfaccia grafica gira in modalità utente, mentre in windows in modalità supervisore.
L'unico SO moderno (ampiamente utilizzato) che è basato su una struttura a microkernel, è Mac OS, basato sul sistema Mach.
Un sistema operativo esegue una varietà di programmi, i programmi in esecuzione vengono detti processi. C'è una distinzione tra programma e processo:
- Programma: entità STATICA
- Processo: entità DINAMICA
Quando il programma va in esecuzione, viene eseguito sequenzialmente istruzione per istruzione, ed ha uno spazio indirizzi a disposizione, suddiviso in:
- Text Section, ovvero il codice del programma
- Program Counter
- Stack, che contiene data temporanea
- Data section, che contiene le variabili globali
- Heap, che contiene memoria allocata dinamicamente durante il tempo di esecuzione.
Lo stato "new" è presente in sistemi che hanno capacità di batching, quindi non è presente in sistemi come windows. In questo caso, il SO prende atto della presenza di un programma che deve essere eseguito, situato in una coda. Il programma ci rimane finché non viene ammesso, e quindi si sposta allo stato ready.
Quando mandiamo in esecuzione un programma, questo riceve dal SO spazio in memoria, viene inserito nella lista dei programmi che utilizzano la CPU. Questo stato viene chiamato READY, proprio perché il programma è pronto per essere eseguito. Questo vuol dire che è presente in memoria, ma non ha ancora ricevuto la CPU per andare in esecuzione a tutti gli effetti.
In un sistema tradizionale, dotato di di un'unica CPU, la gran parte dei processi sono nello stato Ready, mentre è presente un unico processo in esecuzione. Prima o poi, il SO darà la possibilità ad un altro processo (nello stato ready) di essere eseguito. Questa operazione viene chiamata dispatch, quindi lo scheduler è quella parte del SO che decide qual è il prossimo processo da mandare in esecuzione.
In questo stato il processo in quell'istante è in esecuzione. Esso resta in esecuzione finché non avviene una delle seguenti cose:
- Il processo termina volontariamente, ovvero esegue una syscall Exit, quindi si sposta nello stato exit.
- Arriva un'interrupt, che potrebbe provenire dalle periferiche, come ad esempio la pressione di un tasto da parte dell'utente, oppure un Interrupt di timer, ovvero quando il processo è stato troppo tempo in esecuzione e quindi ne viene scelto un altro per l'esecuzione.
Un'altra possibilità di Interrupt potrebbe essere quella dell'attesa di input da parte dell'utente, quindi il processo perde la CPU, e va nello stato di attesa. Il processo puo uscire dallo stato di attesa in due casi: - Viene completata l'operazione di I/O
- L'eventuale timer che lo ha fatto spostare nello stato di attesa finisce.
Quando un processo è in esecuzione il SO deve prenderne atto, e quindi gestire delle informazioni relative a quel processo. Quindi, siccome il SO ha a disposizione delle informazioni per ogni processo ammesso, si dice, in maniera astratta, che è presente un blocco di controllo dei processi.
Anche se queste informazioni non risiedono tutte nello stesso punto, sono sempre presenti:
- Stato del processo: running - waiting - ecc
- Program Counter: posizione della prossima istruzione da eseguire
- Registri CPU: contenuti di tutti i registri del processo
- Informazioni di scheduling CPU: priorità, puntatori a code di scheduling
- memoria: informazioni sulla memoria allocata dal processo
- Informazioni di accounting: CPU utilizzata, tempo passato dall'esecuzione, utilizzato nei sistemi a pagamento per tenere traccia del tempo di CPU usato per poi calcolare un pagamento.
- Informazioni sullo stato di I/O: lista di files aperti ecc.
Un processo, ha un program counter, e quindi esegue codice in maniera sequenziale. Negli ultimi anni si è diffuso sempre di più l'utilizzo di tecniche di threading. Questo significa che esiste la possibilità di più esecuzioni di codice, contemporaneamente.
🏁 1:35 05-06
Il SO deve Schedulare i processi ed alternarli, in modo tale che la CPU sia quanto più utilizzata possibile. La parte del SO che si occupa di queste operazioni, è proprio il Process Scheduler, che seleziona tra i processi in attesa di esecuzione tra una coda.
Questi processi sono posti in apposite strutture dati molto efficienti, in modo da avere sempre a disposizione il prossimo processo da eseguire.
Le operazioni che fermano un processo e ne attivano un altro, sono dette context switch; nel momento in cui il SO decide di fermare un processo per attivare il prossimo della coda, salva lo stato di program counter e registri del processo precedente, e ne manda un altro in esecuzione.
Siccome questo switch richiede del tempo di CPU, deve essere il quanto più veloce ed ottimizzato possibile. Questo tempo dipende sopratutto dall'hardware, perchè più registri ci sono da salvare, maggiore sarà il tempo dell'operazione.
Inoltre, questa parte di codice (del context switch) è scritta in assembly, proprio perché è l'unico codice che ci permette di maneggiare i registri, oltre al fatto che l'assembly ci permette di scrivere un codice molto ottimizzato e veloce.
🏁 fine lezione 4