Eliminato 231 punti

Il software può essere diviso un due grandi classi:
• i programmi di sistema che gestiscono le operazioni del sistema di elaborazione;
• i programmi applicativi che risolvono i problemi dei loro utilizzatori;
L’insieme dei Programmi di Sistema viene comunemente identificato con il nome di Sistema Operativo (SO).

Il sistema di elaborazione è costituito da Hardware, Programmi di Sistema e Programmi Applicativi.
Programmi Applicativi
Utility di Sistema Interprete dei Comandi
Sistema Operativo
Linguaggio Macchina
Microprogrammazione (Control Unit: CU)
Dispositivo Fisico

SCOPO DEL SISTEMA OPERATIVO:
• Gestione delle risorse del sistema di elaborazione;
• Rendere AGEVOLE l’interfaccia tra l’uomo e la macchina;

ATTIVITÀ SVOLTE DAL SISTEMA OPERATIVO:

• Gestione della memoria di massa (file system);
• Gestione della memoria RAM;
• Gestione dei processi;
• Gestione dell’ interfaccia utente;
• Accesso simultaneo di più utenti alla stessa macchina;
• Esecuzione simultaneamente di più processi sulla stessa macchina;

STRUTTURA DEL SISTEMA OPERATIVO:
• I SO sono generalmente costituiti da un insieme di moduli, ciascuno dedicato a svolgere una determinata funzione;
• I vari moduli del SO interagiscono tra di loro secondo regole precise al fine di realizzare le funzionalità di base della macchina;


Il sistema operativo dal punto di vista strutturale è formato da un insieme di livelli, che formano la cosiddetta struttura a cipolla. Idealmente l’utente è ignaro di tutti i dettagli delle operazioni svolte ai livelli inferiori e conosce solo le operazioni del livello più alto.

IL MODELLO STRATIFICATO DI UN S.O.

S.O. COME GESTORE DELLE RISORSE
• Punto di vista Bottom Up (dal basso verso l’alto);
• Gestisce tutte le componenti di un sistema complesso;


CLASSIFICAZIONE DEI S.O.
• MonoTask: è presente un solo processo/programma alla volta; (MS-DOS);
• Mono Utente: è collegato un solo utente alla volta; (Win98);
• MonoTask -> Mono Utente
• MultiTask: sono presenti più processi/programmi alla volta; (Win98) (WinXP, Unix, Linux);
• Multi Utente: sono collegati più utenti alla volta; (WinXP, Unix, Linux);

GESTORE DEI PROCESSI
• E’ il modulo che si occupa di controllare la sincronizzazione, interruzione e riattivazione dei programmi in esecuzione cui viene assegnato un processore;

• La gestione dei processi viene compiuta in vari modi , in funzione del tipo di utilizzo cui il sistema è rivolto;
• Il programma che si occupa della distribuzione del tempo di CPU tra i vari processi attivi, decidendone l’avvicendamento, è comunemente chiamato Scheduler;
• Nel caso di elaboratori multi-processore si occupa anche di gestire la cooperazione tra le varie CPU presenti nel sistema;


POLITICHE DI SCHEDULING
• Le politiche di schedulazione utilizzate dallo scheduler sono raggruppabili in due grandi categorie:
 Preemptive: la CPU in uso da parte di un processo può essere tolta e passata a un altro in un qualsiasi momento;
 Non Preemptive: una volta che un processo ha ottenuto l’uso della CPU non può essere interrotto fino a che lui stesso non la rilascia;

SISTEMI MONO-TASKING
• I SO che gestiscono l’esecuzione di un solo programma per volta sono catalogati come mono - tasking;
• Non è possibile sospendere l’esecuzione di un programma per assegnare la CPU a un altro;
• Sono storicamente i primi SO (es MS-DOS);

SISTEMI MULTI-TASKING
• I SO che permettono l’esecuzione contemporanea di più programmi sono definiti multi-tasking (Windows-NT, Linux);
• Un programma può essere interrotto e la CPU passata a un altro programma;

SISTEMI TIME-SHARING
• Un’evoluzione dei sistemi multi-tasking sono i sistemi time sharing;
• Ogni programma in esecuzione viene eseguito ciclicamente per piccoli quanti di tempo;
• Se la velocità del processore è sufficientemente elevata si ha l’impressione di un’evoluzione parallela dei processi;

