Il presente articolo è stato tradotto automaticamente.

CLR

Sviluppo in .NET per processori ARM

Andrew Pardoe

 

I consumatori sono oggi un grande driver del mercato tecnologia. Come dimostra la tendenza conosciuta come "la consumerizzazione dell'IT", lunga durata della batteria ed esperienze sempre connesso e multimediali sono importanti per tutti i clienti la tecnologia. Per consentire la migliore esperienza per i dispositivi con batteria a lunga durata, Microsoft sta portando il sistema operativo Windows 8 per sistemi integrati su processore ARM bassa potenza, che alimenta i dispositivi mobili più oggi. In questo articolo, mi occuperò di dettagli circa il Microsoft .net Framework e il processore ARM, che cosa voi come uno sviluppatore .net dovrebbe tenere a mente e che cosa abbiamo di Microsoft (dove io sono un program manager del team CLR) aveva a che fare per portare .net al braccio.

Come sviluppatore .net, si può immaginare che la scrittura di applicazioni per funzionare su una varietà di diversi processori porrebbe un po' di imbarazzo. Architettura del processore ARM (ISA) è incompatibile con Isaia del processore x86 Costruito per funzionare in modalità nativa su x 86 di apps funzionare bene su x 64 perché ISA del processore x64 è un superset del x86 ISA. Ma lo stesso non è vero nativo x86 applicazioni in esecuzione sul braccio — hanno bisogno di essere ricompilati per eseguire sull'architettura incompatibile. Essere in grado di scegliere da una gamma di dispositivi differenti è grande per i consumatori, ma che porta alcune complessità alla storia dello sviluppatore.

Scrivendo il vostro app in un linguaggio .net non solo consente di riutilizzare il codice e le competenze esistenti, consente anche il vostro app funzionare su tutti i Windows 8 processori senza ricompilazione. Porting del Framework .net al braccio, abbiamo aiutato di astrarre le caratteristiche uniche dell'architettura che sono sconosciute alla maggior parte degli sviluppatori di Windows. Ma ci sono ancora alcune cose che dovete guardare fuori per quando si scrive codice per eseguire sul braccio.

La strada per braccio: .Net, passato e presente

Il Framework .net già gira su processori ARM, ma non è esatto stesso .net Framework come la versione che viene eseguito sul desktop. Quando abbiamo iniziato a lavorare sulla prima versione di .net, ci siamo resi conto che essere in grado di scrivere codice facilmente portabile tra processori era chiave alla nostra proposta di valore di maggiore produttività. Il processore x86 ha dominato il desktop computing spazio, ma un'enorme varietà di processori ha esistito nello spazio embedded e mobile. Per consentire agli sviluppatori di questi processori di destinazione, abbiamo creato una versione del .net Framework chiamato .net Compact Framework, che gira su macchine che hanno vincoli di memoria e processore.

I primi dispositivi che .net Compact Framework supportato erano così piccolo quanto 4 MB di RAM e una CPU a 33 MHz. Il design di .net Compact Framework ha sottolineato un'implementazione efficiente (che ha permesso di eseguire su tali dispositivi vincolate) e la portabilità (così potrebbe eseguire tutta la vasta gamma di processori che erano comuni negli spazi mobili and embedded). Ma i più popolari dispositivi mobili-smartphone — ora eseguire configurazioni che sono paragonabili ai computer di 10 anni fa. .NET Framework desktop è stato progettato per funzionare su macchine Windows XP con almeno un processore da 300 MHz e 128 MB di RAM. Windows Phone dispositivi oggi richiedono almeno 256 MB di RAM e un processore ARM Cortex moderno.

NET Compact Framework è ancora una grande parte della storia dello sviluppatore Windows Embedded Compact. Dispositivi embedded scenari eseguire nelle configurazioni vincolate, spesso con un minimo di 32 MB di RAM. Abbiamo anche creato una versione di .net Framework chiamato il Micro Framework .net, che gira su processori che hanno più di 64 KB di RAM. Quindi in realtà abbiamo tre versioni del Framework .net, ognuno dei quali viene eseguito su una diversa classe di processore. Ma questa è la prima volta che il nostro prodotto di punta, il Framework .net desktop, ha aderito al compatto e Micro Framework in esecuzione su processori ARM.

In esecuzione su braccio

