C spesifikasyonun’da volatile tanımı:

“A volatile declaration may be used to describe an object corresponding to a memory-mapped input/output port or an object accessed by an asynchronously
interrupting function. Actions on objects so declared shall not be “optimized out” by an implementation or reordered except as permitted by the rules for evaluating expressions.”

Harika! Bariyer kullanmama gerek kalmadı. Inline assembly de neymiş, ben C programcısıyım. C volatile beni kurtarır. Yanlııış! (Kevin Spacey seslendirmesiyle :))
Spesifikasyon’da tanımı yapılan volatile, ilgili değişken üzerinde optimizasyon yapılmayacağı (mesela lüzumsuz gibi görünüyorsa kaldırılmayacağı) ve tanımlanan değişken işlemlerinde sıralama (yükle/sakla) yapılmayacağını garantiliyor. Ama spesifikasyon günümüz modern mimarilerinde, donanımlar tarafından yapılabilecek optimizasyonlardan bahsetmiyor. Bu şekilde derleyici’nin sıralama yapmaması sağlanabilir ancak işlemci (diğer işlemci bakışı açısından) ve ya farklı donanımların sıralama yapması (mmio ihtiyaçları) engelenemez. Dolayısı ile yalnızca bu tanıma bakarak alınan bilinçsiz kararlar hatalı sonuçlara sebep olacaktır.

Linus şu şekilde ifade etmiş:

“‘volatile’ is useless. The things it did 30 years ago are much more complex these days, and need to be tied to much more detailed rules that depend on the actual particular problem, rather than one keyword to the compiler that doesn’t actually give enough information for the compiler to do anything useful”

Ancak, volatile’ın çekirdek içerisinde kullanıldığı yerler var. Bunlardan hangilerinin “legal” oldukları’nın açıklamasını Linus’den dinlemeden önce, geliştiricilerin
yanlış kullanımlarının doğurabileceği iki kötü etkiden/sorundan bahsedelim. Eski bir

örnek:

int del_timer(struct timer_list * timer)
{
    int ret = 0;
    if (timer->next) {
        unsigned long flags;
        struct timer_list * next;
        save_flags(flags);
        cli();
        if ((next = timer->next) != NULL) {
            (next->prev = timer->prev)->next = next;
            timer->next = timer->prev = NULL;
            ret = 1;
        }
        restore_flags(flags);
    }
    return ret;
}

Mail’da vakti zamanında Sparc’da ortaya çıkmış olan bir problem’den bahsediliyor. Burada if içerisinde timer->next değişkeni’nin okunduğu daha sonra içerisi dolu ise interrupt’ların kapatılarak timer’ın liste’den çıkarıldığı görülüyor. Burada compiler açısından bakıldığında timer->next bir kere okunmuş, sonrasında da yeniden okunarak next değişkeni içerisine kopyalanmış. Bu durumda compiler’a timer->next gayet güzel bir biçimde register’da cachlenebilir gibi görünüyor. Fakat interrupt’lar kapatılmadan hemen önce interrupt geldiğini ve bu interrupt kodu içerisinde de timer->next’in değiştiğini düşünün. Bu durumda cache’lenen
değer hatalı olacak ve gidip yanlış bir timer kaldırılacak. Bu durumda volatile kullanılabilir gibi düşünülebilir. Ancak…

Volatile’ın kötü bir tarafı, compiler tarafından optimize edilebilecek bir çok yere, gereksiz bir sürü saçma sapan kod ekleterek hafıza erişimleri yapmaya zorlaması. Halbuki yukarıdaki durumda biz sadece bu eleman’ın belli bir noktadaki erişimi’nin, cachelenmemesi(cache’den olmaması), hafızadan olması gerektiğini belirtmek istiyoruz. Tamam yukarıdaki örnekte, ilgili yerde, istediğimizi gerçekleştirmiş oluyoruz, ancak başka optimize edilebilecek bir çok noktada gereksiz kod üretilmesini
ve kullanılmasını da sağlamış oluyoruz. Gereksiz fazla kod = ziyan olan işlemci zamanı. Bu bahsi geçen kötü etki/sorundan ilki idi.