GESTORE DELLA MEMORIA
• L’organizzazione e la gestione della memoria centrale è uno degli aspetti più critici nel disegno di un SO;
• Il gestore della memoria è quel modulo del SO incaricato di assegnare la memoria ai vari task (per eseguire un task è necessario che il suo codice sia caricato in memoria);
• La complessità del gestore della memoria dipende dal tipo di SO;
• Nei sistemi multi-tasking più programmi contemporaneamente possono essere caricati in memoria;
• Problema: come allocare lo spazio in maniera ottimale;

MEMORIA VIRTUALE
Spesso la memoria non è sufficiente per contenere completamente tutto il codice dei vari task;
• Si può simulare una memoria più grande tenendo nella memoria di sistema (RAM) solo le parti di codice e dei dati che servono in quel momento;
• Si usa il concetto di memoria virtuale;
• I dati dei programmi non in esecuzione possono essere tolti dalla memoria centrale e parcheggiati su disco nella cosiddetta area di swap;
• Il rapporto tra le dimensioni dell’area di swap e della RAM è di 3 : 1 (max);
• I moderni processori posseggono meccanismi hardware per facilitare la gestione della memoria virtuale;
GESTORE DEL FILE SYSTEM
• Il gestore del file system è quel modulo del sistema operativo incaricato di gestire le informazioni memorizzate sui dispositivi di memoria di massa;
• Il gestore del file system deve garantire la correttezza e la coerenza delle informazioni;
• Nei sistemi multi-utente, deve mettere a disposizione dei meccanismi di protezione in modo tale da consentire agli utenti di proteggere i propri dati dall’accesso da parte di altri utenti non autorizzati;

Le funzioni tipiche che deve svolgere sono:
• Fornire un meccanismo per l’identificazione dei File;
• Fornire opportuni metodi per accedere ai dati;
• Rendere trasparente la struttura fisica del supporto di memorizzazione;
• Implementare meccanismi di protezione dei dati;

ORGANIZZAZIONE
• Quasi tutti i sistemi operativi utilizzano un’organizzazione gerarchica del File System;
• L’elemento utilizzato per raggruppare più file insieme è la directory;
• L’insieme gerarchico delle directory e dei file può essere rappresentato attraverso un grafo delle directory;

GESTORE DEI DISPOSITIVI DI I/O
• Il gestore dei dispositivi di I/O è quel modulo del SO incaricato di assegnare i dispositivi ai task che ne fanno richiesta e di controllare i dispositivi stessi;
• Da esso dipende la qualità e il tipo di periferiche riconosciute dal sistema;

DEVICE DRIVER
• Il controllo dei dispositivi di I/O avviene attraverso speciali programmi detti Device Driver;
• I device driver sono spesso realizzati dai produttori dei dispositivi stessi che ne conoscono le caratteristiche fisiche in maniera approfondita;
Questi programmi implementano normalmente le seguenti funzioni:
• Rendono trasparenti le caratteristiche fisiche tipiche di ogni dispositivo;
• Gestiscono la comunicazione dei segnali verso i dispositivi;
• Gestiscono i conflitti, nel caso in cui due o più task vogliono accedere contemporaneamente allo
• stesso dispositivo;

INTERFACCIA UTENTE
• Tutti i Sistemi Operativi implementano dei meccanismi per rendere agevole l’utilizzo del sistema da parte degli utente;
• L’insieme di questi meccanismi di accesso al computer prende il nome di Interfaccia Utente;

• Interfaccia testuale:
Interprete dei comandi (shell). Esempio MS-DOS;
• Interfaccia grafica (a finestre):
L’output dei vari programmi viene visualizzato in maniera grafica all’interno di finestre;
L’utilizzo di disegni rende più intuitivo l’uso del calcolatore. Esempio WINDOWS;

I SISTEMI PRESENTI IN COMMERCIO
• In commercio sono presenti una grande quantità di diversi Sistemi Operativi;
• In passato la tendenza delle case costruttrici di sistemi di elaborazione era di sviluppare sistemi operativi proprietari per le loro architetture;
• La tendenza attuale è quella di sistemi operativi eseguibili su diverse piattaforme;


MS-DOS
• CPU Intel 80x86 (16 bit)
• Monotask
• Monoutente
• File-system gerarchico
• Memoria limitata (1 MB / 640 KB)
• Nessuna protezione
• PC- / IBM- / DR-DOS

