Elaborato Mazzeo Giovanni N46000095

Transcript

Elaborato Mazzeo Giovanni N46000095
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