İkinci kötü etkisi ise Linus’un deyimiyle geliştirici’nin “bu kod hatalı çalışıyor, compiler sen volatile ile burayı _sihirli bir şekilde_ düzelt” düşüncesi, aslında kodda varolan başka problemleri gizleyebiliyor. Yakın zamanda geçen bir tartışmadan:

alternative_smp(
                __raw_spin_lock_string,
                __raw_spin_lock_string_up,
                "=m" (lock->slock) : : "memory");

Burada lock->slock’ın neden hala kod’da volatile olarak tanımlı olduğu ve kaldırılabilirliği tartışılıyor. Fakat Linus’un tespiti ile yanlızca volatile’ı kaldırmak gizlenmiş olan başka bir hata’yı ortaya çıkarabilir. O da şu ki:

spin_lock_init(&lock);
spin_lock(&lock);

İlgili kod’un assembly’sine bakıldığı zaman lock->slock’ın yalnızca bir output değeri olduğu belirtilmiş. Bu durumda c kodundan bakıldığında, init ve lock sonrasında, compiler “madem ki buradaki değer lock->slock’a yazılıyor ve bir önceki değer’de bu şekilde ezilmiş oluyor” diyebilir ve init kısmını tamamen ortadan kaldırarak optimize etmeye gidebilir. Burada volatile bunu engelliyor çünkü değişken volatile tanımlandığından, değişken üzerinde compiler optimizasyona gitmiyor. İlginç bir tespit. :)
Bu durumu çözmek için ise Linus, yalnız “=m” yerine, bu değişkenin hem input/hem output olduğunun belirtilmesini öneriyor. Böylece optimizasyon engelenebilir. Bu ya “=m”/”m” ikilisi ile ya da yalnızca “+m” kullanılarak olabilir.

Şimdi… Volatile hem kötü ve fazla kod üretiyor, hem geliştirici tembelliğinden var olan hataların üstü örtülmüş oluyor, hem de ilk örneke geçtiği gibi varsayımların yanlış sonuçları hatalara sebebiyet veriyor. Peki volatile tamamen mi gereksiz?

Son iki örneğe bakarsak volatile’ın, değişken’in tanımında kullanıldığını görüyoruz. Linus volatile’ın kullanımını tanımlayabilmek için ikiye ayırıyor:_Veri_ üzerinde volatile ve _kod_ üzerinde volatile. Birincisi yanlış olan ve bahsi geçen son iki soruna sebep oluyor. Kod üzerinde volatile ise doğru kullanım. Doğru kullanım için verdiği örnekler ise şunlar:

* Volatile tanımlı değişken _verilebilen_(geliştiriciler tarafından) genel fonksiyonlarda:

int test_bit(int nr, const volatile void * addr)

test_bit’e verilen değişken volatile tanımlı ise test_bit’de volatile olmalı:“1471) If an attempt is made to refer to an object defined with a volatile-qualified type through use of an lvalue with non-volatile-qualified type, the behavior is undefined”

* Bu değişkene/değere işte tam burada sen hafızadan eriş, bu şekilde erişmek zorundasın demek için volatile’a cast edilebilir:

static inline unsigned int readl(const volatile void __iomem *addr)
{
    return *(volatile unsigned int __force *) addr;
}

İşte bu iki örnek _veri_ üzerinde değil, _kod_’da volatile kullanımı oluyor. Bunun dışında Linus çekirdek içerisinde yalnızca bir yerde _veri_ üzerinde volatile kullanımına uygun diyor, o da “jiffies” değişkeni üzerinde. Bunun da sebebi, (MMIO) işleminde kullanılan bir adres olmaması,locking gerektirmemesi, sıralama sorunlarının olmaması (önce veya sonra okunması’nın bir sakıncası yok) sebeplerinden volatile’ın sorun yaratmadan bu tarz bir _veri_ üzerinde kullanılabileceğini
belirtmiş.

Son. :)