Gömülü C geliştirmede uçucu olanı kullanma


44

volatileDerleyicinin, derleyicinin belirleyemediği şekillerde değişebilecek nesneler üzerinde herhangi bir optimizasyon uygulamamasını önlemek için bazı makaleleri okudum ve Stack Exchange cevaplarını anahtar sözcüğün kullanımıyla ilgili yanıtlar .

Bir ADC'den okuyorum (değişkeni arayalım adcValue) ve bu değişkeni global olarak ilan ediyorum volatile, bu durumda anahtar kelimeyi kullanmalı mıyım?

  1. volatileAnahtar kelime kullanmadan

    // Includes
    #include "adcDriver.h"
    
    // Global variables
    uint16_t adcValue;
    
    // Some code
    void readFromADC(void)
    {
       adcValue = readADC();
    }
    
  2. volatileAnahtar kelimeyi kullanarak

    // Includes
    #include "adcDriver.h"
    
    // Global variables
    volatile uint16_t adcValue;
    
    // Some code
    void readFromADC(void)
    {
       adcValue = readADC();
    }
    

Bu soruyu soruyorum çünkü hata ayıklama sırasında her iki yaklaşım arasında bir fark göremiyorum, ancak en iyi uygulamalar benim durumumda (doğrudan donanımdan doğrudan değişen bir global değişken) sonra volatilekullanımın zorunlu olduğunu söylüyor .


1
Bazı hata ayıklama ortamları (kesinlikle gcc) optimizasyon uygulamıyor. Bir üretim inşa normalde olacaktır (seçimlerinize bağlı olarak). Bu, yapılar arasında 'ilginç' farklılıklara yol açabilir. Bağlayıcı çıkış haritasına bakmak bilgilendiricidir.
Peter Smith

22
"Benim durumumda (donanım doğrudan değiştiren genel değişken) 'de" - Sizin genel değişken olduğu değil değişti donanım tarafından ancak derleyici farkında olduğu C kodu ile. - Bununla birlikte, ADC'nin sonuçlarını sağladığı donanım kaydı, değişken olmalıdır , çünkü derleyici, değerinin ne zaman / ne zaman değişeceğini bilemez (ADC donanımı bir dönüşümü tamamladığında / değiştirirse değişir).
JimmyB

2
Her iki versiyon tarafından üretilen montajcıyı karşılaştırdınız mı? Bu size başlık altında neler olduğunu göstermeli
Mawg

3
@stark: BIOS? Bir mikrodenetleyicide mi? Bellek eşlemeli G / Ç alanı, önbellekleme kuralları ile bellek haritası arasındaki tasarım tutarlılığı sayesinde önbelleklenebilir olmayacaktır (mimarlık ilk başta bir veri önbelleği olsa bile, bu garanti edilmez). Ancak geçici olanların bellek denetleyicisi önbelleğiyle hiçbir ilgisi yoktur.
Ben Voigt

1
@Davislor Dil standardının genel olarak başka bir şey söylemesi gerekmez. Uçucu bir nesneye yapılan bir okuma gerçek bir yük gerçekleştirecektir (derleyici yakın zamanda bir tane yapmış olsa ve genellikle değerin ne olduğunu bilse bile) ve bu nesneye bir yazma gerçek bir depo gerçekleştirir (aynı değer nesneden okunsa bile) ). Bu nedenle if(x==1) x=1;yazma işleminde uçucu olmayan bir xşey için optimize edilebilir ve xuçucu ise optimize edilemez . OTOH, harici cihazlara erişmek için özel talimatlar gerekliyse, bunları eklemek size bağlıdır (örneğin, bir hafıza aralığının yazılması gerekiyorsa).
curiousguy

Yanıtlar:


87

Bir tanımı volatile

volatilederleyiciye değişkenin değerinin derleyici bilmeden değişebileceğini söyler. Dolayısıyla, derleyici, değerin değişmediğini kabul edemez, çünkü C programı değişmemiş gibi görünüyor.

Öte yandan, değişkenin değerinin, derleyicinin bilmediği bir yerde gerekli olabileceği (okunabileceği) anlamına gelir, bu nedenle değişkene yapılan her atamanın aslında bir yazma işlemi olarak yapıldığından emin olması gerekir.

Davaları kullanın