Anche se il Framework .net è stato progettato per essere indipendente dalla piattaforma, che principalmente è stato eseguito su x hardware x86 in tutta la sua esistenza. Questo significa che alcuni modelli di x 86-specifici sono scivolati nella mente collettiva dei programmatori .net. Si dovrebbe essere in grado di concentrarsi sulla scrittura di applicazioni grande invece di scrivere per l'architettura del processore, ma si dovrebbe tenere un paio di cose in mente, quando si scrive codice .net per eseguire sul braccio. Questi includono un modello di memoria più debole e più severi requisiti di allineamento dei dati, nonché alcuni luoghi dove i parametri della funzione sono trattati in modo diverso. Infine, ci sono a pochi passi di configurazione del progetto in Visual Studio che differiscono quando i dispositivi di destinazione. Tratterò ciascuno di questi.

Un modello di memoria più debole un "modello di memoria" si riferisce alla visibilità delle modifiche apportate allo stato globale in un programma multithread. Un programma che condivide i dati tra due (o più) thread avrà normalmente un blocco sui dati condivisi. A seconda del particolare serratura usata, se un thread è l'accesso ai dati, altri thread che tentano di accedere ai dati si bloccherà fino a quando il primo thread è completato con i dati condivisi. Ma i blocchi non sono necessari se si sa che ogni thread che accede ai dati condivisi sarà farlo senza interferire con la vista di altri thread di tali dati. Programmazione in tal modo viene chiamato utilizzando un algoritmo di "lock-free".

Guai con gli algoritmi senza blocco arriva quando non si conosce l'ordine preciso in cui verrà eseguito il codice. Processori moderni riordino le istruzioni affinché il processore può progredire in ogni ciclo di clock e combinare scrive alla memoria al fine di ridurre la latenza. Anche se quasi ogni processore esegue queste ottimizzazioni, c'è una differenza in come l'ordine di lettura e scrittura è presentato al programma. x processori basati su x86 garanzia che il processore sarà simile è in esecuzione la maggior parte delle letture e scrive nello stesso ordine che il programma specifica li. Questa garanzia è chiamata un ordinamento di scrittura modello, o forte forte memoria. Processori ARM non fare come molti garanzie — sono generalmente libere di muoversi le operazioni fintanto che facendo così non cambia il modo che il codice viene eseguito in un programma a thread singolo. Il processore ARM fare alcune garanzie che consentono attentamente costruito codice lock-free, ma ha che cosa ha denominato un modello debole memoria.

È interessante notare che, il .net Framework CLR si ha un modello di memoria debole. Tutti i riferimenti a scrivere ordinare nella specifica ECMA Common Language Infrastructure (CLI) (disponibile in formato PDF a bit.ly/1Hv1xw), lo standard che il CLR è progettato per soddisfare, fare riferimento a volatili accessi. In c# questo significa gli accessi alle variabili contrassegnato con la parola chiave volatile (vedi sezione 12.6 della specifica CLI per riferimento). Ma nell'ultimo decennio, la maggior parte del codice gestito è stato eseguito su x86 sistemi e il compilatore just-in-time (JIT) CLR non ha ancora aggiunto molto a reorderings consentita dall'hardware, quindi c'erano relativamente pochi casi dove il modello di memoria rivelerebbe bug latente della concorrenza. Questo potrebbe presentare un problema se macchine x86-based scritte per e testati solo su x è previsto per funzionare allo stesso modo su sistemi ARM codice gestito.

La maggior parte dei modelli che richiedono ulteriore attenzione in materia di riordino sono rara nel codice gestito. Ma alcuni di questi modelli che esistono sono ingannevolmente semplice. Ecco un esempio di codice che non sembra ha un bug, ma se questi statica è cambiato in un altro thread, questo codice potrebbe rompere su una macchina con un modello di memoria debole:

static bool isInitialized = false;
static SomeValueType myValue;
if (!isInitialized)
{
  myValue = new SomeValueType();
  isInitialized = true;
}
myValue.DoSomething();

Per rendere questo codice corretto, indicano semplicemente che il flag isInitialized è volatile:

static volatile bool isInitialized = false; // Properly marked as volatile

Esecuzione di questo codice senza riordino è mostrato nel blocco di sinistro in Figura 1. Thread 0 è il primo a inizializzare SomeValueType sul suo stack locale e copia il SomeValueType creati localmente una posizione globale di AppDomain. Thread 1 determina controllando isInitialized che ha anche bisogno di creare SomeValueType. Ma non non c'è nessun problema, perché i dati sono scritto indietro nella stessa posizione globale di AppDomain. (Più spesso, come in questo esempio, eventuali mutazioni fatte con il metodo DoSomething sono idempotenti.)

Write Reordering
Figura 1 scrivere riordino

