Cortex M Unaligned Access Meselesi

Başlatan Tagli, 24 Haziran 2019, 15:52:35

Tagli

Bu unaligned access (hizasız erişim) meselesi arada sırada karşıma çıkıyor. Bugün yine karşılaştım ve birkaç deneyimimi aktarmamın benzer sorunlarla karşılaşabilecek arkadaşlar için faydalı olabileceğini düşündüm. Yazacaklarımın bir kısmı kendi varsayımlarımı içeriyor ve yanlış olabilirler. Üstadlar bunları düzeltirse sevinirim.

Öncelikle bir ön bilgi: Cortex M0 hizasız erişimi desteklemiyor ve hard fault'a düşüyor. Ancak Cortex M4 (ve sanırım M3) hizasız erişime olanak veriyor. Anladığım kadarıyla bunu bus üzerinde 2 ayrı erişime dönüştürerek yapıyorlar, bu da muhtemelen ufak bir performans kaybına neden oluyor. Dilerseniz Cortex M4'te "Ben hizasız erişimlerin hard fault'a düşmesini istiyorum" diyebiliyorsunuz. Bunun için aşağıdaki kod yeterli olacaktır:
SCB->CCR |= SCB_CCR_UNALIGN_TRP_Msk;
Peki bunu neden isteyelim? Bir sebebi hizasız erişimin getirebileceği (az da olsa) performans kaybı olabilir. Ama benim için asıl sebep şu: Yazdığım kodları M0 ile M3 & M4 işlemciler arasında taşıyorum bazen. M4'te çalışan kodumun M0'a geçince bozulmasını istemem. Bu yüzden bu durumun baştan ortaya çıkması için M4'te de hizasız erişimi kapatıyorum.

Hizasız erişim sorunuyla ilk kez, yazdığım bir Modbus kodunda şuna benzer bir hata yapınca karşılaşmıştım. Burada bir tane 16 bit, bir tane de 32 bit değişkeni 3 Modbus register'ına oturtmaya çalışıyoruz.
uint16_t holdingRegs[3];
uint16_t *reg1 = (uint16_t*)&(holdingRegs[0]);
uint32_t *reg2 = (uint32_t*)&(holdingRegs[1]);
*reg2 = 1; // !!! Burada hard fault'a düşüyoruz !!!
Bu sorunun sebebi pointerlar ile maymunluk yapmamız. Derleyicinin bizim yaptığımız her üçkağıdı yakalamasını beklemek yanlış olur. Zaten pointer casting aslında bir çeşit "Sen benim işime karışma!" demektir derleyiciye.

Şimdi, emin değilim ama öyle sanıyorum ki bu hizasız erişimi bir struct üyesine yapsak, derleyici durumu fark edecek ve ona göre daha uzun bir kod üretip hard fault'tan kaçacaktı. Bunu deneyip denemediğimi unuttum, ama okuduklarımdan anladığım bu. Örnek:
struct TestData {
    uint16_t reg1;
    uint32_t reg2;
} __attribute__((packed));
struct TestData td;
td.reg2 = 1; // Bunun sorun çıkarmaması lazım
Elbette packed kısmına dikkat. Bunu yazmasak derleyici zaten struct üyelerini 4'e bölünebilen adreslere hizalayacak ve sorun çıkmayacaktı. Ancak bazı durumlarda, mesela seri porttan gelen veriler ile bir struct doldurmak istediğimizde, aradaki hizalama boşlukları işimizi zorlaştırabiliyor ve o yüzden struct'ı packed yapmak işimize geliyor. Ama dediğim gibi, pointer üçkağıdı yapmadığımız için derleyici olan bitenin farkında, o yüzden hard fault'tan kaçmak için gerekenleri yapıyor.

Bugün yaşadığım sorun, Cortex M4'te derleyicinin (gcc) yukarıda bahsettiğim "gereken"i yapmaması idi. Halbuki erişimin hard fault'a sebep olabileceğini görmeli idi.

Vardığım (ve üstadlardan doğrulama istediğim) sonuç şu: Derleyici M4'ün hizasız erişimi desteklediğini varsayıyor. Benim bunu özel bir kod ile kapattığımdan haberi yok. Haliyle, hizasız erişim için bir önlem almıyor. Bu sorunu aşağıdaki derleme bayrağını ekleyerek çözdüm (bu gcc için tabi):
-mno-unaligned-access
Tahminimce, M0 için kod yazıyor olsam buna gerek kalmayacaktı, çünkü derleyici M0'ın hizasız erişimi zaten hiçbir koşulda desteklemediğini biliyor.
Gökçe Tağlıoğlu

OptimusPrime