MS-WINDOWS
• CPU Intel 80386/486/Pentium
• Multitask imperfetto (non ha la preemption)
• Monoutente
• Stesso file system del MS-DOS
• Interfaccia grafica a finestre e menù
• Sistema a 16 bit !!!

WINDOWS-NT
• CPU Intel 80386/486/Pentium/Sparc/Alfa
• Multitask
• Monoutente
• NTFS (NT File System)
• Microkernel, thread
• Non solo per Intel 80x86 (DEC-AXP, MIPS-R4000, ...)
• Sistema a 32 bit

UNIX
• Nato negli anni ‘60 (AT&T Bell Labs)
• Rimasto all’avanguardia perchè sviluppato nelle università (UCB)
• Multitask con timesharing
• Multiutente
• Ottima integrazione in rete
• Portabilità dei programmi

LINUX
• Nato negli anni ‘90 sull’esperienza di UNIX
• Multitasking
• Time-sharing
• Multiutente
• Varie distribuzioni disponibili
• Debian
• Ubuntu
• Interfaccia grafica tipo Windows
• Shell molto usata
• File system strutturato ad albero
• Permessi sui file (lettura, scrittura, esecuzione)

I SISTEMI OPERATIVI (PARTE 2)
Il calcolatore è formato da circuiti elettronici che eseguono programmi scritti in linguaggio macchina binario. All’accensione il programma di bootstrap deve essere caricato in memoria centrale RAM e successivamente mandato in esecuzione. Il sistema operativo è sempre attivo dal momento dell’accensione, al momento dello spegnimento del computer.
Con Sistema Operativo intendiamo un gruppo di programmi che gestisce il funzionamento del computer agendo come intermediario tra l’utente e il calcolatore. Il SO fa parte del software di base. Il software di sistema serve alla macchina per funzionare, mentre il software applicativo serve all’utente per lavorare. Su una macchina è possibile installare diversi sistemi operativi, o anche installare contemporaneamente due sistemi operativi sulla stessa macchina. Il SO svolge due compiti:
• È il gestore delle risorse hardware;
• Fa da interfaccia tra l’utente e la macchina;

Le risorse sono gli elementi hardware e software del PC che vengono usate da specifici programmi per eseguire il proprio compito. Il SO risiede sull’hard disk e viene caricato nella memoria RAM all’accensione della macchina. Solo una parte di SO, chiamata nucleo rimane sempre caricata in memoria.
Propria dei SO è la stratificazione, cioè l’organizzazione secondo il modello “onion skin”, cioè è formato da gusci concentrici che circondano l’hardware.

KERNEL
Il Kernel è il nucleo del sistema operativo che gestisce i processi, la memoria e il file system. I programmi utente non devono accedere direttamente ai dispositivi fisici, ma possono utilizzare solo dispositivi logici attraverso primitive di sistema costituenti i kernel. Le operazioni che vengono eseguite dal kernel sono:
• Avvio e terminazione dei programmi;
• Assegnazione della CPU ai diversi processi;
• Sincronizzazione tra i processi;
• Sincronizzazione dei processi con l’ambiente esterno;

SHELL
La shell avvolge il kernel come una “conchiglia” per proteggerlo. L’utente può accedere alle funzioni di sistema solo attraverso lo shell, che prende il nome di interfaccia utente. Praticamente è la prima interfaccia fisica per l’utente e il SO.
Con interfaccia utente (o shell) si intende ciò che si frappone tra la macchina e l’utente, ciò che fa dialogare l’uomo con la macchina: è qualsiasi cosa permetta a un utente di gestire semplicemente le funzionalità di un sistema. Può essere di tipo CUI (Command User Interface) per MS-DOS, Unix e Linux; e di tipo GUI (Graphical User Interface) per Windows, MacOS e Linux.

