ARM Cortex A9 üzerinde kritik bölümler nasıl uygulanır


15

Bir ARM926 çekirdeğinden CortexA9'a bazı eski kodları taşıyorum. Bu kod baremetaldir ve tümü özel olan bir işletim sistemi veya standart kitaplıklar içermez. Kodun kritik bölüm tarafından engellenmesi gereken bir yarış durumu ile ilgili gibi görünen bir hata yaşıyorum.

Kritik bölümlerimin bu CPU için doğru bir şekilde uygulanıp uygulanamayacağını görmek için yaklaşımım hakkında bazı geri bildirimler istiyorum. GCC kullanıyorum. Bazı ince hatalar olduğundan şüpheleniyorum.

Ayrıca, ARM için bu tür ilkellere sahip bir açık kaynak kütüphanesi (veya iyi bir hafif spinlock / semafor kütüphanesi) var mı?

#define ARM_INT_KEY_TYPE            unsigned int
#define ARM_INT_LOCK(key_)   \
asm volatile(\
    "mrs %[key], cpsr\n\t"\
    "orr r1, %[key], #0xC0\n\t"\
    "msr cpsr_c, r1\n\t" : [key]"=r"(key_) :: "r1", "cc" );

#define ARM_INT_UNLOCK(key_) asm volatile ("MSR cpsr_c,%0" : : "r" (key_))

Kod aşağıdaki gibi kullanılır:

/* lock interrupts */
ARM_INT_KEY_TYPE key;
ARM_INT_LOCK(key);

<access registers, shared globals, etc...>

ARM_INT_UNLOCK(key);

"Anahtar" fikri, iç içe geçmiş kritik bölümlere izin vermektir ve bunlar, yeniden giriş işlevleri oluşturmak için işlevlerin başında ve sonunda kullanılır.

Teşekkürler!


1
lütfen infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dht0008a/… 'a bakın ve bunu gömülü asm btw'de yapma. makalenin yaptığı gibi bir işlev yapın.
Jason Hu

ARM hakkında hiçbir şey bilmiyorum, ancak muteks (veya herhangi bir çapraz iplik veya çapraz işlem senkronizasyon işlevi) için, a) şu anda kayıtlarda önbelleğe alınan tüm bellek değerlerinin temizlendiğinden emin olmak için "bellek" clobber'ı kullanmanız beklenir asm yürütülmeden önce belleğe dönme ve b) asm yeniden yüklendikten sonra erişilen herhangi bir değer. Bir çağrı yapmanın (HuStmpHrrr'nin önerdiği gibi) sizin için bu yavaşlatıcıyı örtük olarak gerçekleştirmesi gerektiğini unutmayın.

Ayrıca, hala ARM konuşamasa da, 'key_' için kısıtlamalarınız doğru görünmüyor. Bunun yeniden giriş için kullanılması amaçlandığından, kilitte "= r" olarak bildirilmesi şüpheli görünüyor. '=', üzerine yazmak istediğinizi ve mevcut değerin önemsiz olduğunu gösterir. Mevcut değeri güncelleme niyetinizi belirtmek için '+' kullanmayı düşündüğünüz görülüyor. Ve tekrar kilidini açmak için, bir giriş olarak listelemek gcc'ye değiştirmek istemediğinizi söyler, ancak yanılmıyorsam (değiştirirsiniz). Bunun bir '+' çıktı olarak da listelenmesi gerektiğini tahmin ediyorum.

1
Böyle yüksek özellikli bir çekirdek için montajda kodlama için +1. Her neyse, bu ayrıcalık modlarıyla ilgili olabilir mi?
Dzarda

Eminim kullanmanız ldrexve strexdüzgün bir şekilde yapmanız gerekecek . İşte size bir döndürme kilidinin nasıl kullanılacağını ve uygulanacağını gösteren bir web sayfası . ldrexstrex

Yanıtlar:


14

İşletim sistemi olmadan kritik bir bölümü kullanmanın en zor kısmı aslında muteksi oluşturmak değil, kod şu anda mevcut olmayan bir kaynağı kullanmak istiyorsa ne olması gerektiğini bulmaktır. Load-exclusive ve koşullu-store-exclusive talimatları, bir tamsayıya bir işaretçi verildiğinde atomik olarak yeni bir değer depolayacak ancak sivri-tamsayı içerdiği şeyi döndürecek bir "takas" işlevi oluşturmayı oldukça kolaylaştırır:

int32_t atomic_swap(int32_t *dest, int32_t new_value)
{
  int32_t old_value;
  do
  {
    old_value = __LDREXW(&dest);
  } while(__STREXW(new_value,&dest);
  return old_value;
}

Yukarıdaki gibi bir fonksiyon göz önüne alındığında, biri gibi bir muteksi kolayca girebilir

if (atomic_swap(&mutex, 1)==0)
{
   ... do stuff in mutex ... ;
   mutex = 0; // Leave mutex
}
else
{ 
  ... couldn't get mutex...
}

Bir işletim sisteminin yokluğunda, ana zorluk genellikle "muteks alınamadı" kodunda yatmaktadır. Muteks korumalı bir kaynak meşgul olduğunda bir kesme meydana gelirse, kesme işleme kodunun bir bayrak ayarlaması ve ne yapmak istediğini belirtmek için bazı bilgileri kaydetmesi ve ardından bu kodu alan herhangi bir ana kodun olması gerekebilir. muteks, muteks tutulurken bir kesintinin bir şey yapmak isteyip istemediğini görmek için muteks'i ne zaman serbest bırakacağını kontrol eder ve eğer öyleyse, kesme adına eylemi gerçekleştirir.

Kesintileri sadece devre dışı bırakarak (ve gerçekten de kesintileri devre dışı bırakmak başka herhangi bir muteks ihtiyacını ortadan kaldırabilir) muteks korumalı kaynakları kullanmak isteyen kesintilerle ilgili sorunlardan kaçınmak mümkün olsa da, genel olarak kesintileri gereğinden fazla devre dışı bırakmaktan kaçınmak istenir.

Yararlı bir uzlaşma yukarıda açıklandığı gibi bir bayrak kullanmak olabilir, ancak muteks devre dışı bırakma kesintilerini serbest bırakacak ve yukarıda belirtilen bayrağı yapmadan önce kontrol edecek ana çizgi koduna sahip olabilir (muteksi serbest bıraktıktan sonra kesintileri yeniden etkinleştirin). Böyle bir yaklaşım, kesintileri çok uzun süre devre dışı bırakmayı gerektirmez, ancak ana hat kodu muteksi serbest bıraktıktan sonra kesmenin bayrağını test ederse, bayrağı gördüğü zaman ile bayrağın zamanı arasında bir tehlike olması olasılığına karşı koruma sağlayacaktır. ona göre hareket ederse, muteksi elde eden ve serbest bırakan ve kesme bayrağına etki eden diğer kodlar tarafından önlenebilir; ana hat kodu muteksi serbest bıraktıktan sonra kesmenin bayrağını test etmezse,

Her durumda, en önemlisi mutex-korumalı bir kaynağı kullanılamadığında kullanmaya çalışan bir kodun, kaynak serbest bırakıldıktan sonra denemesini tekrarlamanın bir yolu olacaktır.


7

Bu kritik bölümler yapmanın ağır bir yoludur; kesintileri devre dışı bırak. Sisteminizde veri hataları varsa / işliyorsa çalışmayabilir. Ayrıca kesme gecikmesini artıracaktır. Linux irqflags.h bu işi bazı makrolar vardır. cpsieVe cpsidtalimatları yararlı olabilmektedir; Ancak, durumu kaydetmezler ve yuvalanmaya izin vermezler. cpsbir kayıt kullanmaz.

İçin Cortex-A serisi, ldrex/strexdaha verimlidir ve oluşturmak üzere çalışabilir muteks'in kritik bölümü için ya da birlikte kullanılabilir kilidi serbest kritik bölüm kurtulmak için algoritmalar.

Bir anlamda, ldrex/strexbir ARMv5 gibi görünüyor swp. Bununla birlikte, pratikte uygulanması çok daha karmaşıktır. Çalışan bir önbelleğe ve önbellekte olması gereken hedef belleğe ldrex/strexihtiyacınız var. Buradaki ARM belgeleri, ldrex/strexmekanizmaların Cortex-A olmayan CPU'larda çalışmasını istediklerinden oldukça belirsizdir. Ancak, Cortex-A için yerel CPU önbelleğini diğer CPU'larla senkronize tutma mekanizması, ldrex/strextalimatları uygulamak için kullanılan mekanizmanın aynısıdır . Cortex-A serisi için yedek granülan ( ldrex/strexayrılmış belleğin boyutu ) bir önbellek hattıyla aynıdır; iki kat bağlantılı bir listede olduğu gibi birden çok değeri değiştirmek istiyorsanız, belleği önbellek satırına hizalamanız gerekir.

Bazı ince hatalar olduğundan şüpheleniyorum.

mrs %[key], cpsr
orr r1, %[key], #0xC0  ; context switch here?
msr cpsr_c, r1

Sekansın hiçbir zaman önceden boşaltılmamasını sağlamanız gerekir . Aksi takdirde, kesintileri etkinleştirilmiş iki kilit değişken alabilirsiniz ve kilit açma yanlış olacaktır. ARMv5'te tutarlılığı sağlamak için swptalimatı anahtar hafızasıyla kullanabilirsiniz, ancak bu talimat Cortex-A'daldrex/strex çoklu CPU sistemleri için daha iyi çalıştığı .

Tüm bunlar, sisteminizin ne tür bir programlamaya sahip olduğuna bağlıdır. Sadece ana hatlarınız ve kesintileriniz var gibi görünüyor. Kritik bölümün çalışmasını istediğiniz düzeylere (sistem / kullanıcı alanı / vb.) Bağlı olarak zamanlayıcıya bazı kancalar almak için genellikle kritik bölüm ilkellerine ihtiyacınız vardır.

Ayrıca, ARM için bu tür ilkellere sahip bir açık kaynak kütüphanesi (veya iyi bir hafif spinlock / semafor kütüphanesi) var mı?

Bunun taşınabilir bir şekilde yazılması zordur. Yani, bu tür kütüphaneler ARM CPU'ların belirli sürümleri ve belirli işletim sistemleri için mevcut olabilir.


2

Bu kritik bölümlerle ilgili birkaç potansiyel sorun görüyorum. Bunların hepsine uyarılar ve çözümler var, ancak bir özet olarak:

  • Optimizasyon veya rastgele başka nedenlerle derleyicinin bu makrolar arasında kod taşımasını engelleyen hiçbir şey yoktur.
  • İşlemcinin bazı bölümlerini kaydeder ve geri yüklerler, derleyici satır içi düzeneğin yalnız bırakılmasını bekler (aksi belirtilmedikçe).
  • Sekansın ortasında bir kesmenin meydana gelmesini engelleyen ve okunduğu zaman ve yazıldığı zaman arasındaki durumu değiştiren hiçbir şey yoktur.

Öncelikle, derleyici bellek engellerine kesinlikle ihtiyacınız var . GCC bunları clobbers olarak uygular . Temel olarak, bu derleyiciye "Hayır, bellek erişimlerini bu satır içi montaj parçası boyunca taşıyamazsınız çünkü bellek erişimlerinin sonucunu etkileyebilir." Özellikle, hem başlangıç ​​hem de bitiş makrolarında hem "memory"ve hem de "cc"clobber'lara ihtiyacınız vardır . Bunlar, diğer işlevlerin (işlev çağrıları gibi) satır içi derlemeye göre yeniden sıralanmasını da önler, çünkü derleyici bellek erişimine sahip olabileceğini bilir. "memory"Clobbers ile satır içi montaj genelinde koşul kodu kayıtları ARM tutma durumu için GCC gördüm , bu yüzden kesinlikle clobber gerekir "cc".

İkincisi, bu kritik bölümler, kesintilerin etkin olup olmadığından çok daha fazlasını kaydeder ve geri yükler. Özellikle, CPSR'nin (Mevcut Program Durum Kaydı) çoğunu kaydediyor ve geri yüklüyorlar ( A9 için güzel bir diyagram bulamadığım için bağlantı Cortex-R4 içindir, ancak aynı olmalıdır). Hangi devlet parçalarının gerçekten değiştirilebileceği konusunda ince kısıtlamalar vardır , ancak burada gereğinden fazladır.

Diğer şeylerin yanı sıra, bu durum kodlarını içerir (gibi talimatların sonuçları cmpsaklanır, böylece sonraki koşullu talimatlar sonuca etki edebilir). Derleyici kesinlikle bununla karıştırılacaktır. Bu, "cc"yukarıda belirtildiği gibi klozet kullanılarak kolayca çözülebilir . Ancak, bu her seferinde kodun başarısız olmasını sağlayacaktır, bu yüzden problemlerle karşılaştığınız gibi görünmüyor. Yine de bir saatli bomba, rastgele diğer kodu değiştirmek derleyicinin biraz farklı bir şey yapmasına neden olabilir, bu da kırılacak.

Bu aynı zamanda Thumb koşullu yürütmeyi uygulamak için kullanılan BT bitlerini kaydetmeye / geri yüklemeye çalışacaktır . Başparmak kodunu asla yürütmezseniz bunun önemli olmadığını unutmayın. GCC'nin satır içi derlemesinin, BT bitleriyle nasıl başa çıktığını, hiç bitmediği, nasıl çözdüğünü hiç anlamadım, yani derleyici hiçbir zaman bir BT bloğuna satır içi derleme yerleştirmemeli ve her zaman derlemenin bir BT bloğunun dışında bitmesini beklemelidir. GCC'nin bu varsayımları ihlal eden kod oluşturduğunu hiç görmedim ve ağır optimizasyonla oldukça karmaşık bir satır içi montaj yaptım, bu yüzden makul bir şekilde tuttuklarından eminim. Bu, muhtemelen BT bitlerini değiştirmeye çalışmadığı anlamına gelir, bu durumda her şey yolundadır. Bu bitleri değiştirmeye çalışmak "mimari olarak öngörülemez" olarak sınıflandırılır, bu yüzden her türlü kötü şeyi yapabilir, ama muhtemelen hiçbir şey yapmaz.

Kaydedilecek / geri yüklenecek son bit kategorisi (gerçekten kesintileri devre dışı bırakacak olanların yanı sıra) mod bitleridir. Bunlar muhtemelen değişmeyecektir, bu yüzden muhtemelen önemli değildir, ancak kasıtlı olarak modları değiştiren herhangi bir kodunuz varsa, bu kesme bölümleri sorunlara neden olabilir. Bunu yapmayı bekleyebileceğim tek ayrıcalık ve kullanıcı modu arasında geçiş yapmak.

Üçüncü olarak, aralarında CPSR diğer kısımlarını değiştirmesini bir kesme engelleyen bir şey yok MRSve MSRde ARM_INT_LOCK. Bu tür değişikliklerin üzerine yazılabilir. Çoğu makul sistemde, eşzamansız kesmeler, kesildikleri kodun durumunu değiştirmez (CPSR dahil). Eğer yaparlarsa, kodun ne yapacağını anlamak çok zorlaşır. Ancak, mümkündür (FIQ devre dışı bitini değiştirmek benim için büyük olasılıktır), bu nedenle sisteminizin bunu yapıp yapmadığını göz önünde bulundurmalısınız.

Bunları, işaret ettiğim tüm potansiyel sorunlara hitap edecek şekilde nasıl uygulayacağım:

#define ARM_INT_KEY_TYPE            unsigned int
#define ARM_INT_LOCK(key_)   \
asm volatile(\
    "mrs %[key], cpsr\n\t"\
    "ands %[key], %[key], #0xC0\n\t"\
    "cpsid if\n\t" : [key]"=r"(key_) :: "memory", "cc" );
#define ARM_INT_UNLOCK(key_) asm volatile (\
    "tst %[key], #0x40\n\t"\
    "beq 0f\n\t"\
    "cpsie f\n\t"\
    "0: tst %[key], #0x80\n\t"\
    "beq 1f\n\t"\
    "cpsie i\n\t"
    "1:\n\t" :: [key]"r" (key_) : "memory", "cc")