Il blocco di destra viene illustrata l'esecuzione del codice stesso con un sistema che supporta la scrittura riordino (e una stalla convenientemente posizionata in esecuzione). Questo codice non riesce a eseguire correttamente perché il Thread 1 determina leggendo il valore di isInitialized che SomeValueType non ha bisogno di essere inizializzato. La chiamata a DoSomething si riferisce alla memoria che non è stato inizializzato. I dati letti da alcuni ValueType verranno impostati da CLR a 0.

Il codice non eseguito spesso in modo errato a causa di questo tipo di riordino, ma capita. Questi reorderings sono perfettamente legali — l'ordine della scrive non importa quando in esecuzione su un singolo thread. È meglio quando si scrive codice simultanee per contrassegnare variabili volatili correttamente con la parola chiave volatile.

Il CLR è permesso di esporre un modello di memoria più forte rispetto alla specifica ECMA CLI richiede. Su x86, ad esempio, il modello di memoria di CLR è forte perché il modello di memoria del processore è forte. Il team di .net potrebbe hai fatto il modello di memoria sul braccio forte come il modello su x86, ma garantendo il perfetto ordine quando possibile può avere un impatto notevole sulle prestazioni di esecuzione del codice. Abbiamo fatto lavoro mirato a rafforzare il modello di memoria sul braccio — in particolare, noi abbiamo inserito barriere di memoria nei punti chiave durante la scrittura dell'heap gestito per garantire la sicurezza di tipo — ma abbiamo reso sicuri di farlo solo con un minimo impatto sulle prestazioni. La squadra è andato attraverso molteplici recensioni di design con esperti per assicurarsi che le tecniche applicate in CLR braccio erano corrette. Inoltre, benchmark mostrano che le prestazioni di esecuzione codice .net bilance lo stesso codice C++ nativo rispetto x 86, x 64 e braccio.

Se il codice si basa su algoritmi di lock-free che dipendono dall'implementazione del x86 CLR (piuttosto che la specifica ECMA CLR), ti consigliamo di aggiungere la parola chiave volatile a variabili rilevanti come appropriato. Una volta che hai segnato lo stato condiviso come volatile, il CLR si prenderà cura di tutto per te. Se siete come la maggior parte degli sviluppatori, si è pronti per eseguire sul braccio perché hai già usato serrature per proteggere i vostri dati condivisi, correttamente contrassegnato variabili volatili e testato la tua app sul braccio.

Dati allineamento requisiti altra differenza che potrebbe interessare alcuni programmi è che i processori ARM richiedono alcuni dati per essere allineati. Il modello specifico in cui si applicano i requisiti di allineamento è quando si dispone di un valore a 64 bit (cioè, int64, un uint64 o un doppio) che non è allineato su un limite di 64-bit. Il CLR si occupa di allineamento per te, ma ci sono due modi per forzare un tipo di dati non allineati. Il primo modo è quello di specificare in modo esplicito il layout di una struttura con l'attributo personalizzato [ExplicitLayout]. Il secondo modo è quello di specificare correttamente il layout di una struttura passata tra codice gestito e nativo.

Se notate una chiamata P/Invoke tornare con spazzatura, si potrebbe voler dare un'occhiata a eventuali strutture eseguito il marshalling. Ad esempio, abbiamo risolto un bug durante il porting di alcune librerie .net in cui un'interfaccia COM passata una struttura POINTL contenente due campi a 32-bit a una funzione nel codice gestito che ha preso un 64-bit doppio come un parametro. La funzione operazioni bit consentono di ottenere i due campi a 32-bit. Qui è una versione semplificata della funzione buggy:

void CalledFromNative(int parameter, long point)
{
  // Unpack native POINTL from long point
  int x = (int)(point & 0xFFFFFFFF);
  int y = (int)((point >> 32) & 0xFFFFFFFF);
  ...  // Do something with POINTL here
}

Il codice nativo non hanno allineare la struttura POINTL su un limite di 64-bit, perché conteneva due campi di 32-bit. Ma braccio richiede il doppio di 64-bit per essere allineati quando è passato nella funzione gestita. Fare certe sono specificati i tipi per essere lo stesso su entrambi i lati della chiamata gestito nativo è fondamentale se i tipi richiedono allineamento.

All'interno di Visual Studio più sviluppatori non avete mai notano le differenze che ho discusso perché codice .net dal design non è specifico per ogni architettura di processore. Ma ci sono alcune differenze in Visual Studio quando profiling o di debug di applicazioni Windows 8 su un dispositivo di braccio, perché Visual Studio non funziona su dispositivi con ARM.