volatile ne zaman gereklidir

  • donanım kayıtlarını (veya hafıza eşlemeli G / Ç'yi) değişken olarak temsil eder - kayıt asla okunmasa bile, derleyici sadece "Aptal programcı" değerini belirten yazma işlemini atlamamalıdır. bir daha asla okuyamayacak. Yazmayı ihmal edersek bile farketmeyecek. " Tersine, program değişkene hiçbir zaman bir değer yazmasa bile, değeri yine de donanım tarafından değiştirilebilir.
  • yürütme bağlamları arasında değişkenleri paylaşma (örneğin ISR'ler / ana program) (bkz. @ kkramo'nun cevabı).

Etkileri volatile

Bir değişken bildirildiğinde volatile, derleyici program kodundaki her bir atama işleminin gerçek bir yazma işlemine yansıdığından ve program kodundaki her okunuşun (mmaplanan) bellekten gelen değeri okuduğundan emin olmalıdır.

Uçucu olmayan değişkenler için, derleyici değişkenin değerinin ne zaman / ne zaman değiştiğini bildiğini ve kodu farklı şekillerde optimize edebileceğini bildiğini varsayar.

Birincisi, derleyici değeri CPU yazmaçlarında tutarak belleğe okuma / yazma sayısını azaltabilir.

Örnek:

void uint8_t compute(uint8_t input) {
  uint8_t result = input + 2;
  result = result * 2;
  if ( result > 100 ) {
    result -= 100;
  }
  return result;
}

Burada derleyici muhtemelen resultdeğişkene RAM tahsis etmeyecek ve ara değerleri hiçbir zaman bir CPU kaydında saklayamayacak.

Eğer resultuçucu olan, her cereyan edişinde resultC kodu derleyici gerektirecektir daha düşük bir performansa yol, RAM için bir erişim (ya da bir I / O bağlantı noktası) ifa edilir.

İkincisi, derleyici, performans ve / veya kod boyutu için geçici olmayan değişkenler üzerindeki işlemleri yeniden sipariş edebilir. Basit örnek:

int a = 99;
int b = 1;
int c = 99;

yeniden sipariş edilebilirdi

int a = 99;
int c = 99;
int b = 1;

bu, bir assembler komutunu kaydedebilir, çünkü değerin 99iki kez yüklenmesi gerekmez.

Eğer a, bve cuçucu olan derleyici programa verilmiştir tam olarak sırayla değerlerini atamak talimatları yayarlar gerekir.

Diğer klasik örnek ise şöyle:

volatile uint8_t signal;

void waitForSignal() {
  while ( signal == 0 ) {
    // Do nothing.
  }
}

Eğer bu durumda signalolmasaydı volatile, derleyici bunun while( signal == 0 )sonsuz bir döngü olabileceğini düşünürdü (çünkü döngü içindekisignal kodla asla değiştirilmeyecekti ) ve eşdeğerini oluşturabilirdi.

void waitForSignal() {
  if ( signal != 0 ) {
    return; 
  } else {
    while(true) { // <-- Endless loop!
      // do nothing.
    }
  }
}

volatileDeğerlerin ele alınmasında dikkate alın

Yukarıda belirtildiği gibi, bir volatiledeğişken gerçekte gerekenden daha sık erişildiğinde performans cezası verebilir. Bu sorunu hafifletmek için, değeri gibi geçici olmayan bir değişkene atayarak değeri "geçici olarak kaldırabilirsiniz".

volatile uint32_t sysTickCount;

void doSysTick() {
  uint32_t ticks = sysTickCount; // A single read access to sysTickCount

  ticks = ticks + 1; 

  setLEDState( ticks < 500000L );

  if ( ticks >= 1000000L ) {
    ticks = 0;
  }
  sysTickCount = ticks; // A single write access to volatile sysTickCount
}

Eğer çabuk olmasını istediğiniz yerdir ISR yıllarda özellikle faydalı olabilir mümkün olduğunca aynı donanım veya bellek birden çok kez erişen değil sen değer, ISR çalışırken değişmeyecek çünkü gerekli değildir biliyorum. Bu, ISR'nin sysTickCountyukarıdaki örnekte olduğu gibi değişken için değerlerin 'üreticisi' olması durumunda yaygındır . Bir AVR'de işlevin doSysTick()bellekte aynı dört bayta (dört komut = erişim başına 8 CPU çevrimi sysTickCount) yalnızca iki kez yerine beş veya altı kez erişmesi özellikle acı verici olacaktır , çünkü programcı değerin değişmeyeceğini bilecektir doSysTick()koşarken başka bir koddan değiştirilemez .

Bu hileyle, derleyicinin geçici olmayan değişkenler için yaptığı aynı şeyi yaparsınız, yani yalnızca gerektiğinde bunları bellekten okuyabilir, değeri bir süre için bir kayıt defterinde tutar ve yalnızca gerektiğinde belleğe geri yazar. ; ama bu sefer sen okuduğunda / yazma eğer / iyi derleyici daha biliyor olmalı gerçekleşmesi Bu optimizasyon görevden derleyici rahatlatmak, böylece onu kendi başınıza yapmak.

Sınırlamaları volatile

Atomik olmayan erişim

volatileyok değil çok kelimeli değişkenlere atomik erişim sağlar. Bu gibi durumlarda, kullanmaya ek olarak , diğer yollarla karşılıklı dışlama sağlamanız gerekecektir volatile. AVR, kullanmak olabilir ATOMIC_BLOCKgelen <util/atomic.h>veya basit cli(); ... sei();aramaları. İlgili makrolar da, erişim sırası söz konusu olduğunda önemli olan bir bellek bariyeri görevi görür:

İcra emri

volatileyalnızca diğer değişken değişkenler için katı yürütme emri uygular. Bu, örneğin

volatile int i;
volatile int j;
int a;

...

i = 1;
a = 99;
j = 2;

garantilidir ilk için atamak 1 ive sonra 2 atamak j. Ancak, aralarında atanacak garanti edilmeza ; derleyici bu atamayı kod pasajından önce veya sonra, temelde ilk (okunabilir) okunana kadar herhangi bir zamanda yapabilir a.

Yukarıda belirtilen makroların hafıza bariyeri olmasaydı, derleyicinin çevrilmesine izin verilirdi

uint32_t x;

cli();
x = volatileVar;
sei();

için

x = volatileVar;
cli();
sei();

veya

cli();
sei();
x = volatileVar;

(Bütünlük adına Aslında kullanımını ortadan kaldırabilir, sei / cli makrolar anlattığına gibi bu bellek engelleri söylemeliyim volatileeğer, bütün erişimler bu engellerin ile parantez edilir.)


7
Performans için oynaklığın iyi bir tartışma :)
awjlogan

