IBM örnek kodu, yeniden girmeyen işlevler sistemimde çalışmıyor


11

Programlamaya yeniden giriş yapıyordum. IBM'in bu sitesinde (gerçekten iyi). Aşağıda kopyalanan bir kod oluşturdum. Bu web sitesi aşağı yuvarlanan ilk kod.

Kod, bir "tehlikeli bağlamda" sürekli değişen iki değer yazdırarak, bir metin programının doğrusal olmayan gelişiminde (asenkroniklik) değişkene paylaşılan erişimi içeren sorunları göstermeye çalışır.

#include <signal.h>
#include <stdio.h>

struct two_int { int a, b; } data;

void signal_handler(int signum){
   printf ("%d, %d\n", data.a, data.b);
   alarm (1);
}

int main (void){
   static struct two_int zeros = { 0, 0 }, ones = { 1, 1 };

   signal (SIGALRM, signal_handler); 
   data = zeros;
   alarm (1);
   while (1){
       data = zeros;
       data = ones;
   }
}

Kod çalıştırmaya çalıştığımda sorunlar ortaya çıktı (veya daha iyi, görünmedi). Varsayılan yapılandırmada gcc 6.3.0 20170516 (Debian 6.3.0-18 + deb9u1) sürümünü kullanıyordum. Yanlış yönlendirilmiş çıktı oluşmaz. "Yanlış" çift değerlerini alma sıklığı 0'dır!

Sonuçta ne oluyor? Statik global değişkenler kullanarak yeniden girişte neden sorun yok?


1
Tüm derleyici optimizasyonunun devre dışı olduğundan emin olun ve tekrar deneyin
roaima

Sanırım ... ama hangi seçenekleri değiştirirdim? Hiç bir fikrim yok. :-(
Daniel Bandeira

5
Bu bir programlama sorusuna benziyor (yığın taşması). Doz buraya iyi yerleştirilmemiş gibi görünüyor. (Üzgünüm, ben daha az alt site vardı; çok kesilmiş. Ama bu böyle.)
ctrl-alt-delor

1
En basit yeniden giriş kodu değiştirilemez.
ctrl-alt-delor

İlk başta, sorunun gcc ve Linux ortamıyla ilgili olacağını düşünüyorum. Örneğin, OS'nin planlanması (örneğin, işleyici rutini çağrılmadan önce kesinti sinyalinden sonra daha fazla program metni yürütme).
Daniel Bandeira

Yanıtlar:


12

Bu gerçekten bir refakat değil ; bir işlevi aynı iş parçacığında (veya farklı iş parçacıklarında) iki kez çalıştırmıyorsunuz . Bunu özyineleme yoluyla veya geçerli işlevin adresini geri arama işlevi işaretçisi arg olarak başka bir işleve geçirerek alabilirsiniz. (Ve güvensiz olmaz çünkü senkron olur).

Bu, bir sinyal işleyici ve ana iş parçacığı arasında sadece düz vanilya veri yarışı UB'dir (Tanımsız Davranış): sadece sig_atomic_tbunun için güvenli olduğu garanti edilir . 8 baytlık bir nesnenin x86-64 üzerinde bir komutla yüklenebileceği veya saklanabileceği gibi, derleyici de bu grubu seçerken diğerleri işe yarayabilir. (@ İcarus'un cevabının gösterdiği gibi).

Bkz. MCU programlama - döngü sırasında C ++ O2 optimizasyonu kesiliyor - tek çekirdekli bir mikro denetleyicideki bir kesme işleyicisi temel olarak tek bir dişli programdaki bir sinyal işleyici ile aynı şeydir. Bu durumda UB'nin sonucu, bir yükün bir döngüden kaldırılmasıdır.

Veri yarışı UB nedeniyle gerçekte yırtılma test durumunuz muhtemelen 32 bit modunda veya yapı üyelerini ayrı ayrı yükleyen eski bir derleyici derleyicisi ile geliştirildi / test edildi.