ECOLUZIONE SISTEMI OPERATIVI
Sistemi operativi di una volta:
• SISTEMI DEDICATI (1945-1955);
I calcolatori erano lenti, costosi e a uso esclusivo di università o centri di ricerca. Il tempo macchina (tempo di utilizzo della CPU), era una risorsa pregiata concessa a pochi utenti privilegiati per brevi periodi. Il programmatore caricava i propri programmi in memoria, li faceva eseguire e ne controllava l’esecuzione. Non esisteva un SO vero e proprio, neanche tastiera e monitor.
ANNI ’80: prendono piede anche software grafici interattivi e il sistema può venir chiamato monoutente.
• SISTEMI A LOTTI (batch) (1955-1965);
Venne introdotto un sistema automatico di caricamento dei programmi: si utilizzarono le schede perforate come supporti su cui memorizzare i programmi. L’utente inseriva un pacco di schede nell’apposito lettore. Ogni scheda perforata conteneva un’istruzione. Il primo SO prese il nome di “Bath monitor”. Le schede di controllo utilizzavano un linguaggio per comunicare con il calcolatore: il JCL o Job Control Language. Un Job è delimitato da due schede speciali di controllo: $JOB e $END. Le principali caratteristiche di questo sistema sono:
 Il SO è sempre residente in memoria;
 In memoria centrale è presente un solo job alla volta;
 Finché il job corrente non è terminato, il successivo non può iniziare l’esecuzione;
 Non è presente interazione tra utente e job;
 Se un job si sospende in attesa di un evento, la CPU rimane inattiva;
 Ha una scarsa efficienza.
• SISTEMI INTERATTIVI (conversazionali, in tempo reale e transazionali) (1965-1980);
Si introducono i canali di I/O che hanno il compito di gestire totalmente le periferiche così da lasciare l’esecuzione dei programmi alla CPU (oggi DMA: permette di accedere direttamente alla memoria del calcolatore per trasferire i dati senza far “sprecare” tempo di elaborazione alla CPU). Viene introdotta, poi, la multiprogrammazione che permette di eliminare i tempi morti di attesa, tenendo pronti altri job in memoria. Con questa, sono caricati in memoria centrale contemporaneamente più job, mentre un job è in attesa di un evento e sospende la sua esecuzione, il SO assegna alla CPU un altro tra quelli pronti all’esecuzione. La diminuzione del “tempo perso” è dovuta al cambio di contesto, nel quale il SO fa eseguire un processo all’esecuzione di un altro processo (istanza del programma in evoluzione, cioè è eseguito dal processore). Il SO effettua delle scelte tra tutti i job:
 Quali job caricare in memoria centrale;
 A quale job assegnare la CPU;
Il Time Sharing è il primo dei sistemi operativi di tipo interattivo e sta a indicare il meccanismo di funzionamento. Con il Time Sharing un job che sta utilizzando la CPU può terminare la sua elaborazione sostanzialmente per due motivi:
 Ha terminato il tempo a lui assegnato, cioè il suo Time Slice;
 Ha bisogno di qualche risorsa non disponibile;
L’utilizzo condiviso dell’elaboratore ha portato anche un nuovo problema: la sicurezza dei dati e dei programmi, questione ancora oggi centrale nell’informatica.

LA GESTIONE DEL PROCESSORE
Il programma è costituito dall’insieme delle istruzioni, memorizzato su memoria di massa.
Il processo è un’istanza di un programma in evoluzione, è eseguito dal processore e quindi deve essere residente nella RAM.
Task è sinonimo di “processo”.
Lo scheduling dei job consiste nell’insieme delle strategie e dei meccanismi utilizzati per la scelta dei programmi che dal disco devono essere caricati in RAM.
Lo scheduling della CPU consiste nell’insieme delle strategie e dei meccanismi che permettono di assegnare e sospendere l’utilizzo della CPU da parte dei vari programmi.
Un processo è un’entità logica in evoluzione.

Distinzione di parallelismo in:
• Multitasking: esecuzione di programmi indipendenti sulla CPU e sul processore di I/O; (non implica la multiutenza).
• Multiprocessing: multiprogrammazione estesa a elaboratori dotati di più CPU e processori di I/O.


I moderni sistemi operativi, sono costituiti da due parti:
• Il codice;
• I dati del programma, suddivisi in:
 Variabili globali;
 Variabili locali e non locali;
 Variabili temporanee introdotte dal compilatore;
 Variabili allocate dinamicamente;

Ci sono tre modelli di computazione per i processi:
• Modello di computazione indipendente;
• Modello di computazione con cooperazione;
• Modello di computazione con competizione;