Hai già familiarità con il processo di sviluppo cross-platform se si scrivono applicazioni per Windows Phone. Visual Studio viene eseguito sul vostro dev 86 x box e si avvia il tuo app da remoto su un emulatore o il dispositivo. L'applicazione utilizza un proxy installato sul vostro dispositivo di comunicare con la vostra macchina di sviluppo attraverso una connessione IP. Diverso il setup iniziale passi, il debug e profiling esperienze comportano lo stesso su tutti i processori.

Un altro punto di essere a conoscenza è che le impostazioni di progetto di Visual Studio Aggiungi braccio x 86 e x 64 come una scelta del processore di destinazione. Normalmente tu scelga di destinazione AnyCPU quando si scrive un'applicazione .net per Windows sul braccio, e il vostro app verrà eseguito solo su tutte le architetture di Windows 8.

Andando in profondità nel braccio di supporto

Dopo aver fatto questo lontano nell'articolo, sapete già molto su braccio. Ora vorrei condividere alcuni dettagli interessanti, profonde tecnici circa il lavoro della squadra .net per sostenere il braccio. Non devi fare questo tipo di lavoro nel codice .net — questa è solo una rapida occhiata dietro le quinte presso il tipo di lavoro che abbiamo fatto.

La maggior parte dei cambiamenti all'interno della stesso CLR erano semplice perché il CLR è stato progettato per essere portabile tra architetture. Abbiamo dovuto fare alcune modifiche per conformarsi al braccio ABI Application Binary Interface (). Abbiamo anche dovuto riscrivere il codice assembly in CLR a target ARM e cambiare il nostro compilatore JIT per emettere istruzioni ARM Thumb 2.

L'ABI specifica le modalità di interfaccia programmabile di un processore. È simile all'API che specifica la cosa delle funzioni a livello di programmazione disponibili di un sistema operativo. Le tre aree dell'ABI che ha colpito il nostro lavoro sono la funzione chiamata Convenzione, le convenzioni del registro e informazioni di rimozione dello stack di chiamata. Tratterò ciascuno.

La convenzione di chiamata Convenzione di chiamata a funzione è un accordo tra il codice che chiama la funzione e la funzione chiamata. La convenzione specifica come parametri e valori restituiti vengono disposte in memoria, così come il bisogno di registri per i valori conservati attraverso la chiamata. Funzione chiamate a lavorare oltre i limiti (ad esempio una chiamata da un programma nel sistema operativo), generatori di codice necessario generare chiamate di funzione che corrispondano alla convenzione che definisce il processore, compreso l'allineamento dei valori a 64 bit.

BRACCIO fu il primo processore a 32 bit dove il CLR ha dovuto gestire parametri di allineamento e oggetti sull'heap gestito su un limite di 64-bit. La soluzione più semplice sarebbe quella di allineare tutti i parametri, ma l'ABI richiede che un generatore di codice non lasciare bolle nello stack quando nessun allineamento è necessario, quindi non non c'è alcun degrado delle prestazioni. Così la semplice operazione di spingere un mucchio di parametri sullo stack diventa più delicato del processore ARM. Perché una struttura utente può contenere un int64, soluzione di CLR era di usare un po ' su ogni tipo di indicare se richiede l'allineamento. Questo dà il CLR informazioni sufficienti per garantire che le chiamate di funzione contenente valori a 64 bit non accidentalmente corrompono lo stack di chiamate.

Convenzione registro il requisito di allineamento dati trasporta oltre quando strutture sono completamente o parzialmente registrati sul braccio. Questo significa che abbiamo dovuto modificare il codice all'interno di Common Language Runtime che si muove frequentemente utilizzati dati dalla memoria in registri per assicurarsi che i dati sono allineati correttamente nei registri. Questo lavoro doveva essere fatto per le due situazioni: in primo luogo, facendo certi valori a 64 bit start nei registri anche che secondo, immissione omogenei aggregati in virgola mobile (HFA) nei registri corretto.

Se un generatore di codice registra un int64 sul braccio, devono essere conservato in una coppia pari-dispari — cioè, R0-R1 o R2-R3. Il protocollo per HFA permette fino a quattro valori a virgola mobile a singoli o doppi in una struttura omogenea. Se questi sono registrati, devono essere immagazzinati della S (singola) o D (doppio), imposta di registro ma non nel General-Purpose R registra.

Unwind rilassarsi informazioni informazioni registra gli effetti che una chiamata di funzione ha sullo stack e dove non volatile registra record vengono salvati su chiamate di funzione. Su x86, Windows Guarda FS: 0 per visualizzare un elenco collegato di informazioni di registrazione di eccezione di ogni funzione in caso di un'eccezione non gestita. Windows a 64-bit ha introdotto il concetto di informazioni che consente a Windows di crawl dello stack in caso di un'eccezione non gestita. Il design del braccio esteso questo distendersi informazioni da disegni di 64-bit. I generatori di codice CLR, a sua volta, ha dovuto cambiare per accogliere il nuovo design.

