Widzę kilka potencjalnych problemów z tymi krytycznymi sekcjami. Istnieją pewne zastrzeżenia i rozwiązania dla nich wszystkich, ale jako podsumowanie:
- Nic nie stoi na przeszkodzie, aby kompilator przenosił kod między tymi makrami w celu optymalizacji lub z innych przypadkowych powodów.
- Zapisują i przywracają niektóre części stanu procesora, którego kompilator oczekuje, że zestaw wbudowany pozostawi w spokoju (chyba że powiedziano inaczej).
- Nic nie stoi na przeszkodzie, aby przerwa pojawiła się w środku sekwencji i zmieniła stan między momentem odczytu a zapisaniem.
Po pierwsze, zdecydowanie potrzebujesz barier pamięci kompilatora . GCC implementuje je jako clobbers . Zasadniczo jest to sposób powiedzenia kompilatorowi: „Nie, nie można przenosić dostępu do pamięci przez ten element zestawu wbudowanego, ponieważ może to wpłynąć na wynik dostępu do pamięci”. W szczególności potrzebujesz zarówno "memory"
i "cc"
clobbers, zarówno na początku, jak i na końcu makra. Zapobiegną one zmianie kolejności innych elementów (np. Wywołań funkcji) w stosunku do zestawu wbudowanego, ponieważ kompilator wie, że mogą mieć dostęp do pamięci. Widziałem stan wstrzymania GCC dla ARM w rejestrach kodów stanu w zespole wbudowanym z "memory"
clobberami, więc na pewno potrzebujesz "cc"
clobbera.
Po drugie, te krytyczne sekcje oszczędzają i przywracają znacznie więcej niż tylko to, czy przerwania są włączone. W szczególności zapisują i przywracają większość CPSR (aktualny rejestr statusu programu) (link dotyczy Cortex-R4, ponieważ nie mogłem znaleźć ładnego schematu dla A9, ale powinien być identyczny). Istnieją subtelne ograniczenia, wokół których elementy stanu mogą być faktycznie modyfikowane, ale tutaj jest to więcej niż to konieczne.
Obejmuje to między innymi kody warunków (gdzie wyniki takich instrukcji cmp
są przechowywane, aby kolejne instrukcje warunkowe mogły działać na wynik). Kompilator na pewno się tym pomyli. Można to łatwo rozwiązać za pomocą "cc"
Clobbera, jak wspomniano powyżej. Spowoduje to jednak, że kod zawiedzie za każdym razem, więc nie brzmi jak z problemami. Jednak nieco tykająca bomba zegarowa, modyfikując losowy inny kod, może spowodować, że kompilator zrobi coś nieco innego, co zostanie przez to zepsute.
Spowoduje to również próbę zapisania / przywrócenia bitów IT, które są używane do implementacji warunkowego wykonywania Thumb . Pamiętaj, że jeśli nigdy nie wykonasz kodu Thumb, nie ma to znaczenia. Nigdy nie zorientowałem się, w jaki sposób wbudowany zestaw GCC radzi sobie z bitami IT, poza stwierdzeniem, że tak nie jest, co oznacza, że kompilator nigdy nie może umieszczać wbudowanego zestawu w bloku IT i zawsze oczekuje, że zestaw skończy się poza blokiem IT. Nigdy nie widziałem, aby GCC generowało kod naruszający te założenia, i zrobiłem dość skomplikowane wstawianie z dużą optymalizacją, więc jestem pewien, że się utrzymują. Oznacza to, że prawdopodobnie nie będzie próbował zmienić bitów IT, w którym to przypadku wszystko jest w porządku. Próba modyfikacji tych bitów jest klasyfikowana jako „nieprzewidywalna architektonicznie”, więc może robić wszelkiego rodzaju złe rzeczy, ale prawdopodobnie nic nie zrobi.
Ostatnią kategorią bitów, które zostaną zapisane / przywrócone (oprócz tych, które faktycznie wyłączają przerwania) są bity trybu. Te prawdopodobnie się nie zmienią, więc prawdopodobnie nie będzie to miało znaczenia, ale jeśli masz jakiś kod, który celowo zmienia tryby, te sekcje przerwań mogą powodować problemy. Zmiana pomiędzy trybem uprzywilejowanym a trybem użytkownika jest jedynym przypadkiem zrobienia tego, czego się spodziewałam.
Po trzecie, nie ma nic zapobiegania przerwanie zmianę innych części CPSR pomiędzy MRS
i MSR
w ARM_INT_LOCK
. Wszelkie takie zmiany mogą zostać zastąpione. W większości rozsądnych systemów asynchroniczne przerwania nie zmieniają stanu kodu, w którym są przerywane (w tym CPSR). Jeśli to zrobią, bardzo trudno będzie zrozumieć, co zrobi kod. Jest to jednak możliwe (zmiana bitu wyłączania FIQ wydaje mi się najbardziej prawdopodobna), więc powinieneś rozważyć, czy twój system to robi.
Oto, w jaki sposób wdrożyłbym je w sposób uwzględniający wszystkie potencjalne problemy, które wskazałem:
#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")
Upewnij się, że kompilujesz, -mcpu=cortex-a9
ponieważ przynajmniej niektóre wersje GCC (takie jak moja) są domyślnie starsze na procesor ARM, który nie obsługuje cpsie
i cpsid
.
Użyłem ands
zamiast tylko and
w, ARM_INT_LOCK
więc jest to 16-bitowa instrukcja, jeśli jest używana w kodzie Thumb. "cc"
Clobber jest konieczne tak czy inaczej, więc jest to ściśle korzyść rozmiar wydajność / code.
0
i 1
są to lokalne etykiety , w celach informacyjnych.
Powinny być one użyteczne na wszystkie te same sposoby, co twoje wersje. ARM_INT_LOCK
Jest tak samo szybka / small jako oryginalne. Niestety nie udało mi się wymyślić sposobu na ARM_INT_UNLOCK
bezpieczne wykonanie zadania w pobliżu tak niewielu instrukcji.
Jeśli twój system ma ograniczenia, kiedy IRQ i FIQ są wyłączone, można to uprościć. Na przykład, jeśli zawsze są one razem wyłączone, możesz połączyć w jeden cbz
+ w cpsie if
następujący sposób:
#define ARM_INT_UNLOCK(key_) asm volatile (\
"cbz %[key], 0f\n\t"\
"cpsie if\n\t"\
"0:\n\t" :: [key]"r" (key_) : "memory", "cc")
Alternatywnie, jeśli w ogóle nie przejmujesz się FIQ, jest to podobne do porzucenia włączania / wyłączania ich całkowicie.
Jeśli wiesz, że nic innego nigdy nie zmienia żadnych innych bitów stanu w CPSR między blokadą a odblokowaniem, możesz również użyć kontynuacji z czymś bardzo podobnym do twojego oryginalnego kodu, z wyjątkiem zarówno "memory"
i "cc"
bloków w obu ARM_INT_LOCK
iARM_INT_UNLOCK