Un processo rispetto a un processore può trovarsi o essere:
• Nuovo (new): è lo stato in cui si trova un processo appena è stato creato;
• Esecuzione (running): il processo sta evolvendo cioè la CPU sta eseguendo le sue istruzioni;
• Attesa (waiting): un processo è nello stato di attesa e sta aspettando che si verifichi un evento;
• Pronto (ready-to-run): un processo è nello stato di pronto se ha tutte le risorse necessarie alla sua evoluzione;
• Finito (terminated): siamo nella situazione in cui tutto il codice del processo è stato eseguito e quindi ha terminato l’esecuzione.

Con stato di processo intendiamo quindi una tra le cinque possibili situazioni in cui un processo in esecuzione può trovarsi: può assumere una sola volta lo stato di nuovo e di terminato, mentre può essere per più volte negli altri stati.
Vita di un processo: al nuovo processo viene assegnato un identificatore e viene inserito nell’elenco dei processi pronti in attesa che arrivi il suo turno di utilizzo della CPU; quando gli viene assegnata la CPU, il processo passa nello stato di esecuzione, dal quale può uscire per tre motivi:
• Termina la sua esecuzione, cioè esaurisce il suo codice e quindi finisce;
• Termina il suo quanto di tempo di CPU e ritorna nella lista dei processi pronti RL;
• Per poter evolvere necessita di una risorsa che al momento non è disponibile: il processo si sospende e passa nello stato di attesa formando la waiting list WL.
Dallo stato di sospeso, cioè dallo stato di attesa, un processo non può passare in quello di esecuzioni.
Il descrittore del processo contiene:
• Identificatore unico;
• Stato corrente;
• Program counter;
• Registri;
• Priorità;
• Puntatori alla memoria del processo;
• Puntatori alle risorse allocate al processo;

I meccanismi con i quali i processi vengono scelti prendono il nome di politiche di gestione o di schedulazione e il componente del SO che si occupa di questa gestione si chiama job scheduler.
In un SO con partizione del tempo un solo processo è in esecuzione e tutti gli altri possono essere in:
• Coda dei processi pronti (RL)
• Coda di attesa di un evento (WL)
Il contesto di un processo è composto da alcune informazioni contenute nel suo specifico PCB, come il valore dei registri, il suo stato, il program counter ecc…. La parte del SO che realizza il cambio di contesto si chiama dispatcher.

CRITERI DI SCHEDULING
Gli obiettivi primari sono:
• Massimizzare la percentuale di utilizzo della CPU;
• Massimizzare il throughput del sistema, cioè il numero di processi completati nell’unità di tempo;
Il Throughput è il numero medio di job, programmi, processi o richieste completati dal sistema nell’unità di tempo.
Il Tempo di completamento (turnaround time) è il tempo dalla sottomissione di un job, programma o processo da parte di un utente nel momento in cui i risultati sono resi effettivamente disponibili all’utente stesso.
Il Tempo di risposta (response time) è il tempo dalla sottomissione di una richiesta da parte dell’utente nel momento in cui il processo risponde, chiamato anche tempo di latenza.
Il Tempo di attesa (wait time) si ottiene dalla somma degli intervalli temporali passati in attesa della risorsa.

LA GESTIONE DELLA MEMORIA
Nel calcolatore sono presenti diversi tipi di memoria in base a velocità e capacità:
• Nastro;
• Disco;
• Memoria principale;
• Cache;
• Registri;
La memoria centrale consiste in un ampio vettore di byte. I compiti del gestore della memoria sono tre:
• Sapere sempre quali parti della memoria centrale sono in uso e quali sono libere;
• Scegliere quale parte di memoria allocare ai processi che la necessitano e quindi deallocarla;
• Gestire lo swapping tra la memoria principale e il disco quando la memoria principale non è sufficientemente grande per mantenere tutti i processi;

Il problema fondamentale che il gestore della memoria deve risolvere è trasformare il programma eseguibile (su memoria di massa) in un processo in esecuzione (in memoria di lavoro).
I programmi che “stanno per diventare processi” vengono messi in una coda di entrata, dalla quale ne verrà selezionato uno da caricare da parte del loader e quindi da collocare nella lista dei processi pronti.
L’assegnazione degli indirizzi è quindi incompleta: vengono cioè generati degli indirizzi logici e all’atto del caricamento vero e proprio questi vengono trasformati in indirizzi fisici.