İle derlemeye emin olun -mcpu=cortex-a9desteklemeyen eski bir ARM CPU çünkü en azından bazı GCC sürümleri (benim gibi) varsayılan cpsieve cpsid.

Kullandığım andsyerine sadece andiçinde ARM_INT_LOCKbu Başparmak kodunda kullanıldığı takdirde 16 bit talimat yüzden. "cc"Bu kesinlikle bir performans / kod boyutu fayda yüzden clobber neyse gereklidir.

0ve 1olduğu yerel etiketler referans için.

Bunlar, sürümlerinizle aynı şekilde kullanılabilir olmalıdır. Orijinali ARM_INT_LOCKkadar hızlı / küçük. Ne yazık ki, ARM_INT_UNLOCKbirkaç talimatın yakınında herhangi bir yerde güvenle yapmanın bir yolunu bulamadım .

Sisteminizde IRQ'ların ve FIQ'ların devre dışı bırakılmasıyla ilgili kısıtlamalar varsa, bu basitleştirilebilir. Örneğin, her zaman birlikte devre dışı bırakılmışlarsa, aşağıdakine benzer bir cbz+ olarak birleştirebilirsiniz cpsie if:

#define ARM_INT_UNLOCK(key_) asm volatile (\
    "cbz %[key], 0f\n\t"\
    "cpsie if\n\t"\
    "0:\n\t" :: [key]"r" (key_) : "memory", "cc")

Alternatif olarak, FIQ'ları hiç umursamıyorsanız, onları tamamen etkinleştirmeyi / devre dışı bırakmayı bırakmaya benzer.

Başka hiçbir şeyin biliyorsanız hiç o zaman da kullanım hem haricinde orijinal koduna çok benzer bir şey ile devam edebilir, kilit ve kilidini arasındaki CPSR diğer devlet bit herhangi değiştirir "memory"ve "cc"clobbers hem de ARM_INT_LOCKveARM_INT_UNLOCK


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.