Facoltà di Ingegneria Corso di Studi in Ingegneria Informatica Tesi di laurea triennale Architettura dei Sistemi Dual-Core Anno Accademico 2011/2012 relatore Ch.mo prof. Nicola Mazzocca candidato Giovanni Mazzeo matr. N46000095 Indice Introduzione 3 Capitolo 1. Soluzioni Architetturali dei Sistemi ad Elevate Prestazioni 4 1.1 1.2 1.3 1.4 Evoluzione dei processori Pipelining Architetture Superscalari Conclusioni 4 6 8 9 Capitolo 2. Architettura Hardware dei Dual-Core 2.1 2.2 2.3 2.4 2.5 2.6 10 Principi di Funzionamento Organizzazione della Memoria Interconnessione nel Chip Generalità Processore Intel Core 2 Duo Conclusioni 10 12 14 16 17 19 Capitolo 3. Gestione dello Scheduling nei Sistemi Multi-Core 20 3.1 3.2 3.3 Problematiche di Progetto Scheduling dei Thread Lo Scheduler di Linux 20 22 23 Conclusioni 25 Bibliografia 26 II Architettura dei Sistemi Dual-Core Introduzione Lo sviluppo della tecnologia dei semiconduttori ha portato ad architetture nelle quali è possibile distribuire a più unità di elaborazione, interne ad uno stesso chip, l’esecuzione dei programmi. Questi sono i processori multi-core che dominano il mercato dei desktop e dei server e che negli ultimi anni sono sempre più usati in molteplici applicazioni embedded come sistemi di controllo industriali, centraline di apparati automobolistici, applicazioni interattive come i web server ed anche nel caso di dispositivi mobili come i telefoni cellulari e i recentissimi tablet. L’uso crescente dei multi-core, in applicazioni di questo tipo, è dovuto alla sempre maggiore esigenza di multitasking come il caso dei cellulari in cui si vogliono eseguire più task multimediali in parallelo come, ad esempio, far girare un’applicazione web ed allo stesso tempo effettuare una chiamata. Oggetto di studio della trattazione saranno, quindi, gli aspetti principali di processori multi-core più semplici, i dual-core. Saranno descritte le tecniche di parallelismo adottate per sistemi ad elevate prestazioni e che sono usate attualmente nei singoli core; in seguito si parlerà dell’architettura di questi processori e sarà fatto l’esempio applicativo di un dual-core della Intel per rendere più chiara l’analisi. Infine sarà discusso come il sistema operativo distribuisce i vari processi ai core ed anche in questo caso lo studio sarà semplificato con l’esempio del kernel di Linux. 3 Architettura dei Sistemi Dual-Core Capitolo 1 SOLUZIONI ARCHITETTURALI DEI SISTEMI AD ELEVATE PRESTAZIONI In questo capitolo sono illustrati gli aspetti principali dei processori single core, focalizzando lo studio a tecniche come il parallelismo interno al processore che si rivelerà utile per la successiva analisi delle architetture dual-core. Dopo un breve studio dei modelli generici su cui si basano i processori d’oggi saranno affrontati il pipelining ed i sistemi superscalari. 1.1 Evoluzione dei Processori L’unità centrale di elaborazione, suddivisa in unità di controllo e ALU, è parte fondamentale del modello di von Neumann, il suo compito è di eseguire le istruzioni di un programma, grazie anche alla comunicazione con altri sottosistemi del calcolatore. L’esecuzione delle istruzioni avviene a valle di determinati passi di uno specifico algoritmo che si traducono in una sequenza di istruzioni che verranno eseguite dal processore. Questi passi, che rappresentano il ciclo di von Neuamann, sono: acquisizione delle istruzioni, decodifica ed esecuzione. Nel modello di Von Neumann però l’aumento delle prestazioni era possibile solo per via elettronica incrementando la frequenza di clock, il che, come si vedrà in seguito, ha un certo limite. Nel caso delle architetture CISC (Complex Instruction Set Computer) e RISC (Reduced Instruction Set Computer), affermatesi sin dagli anni ’90 ed attualmente in uso, l’aumento prestazionale è possibile sia grazie all’elettronica che all’architettura, mediante le gerarchie di memoria (ad es. le cache), il processore e le periferiche (ad es. i dischi solidi). 4 Architettura dei Sistemi Dual-Core L’evoluzione dai CISC ai RISC è legata alla maggiore densità di integrazione dei chip VLSI (Very Large Scale Integration) che integrano il processore. I due modelli architetturali differiscono per: il numero di codici operativi (in maggior numero per i CISC) che comportano una maggiore o minore complessità della rete di controllo del processore; operandi ammessi dal processore; ed infine i possibili modi di indirizzamento, i RISC infatti prevedono i soli modi di indirizzamento immediato ed indiretto. L’evoluzione dei processori è stata sempre incentrata sul miglioramento delle prestazioni, sono state sviluppate diverse tecniche nel corso degli anni per diminuire i tempi di risposta (tempo che intercorre tra l’inizio e la fine di un task) ed aumentare il throughput (quantità di task eseguiti in un determinato tempo) delle CPU. Volendo restringere il campo al caso di un monoprocessore, le strade percorribili per un aumento delle performance sono l’aumento del clock, l’uso di memorie cache e l’ennuplazione delle risorse hardware con l’implementazione di forme di parallelismo. L’incremento delle performance comporta un incremento della potenza dissipata dai componenti, la quale limita fortemente l’aumento del clock. I transistor infatti hanno un’aliquota di dissipazione di potenza detta dinamica che fa riferimento alla potenza consumata durante gli switch; di conseguenza la dissipazione dipende anche dalla frequenza di switch del componente: Potenza = Frequenza di switch * Voltaggio 2 * Carico capacitivo Questa potenza dissipata implica inevitabilmente l’aumento della temperatura dei transistor componenti il microprocessore con conseguente difficoltà di raffreddamento del componente. In base a quanto detto ne deriva che il clock del processore è limitato; gli attuali clock, dell’ordine dei 4GHz rappresentano già un tale limite. Si preferisce quindi implementare tecniche di parallelismo che permettono un aumento dei tempi di throughput, miglioramento dell’affidabilità ed un’attività del processore continua al costo di una maggiore complessità dell’architettura. Le forme di parallelismo implementabili nei processori sono fondamentalmente legate all’uso delle pipeline ed all’impiego di forme di scalarità dei componenti funzionali. Queste sono oggetto di descrizione nei paragrafi successivi. 5 Architettura dei Sistemi Dual-Core 1.2 Pipelining Per semplificare la descrizione della tecnica del pipeline si ipotizza che il processore debba eseguire un programma prelevando ed eseguendo specifiche istruzioni. Le possibilità sono che il processore o esegua queste istruzioni sequenzialmente, come nel caso di una macchina di Von Neumann, una dopo l’altra (figura 1.1) oppure che, nel caso di un calcolatore con più unità separate, effettui un’ esecuzione in pipeline. F1 E1 F2 E2 F3 E3 Figura 1.1 Nel secondo caso (Figura 1.2) avremo quindi un’unità addetta al prelievo (F) ed un’altra unità addetta all’esecuzione (E). L’istruzione prelevata dall’unità di prelievo sarà posta in un buffer di memoria intermedio (B) mentre il risultato dell’esecuzione in un indirizzo di memoria specifico. Sotto la temporizzazione del clock quindi vi sarà una concatenazione delle istruzioni. Infatti in un primo ciclo di clock verrà prelevata la prima istruzione (F1), nel secondo ciclo mentre un’unità eseguirà l’istruzione 1 (E1) verrà prelevata dall’altra unità F2. Per il terzo ciclo sarà lo stesso. Si ottiene in questo modo un vantaggio in termini di tempo di esecuzione e le unità hardware non saranno mai inattive. Con il pipelining il tempo necessario per eseguire il singolo task non cambia, è sempre lo stesso. Ciò che si ottiene è un incremento del throughput, in quanto è facile notare che aumentano di molto il numero di programmi eseguiti in un determinato tempo. Ciclo di clock 1 Istruzione 1 2 F1 2 3 4 E1 F2 E2 F3 3 E3 Figura 1.2 6 Architettura dei Sistemi Dual-Core In realtà nei processori reali le fasi per l’esecuzione di un programma sono cinque: prelievo (Fetch), interpretazione (Decode), esecuzione (Execution), Memorizzazione ed eventuale Scrittura dei risultati in memoria (Write Back). In questo caso in ogni istante sono in elaborazione progressiva almeno quattro istruzioni e ciò implica che sono necessarie almeno quattro unità hardware distinte, ognuna delle quali opera su dati diversi. Con una pipeline a quattro stadi, la frequenza di completamento delle istruzioni è teoricamente pari a quattro volte quella dell’esecuzione sequenziale. Più in particolare, sarebbe possibile caratterizzare la pipeline attraverso lo speedup definito come il rapporto tra il tempo di esecuzione sequenziale ed il tempo di esecuzione in parallelo. Bisogna però tener conto di alcune limitazioni del pipelining. Un primo problema si potrebbe avere ad esempio nel caso di un’istruzione che richieda il calcolo di un’operazione matematica complessa, e quindi l’uso di più cicli di clock per la singola istruzione (detta condizione di stallo), ciò comporterebbe un ritardo complessivo di tutte le altre istruzioni che seguono. Nella pipeline, come detto precedentemente, le unità depongono nei buffer intermedi le istruzioni prelevate, ciò potrebbe essere causa di un conflitto nel caso in cui un’unità vada a modificare un’informazione contenuta nel buffer che ancora non era stata prelevata dallo stadio successivo. Un’altra situazione di stallo che andrebbe a peggiorare il throughput si potrebbe verificare con le istruzioni di salto presenti nei programmi, infatti, ogni volta che un’istruzione di questo tipo si presenta in un programma la CPU deve eseguire un nuovo flusso di operazioni e quindi deve svuotare la pipeline del precedente flusso e caricare quello nuovo, il che comporta un numero di cicli di clock elevati. I processori, per risolvere questo problema, adottano delle unità chiamate unità di predizione delle diramazioni che fanno predizioni sul flusso del programma, permettendo il recupero di cicli di clock che altrimenti andrebbero persi. L’uso delle architetture multi-core risolve molti dei conflitti che si generano con l’uso delle pipeline, questa strategia progettuale attenua i problemi di coerenza e di predizione dei salti, infatti ogni CPU esegue un programma separato e quindi tra i diversi programmi non si possono avere problemi di coerenza delle istruzioni. 7 Architettura dei Sistemi Dual-Core 1.3 Architetture Superscalari I processori, menzionati nella descrizione del pipelining, sono i più semplici ovvero quelli scalari, questi compiono un’operazione alla volta su un numero specifico di operandi. In un processore vettoriale, invece, una singola istruzione viene applicata ad un vettore formato da più dati raggruppati cosiche un’applicazione che deve eseguire un’operazione su una grande quantità di dati viene svolta con maggiore rapidità. Un processore superscalare è una forma intermedia tra i due: istruzioni diverse trattano i propri operandi contemporaneamente, su diverse unità hardware all'interno dello stesso chip. Come nel caso del pipelining anche le architetture superscalari implementano una forma di parallelismo interno al singolo processore. Una CPU superscalare è caratterizzata dall’avere più unità dello stesso tipo, questa ridondanza comporta vantaggi di non poco conto nei tempi di clock e nel throughput. Il processore superscalare, infatti, in base al numero di unità ridondanti, esegue più di un’istruzione durante un singolo ciclo di clock. Figura 1.3 In figura 1.3 viene fatto l’esempio di una semplice pipeline superscalare dove il numero di unità è raddoppiato, in questo caso si ha l’esecuzione di due istruzioni per ciclo di clock. 8 Architettura dei Sistemi Dual-Core 1.4 Conclusioni Le architetture operanti in pipe sono anche utilizzate nel progetto di sistemi SIMD (Single Instruction Multiple Data). In questo caso le istruzioni operano su dati di tipo vettoriale. Quindi una singola istruzione richiede un significativo numero di operazioni sui dati che vengono elaborati in unità operanti in pipe. Rispetto al modello precedentemente descritto sono limitati i conflitti ma è richiesta una maggior banda di memoria, ottenuta interlacciando banchi differenti. Le limitazioni dei SIMD possono essere superate con un’architettura parallela MIMD (Multiple Instruction Multiple Data), in questo caso ogni processore prevede una propria unità di controllo ed una memoria locale. Mentre nei SIMD il flusso di istruzioni è unico ed i processori eseguono la stessa istruzione ma su elementi di dati diversi, nei MIMD si hanno n flussi di istruzioni, ognuno per ciascun processore, il quale potrà eseguire programmi diversi su dati diversi. I MIMD sono alla base dei multiprocessori e dei multicomputer e si distinguono in base all’organizzazione della memoria, condivisa per il primo, distribuita per il secondo; un’analisi più dettagliata sarà fatta nel capitolo seguente. I multi-core, oggetto di studio della trattazione, sono un caso particolare di multiprocessori. Mentre quest’ultimi sono strutturati con due CPU separate, i multicore integrano nel singolo chip da due a più processori con un evidente vantaggio di bassa dissipazione e di intercomunicazione tra processori, dovuto al fatto che le distanze micrometriche tra le CPU rendono la trasmissione dei segnali più veloce. La caratteristica principale dei sistemi multi-core sta nel fatto che le performance aumentano con l’aumentare del numero dei core e contemporaneamente la frequenza del processore si mantiene bassa, il tutto a vantaggio della potenza dissipata. 9 Architettura dei Sistemi Dual-Core Capitolo 2 ARCHITETTURA HARDWARE DEI DUAL-CORE Il parallelismo nei processori single-core è un aspetto importante delle CPU multi-core, infatti ciascun core prevede che i programmi siano eseguiti in pipe, e quindi alle problematiche esposte nel capitolo precedente si aggiunge un ulteriore fattore di ritardo che è la comunicazione tra i core. Nella trattazione sarà oggetto di studio un processore dual-core, non altro che l’implementazione più semplice di un multi-core. Una volta fatta un’analisi sui principi di funzionamento che sono alla base di queste CPU, verrà trattata l’organizzazione della memoria, che è parte del meccanismo di interazione tra i core e dalla quale, quindi, dipendono le prestazioni finali; sarà descritta poi l’interconnessione nel chip della CPU ed infine verrà fatto l’esempio del processore Intel Core 2 Duo per vedere come realmente è composta l’architettura di un dual-core. 2.1 Principi di Funzionamento La tecnologia dei circuiti integrati ha fatto sì che le dimensioni dei processori si riducessero fortemente, sino ad arrivare ad un limite minimo. Per questo motivo nel 2005, essendo diventato troppo oneroso e complicato aumentare oltre un certo livello la frequenza del clock, arrivarono sul mercato dei microprocessori i multi-core. Questi concettualmente sono simili agli SMP (Symmetric Multi Processing), i quali sono composti da più processori che condividono una memoria comune, che, dato l’elevato prezzo, sono usati prevalentemente nei sistemi server e nelle workstation. I multicore d’altro canto integrano all’interno del singolo chip da due a più core, collegati attraverso 10 Architettura dei Sistemi Dual-Core una rete di interconnessione sempre interna al chip, visti dal sistema operativo come più processori col vantaggio che nel caso dei multicore lo spazio occupato rimane limitato ed inoltre il ritardo di trasmissione di un segnale elettrico sarà minore rispetto al caso degli SMP. Oggetto della trattazione sono i dual-core (figura 2.1), i quali prevedono solo due core e che possono essere di riferimento per architetture a più di due nuclei. L’architettura di ogni singolo core si avvicina molto a quella Figura 2.1 del corrispondente monoprocessore con qualche modifica che supporti il parallelismo come l’aggiunta di istruzioni atomiche per la sincronizzazione. Per la realizzazione del chip sul quale andranno i due core esistono tre approcci possibili: a die singolo, a die doppio o a die monolitico, dove il die è quella parte di silicio che si trova al centro del processore e che contiene il core. La struttura a die singolo, adottata nel Pentium D Smithfield della Intel, integra i due core sul singolo die, il che implica da una parte un’architettura semplice ed economica, dall’altra una riduzione delle prestazioni e la perdità di affidabilità dato che se un core fallisce l’intero chip del processore andrà fuori uso. Per quanto riguarda la gestione della cache, in particolare quella di livello 2 e 3, si ha che quella di ultimo livello (la L2 per il die singolo) è associata a ciascun core, con la possibilità per un core di accedere alla cache dell’altro attraverso il bus, col rischio di saturarlo. Nel caso dell’approccio a die doppio, l’architettura diventa più complessa rispetto al caso singolo dato che si hanno due die fisicamente separati posti sullo stesso package il che implica la necessità di una rete di interconnessione tra i core ed anche la separazione delle cache di ultimo livello. Proprio questa rete di interconnessione esterna ne rende la progettazione più complicata ma allo stesso tempo da vantaggi al progettista in termini di affidabilità e prestazioni. Allo stesso tempo però c’è il problema che i due core, essendo nello stesso package, accedono entrambi ai dati con un evidente utilizzo di risorse non necessarie. Per questo motivo i costruttori di microprocessori prediligono strutture a die monolitico nelle quali vi è una condivisione di determinate unità del processore come la cache, il controller della memoria o lo scheduler che ripartisce il carico tra i 11 Architettura dei Sistemi Dual-Core core. Proprio la condivisione della cache (come in figura 2.1) comporta grandi vantaggi grazie ad un minor numero di dati presenti sul bus, il quale si limita a far transitare i dati da e verso la memoria. Questa struttura viene utilizzata nei processori degli ultimi anni come l’Intel Core i7. L’uso di processori multicore esige un adattamento dei programmi che girano su queste CPU. Infatti, se questi ultimi non fossero ottimizzati per un utilizzo multithread non vi sarebbe alcun vantaggio nell’uso di un’architettura multicore dato che tutto il programma verrebbe eseguito da un singolo core con l’evidente perdita di parallelismo nell’esecuzione. Riveste particolare importanza lo scheduling dei processi sui core, infatti, è proprio da questo che dipende l’attività dei processori. Lo scheduling sui dual-core sarà oggetto di studio più approfondito nel corso della trattazione. Altro aspetto importante nei multi-core è la gestione della memoria, questa determina come i core comunicano tra di loro, le performance delle applicazioni in parallelo ed il numero di core che il sistema potrebbe supportare. Nel paragrafo successivo sarà fatta un’analisi sui modelli di organizzazione della memoria implementati nei dual-core. 2.2 Organizzazione della Memoria La memoria potrebbe rappresentare in un sistema dual-core un collo di bottiglia per le prestazioni, non importa quanto si renda veloce l’unità di elaborazione, se la memoria non può fornire dati ed istruzioni ad una buona velocità non si avrà alcun miglioramento nelle performance. La causa dei problemi sta nel fatto che il tempo di ciclo della memoria, definito come il tempo che intercorre tra due operazioni successive è molto più lungo di quello del processore, ciò comporta che quando la CPU comunica con la memoria per un trasferimento dovrà aspettare l’esaurimento del tempo di ciclo della memoria con una conseguente inattività del processore. Nelle architetture possibile avere parallele due è possibili configurazioni della memoria: una prima, detta a memoria condivisa Figura 2.2 12 Architettura dei Sistemi Dual-Core (figura 2.2), prevede che gli n processori condividano la stessa memoria primaria. In questi sistemi detti multiprocessore, tutti i processi che girano sulle varie CPU condividono lo stesso spazio di indirizzamento logico mappato su una memoria fisica che può anche essere distribuita fra i vari processi. Figura 2.3 Ogni processo può leggere e scrivere un dato in memoria usando una load e una store, e la loro comunicazione avviene tramite la memoria condivisa. Chiaramente con l’aumentare del numero di processori il traffico di dati sul bus che è condiviso diventa eccessivo e ciò comporterebbe un forte rallentamento del sistema complessivo. Per questo nel caso in cui il numero di CPU sia elevato e si voglia aumentare la potenza di calcolo si preferisce un’architettura del sistema parallelo a memoria distribuita (figura 2.3). In questo caso ogni processore ha una propria memoria con la possibilità che questa sia condivisa o esclusiva; nel primo caso ogni processore può accedere a dati che risiedono nella memoria degli altri processori e quindi tutti i processori avranno lo stesso spazio di indirizzi, nel secondo caso ad ogni processore è fisicamente ed anche logicamente associata una memoria. Ciò vuol dire che ogni processore può accedere esclusivamente alla memoria alla quale è associato per mezzo di uno spazio di indirizzi proprio. In questa categoria rientrano i multicomputer e i cluster. Nel caso dei dual-core e più in generale dei multi-core la struttura è chiaramente a memoria condivisa dato che fisicamente il processore è unico, e viene considerato come un multiprocessore dal sistema operativo. La principale difficoltà che si ha nella gestione della memoria in queste architetture parallele sta nel garantire la coerenza delle memorie cache, il che incide su tutto il sistema complessivo. Nei sistemi multi-core i singoli core mantengono le cache di una risorsa di memoria condivisa, supponendo che il core P1 ha una copia di un blocco di memoria nella cache C1 (in seguito ad una precedente lettura) e l’altro core P2 cambia quel blocco di memoria allora i dati presenti in C1 non saranno più validi. Il problema può essere risolto secondo due alternative, scelte in base al tipo di programmi da eseguire: 13 Architettura dei Sistemi Dual-Core • Write-Through – La cache in questo caso non viene usata per supportare accessi in scrittura da parte della CPU, quindi in presenza di una richiesta di scrittura da parte del processore, il controllore di cache genera sempre una richiesta di scrittura in memoria RAM, con il conseguente sistematico ritardo nel completamento dell'istruzione. • Write-Back – Questa soluzione prevede che il dato da scrivere viene memorizzato solo nella cache e non nella memoria primaria in modo tale da poter completare il ciclo di scrittura del processore, ciò genererà inconsistenza tra il valore contenuto nella cache ed il valore differente contenuto allo stesso indirizzo nella RAM. È quindi evidente che la prima alternativa, di più semplice realizzazione, è praticabile solo nel caso in cui i programmi abbiano un numero di istruzioni di scrittura da eseguire molto minore del numero di istruzioni di lettura. Contribuisce alla consistenza della cache, da un punto di vista fisico, la rete di interconnessione tra i core grazie alla quale risulta possibile l’invio dei segnali di validazione e di invalidazione agli altri processori. 2.3 Interconnessione nel Chip L’interconnessione tra i core è responsabile della comunicazione tra i thread e della coerenza della cache (se prevista). Sono possibili vari tipi di interconnessioni come quelle a bus, crossbar, ring e NoC (Network-on-Chip), ciascuno dei quali presenta vantaggi e svantaggi in termini di semplicità di implementazione e di performance. Ad esempio, la connessione a bus è caratterizzata da una progettazione più semplice ma allo stesso tempo non appena il numero di processi aumenta si ha una saturazione del canale. Contrariamente il NoC supporta un alto numero di processi ma l’implementazione è più complicata. Per quanto concerne la coerenza della cache, la rete di interconnessione scelta può implementare due possibili protocolli di scambio messaggi: broadcast based o directory based (figura 2.4). Nel primo, più semplice, quando deve essere fatta una write (W) viene inviato a tutti gli altri processori un singolo messaggio di invalidazione per avere il permesso di scrittura (figura 2.4 a), una volta ricevuto il messaggio di risposta ACK 14 Architettura dei Sistemi Dual-Core (acknowledgement) sarà possibile la scrittura. Intanto la read di P3 dovrà essere ritardata dato che il protocollo broadcast (solitamente a Bus) prevede un occupazione totale della rete di interconnessione. Nel protocollo directory based vengono attivate concorrentemente più attività Figura 2.4 per la coerenza della cache. È prevista una coherence directory associata a ciascun core che non è altro che una tabella che contiene blocchi di indirizzi di memoria associati a quella cache e i processori che ne fanno uso. Quando viene fatta la richiesta di accesso ad un blocco di indirizzi, il processore dovrà chiedere al nodo che ha la coherence directory con quel blocco indirizzi ed ottenere così i processori che correntemente usano quel blocco di cache. In figura 2.4 b è mostrato un esempio del protocollo directory based. P1 deve fare una write in uno specifico blocco di cache, quindi fa richiesta al nodo dove c’è la coherence directory associata a quel blocco (nell’esempio P2) e determina quindi i processori che possegono in quell’istante quel blocco di cache (P3). Allora P1 invierà il messaggio di invalidazione al quale P3 risponderà con un ACK e così P1 potrà fare la write. Stesso per la read, P3 chiede al nodo principale dove sono gli indirizzi di interesse (P4) che sarà anche il processore che possiede in quell’istante la cache, P3, quindi, una volta fatta la richiesta a P4 potrà leggere. Il vantaggio del directory based sta nel fatto che la read e la write possono essere fatte in parallelo dato che il network non è interamente occupato da un broadcast. Figura 2.5 Figura 2.5 15 Architettura dei Sistemi Dual-Core 2.4 Generalità Una volta studiati gli aspetti hardware principali dei processori dual core, è utile avere un’idea chiara di come è strutturato una generica CPU di questo tipo e quali sono i parametri principali che ne caratterizzano la qualità. Ogni Figura 2.6 CPU attualmente in commercio, eccetto qualche piccola variazione, ha uno schema come quello di figura 2.6. In questo processore a due core osserviamo che la cache di livello 1 è interna a ciascun core e si divide in cache dati e cache istruzioni, questa è una memoria molto veloce e permette di immagazzinare dati frequentemente usati dal processore, solitamente le sue dimensioni sono intorno ai 32 KB. Sotto alla cache L1 c’è la cache di livello 2 sottostante, che in base al tipo di architettura potrà essere comune ad entrambi i core o propria del singolo core ed è dell’ordine dei 2MB. I due livelli sottostanti, system request queue e crossbar, sono addetti alla comunicazione con il resto dei componenti del sistema, più in particolare, il memory controller e i link di I/O. I core possono comunicare tra di loro attraverso il system request interface, grazie al quale può essere effettuato l’aggiornamento per la coerenza della cache ed i vari trasferimenti di dati tra le cache dei processori. Ogni processore è caratterizzato da parametri specifici, schematicamente questi sono: • Numero di Core: chiaramente indica il numero di core montati sul package • Clock: indica la frequenza di clock alla quale lavora il processore, questa varia dal numero di core scelti e dal tipo di utilizzo della CPU (desktop, notebook, server). Genericamente varia dai 1.2GHz sino ad arrivare ai 3.8GHz. 16 Architettura dei Sistemi Dual-Core • Socket: è quella parte della scheda madre dove viene inserito il processore, il numero rappresenta il numero di pin di contatto. • Bus: rappresenta la frequenza del bus di sistema (dell’ordine di 10! MHz). • Cache: sta ad indicare le dimensioni delle cache di 1°, 2° e 3° livello. • Coherence: è il tipo di tecnica adottata per la coerenza, se broadcast o directory. • Watt: è il consumo massimo dello specifico processore. Vi saranno inoltre altri parametri che variano sulla base delle tecnologie adottate dai cotruttori di microprocessori. Viene analizzato nel paragrafo successivo il processore Dual-Core della Intel, il modello Core 2 Duo che prevede numerose varianti; ne verrà scelta una, specifica per sistemi desktop. 2.5 Processore Intel Core 2 Duo L’Intel Core 2 Duo presentato nel 2006 è stato il primo processore dual-core progettato dalla Intel, esso è stato usato in varie applicazioni possibili che vanno dai sistemi desktop sino ai sistemi mobile. Per questo motivo, come scritto ne “Intel Technology Journal”, è necessario garantire che il sistema mantenga alti livelli di performance adattandosi, allo stesso tempo, a differenti inviluppi termici. Tutti gli sforzi fatti sono stati incentrati proprio sugli aspetti termici e di potenza dissipata dal dual-core con l’implementazione di tecniche specifiche in questo ambito. L’Intel Core 2 Duo è costituito da due core Pentium M i quali condividono una memoria cache di 2° livello. Figura 2.7 17 Architettura dei Sistemi Dual-Core In figura 2.7 è possibile vedere la struttura gerarchica del processore: • Ogni processore ha un indipendente APIC (Advanced Programmable Interrupt Controller) ovvero un sistema di controllori Figura 2.8 avanzati di interruzioni programmabili progettato dalla Intel. In questo modo il SO considera le unità come logicamente separate. • Ogni core ha un proprio thermal control, di vitale importanza per garantire l’affidabilità del processore, questo permette di massimizzare le performance entro i limiti termici. Per migliorare e tenere traccia delle condizioni termiche del sistema la Intel usa sensori digitali ed il dual-core multiple-level thermal control, a differenza dei Pentium M che prevedevano un singolo analog thermal diode che non poteva essere posizionato nei punti più caldi del die. La tecnologia Intel prevede un dual-core power monitor capability (figura 2.8) con la funzione di prevenire eccezioni termiche, questo infatti “strozza” la CPU al verificarsi del superamento delle temperature oltre il limite prestabilito. Nel caso di un malfunzionamento del sistema di raffreddamento verrà iniziata la sequenza Out of Spec che porterà allo shutdown totale. • Il power management logic riveste un ruolo importante specialmente nel caso di applicazioni mobile dato che specialmente da questo dipende il consumo della batteria. Il sistema Intel Core Duo, per controllare il consumo di potenza, usa la tecnologia Intel SpeedStep. La via tradizionale per controllare la dissipazione di potenza e quindi anche la temperatura è attraverso un’interfaccia software/hardware. Si fa uso dello schema ACPI (Advanced Configuration and Power Interface) nel quale vengono definiti vari livelli di modalità di riposo, in cui ognuno degli stati rappresenta una più efficiente strada di risparmio energetico. Questi stati sono: 18 Architettura dei Sistemi Dual-Core o Active -> CPU ON o Auto Halt -> Clock del core OFF o Stop Clock -> Clock del core e del bus OFF o Deep Sleep -> Generatore di clock OFF o Deeper Sleep -> Riduzione voltaggio 2.6 Conclusioni Nel corso del capitolo sono state trattate le problematiche principali dei sistemi dual-core, dall’organizzazione della memoria sino alle problematiche sulla potenza ed il calore dissipato. È stato accennato che nei singoli core sono eseguiti i programmi in pipe; è sulle pipe che, per ottimizzare l’uso del processore, evitare tempi di attesa per un processo troppo lunghi e migliorare lo scambio di dati tra processi su più core, è necessario implementare politiche di scheduling adatte al tipo di applicazione sulla quale il processore deve lavorare. Infatti, in base al numero di interazioni tra i processi, è possibile fare una distinzione tra CPU ad accoppiamento stretto, le quali prevedono un alto numero di interazioni, e ad accoppiamento lasco con un basso numero di interazioni tra i processi. Ad esempio nel caso di applicazioni web l’accoppiamento è lasco dato che per ogni utente che chiede di connettersi ad un sito web viene dedicato un thread indipendente. Lo scheduling nei processori multi-core sarà trattato nel capitolo seguente con alcuni accenni a quello implementato nel sistema operativo Linux. 19 Architettura dei Sistemi Dual-Core Capitolo 3 GESTIONE DELLO SCHEDULING NEI SISTEMI MULTICORE Lo scheduler dei processi è parte fondamentale di un sistema operativo, esso gestisce l’assegnamento della CPU ai task permettendo così di massimizzare il throughput e minimizzare il tempo di risposta. Nel caso di architetture multiprocessore lo scheduling si deve occupare sia di gestire i processi nel singolo processore che distribuirli tra i vari processori sulla base di criteri che saranno mostrati a breve. Durante questo capitolo saranno trattate le possibili politiche adottate dal sistema operativo per schedulare i processi tra più core; saranno compresi i vantaggi dello scheduling dei thread; infine sarà fatto l’esempio delle tecniche adottate nei sistemi multicore dallo scheduler del kernel di Linux 3.1 Problematiche di Progetto Una delle problematiche principali nel progetto dello scheduling multicore è l’assegnamento dei processi ai core. La strada più semplice per l’implementazione di una buona politica è di trattare i core come delle risorse comuni ed assegnare quindi i processi ai core su richiesta in modo statico o dinamico. 20 Architettura dei Sistemi Dual-Core Nel primo caso, viene creata una coda d’attesa per ogni core, questo esegue processi dal loro avvio sino al loro completamento. Un vantaggio di quest’approccio è che l’overhead nelle operazioni di scheduling è ridotto dato che l’assegnamento del core è fatto Figura 3.1 una sola volta per tutti. Un chiaro svantaggio, invece, è che si potrebbero verificare situazioni in cui la coda dei task è vuota e quindi il core associato rimane inattivo mentre gli altri continuano a lavorare. È per questo motivo che si preferisce un approccio dinamico, nel quale si fa uso di una coda comune ai due processori dalla quale sono schedulati i processi al primo core libero. Un’altra soluzione ancora, usata in Linux, è la dynamic load balancig. In questo caso vi è una coda per ogni core ed i processi in attesa si spostano tra le code. La gran parte degli scheduling multicore prevede n code per n core e i processi sono inseriti nella coda più corta. I processi da eseguire hanno varie priorità, ad esempio quelli relativi al kernel del SO rivestono una particolare importanza. Sono possibili due approcci per l’esecuzione di task con determinate priorità, il Master/Slave o Peer. Nel primo caso le funzioni chiave del kernel vengono eseguite da uno specifico core (il Master), gli altri core eseguono i programmi utente (gli Slave). Il Master è anche responsabile dello scheduling dei vari job; se uno Slave necessita di un servizio (ad esempio una I/O call) dovrà fare richiesta al Master. Una soluzione di questo tipo non è ottima poichè il fallimento del Master comporterà la caduta di tutto il sistema ed inoltre è chiaro che se le richieste di servizi sono elevate il Master farà da collo di bottiglia per le performance. Nel caso dell’approccio Peer il kernel può eseguire su qualunque core, il quale sceglie autonomamente il processo da schedulare. Questo rende più complicato il SO poichè deve garantire che due processori non scelgano gli stessi processi e che non venga perso nessun processo. L’ultima problematica dello scheduling è su come scegliere i processi da eseguire, questo verrà trattato nel paragrafo successivo dove si parlerà dello scheduling dei thread, preferito a quello fra processi. 21 Architettura dei Sistemi Dual-Core 3.2 Scheduling dei Thread Nel paragrafo precedente si è parlato di scheduling dei processi, in verità nei sistemi multicore allo scheduling dei processi (multitasking) si preferisce lo scheduling dei thread (multithreading). Le ragioni di tale scelta sono varie. Nei SO che supportano il multithread, un processo è definito come un’unità che possiede risorse allocate ed un’unità di protezione, esso ha uno specifico spazio di indirizzamento e prevede un accesso protetto ai processori, ad altri processi per l’intercomunicazione tra di essi, ai file e alle risorse di I/O. All’interno di un processo, ci possono essere da uno a più thread i quali hanno un proprio stato di esecuzione, un proprio stack di esecuzione e lo stesso spazio di indirizzamento il che implica che più thread in uno stesso processo condividono le risorse ed hanno la possibilità d’accesso agli stessi dati. L’uso dei thread implica diversi vantaggi, ad esempio la creazione e la terminazione di un thread è molto più semplice e veloce che quella di un processo, lo switch di due thread interni ad un processo è meno dispendioso in termini di tempo, la comunicazione tra thread è più efficiente dato che a differenza dei processi non deve essere invocato il kernel del SO con evidenti vantaggi in termini prestazionali. Nei sistemi multi-core i thread possono essere usati per sfruttare a pieno il parallelismo di un’applicazione. Infatti se i vari thread di un’applicazione sono eseguiti simultaneamente sui core le performance aumentano di molto. C’è da dire però che l’uso dei thread nei multi-core è preferibile quando il codice che deve essere eseguito dal thread è il più indipendente possibile dal resto del programma, ovvero che le interazioni tra i thread siano basse, ciò per garantire la consistenza dei dati in memoria e per evitare troppi punti di sincronizzazione nei quali un thread deve aspettare la terminazione di un altro per andare avanti nell’esecuzione. Le politiche di assegnazione del core al thread sono principalmente due: load sharing e gang scheduling. Nella prima i processi sono assegnati a determinati core, vi è poi una coda globale di thread in attesa ed ogni core quando inattivo seleziona il thread dalla coda. Nel caso del gang scheduling un set di thread viene schedulato per eseguire su più core contemporaneamente. 22 Architettura dei Sistemi Dual-Core Il funzionamento vero e proprio di uno scheduling multi-core sarà descritto nel prossimo paragrafo con l’esempio del kernel Linux 2.6 ed un accenno al 2.6.23 CFS. 3.3 Lo Scheduler di Linux La versione dello scheduler di Linux 2.6 esegue i task con una complessità O(1) e prevede code di thread a priorità (run queues) per ciascun core (figura 3.2), ogni coda è divisa in Figura 3.2 active array e expired array, ciò per distinguere i thread che ancora non hanno usato a pieno i loro time slice rispetto a quelli che l’hanno fatto. Ad ogni task viene assegnata una priorità statica alla sua creazione, questa può essere modificata in base al quantitativo di tempo che il task è in fase di run o sleep. Quelli che spesso sono in fase di sleep sono solitamente i task I/O Bound, ai quali viene aumentata la priorità, gli altri sono i task CPU Bound che si vedono abbassare la priorità. Il time slice assegnato al task è in funzione della priorità, infatti se un task non usa interamente il suo quanto di tempo oppure esegue troppo a lungo mentre un altro task con la stessa priorità è in attesa allora verrà arretrato alla fine della coda active array. All’interno di ciascun core i task sono selezionati in base al grado di priorità, per quelli aventi lo stesso grado, la scelta è fatta con politica FIFO. I task nell’expired array, avendo consumato tutto il time slice, hanno poco bisogno rispetto a quelli nell’active array del tempo di CPU, questi quindi potrebbero non eseguire per lasciare ai task nell’active array una parte equa di CPU. Quando l’active array diventa vuoto avviene uno swap tra i due array e si ripete il processo. Per utilizzare efficientemente tutti i core viene usato nello scheduling Linux il load balancing, generalmente lo scheduler cerca di mantenere i thread associati allo stesso core di modo tale da incrementare la località della cache. Ogni 200 ms si verifica un trigger, questo fa si che il carico 23 Architettura dei Sistemi Dual-Core viene spostato dal core più occupato a quello avente un miglior bilanciamento di carico. C’è da dire però che alcuni task non possono essere spostati, o perché non possono eseguire su uno specifico core oppure perché hanno eseguito recentemente e necessitano ancora dei dati nelle cache proprie del core; nel caso in cui il load balancing fallisce troppe volte, i task vengono spostati forzatamente. Questa versione dello scheduler di Linux è stata abbandonata recentemente, si fa uso negli ultimi anni della versione 2.6.23 Linux CFS (Completely Fair Scheduler). Questa versione dello scheduler non fa più distinzione tra i tipi di processi, sono trattati equamente senza priorità, è stato fatto ciò perché spesso il sistema falliva nel distinguere i tipi di task creando problemi. A differenza della versione precedente lo scheduling non è fatto su una run queue ma su una struttura dati ad albero nota come red-black tree (figura 3.3). Lo scheduler memorizza le strutture riferenti ai task nel redblack tree, dove la chiave dell’albero è il tempo speso dal processore. Ciò permette di selezionare in maniera opportuna i processi che hanno fatto basso uso del tempo del processore, i quali si Figura 3.3 trovano nei nodi a sinistra dell’albero. La entry del processo selezionato sarà allora rimossa dall’albero, il tempo di esecuzione speso sarà aggiornato ed allora la entry rientra nell’albero in una specifica posizione. Ci sarà un nuovo nodo a sinistra da prelevare e si ripeterà l’operazione. Nel caso in cui un task dovesse impiegare parte del suo tempo in fase di sleep allora il valore del suo tempo speso sarà basso e quindi automaticamente sarà posto nella parte sinistra dell’albero, ciò implica che questi task faranno uso del processore tanto quanto quelli che costantamente sono in fase di run. 24 Architettura dei Sistemi Dual-Core Conclusioni Nel corso della trattazione è stato visto come sia possibile ottenere notevoli prestazioni dai processori attraverso un parallelismo implementato sia a livello hardware che a livello software al costo di una maggiore complessità dell’architettura. Questa nuova tecnologia di microprocessori oltre che in applicazioni desktop o embedded è anche usata in sistemi più complessi come i data center organizzati con architetture cluster o multicomputer in cui ogni processore componente la rete di calcolatori è multi-core. In questo modo è evidente che la potenza di calcolo dell’intero sistema diventa elevatissima; e non solo, infatti, notevoli vantaggi si hanno in termini di affidabilità. Questo continuo evolversi delle architetture dei sistemi di calcolo, però, deve andare di pari passo con lo sviluppo di software e di linguaggi di programmazione che siano in grado di garantire un utilizzo ottimale dell’hardware. L’architettura multi-core è implementabile oggi anche negli FPGA (Field Programmable Gate Array). Il loro utilizzo rappresenta una valida, efficiente ed economica soluzione per la realizzazione di sistemi embedded. Tale tecnologia offre la possibilità all’utente finale di sintetizzare sistemi composti da componenti complessi quali i processori. Negli ultimi anni la maggiore densità di integrazione in essi possibile ha reso praticabile, oltre alla realizzazione di SoC, anche la realizzazione di MPSoC, ciò consente, quindi, anche l’integrazione di architetture multi-core e di reti di interconnessione avanzate per la realizzazione di nuovi e più potenti sistemi dedicati a specifiche applicazioni. Tali realizzazioni sono oggi oggetto di una vasta ricerca mediante sia la definizione di architetture più performanti, a basso costo e a basso consumo sia per la realizzazione di ambienti di sviluppo in grado di rendere trattabile la complessità insita in tali sistemi. 25 Architettura dei Sistemi Dual-Core Bibliografia [1] Patterson, Hennessy 2009 “Computer Organization and Design” [2] Hamacher, Vranesic, Zaky, Manjikian 2010 “Computer Organization and Embedded systems” [3] Hamacher, Vranesic, Zaky 1997 “Introduzione all’Architettura dei Calcolatori” [4] Intel Technology Journal 2006 “Introduction to Intel Core Duo Processor Architecture” [5] Blake, Dreslinsky, Mudge 2009 “A Survey of Multicore Processors” [6] Stallings 2006 “Operating Systems, Internals and Design Principles” [7] Intel Technology Journal 2010 “Process Scheduling Challenges in the Era of MultiCore Processors” [8] Fahringer 2008 “Operating System Scheduling on Multi-Core Architecture” [9] Ziemba, Upadhyaya, Pai 2010 “Analyzing the Effectiveness of Multicore Scheduling” 26