3
ISO / IEC 9899: 1999 6.7.3 (6) ' daki uçucu tanımını hep belirtmekten hoşlanıyorum : An object that has volatile-qualified type may be modified in ways unknown to the implementation or have other unknown side effects. Daha fazla insan okumalı.
Jeroen3

3
Bu değer söz olabilir cli/ seisenin tek amacı kesmelerini engellemez, bir bellek bariyer elde etmek ise çok ağır bir çözümdür. Bu makrolar gerçek cli/ seitalimatlar ve ayrıca ek olarak bellek hafızası oluşturur ve bu da engelle sonuçlanan bu gizliliktir. Kesintileri devre dışı bırakmadan sadece bir hafıza bariyerine sahip olmak için kendi makronuzu vücut benzeri ile tanımlayabilirsiniz __asm__ __volatile__("":::"memory")(örn., Bellek takozlu boş montaj kodu).
Ruslan

3
@NicHartley No. C17 5.1.2.3 §6 gözlemlenebilir davranışı tanımlar : "Uçucu nesnelere erişim kesinlikle soyut makinenin kurallarına göre değerlendirilir." C standardı, genel olarak nerede hafıza bariyerlerinin gerektiği konusunda net değil. Kullanılan ifadenin sonunda volatilebir sıralama noktası vardır ve bundan sonraki her şey "sonra sıralanmalıdır". Bu ifadeyi Anlamı olan bir çeşit hafıza engeli. Derleyici satıcıları, programcıya bellek engellerinin sorumluluğunu üstlenmek için her türlü efsaneyi yaymayı seçti, ancak bu “soyut makinenin” kurallarını ihlal ediyor.
Lundin

2
@JimmyB Yerel uçucu gibi kod için belki yararlı olabilir volatile data_t data = {0}; set_mmio(&data); while (!data.ready);.
Maciej Piechotka

13

Uçucu anahtar kelime, derleyiciye değişkene erişimin gözlenebilir bir etkiye sahip olduğunu söyler. Bu, kaynak kodunuz her değişkeni kullandığında, derleyici değişkene bir erişim yaratması GEREKİR. Bir okuma veya yazma erişimi olması.

Bunun etkisi, normal kod akışı dışındaki değişkende yapılan herhangi bir değişikliğin de kod tarafından gözlenmesidir. Örneğin, eğer bir kesme işleyicisi değeri değiştirirse. Veya değişken aslında kendi başına değiştirilen bazı donanım kayıt defterleri ise.

Bu büyük yarar, aynı zamanda olumsuz. Değişkene her erişim, değişkenden geçer ve herhangi bir süre için daha hızlı erişim için değer asla bir kayıt defterinde tutulmaz. Bu, değişken bir değişkenin yavaş olacağı anlamına gelir. Büyüklükler daha yavaş. Bu yüzden sadece gerçekten gerekli olduğu yerlerde uçucu kullanın.

Durumunuzda, kod gösterdiğiniz kadarıyla, genel değişken yalnızca kendiniz güncellediğinizde değiştirilir adcValue = readADC();. Derleyici bunun ne zaman olacağını bilir ve adcValue değerini hiçbir zaman bir işlevde gösteremez readFromADC(). Ya da bilmediği herhangi bir fonksiyon. Ya da işaret edebilecek işaretçileri manipüle edecek herhangi bir şey adcValue. Değişken öngörülemeyen şekillerde asla değişmediğinden uçuculuğa gerçekten gerek yoktur.


6
Bu cevaba katılıyorum ama "büyüklükler yavaş" sesleri çok korkunç.
kkrambo

6
Modern superscalar işlemcilerde CPU kaydına işlemci çevriminden daha az bir sürede erişilebilir. Öte yandan, önbelleğe alınmamış gerçek belleğe erişim (bazı harici donanımların bunu değiştireceğini unutmayın, bu nedenle CPU önbelleklerine izin verilmez) 100-300 CPU döngü aralığında olabilir. Yani, evet, büyüklükler. Bir AVR veya benzer bir mikro denetleyicide çok kötü olmayacak, ancak soru donanım belirtmiyor.
Goswin von Brederlow,

7
Gömülü (mikrodenetleyici) sistemlerde, RAM erişimi için ceza genellikle çok daha azdır. Örneğin, AVR'ler RAM'den bir okuma veya yazma için sadece iki CPU döngüsü alır (bir kayıt-yazma hareketi bir döngü alır), bu nedenle kayıtlardaki şeyleri tutmada tasarruf birikimi yaklaşır (ama asla erişilmez). Erişim başına 2 saat döngüsü. - Tabii ki, göreceli olarak konuşursak, X'den RAM'e bir değer kaydetme, ardından daha sonra hesaplamalar için bu değeri hemen X'e tekrar kaydetme, 0 döngü yerine 2x2 = 4 alacaktır (sadece X değerini korurken) ve dolayısıyla sonsuzdur. daha yavaş :)
JimmyB