Az degil ciddi bir performas kaybi oluyor cunki vatandas bir kere fetch edecegine birden fazla fetch etmesi gerekiyor ki buna 2 kere de isini goruyor dersek performans yariya iniyor.  ::)

-mno-unaligned-access sanirim dogru bir cozum degil. Bu erisilen yer hizalida olsa hizasizda olsa bunu hizasiz kabul et ona gore islem yap demek oluyor  :du:

Bunun yerine -munaligned-access deneyebilirsin.  ::ok ki buda aslinda default olmasi lazimdi. Sen derleyici ayarlarini bi kontrol et. Bir mikrodan diger mikroya gecerken sifirdan proje olusturmuyorsan bazi parametleri sabit tutup diger projeye uygun olmasada tasiyabilir.  :-\
https://donanimveyazilim.wordpress.com || Cihân-ârâ cihân içredir ârâyı bilmezler, O mâhîler ki deryâ içredir deryâyı bilmezler ||

Tagli

#2
Bence performans meselesi, hizasız veriye erişim sıklığına bağlı. Pek sık erişilmeyen bir veri için erişim hızının yarıya inmesi sorun olmayacaktır. Öte yandan, mesela bir döngü içinde arka arkaya 1000 kez hizasız erişim yapmak sorun olacaktır.

Şimdi, sorunun kaynağına geri dönelim. Pointer cambazlığı yapmıyorsak eğer, hizasız erişim durumu ile packed struct'larda karşılaşıyoruz genelde. Ve derleyici olan bitenin farkında. Yani içerideki her değişkenin hizalı olup olmadığını, bunlara nasıl erişirse hizasız erişim durumu oluşacağını (veya oluşmayacağını) biliyor. Biz hizasız erişime sebep olacak bir kod yazdığımızda derleyicinin iki seçeneği var:
1) Öyle bir kod üret ki hizasız erişim olmasın. Örneğin, hizasız bir uint32_t'ye 4 defada uint8_t gibi eriş, sonra bunları birleştir. Bunun erişim süresini en az 4 katına çıkaracağını düşünebiliriz sanırım.
2) Hiç bir şey yapma. İşlemci ne yaparsa yapsın, isterse hard fault'a düşsün, bu onun sorunu...

Anladığım kadarıyla M4 için derlerken varsayılan seçenek 2 numara. -munaligned-access bu anlama geliyor. Eğer donanım destekliyorsa bu aslında daha mantıklı. Çünkü donanımın bu işi halletmesi derleyiciye göre daha performanslı oluyor. Ne bileyim, belki hizasız veriye 4 değil 2 misli sürede erişiyor. M0 gibi donanımlarda zaten böyle bir imkan yok.

-mno-unaligned-access ise derleyiciyi 1 nolu seçeneği seçmeye zorluyor. M0 gibi donanımlarda bu varsayılan tercih ve anladığım kadarıyla zaten değiştirilemiyor da.

Aslında bir açıdan bakıldığında, M4'e kod yazarken packed struct'ların hizasız üyelerine erişim için 1 nolu seçeneği seçmek mantıksız gerçekten de. Çünkü 2. seçenek daha hızlı çalışacak. Ama yukarıda da dediğim gibi, hizasız erişim sadece packed struct'larda olmuyor. Ve bu olduğunda gözümden kaçsın istemiyorum, bu yüzden de işlemcinin hizasız erişim desteğini kasıtlı olarak devre dışı bırakıyorum.

Muhtemelen en iyi yöntem, kodu geliştirme aşamasında yukarıda belirttiğim gibi 1'i seçip, işler rayına oturduktan sonra bu görevi derleyiciden alıp işlemciye vermek, yani 2 nolu yönteme geçmek.

Ekleme:
Alıntı yapılan: OptimusPrime - 24 Haziran 2019, 19:15:09Bu erisilen yer hizalida olsa hizasizda olsa bunu hizasiz kabul et ona gore islem yap
GCC dokümanından anladığım kadarıyla, bunu sadece packed struct'larda yapıyor.
Gökçe Tağlıoğlu

OptimusPrime

https://donanimveyazilim.wordpress.com || Cihân-ârâ cihân içredir ârâyı bilmezler, O mâhîler ki deryâ içredir deryâyı bilmezler ||

Tagli

Bu sayfaya denk gelmiştim. GCC dokümanları ile aşağı yukarı aynı şeyi söylüyor.
Gökçe Tağlıoğlu

OptimusPrime

Birden __packed int32_t ile int32_t yi ayni kefeye koyunca -mno-unaligned-access anlatilandan farkli cikiyor. Dogru haklisin sadece __packed icin gecerli bu.  ;)
https://donanimveyazilim.wordpress.com || Cihân-ârâ cihân içredir ârâyı bilmezler, O mâhîler ki deryâ içredir deryâyı bilmezler ||