Sizin durumunuzda, derleyici depoları sonsuz döngüden optimize edebilir, çünkü hiçbir UB içermeyen program bunları gözlemleyemez. datadeğil _Atomicya davolatile , ve döngüde başka hiçbir yan etkisi yoktur. Yani herhangi bir okuyucunun bu yazıcıyla senkronize edebilmesi mümkün değil. Bu aslında optimizasyon etkinken derlerseniz gerçekleşir ( Godbolt ana alt kısmında boş bir döngü gösterir). Ayrıca yapıyı ikiye değiştirdim long longve gcc movdqa, döngüden önce tek bir 16 baytlık depo kullanıyor . (Bu garanti atomik değildir , ancak pratikte neredeyse tüm CPU'larda, hizalı olduğu varsayılarak veya Intel'de sadece bir önbellek sınırını geçmez. X86'da neden doğal olarak hizalanmış bir değişken atomda tamsayı ataması var? )

Dolayısıyla optimizasyon etkinken derleme de testinizi kıracak ve size her seferinde aynı değeri gösterecektir. C taşınabilir bir montaj dili değildir.

volatile struct two_intayrıca derleyiciyi onları optimize etmemeye zorlar , ancak tüm yapıyı atomik olarak yüklemeye / depolamaya zorlamaz. (Bu olmaz durdurmak Not. Gerçi ya, bunu yaparken onu) volatileyok değil veri yarış UB önlemek, ama pratikte arası iplik iletişim için yeterli ve insanlar (satır içi asm) ile birlikte elle sarılmış atomics inşa nasıldı normal CPU mimarileri için C11 / C ++ 11'den önce. Onlar ediyoruz önbellek tutarlı öylesine volatileolduğu için çoğunlukla benzer pratikte _Atomicilememory_order_relaxed saf yük ve saf-mağaza için, türleri için kullanılan eğer yırtılması alamadım bu yüzden derleyici tek talimat kullanacağı yeterince dar. Ve tabi kivolatileISO C standardından, aynı _Atomicmodayı kullanan ve mo_relaxed ile derleyen kod yazma garantisine sahip değildir .


Yaptığın bir işlevi olsaydı global_var++;, bir de intya long longana çalıştırmak olduğunu ve bir sinyal işleyici gelen uyumsuz, yani veri yarış UB oluşturmak için kullanımı yeniden entrancy bir yolunu olacaktır.

Nasıl derlendiğine bağlı olarak (bellek varış yeri inc veya ekleme veya ayrı yükleme / inc / depolama için), aynı iş parçasındaki sinyal işleyicilerine göre atomik veya değil olacaktır. Bkz . 'İnt num' için num ++ atomik olabilir mi? x86 ve C ++ 'da atomisite hakkında daha fazla bilgi için. (C11 stdatomic.hve _Atomicözniteliği C ++ 11'in std::atomic<T>şablonuna eşdeğer işlevsellik sağlar )

Bir komutun ortasında bir kesinti veya başka bir istisna olamaz, bu nedenle bellek-hedef ekleme atomik wrt'tur. bağlam anahtarları tek çekirdekli bir CPU üzerinde. Yalnızca (önbellek uyumlu) bir DMA yazarı, tek çekirdekli bir CPU'da öneki add [mem], 1olmayan bir lockartıştan "adım atabilir" . Başka bir iş parçacığının üzerinde çalışabileceği başka hiçbir çekirdek yoktur.

Bu, sinyallere benzer: sinyali işleyen ipliğin normal yürütülmesi yerine bir sinyal işleyici çalışır , bu nedenle bir komutun ortasında işlenemez.


2
Icaru'nun cevabının bana yeterli olmasına rağmen, sizinkini en iyi cevap olarak kabul etmek zorunda kaldım . Bize anlattığınız net kavramlar, tüm gün (ve daha ileri) eğitim almam için bir grup konu verdi. Aslında, ilk iki paragrafta yazdıklarınızı neredeyse hiç görmedim. Teşekkür ederim! İnternette bilgisayarlar ve programlama hakkında kamuya açık makaleler varsa, bize bağlantı verin!
Daniel Bandeira

17

Godbolt derleyici gezgini (eksik ekledikten sonra) bakıldığında, #include <unistd.h>hemen hemen her x86_64 derleyicisi için oluşturulan kodun onesve zerostek bir komutla QWORD hareketlerini kullandığını görür .

        mov     rax, QWORD PTR main::ones[rip]
        mov     QWORD PTR data[rip], rax

IBM sitesi On most machines, it takes several instructions to store a new value in data, and the value is stored one word at a time., 2005'te tipik cpus için hangisinin doğru olabileceğini, ancak kodun gösterdiği gibi şimdi doğru olmadığını söylüyor. Yapıyı iki int yerine iki uzun olacak şekilde değiştirmek sorunu gösterecektir.

Daha önce bunun tembel olan "atom" olduğunu yazmıştım. Program sadece tek bir işlemci üzerinde çalışıyor. Her talimat bu cpu bakış açısından tamamlanacaktır (dma gibi hafızayı değiştiren başka bir şey olmadığı varsayılarak).

Bu nedenle C, derleyicinin yapıyı yazmak için tek bir talimat seçeceği tanımlanmamıştır ve böylece IBM belgesinde belirtilen yolsuzluk meydana gelebilir. Mevcut cpus'u hedefleyen modern derleyiciler tek bir komut kullanır. Tek bir komut dizisi, tek bir iş parçacığı programının bozulmasını önlemek için yeterince iyidir.


3
Veri türünü değiştirmeyi deneyin intiçin long longve 32bit için derlemek. Ders, kırılıp kırılmayacağını / ne zaman kırılacağını asla bilemeyeceğinizdir.
ctrl-alt-delor

2
Bu, makinemde, bu iki değerin atanmasının atomik bir işlem olduğu anlamına mı geliyor? (x86_64 mimarisi için derleme dikkate alındığında)
Daniel Bandeira

1
long longhala x86-64: 16-bayt için bir komut derler movdqa. Optimizasyonu devre dışı bırakmazsanız, Godbolt bağlantınızdaki gibi. (GCC'nin varsayılanı, -O0depolama / yeniden yükleme gürültüsü ile dolu ve genellikle ilginç olmayan hata ayıklama modudur.)
Peter Cordes

Tüm yorumları okuduktan sonra türü "uzun" olarak değiştirdim. Sonuç ilginçti: Beklenen sonuçlar elde edildi ve bazı sayaçlar kurarak, eşleşmeyen verilerin oranının kodun geri kalanından nasıl etkilendiği gibi diğer kavramları geliştirebildi. Tüm yardımlarınız için teşekkürler!
Daniel Bandeira
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.