1
"Belli bir değişkene yazma veya okuma" bağlamında "daha büyük boyutlar", evet. Ancak, tam bir program bağlamında, muhtemelen bir değişkene tekrar tekrar okumaktan / yazmaktan önemli ölçüde fazlasını yapan, hayır, gerçekten değil. Bu durumda, genel fark muhtemelen 'önemsiz olana kadar küçüktür'. Performansla ilgili iddialarda bulunurken, iddianın belirli bir operasyonla mı yoksa bir bütün olarak bir programla mı ilgili olduğunu açıklığa kavuşturmak için özen gösterilmelidir. Nadiren kullanılan bir op bir ~ 300x faktörü tarafından yavaşlatılması neredeyse hiç büyük bir şey değil.
aroth

1
Yani, bu son cümle mi? "Erken optimizasyon tüm kötülüklerin köküdür" anlamında çok daha fazla ifade ediyordu. Belli ki volatileher şeyi kullanmamalısınız çünkü , ancak, meşru performans endişeleri nedeniyle meşru bir şekilde çağrıldığını düşündüğünüz durumlarda da bunlardan uzak durmamalısınız.
46'da

9

Volatile anahtar sözcüğünün gömülü C uygulamalarında ana kullanımı, kesme işleyicisinde yazılmış bir genel değişkeni işaretlemektir . Bu durumda kesinlikle isteğe bağlı değil.

Onsuz, derleyici, başlatma işleminden sonra değerin hiç yazıldığını ispatlayamaz, çünkü kesme işleyicisinin hiç çağrıldığını ispatlayamaz. Bu nedenle değişkenin varlığından optimizasyon yapabileceğini düşünüyor.


2
Kesinlikle başka pratik kullanımlar var, ama bu en yaygın olanı.
vicatcu

1
Değer yalnızca bir ISR'de okunuyorsa (ve main'den () değiştirilirse), çoklu bayt değişkenleri için ATOMIC erişimini garanti etmek için potansiyel olarak uçucu da kullanmanız gerekir.
Rev1.0

15
Rev1.0 No @, uçucu olmayan aromicity garanti. Bu kaygı ayrı ayrı ele alınmalıdır.
Chris Stratton

1
Donanımdan okunan herhangi bir kod ya da gönderilen kodda kesinti yok. Orada olmayan sorudan bir şeyler varsayıyorsunuz. Gerçekten mevcut haliyle cevaplanamaz.
Lundin

3
"kesme işleyicisinde yazılmış bir genel değişkeni işaretleyin" hayır. Bir değişkeni işaretlemek; küresel veya başka türlü; derleyici anlayışının dışında bir şeyle değişebileceğini. Kesinti gerekli değil. Paylaşılan hafıza veya belleğe bir sonda sokan biri olabilir (ikincisi 40 yıldan daha modern bir şey için önerilmez)
UKMonkey

9

volatileGömülü sistemlerde kullanmanız gereken iki durum vardır .

  • Bir donanım kayıt defterinden okurken.

    Bu, hafıza eşlemeli kayıt defterinin kendisi, MCU içindeki donanım çevre birimlerinin bir parçası olduğu anlamına gelir. Büyük olasılıkla "ADC0DR" gibi bazı şifreli bir isme sahip olacaktır. Bu kayıt, C kodu ile, araç satıcısı tarafından teslim edilen bir kayıt defteri haritası aracılığıyla veya kendiniz tarafından tanımlanmalıdır. Kendiniz yapmak için yapardınız (16 bit kayıt varsayarak):

    #define ADC0DR (*(volatile uint16_t*)0x1234)

    0x1234, MCU’nun kaydı eşleştirdiği adres. Yana volatileYukarıda makro bir parçasıdır, ona herhangi bir erişim uçucu nitelikli olacaktır. Yani bu kod iyi:

    uint16_t adc_data;
    adc_data = ADC0DR;
  • Bir ISR ile ilgili kod arasında bir değişken paylaşırken, ISR sonucunu kullanarak.

    Böyle bir şey varsa:

    uint16_t adc_data = 0;
    
    void adc_stuff (void)
    {
      if(adc_data > 0)
      {
        do_stuff(adc_data);
      } 
    }
    
    interrupt void ADC0_interrupt (void)
    {
      adc_data = ADC0DR;
    }

    Sonra derleyici düşünebilir: "adc_data her zaman 0'dır, çünkü hiçbir yerde güncellenmez. Ve bu ADC0_interrupt () işlevi asla çağrılmaz, bu nedenle değişken değiştirilemez". Derleyici genellikle kesintilerin yazılım tarafından değil donanım tarafından çağrıldığını anlamamaktadır. Bu yüzden derleyici gider ve if(adc_data > 0){ do_stuff(adc_data); }hiçbir zaman gerçek olamayacağını düşündüğü için kodu kaldırır, bu çok garip ve hata ayıklaması zor bir hataya neden olur.

    Bildirerek adc_data volatile, derleyicinin böyle bir varsayımda bulunmasına izin verilmez ve değişkene erişimi optimize etmek için izin verilmez.


