GCC - Cache Risk(Önbellek Riski)
Cache Risk(Önbellek Riski) — volatile
Bu makalemde,özellikle hardware programlama düzeyinde sıklıkla kullandığımız veya kullanmak zorunda olduğumuz ‘volatile’ anahtar sözcüğüne değineceğim.
Özellikle ‘hardware’ olarak belirtmemdeki sebep,bu alanda yaşadığım bir deneyim neticesinde konuya bağlı kalmak için belirtiyorum.
Ancak geliştirmekte olduğunuz kod,amacına uygun olarak belli kullanım gereksinimleri doğurur. Dolayısıyla ‘hardware’ olarak sınırlanamaz.
Volatile Nedir Ve Neden Kullanmalıyız?
Bu anahtar kelimeyi tanıtmadan evvel,derleyici bazında kod optimizasyon konusunda biraz bilgi sahibi olmamız gerekir. Bu noktada kullandığımız derleyici ve derleyici’den istediğimiz işlevler önem teşkil etmektedir.
Daha açık olarak: Gelişmiş derleyiciler,geliştirici isteği dışında kodların işleyiş mantığına müdahalede bulunur. Buradaki amaç,gereksiz kod boyutundan ve çalışma zamanından feragat etmektir.
Örneğin tanımladığımız bir değişkeni hiçbir yerde kullanmıyorsak bunun uygulama boyutuna gereksiz etkisi vardır. Veya uygulama çalışma esnasında sıkça başvurulan bir alan(değişken,fonksiyon vb) çalışma zamanına olumsuz etki edebilir. Bu noktada derleyici optimizasyon özelliği devreye girer.
Sık sık başvurulan alanların değerleri,daha hızlı erişim amaçlı çeşitli şekillerde saklanarak optimizasyon sağlanılır.
Optimizasyona gerek duyulan örnek:
int c = 0;
for(int i =0; i<100;i++)
{
c = i;
}
Bu kod parçasında ‘c’ değişkeni tanımlanmış fakat herhangi bir yerde kullanılmamıştır. Veya aşağıdaki kod parçası örnek verilebilir:
if(false){
printf("zamazingo...");
}
Bu kullanımda hiçbir zaman if içerisine girilmeyecektir.
Bu durumda uygulama boyutu düşünülerek optimizasyon uygulanır.
Ancak derleyici tarafından sağlanan optimizasyon özelliğini kullanmak geliştirici insiyatifine verilmiştir. Dolayısıyla geliştirici bu özelliği kullanmak istediğini/istemediğini belirtebilir.
Derleyici optimizasyon özelliğini kullanmak yarar sağlayacağı için önerilir. Fakat optimizasyon durumları bazı istenmeyen sonuçları meydana getirebilir. Bu sonuçlardan biri de sıklıkla başvurulan/çağrılan(değer yazılıp,değer okunan) alanlardır. Bu şekilde kullanımda olan alanlara hızlıca erişim sağlanabilmesi için(çalışma zamanı) değeri genelde cache kısmında saklanır.
Ancak bu işleyiş çok işlemcili cihazlarda istenmeyen durumlar meydana getirebilir.
Örneğin böyle bir alanın işleyişini aynı anda birden fazla işlemci erişmesi(işleme alması) durumunda,biri nihai değer yazarken diğeri bu değeri değiştirebilir. Yani birden fazla Thread’in aynı anda erişmesi bu durumu yaşatabilir. Böyle bir durumda işlemin sonucu,dolayısıyla ilgili alanın değeri değişecektir. Örneğin SD kart okuma esnasında ilgili donanımın hazır olup olmadığını belirleyen kritik değer alanı böyle bir tehdit altında olmamalıdır. Veya bir donanım REG(register) değeri için de aynı durum söz konusudur. Sonuç itibariyle böyle bir durumun yaşanmaması için ihtimallere yer verilmemelidir.Bunun için başvurulacak anahtar kelime ‘volatile’’dir.
‘Volatile’ olarak tanımlanmış bir alan,derleyici optimizasyon işleminden emin olmak içindir. Yani derleyici ‘volatile’ olarak tanımlanmış bir alana optimizasyon işlemi uygulamaz. Ayrıca,’volatile’ tanımlanmış bir alanın değeri “cache” yerine main(ana) memory’e yazılır ve okunur.
Bu alanın değeri ‘cache’a yazılıp okunmadığı için yukarıda anlatılan istenmeyen sonuçlara karşı bir miktar garantiye alınmıştır. Ancak bu tanımlama tek başına yeterli değildir.
Örneğin birden fazla Thread ‘volatile’ bir değişkenin değerini main memory’dan aynı anda işleme alırsa risk meydana gelir.
Bu durumdan tamamen emin olmak için “mutex” ile “lock/unlock”(Cpp için) ile garantiye alınmalıdır.
Örnek:
#include<mutex>
std::mutex _mutex;
volatile int i;
volatile int REG;
_mutex.lock();//kilitle
for(i =0 ; i<32; i++)
{
REG = i << 10;
REG = ..
REG = ..
}
_mutex.unlock();//kilidi kaldır.
NOT: Bu yazıda Cpp kullanım örneği verilmiştir. Diğer teknolojiler için eşdeğer yöntemler kullanmalısınız.
Java’da,mutex’e karşılık gelen kullanımlara aşağıda yer verilmiştir.
Örnek1:
volatile int count = 0;
public synchronized void increment() {
count++;
}
Örnek2:
Lock lock = new Lock();
volatile int count = 0;public int inc(){
lock.lock();
count++;
lock.unlock();
return count;
}
Yukarıda verilen örneklerde “synchronized” fonksiyon ile birlikte kullanıldı. Ancak ”synchronized” anahtarı “lock” gibi kod blokları içinde kullanılabilir.
Volatile Cpp Nesnesine Ne Zaman Erişilir?
C ve Cpp standardı “volatile” nesneler kavramına sahiptir. Bu nesnelere normalde pointer(işaretçi)’lar ile erişilir ve donanıma erişmek için kullanılır. Standartlar,derleyicileri “volatile” olmayan nesnelerde gerçekleştirebilecekleri “volatile” nesnelere erişimle ilgili optimizasyonlardan kaçınmaya teşvik eder. C standardı,uygulamayı neyin geçici bir erişim oluşturduğuna göre tanımlanmış olarak bırakır. Ancak Cpp standardı, mümkün olduğunda “volatile”’lar ile ilgili olarak C++’ın C’ye benzer şekilde davranması gerektiğini söylemek dışında bunu belirtmeyi atlar. C ve C++ standartlardan herhangi birinin belirttiği minimum durum,bir sekans noktasında volatile nesnelere önceki tüm erişimlerin stabilize olması ve sonraki erişimlerin gerçekleşmemesidir.
Bu nedenle,bir uygulama sekans noktaları arasında meydana gelen volatile erişimlerin yeniden sıralanması ve birleştirilmesi için serbesttir,ancak bunu bir sekans noktası boyunca erişimler için yapamaz.
Çoğu ifadede,sezgisel olarak neyin okunduğu ve neyin yazılacağı açıktır.
Örneğin:
volatile int *dst = somevalue;
volatile int *src = someothervalue;
*dst = *src;
Bu kullanımda volatile tanımlanmış “src” ile bir nesne okunuyor ve yine volatile olarak tanımlanmış “dst” içine depolanıyor.
Bu okuma ve yazma işlemlerinin,özellikle int’den büyük nesneler(long,double) için atomik olduğuna dair bir garanti yoktur.
NOT: Programlamada “atomik(atomic)” bir işlem-eylem,tamamen başarılı olan veya tamamen başarısız olan bir eylemdir.
Bir atomik eylem ortada duramaz. Dolayısıyla ya tamamen başarılıdır ya da başarısızdır.
Daha az belirgin ifadeler,erişim gibi görünen bir şeyin geçersiz bir bağlamda kullanıldığı yerdir. Aşağıda verilen tanımlama buna örnektir:
volatile int *src = somevalue;
*src;
C de bu tür ifadeler r değerleridir ve r değerleri nesnenin okunmasına neden olduğundan,GCC bunu işaret edilen bir volatile okuması olarak yorumlar. Ancak C++’da,bu tür ifadelerin l değerinden r değerine dönüştürülmediğini ve başvurulan nesnenin türünün eksik olabileceğini belirtir.
Bu makaleyi hazırlamamın nedeni,embedded linux alanındaki çalışmalarımda karşılaştığım bir “main memory” problemi ve buna getirdiğim çözümü size aktarmaktır. Her ne kadar eş zamanlı “main memory” erişimi nadir(düşük/çok düşük ihtimal) bir durum olsa da,risk ihtimali varsa buna kesinlikle önlem alınmalıdır.
GCC ile ilgili yazılara seri halinde devam edilecektir.
-end of