Assembly di codice anche se la maggior parte del motore di runtime CLR è scritto in C++, abbiamo il codice assembly che deve essere portato a ogni nuovo processore. La maggior parte di questo codice assembly è ciò che chiamiamo "stub functions", o "Stub". Categoria: servire come collante di interfaccia che consente di legare insieme il C++ e porzioni compilato tramite JIT di Common Language runtime. Il resto del codice assembly all'interno del CLR è scritto in assembly per le prestazioni. Ad esempio, il garbage collector scrittura barriera deve essere estremamente veloce perché è denominato frequentemente — ogni volta che un riferimento all'oggetto è scritto in un oggetto sull'heap gestito.

Un esempio di uno stub è ciò che noi chiamiamo "thunk shuffle". Noi lo chiamiamo un thunk shuffle perché rimescola i valori dei parametri attraverso registri. A volte il CLR deve cambiare il posizionamento dei parametri nei registri poco prima viene effettuata una chiamata di funzione. CLR utilizza il thunk shuffle per fare questo, quando si richiama delegati.

Concettualmente, quando si richiama un delegato, basta fare una chiamata al metodo Invoke. In realtà, il CLR effettua una chiamata indiretta attraverso un campo di delegato, piuttosto che fare un metodo denominato call (tranne quando si chiama in modo esplicito Invoke tramite reflection). Questo metodo è molto più veloce di una chiamata di metodo denominato perché il runtime può semplicemente scambiare l'istanza del delegato (ottenuto da target puntatore) per il delegato nella chiamata della funzione. Che è, per un'istanza foo di delegato d, la chiamata al metodo d.Member viene mappata a foo.Metodo membro.

Se fai un'istanza chiusa chiamata, di delegare il questo puntatore è memorizzato all'interno del primo registro utilizzato per il passaggio di parametri, R0, ed il primo parametro viene memorizzato nel registro successivo, R1. Ma questo funziona solo quando hai un delegato associato a un metodo di istanza. Cosa succede se stai chiamando un delegato statico aperto? In tal caso, si prevede che il primo parametro viene memorizzato in R0 (come non non c'è nessun questo puntatore.) Il thunk shuffle sposta il primo parametro da R1 in R0, il secondo parametro in R0 e così via, come mostrato in Figura 2. Poiché lo scopo di questo thunk shuffle è spostare i valori da registro a registro, esso deve essere riscritta in modo specifico per ogni processore.

A “Shuffle Thunk” Shuffles Value Parameters Across Registers
Figura 2 "Shuffle Thunk" riordini i parametri di valore attraverso registri

Concentrarsi solo sul codice

Per rivedere, porting del Framework .net al braccio era un progetto interessante e un sacco di divertimento per la squadra di .net. E scrivere applicazioni .net per eseguire il .net Framework sul braccio dovrebbe essere divertente per voi come sviluppatore .net. Codice .net possa essere eseguito in modo diverso sul braccio che fa su x processori basati su x86, in alcune situazioni, ma l'ambiente di esecuzione virtuale .net Framework astrae normalmente tali differenze per te. Questo significa che non devi preoccuparti di ciò che l'architettura del processore applicazione .net gira su. Può solo concentrarsi sulla scrittura di codice grande.

Credo di avere Windows 8 disponibile sul braccio sarà grande per gli sviluppatori e gli utenti finali. Processori ARM sono particolarmente indicate per la lunga durata della batteria, così essi attivare dispositivi leggeri, portatili, sempre connessi. Le questioni più significative che si vedrà quando porting tuo app al braccio sono differenze di prestazioni da processori per PC desktop. Ma assicurarsi di eseguire il codice sul braccio prima dicendo che effettivamente lavora sul braccio — non si fidano che sviluppa su x86 è sufficiente. Per la maggior parte degli sviluppatori, che è tutto ciò che è necessario. E se si esegue in eventuali problemi, è possibile fare riferimento indietro a questo articolo per ottenere qualche intuizione dove avviare le indagini.

Andrew Pardoe è un program manager del team CLR, aiutando a spedire il Microsoft .net Framework su tutti i tipi di processori. Suo favorito personale rimane l'Itanium. Può essere raggiunto a Andrew.Pardoe@microsoft.com.

Grazie ai seguenti esperti tecnici per la revisione di questo articolo: Brandon Bray, Layla Driscoll, Eric Eilebrecht e Rudi Martin