Önemli notlar:

  • Donanım sürücüsünde her zaman bir ISR bildirilir. Bu durumda, ADC ISR, ADC sürücüsünün içinde olmalıdır. Sürücü dışında hiçbiri ISR ​​ile iletişim kurmamalıdır - diğer her şey spagetti programlamasıdır.

  • C yazarken, bir ISR ve arka plan programı arasında tüm iletişim olmalıdır yarış koşullarına karşı korunmalıdır. Her zaman , her zaman, istisnalar yok. MCU veri yolunun boyutu farketmez, çünkü C de tek bir 8 bit kopya yapsanız bile, dil işlemlerin atomikliğini garanti edemez. C11 özelliğini kullanmadığınız sürece hayır _Atomic. Bu özellik mevcut değilse, bir tür semafor kullanmanız veya okuma vb. Kesintiyi devre dışı bırakmanız gerekir. Satır içi assembler başka bir seçenektir. volatileatomikliği garanti etmez.

    Olabilir şudur: -
    Yığından register'a
    yükleme
    değeri - Kesintisiz oluş - Kayıttan kullanım değeri

    Ve sonra "kullanım değeri" bölümünün kendi içinde tek bir talimat olması önemli değil. Maalesef, tüm gömülü sistem programcılarının önemli bir kısmı bunu umursamıyor, muhtemelen onu şimdiye dek en yaygın gömülü sistemler böceği yapıyor. Her zaman aralıklı, kışkırtması zor, bulması zor.


Doğru yazılmış bir ADC sürücüsü örneği şöyle görünür (C11’in _Atomicmevcut olmadığı varsayılarak ):

adc.h

// adc.h
#ifndef ADC_H
#define ADC_H

/* misc init routines here */

uint16_t adc_get_val (void);

#endif

adc.c

// adc.c
#include "adc.h"

#define ADC0DR (*(volatile uint16_t*)0x1234)

static volatile bool semaphore = false;
static volatile uint16_t adc_val = 0;

uint16_t adc_get_val (void)
{
  uint16_t result;
  semaphore = true;
    result = adc_val;
  semaphore = false;
  return result;
}

interrupt void ADC0_interrupt (void)
{
  if(!semaphore)
  {
    adc_val = ADC0DR;
  }
}
  • Bu kod, bir kesmenin kendi içinde kesilemeyeceğini varsaymaktadır. Bu tür sistemlerde, basit bir boole semafor gibi davranabilir ve booleanın ayarlanmasından önce kesinti olursa zarar vermeyeceğinden atomik olması gerekmez. Yukarıdaki sadeleştirilmiş yöntemin aşağı tarafı, önceki koşullar yerine, yarış koşulları gerçekleştiğinde ADC okumalarını atmasıdır. Bu da önlenebilir, ancak daha sonra kod daha karmaşık hale gelir.

  • İşte volatileoptimizasyon hatalarına karşı korur. Bir donanım kaydından kaynaklanan verilerle hiçbir ilgisi yoktur, yalnızca verilerin bir ISR ile paylaşılması gerekir.

  • staticspagetti programlamaya ve isim alanı kirliliğine karşı korur, sürücüyü yerel değişken yapar. (Bu tek çekirdekli, tek iş parçacıklı uygulamalarda iyidir, ancak çok iş parçacıklı uygulamalarda bu iyidir.)


Hata ayıklamak zor görecelidir, eğer kod kaldırılırsa, değerli kodunuzun gittiğini fark edeceksiniz - bu bir şeyin yanlış olduğuna dair oldukça cesur bir ifadedir. Ancak katılıyorum, etkileri ayıklamak için çok garip ve zor olabilir.
Arsenal

@Arsenal C ile birleştiriciyi sıraya sokan hoş bir hata ayıklayıcınız varsa ve en azından bir miktar takma bilginiz varsa, evet, tespit edilmesi kolay olabilir . Ancak daha büyük karmaşık kodlar için, makine tarafından üretilen çok sayıda grubun geçmesi önemsiz değildir. Ya da asm'ı bilmiyorsan. Ya da hata ayıklayıcınız berbatsa ve hiçbir şey göstermiyorsa (öksürük kremi).
Lundin

O zamanlar Lauterbach hata ayıklayıcılarını kullanarak biraz şımarık olabilirim. Bir kırılma noktası kodunu en iyi duruma getirilmiş olarak ayarlamaya çalışırsanız, farklı bir yere yerleştirir ve orada bir şeyler olduğunu bilirsiniz.
Arsenal

@Arsenal Yep, Lauterbach'da bulabileceğiniz karışık C / asm türlerinden hiçbir şekilde standart değildir. Hata ayıklayıcıların çoğu, eğer varsa, ayrı bir pencerede asm görüntüler.
Lundin

semaphorekesinlikle olmalı volatile! Aslında, bu kadar çağrısı wich en temel kullanım durumunda başka bir yürütme bağlamdan Sinyal şey. - Örneğinizde, derleyici ihmal edebilir çünkü değerinin üzerine yazılmadan önce asla okunmadığını görür . volatilesemaphore = true;semaphore = false;
JimmyB

5

Soruda sunulan kod parçacıklarında, uçucu kullanmak için henüz bir neden yoktur. Değerinin adcValuebir ADC'den gelmesi önemli değildir . Ve adcValueküresel olmak sizi adcValuegeçici olup olmamasından şüphelendiriyor olmalı, fakat bu kendi başına bir sebep değil.

