2010-07-21 11 views
8

Sto lavorando a un progetto incorporato (destinazione PowerPC, compilatore Freewale Metrowerks Codewarrior) in cui i registri sono mappati in memoria e definiti in bitfield di qualità per rendere facile il twittling dei singoli bit.Cosa fa il compilatore C con i bitfield?

Al momento, stiamo utilizzando questa funzione per cancellare i flag di interrupt e controllare il trasferimento dei dati. Sebbene non abbia ancora notato alcun bug, ero curioso di sapere se questo è sicuro. C'è un modo per utilizzare in modo sicuro i campi di bit, o ho bisogno di avvolgere ciascuno in DISABLE_INTERRUPTS ... ENABLE_INTERRUPTS?

Per chiarire: l'intestazione fornito con il micro ha campi come

union { 
     vuint16_t R; 
     struct { 
      vuint16_t MTM:1;  /* message buffer transmission mode */ 
      vuint16_t CHNLA:1;  /* channel assignement */ 
      vuint16_t CHNLB:1;  /* channel assignement */ 
      vuint16_t CCFE:1;  /* cycle counter filter enable */ 
      vuint16_t CCFMSK:6;  /* cycle counter filter mask */ 
      vuint16_t CCFVAL:6;  /* cycle counter filter value */ 
     } B; 
    } MBCCFR; 

presumo impostando un bit in un campo di bit non è atomica. Questa è una supposizione corretta? Che tipo di codice genera realmente il compilatore per i bitfield? Eseguire personalmente la maschera utilizzando il campo R (raw) potrebbe rendere più facile ricordare che l'operazione non è atomica (è facile dimenticare che un compito come CAN_A.IMASK1.B.BUF00M = 1 non è atomico).

Il tuo consiglio è apprezzato.

+1

Sono corretto nel pensare che il 'v' in' vuint16_t' sta per "volatile"? – JAB

+0

Sì, i tipi sono volatili poiché sono tutti percorsi di registrazione mappati in memoria. Ci sono anche le istruzioni di allineamento che ho tralasciato per mantenere le cose corte. –

+0

Evita i bitfield e usa invece maschera e shift. – starblue

risposta

3

L'atomicità dipende dall'obiettivo e dal compilatore. AVR-GCC per esempio trys per rilevare l'accesso ai bit ed emettere istruzioni bit set o clear se possibile. Controllare l'output assembler per essere sicuri ...

EDIT: Ecco una risorsa per le istruzioni atomiche su PowerPC direttamente dalla bocca del cavallo:

http://www.ibm.com/developerworks/library/pa-atom/

+0

Quando può, ma non tutti gli indirizzi supportano questo, e questo non può essere fatto a meno che l'indirizzo non sia noto in fase di compilazione. – nategoose

+0

@nategoose Sì, meglio pronunciarlo come hai fatto piuttosto che implicare quello che ho fatto. –

3

È corretto presumere che l'impostazione di campi di bit non è atomico. Lo standard C non è particolarmente chiaro su come i bitfield dovrebbero essere implementati e vari compilatori hanno vari modi su di essi.

Se si interessa solo dell'architettura e del compilatore di destinazione, disassemblare il codice oggetto.

Generalmente, il codice raggiungerà il risultato desiderato ma sarà molto meno efficiente del codice che utilizza macro e turni. Detto questo, è probabilmente più leggibile utilizzare i campi di bit se non ti interessa le prestazioni qui.

Si può sempre scrivere una funzione di wrapper setter per i bit che sono atomici, se si è preoccupati che i futuri programmatori (incluso te stesso) vengano confusi.

0

Dipende totalmente dall'architettura e dal compilatore se le operazioni bitfield sono atomiche o meno. La mia esperienza personale dice: non usare bitfield se non è necessario.

3

Sì, la tua ipotesi è corretta, nel senso che non puoi assumere l'atomicità. Su una piattaforma specifica potresti ottenerla come extra, ma non puoi fare affidamento su di essa in nessun caso.

Fondamentalmente il compilatore esegue mascheramento e cose per te. Potrebbe essere in grado di approfittare di casi d'angolo o di istruzioni speciali. Se sei interessato all'efficienza guarda nell'assemblatore che il tuo compilatore produce con quello, di solito è abbastanza istruttivo. Come regola generale direi che i compilatori moderni producono codice che è efficiente quanto lo sforzo di programmazione medio sarebbe. Il vero twepping deep-bit per il tuo compilatore specifico potrebbe forse farti guadagnare alcuni cicli.

0

sono abbastanza sicuro che su PowerPC questo non è atomica, ma se il vostro obiettivo è un unico sistema centrale allora si può solo:

void update_reg_from_isr(unsigned * reg_addr, unsigned set, unsigned clear, unsigned toggle) { 
    unsigned reg = *reg_addr; 
    reg |= set; 
    reg &= ~clear; 
    reg ^= toggle; 
    *reg_addr = reg; 
} 