Il calcolo degli indirizzi logici viene effettuato nella fase di linking dei programmi; il passaggio dell’indirizzo logico a quello fisico avviene nella fase di binding che può essere fatta in momenti diversi:
• Durante la compilazione;
• Nel momento del loading del programma;
• Durante l’esecuzione, soprattutto se si hanno librerie dinamiche;

Nello specifico lo swapping si compone di quattro fasi:
• Identificare i processi inattivi presenti in memoria;
• Salvare sulla memoria temporanea i loro dati;
• Rimuoverli dalla memoria centrale;
• Caricare nello spazio appena liberato il processo che deve essere eseguito;

ALLOCAZIONE DELLA MEMORIA - PARTIZIONAMENTO
Il sistema più semplice per allocare la memoria centrale nei sistemi multiprogrammati è quello di suddividerla in partizioni.
Nello schema a PARTIZIONE FISSA la dimensione della partizione viene definita all’atto dell’inizializzazione del sistema, quindi staticamente, e viene creata una tabella dove si memorizza lo stato delle partizioni, indicando quali sono libere e quali occupate.
In questa si possono verificare due problemi:
• Il problema della frammentazione interna, che si presenta quando le singole partizioni sono di grandi dimensioni; si può risolvere mediante l’uso di una singola coda di ingresso;
• Il problema della frammentazione esterna, che si presenta quando le singole partizioni sono di piccole dimensioni;
Il grado di multiprogrammazione è limitato al numero di partizioni: non posso eseguire processi se non sono in grado di collocare una partizione.
Nello schema a PARTIZIONE VARIABILE è possibile modificare dinamicamente sia il numero sia la dimensione di ogni singola partizione e creare blocchi di dimensione specifica per il nuovo processo direttamente al momento del suo caricamento: avremo quindi tante partizioni quanti sono i processi attivi. Il problema dell’allocazione dinamica della memoria centrale ha come strategia risolutiva tre possibili alternative:
• First-fit
• Best-fit
• Worst-fit

MEMORIA VIRTUALE
La tecnica della compattazione non sempre può essere applicata e in ogni caso è piuttosto onerosa in termini di computazione. I moderni sistemi operativi introducono quindi il concetto di memoria virtuale, realizzata con le tecniche di paginazione e segmentazione. Caricando un pezzo di programma per volta è quindi possibile mandare in esecuzione programmi di qualunque dimensione.

Gli obiettivi della paginazione sono:
• Mantenere in memoria solo le parti necessarie;
• Gestire ogni volta solo piccole porzioni di memoria;
• Non sprecare spazio evitando la frammentazione;
• Poter utilizzare porzioni di memoria non contigue per lo stesso programma;
• Non porre vincoli al programmatore;

Nella paginazione sia il programma sia la memoria centrale vengono suddivisi in pagine di dimensione fissa:
• La memoria fisica in blocchi chiamati frame o pagine fisiche;
• Il programma in blocchi di uguale dimensione detti pagine;
Il numero di pagine logiche può essere diverso dal numero di pagine fisiche:
• Se il numero delle pagine logiche è minore del numero delle pagine fisiche il programma potrebbe anche essere caricato tutto in memoria;
• Se il numero delle pagine logiche è maggiore del numero delle pagine fisiche il programma verrà caricato in memoria parzialmente.
Non necessariamente le pagine fisiche devono essere contigue e quindi le pagine logiche possono trovarsi “mischiate” nella memoria centrale.
La MMU è parte integrante del processore e coopera con l’unità di controllo: la CU genera indirizzi logici e li invia alla MMU, che li traduce nei corrispettivi indirizzi fisici e li scrive sul BUS.
Il SO cura una tabella delle pagine dove il numero stesso di pagina fisica viene utilizzato come indice. A ogni processo viene associata una tabella delle pagine specifica dove sono indicati quali segmenti di codice sono caricati in RAM e quali su disco. Per ottenere un indirizzo di memoria dobbiamo avere a disposizione il numero di pagina e lo spiazzamento nella pagina. Dal numero di pagina la MMU preleva dalla tabella delle pagine l’indirizzo fisico al quale deve essere sommato lo spiazzamento nella pagina.
La paginazione elimina completamente il problema della frammentazione della memoria.

Hai bisogno di aiuto in Informatica?
Trova il tuo insegnante su Skuola.net | Ripetizioni
Registrati via email