Global olmak bir ipucudur çünkü adcValuebirden fazla program bağlamından erişilebilme olanağını açar. Bir program bağlamı bir kesme işleyicisi ve bir RTOS görevi içerir. Genel değişken bir bağlamla değiştirilirse, diğer program bağlamları önceki bir erişimin değerini bildiklerini varsayamaz. Her bağlamda, değişken farklı bir program bağlamında değişmiş olabileceğinden, her kullanıldığında değişken değerini tekrar okumalıdır. Bir program bağlamı, kesme veya görev anahtarının ne zaman gerçekleştiğini bilmediğinden, birden fazla bağlam tarafından kullanılan herhangi bir global değişkenin, olası bir bağlam anahtarından dolayı değişkenin herhangi bir erişimi arasında değişebileceğini varsayması gerekir. Geçici bildirimin bunun için var. Derleyiciye, bu değişkenin içeriğinizin dışında değişebileceğini, bu nedenle her erişim için onu okuduğunu ve değeri zaten bildiğinizi varsaymadığını söyler.

Değişken bir donanım adresiyle hafıza eşleştirilirse, donanım tarafından yapılan değişiklikler programınızın kapsamı dışında etkili bir başka bağlamdır. Yani hafıza haritası da bir ipucu. Örneğin, readADC()fonksiyonunuz ADC değerini elde etmek için hafıza haritalanmış bir değere erişiyorsa, o zaman hafıza haritalanmış değişken muhtemelen değişken olmalıdır.

Bu nedenle sorunuza geri dönersek, kodunuzda daha fazlası varsa ve adcValuefarklı bir bağlamda çalışan diğer kodlardan erişilirse, evet, adcValuegeçici olmalıdır.


4

"Doğrudan donanımdan değiştirilen global değişken"

Değer bazı donanım ADC sicilinden geliyor olması, donanım tarafından "doğrudan" değiştirildiği anlamına gelmez.

Örnekte, sadece bazı ADC kayıt değeri veren readADC () 'yi çağırırsınız. Bu, derleyiciye göre gayet iyi, adcValue öğesine o noktada yeni bir değer verildiğini bilmek.

Yeni bir değer atamak için bir ADC kesme rutini kullanıyor olsaydınız, bu yeni bir ADC değeri hazır olduğunda çağrılırsa farklı olurdu. Bu durumda, derleyicinin, karşılık gelen ISR'nin ne zaman çağrılacağına dair hiçbir fikri yoktur ve adcValue'a bu şekilde erişilmeyeceğine karar verebilir. Burası uçucunun yardım edeceği yer.


1
Kodunuz asla ISR işlevini "çağırmaz", Derleyici, bu değişkenin yalnızca kimsenin aramadığı bir işlevde güncellendiğini görür. Böylece derleyici onu optimize eder.
Swanand

1
AdcValue herhangi bir yerde okunmuyorsa (yalnızca hata ayıklayıcıdan okunuyorsa) veya tek bir yerde yalnızca bir kez okunursa, derleyici bunu en iyi duruma getirecektir.
Damien

2
@Damien: Her zaman "bağlıdır", ancak asıl soruyu ele almak istiyordum "Bu durumda geçici anahtar kelimesini kullanmalı mıyım?" mümkün olduğu kadar kısa.
Rev1.0

4

Davranışı volatileArgümanın büyük ölçüde kodunuza, derleyiciye ve yapılan optimizasyona bağlıdır.

Şahsen kullandığım iki kullanım durumu var volatile :

  • Değişken varsa, hata ayıklayıcısına bakmak istiyorum, ancak derleyici onu en iyi duruma getirdi (bu değişkenin olması gerekmediği için onu sildiği anlamına gelir), volatile derleyiciyi beklemeye zorlar ve dolayısıyla hata ayıklama üzerinde görülebilir.

  • Değişken "kodun dışında" değişebilirse, genellikle erişen bir donanıma sahipseniz veya değişkeni doğrudan bir adresle eşlerseniz.

Gömülü olarak, derleyicilerde bazen gerçekten işe yaramayan optimizasyonlar yaparak bazen de bazı hatalar olabilir. volatile sorunları çözebilecek .

Değişkeniniz global olarak bildirildiğinde, değişken kodda kullanıldığı sürece, en azından yazılan ve okunan büyük olasılıkla optimize edilmeyecektir.

Örnek:

void test()
{
    int a = 1;
    printf("%i", a);
}

Bu durumda, değişken muhtemelen printf ("% i", 1);

void test()
{
    volatile int a = 1;
    printf("%i", a);
}

optimize edilmeyecek

Bir diğeri:

void delay1Ms()
{
    unsigned int i;
    for (i=0; i<10; i++)
    {
        delay10us( 10);
    }
}

Bu durumda, derleyici tarafından (hız için optimize ederseniz) optimizasyon yapabilir ve böylece değişkeni atabilirsiniz.

void delay1Ms()
{
       delay10us( 10);
       delay10us( 10);
       delay10us( 10);
       delay10us( 10);
       delay10us( 10);
       delay10us( 10);
       delay10us( 10);
       delay10us( 10);
       delay10us( 10);
       delay10us( 10);
}

Kullanım durumunuz için, kodunuzun geri kalanına, adcValuebaşka bir yerde nasıl kullanıldığına ve kullandığınız derleyici sürüm / optimizasyon ayarlarına bağlı olabilir.