void update_reg(unsigned * reg_addr, unsigned set, unsigned clear, unsigned toggle) { 
    interrupts_block(); 
    update_reg_from_isr(reg_addr, set, clear, toggle); 
    interrupts_enable(); 
} 

Non mi ricordo se gestori di interrupt di PowerPC sono interrompibili, ma se lo sono, dovresti semplicemente usare sempre la seconda versione.

Se l'obiettivo è un sistema multiprocessore, è necessario eseguire blocchi (spinlock, che disabilitano gli interrupt sul processore locale e attendere l'eventuale completamento di altri processori con il blocco) che proteggono l'accesso a elementi come i registri hardware e acquisiscono i blocchi necessari prima di accedere al registro e quindi rilasciare i blocchi immediatamente dopo aver completato l'aggiornamento del registro (o dei registri).

Ho letto una volta come implementare i blocchi in powerpc - comportava dire al processore di guardare il bus di memoria per un determinato indirizzo mentre facevi alcune operazioni e poi ricontrollare alla fine di quelle operazioni per vedere se l'indirizzo dell'orologio era stato scritto da un altro nucleo. Se così non fosse, la tua operazione ha avuto successo; se avesse quindi dovuto ripetere l'operazione. Questo era in un documento scritto per sviluppatori di compilatori, librerie e sistemi operativi. Non ricordo dove l'ho trovato (probabilmente da qualche parte su IBM.com) ma un po 'di caccia dovrebbe alzarlo. Probabilmente ha anche informazioni su come eseguire il bit twid del bit atomico.

+0

Wow, sembra una ricetta per il deadlock: Il processore A avvia la sezione critica, controlla l'indirizzo. Il processore B avvia la sezione critica, esamina l'indirizzo. Il processore A termina la scrittura. Il processore B termina la scrittura. Processore A controlla l'indirizzo. È cambiato! Processore A riscrive i dati. Il processore B esamina l'indirizzo. È cambiato! Il processore B riscrive i dati. Continua in loop infinito. (Mi dispiace per la formattazione, i commenti non sembrano graditi.) – nmichaels

+0

@Nathon: è un po 'più complicato e semplice, ma è passato un po' di tempo da quando ho letto la documentazione. C'era davvero qualcosa che impediva che si trasformasse in una situazione di deadlock, ma consentiva anche una maggiore flessibilità rispetto all'approccio dell'istruzione atomica utilizzato su altre architetture. Può essere che la scrittura finale in RAM non si sia verificata se un altro processore ha scritto su quell'indirizzo dopo che questo processore ha iniziato a guardarlo. – nategoose

+1

È noto come "load linked/store condizionale" su alcune architetture. PPC lo chiama 'lwarx' (carica parola e riserva indicizzata) e' stwcx'. (Parola di archivio indicizzata (e registra)). Il negozio è subordinato al fatto che nessun altro abbia scritto alla memoria; un processore vincerà sempre. Inoltre, la "prenotazione" viene cancellata attraverso le opzioni di contesto. Ad ogni modo, non si vuole farlo su bitfield hardware ... –

3

Penso che usare i bitfield per modellare i registri hardware non sia una buona idea.

Così tanto su come i campi bit vengono gestiti da un compilatore è definito dall'implementazione (compreso il modo in cui vengono gestiti i campi che superano i confini di byte o word, i problemi di endianess e esattamente come vengono implementati i bit di acquisizione, impostazione e cancellazione). Vedi C/C++: Force Bit Field Order and Alignment

Per verificare che gli accessi ai registri vengano gestiti in base a come ci si potrebbe aspettare o se devono essere gestiti, è necessario studiare attentamente i documenti del compilatore e/o guardare il codice emesso. Suppongo che se le intestazioni fornite con il set di strumenti del microprocessore le utilizza, si può presumere che la maggior parte delle mie preoccupazioni siano prese in considerazione. Tuttavia, suppongo che l'accesso atomico non sia necessariamente ...

Penso che sia meglio gestire questo tipo di accessi a livello di bit dei registri hardware usando funzioni (o macro, se necessario) che eseguono letture esplicite/modifica/scrivi le operazioni con la maschera di bit di cui hai bisogno, se questo è ciò che richiede il tuo processore.

Queste funzioni possono essere modificate per architetture che supportano accessi a livello di bit atomici (come l'indirizzamento "bit-banding" di ARM Cortex M3). Non so se il PowerPC supporti qualcosa di simile - l'M3 è l'unico processore con cui ho avuto a che fare e che lo supporta in generale. E anche il bit-banding M3 supporta accessi a 1 bit; se hai a che fare con un campo di 6 bit di larghezza, devi tornare allo scenario di lettura/modifica/scrittura.