Bazen optimizasyon olmadan çalışan bir kodun olması rahatsız edici olabilir, ancak bir kez en iyi duruma getirme sonları.

uint16_t adcValue;
void readFromADC(void)
{
  adcValue = readADC();
  printf("%i", adcValue);
}

Bu, printf ("% i", readADC ());

uint16_t adcValue;
void readFromADC(void)
{
  adcValue = readADC();
  printf("%i", adcValue);
  callAnotherFunction(adcValue);
}

-

uint16_t adcValue;
void readFromADC(void)
{
  adcValue = readADC();
  printf("%i", adcValue);
}

void anotherFunction()
{
   // Do something with adcValue
}

Bunlar muhtemelen optimize edilmeyeceklerdir, ancak "derleyicinin ne kadar iyi olduğunu" asla bilemezsiniz ve derleyici parametreleriyle değişebilir. Genellikle iyi optimizasyona sahip derleyiciler lisanslıdır.


1
Örneğin a = 1; b = a; ve c = b; derleyici bir dakika bekleyebilir düşünebilir, a ve b işe yaramaz, doğrudan c'ye 1 koyalım. Elbette bunu kodunuzda yapmayacaksınız, ancak derleyici bunları bulmakta sizden daha iyidir, ayrıca derhal optimize edilmiş bir kod yazmaya çalışırsanız okunamaz olur.
Damien

2
Doğru bir derleyiciye sahip doğru bir kod, optimizasyonların açık olmasıyla bozulmaz. Derleyicinin doğruluğu biraz problemlidir, fakat en azından IAR ile optimizasyonun kodun kırılmaması gereken yerde olmasıyla sonuçlanan bir durumla karşılaşmadım.
Arsenal

5
Eğer cesaretini zaman optimizasyon sonları kodudur durumlarda bir çok UB çok ülkesinde ..
boru

2
Evet, uçucunun yan etkisi, hata ayıklamaya yardım edebilmesidir. Ancak bu geçici kullanmak için iyi bir neden değildir. Kolay hata ayıklama amacınızsa, muhtemelen optimizasyonları kapatmalısınız. Bu cevap kesintilerden bile bahsetmiyor.
kkrambo

2
Hata ayıklama bağımsız değişkenine ekleme volatile, derleyiciyi RAM'deki bir değişkeni saklamaya ve değişkene bir değer atanır atanmaz bu RAM'i güncellemeye zorlar. Çoğu zaman, derleyici değişkenleri 'silmez', çünkü genellikle etkisiz atamalar yazmıyoruz, ancak değişkeni bazı CPU kayıt defterlerinde tutmaya karar verebilir ve daha sonra veya bu kayıt defterinin değerini RAM'e yazabilir. Hata ayıklayıcılar, genellikle değişkenin tutulduğu CPU kaydını bulmada başarısız olur ve bu nedenle değerini gösteremez.
JimmyB

1

Çok fazla teknik açıklama var ama pratik uygulamaya yoğunlaşmak istiyorum.

volatileAnahtar kelime kuvvetler derleyici okumak veya bellekten kullanıldığı her zaman değişkenin değerini yazmak için. Normalde, derleyici optimize etmeye çalışacak ancak gereksiz okuma ve yazma yapmaya çalışmayacaktır, örneğin, her seferinde belleğe erişmek yerine, değeri bir CPU kaydında tutarak.

Bunun gömülü kodda iki ana kullanımı vardır. Öncelikle donanım kayıtları için kullanılır. Donanım kayıtları değişebilir, örneğin bir ADC sonuç kaydı ADC çevre birimi tarafından yazılabilir. Donanım kayıtları erişildiğinde de eylemler gerçekleştirebilir. Yaygın bir örnek, okurken genellikle kesme bayraklarını silen bir UART'ın veri kaydıdır.

Derleyici normalde tekrarlanan okumaları en iyi duruma getirmeye çalışır ve kaydın değerinin asla değişmeyeceği varsayımına dayanır ve böylece erişmeye devam etmeye gerek kalmaz. volatile anahtar kelime bir okuma işlemi her zaman gerçekleştirmek için zorlayacaktır.

İkinci yaygın kullanım, hem kesme hem de kesme kodu tarafından kullanılan değişkenler içindir. Kesintiler doğrudan çağrılmaz, bu nedenle derleyici ne zaman yürütüleceğini belirleyemez ve böylelikle kesmenin içindeki herhangi bir erişimin asla gerçekleşmeyeceğini varsayar. Çünkü volatilekelime kuvvetler derleyici değişkeni her zaman erişmek için, bu varsayım kaldırılır.

volatileAnahtar kelimenin bu sorunlara tam bir çözüm olmadığına dikkat etmek önemlidir ve bunlardan kaçınmak için özen gösterilmelidir. Örneğin, 8 bitlik bir sistemde 16 bitlik bir değişken, okumak veya yazmak için iki bellek erişimi gerektirir ve bu nedenle derleyici bu erişimleri sıralı olarak gerçekleştirmeye zorlansa bile, donanımın ilk erişime veya ikisi arasında meydana gelen bir kesinti.


0

Bir volatileniteleyicinin yokluğunda , bir nesnenin değeri kodun belirli bölümleri sırasında birden fazla yerde saklanabilir. Örneğin, şöyle bir şey düşünün:

int foo;
int someArray[64];
void test(void)
{
  int i;
  foo = 0;
  for (i=0; i<64; i++)
    if (someArray[i] > 0)
      foo++;
}

C'nin ilk günlerinde, bir derleyici bildirimi işlemiş

foo++;

adımları ile:

load foo into a register
increment that register
store that register back to foo

Bununla birlikte, daha sofistike derleyiciler, "foo" değerinin döngü sırasında bir kayıt defterinde tutulması durumunda, döngüden önce sadece bir kez yüklenmesi ve bir kez sonra depolanması gerektiğini anlayacaktır. Bununla birlikte, döngü boyunca, bu, "foo" değerinin iki yerde tutulduğu anlamına gelir - küresel depolama alanı içinde ve sicil içinde. Derleyici döngü içinde "foo" 'ya erişilebilecek tüm yolları görebiliyorsa bu sorun olmayacak, ancak derleyicinin bilmediği bir mekanizmada "foo" değerine erişildiğinde sorun yaratabilir ( kesme işleyicisi gibi).

Standard'ın yazarlarının, derleyiciyi bu tür optimizasyonlar yapmaya açıkça davet edecek yeni bir niteleyici eklemesi ve eski moda semantiksinin yokluğunda uygulanacağını söylemesi mümkün olabilirdi, ancak optimizasyonların çok fazla sayıda yararlı olduğu durumlarda sorunlu olacakları için, Standart yerine derleyicilerin bu tür optimizasyonların kanıt olmadıklarına dair güvencede olduklarını varsaymalarına izin verir. AmacınınvolatileAnahtar kelimenin böyle bir kanıt sağlamaktır.

Bazı derleyici yazarlar ve programcılar arasındaki birkaç çekişme noktası, aşağıdaki gibi durumlarla ortaya çıkar:

unsigned short volatile *volatile output_ptr;
unsigned volatile output_count;

void interrupt_handler(void)
{
  if (output_count)
  {
    *((unsigned short*)0xC0001230) = *output_ptr; // Hardware I/O register
    *((unsigned short*)0xC0001234) = 1; // Hardware I/O register
    *((unsigned short*)0xC0001234) = 0; // Hardware I/O register
    output_ptr++;
    output_count--;
  }
}

void output_data_via_interrupt(unsigned short *dat, unsigned count)
{
  output_ptr = dat;
  output_count = count;
  while(output_count)
     ; // Wait for interrupt to output the data
}

unsigned short output_buffer[10];

void test(void)
{
  output_buffer[0] = 0x1234;
  output_data_via_interrupt(output_buffer, 1);
  output_buffer[0] = 0x2345;
  output_buffer[1] = 0x6789;
  output_data_via_interrupt(output_buffer,2);
}

Tarihsel olarak, çoğu derleyici ya bir volatiledepolama yeri yazmanın isteğe bağlı yan etkileri tetikleyebilme olasılığına izin verir ve bu tür bir mağazadaki kayıtlardaki değerleri önbelleğe almaktan kaçınırdı; ya da başka bir deyişle, kayıtlar arasındaki değerleri önbelleğe almaktan kaçınırlar. nitelikli "satır içi" olmadı ve bu nedenle output_buffer[0], 0x1234 yazdı , verileri çıktısı alacak şekilde ayarlayın, tamamlanmasını bekleyin, sonra 0x2345 yazıp oradan output_buffer[0]devam edin. Standart , adresini bir adrese kaydetme eylemini yerine getirmek için uygulama gerektirmez.output_buffervolatileBununla birlikte, bir şeyin başına gelebileceğinin bir işareti olarak kalifiye işaretçi derleyicinin anlayamadığı anlamına gelir, çünkü yazarlar derleyicilerin çeşitli platformlar için tasarlanan derleyicilerin yazarlarını derlediğini ve bunun için bu platformlarda bu amaçlara hizmet edeceğini kabul edeceğini düşünür. söylenmeden. Sonuç olarak, gcc ve clang gibi bazı "akıllı" derleyiciler, adresleri output_bufferiki mağaza arasındaki uçucu nitelikte bir işaretçiye yazılsa bileoutput_buffer[0] da, bu nesnede tutulan değerle ilgili hiçbir şeyin umursamayacağını varsaymak için bir neden olmadığını varsayacaktır. o zaman.

Ayrıca, tam sayılardan doğrudan atılan işaretçiler nadiren derleyicilerin anlama olasılığı olmayan şekillerde işlem yapmaktan başka bir amaç için kullanılmasa da, Standart tekrar böyle derleyicilere bu tür erişimleri tedavi etmeyi gerektirmez volatile. Sonuç olarak, ilk yazma, *((unsigned short*)0xC0001234)gcc ve clang gibi "zeki" derleyiciler tarafından atlanabilir, çünkü bu derleyicilerin volatilesahipleri, böyle bir kodla uyumluluğun uygun olduğunu kabul etmekten ziyade "kırılmış" olarak nitelendirmeyi ihmal eden kodun kullanıldığını iddia ederler. . Çok sayıda satıcı tarafından sağlanan başlık dosyası volatileniteleyicileri çıkarır ve satıcı tarafından sağlanan başlık dosyalarıyla uyumlu bir derleyici olmayandan daha kullanışlıdır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.