Przenoszę trochę starszego kodu z rdzenia ARM926 na CortexA9. Ten kod jest niemetalowy i nie zawiera systemu operacyjnego ani standardowych bibliotek, wszystkie niestandardowe. Mam awarię, która wydaje się być związana ze stanem wyścigu, któremu należy zapobiegać przez krytyczne dzielenie kodu.
Chcę uzyskać informacje zwrotne na temat mojego podejścia, aby sprawdzić, czy moje krytyczne sekcje mogą nie zostać poprawnie zaimplementowane dla tego procesora. Korzystam z GCC. Podejrzewam, że jest jakiś subtelny błąd.
Czy istnieje również biblioteka typu open source, która zawiera te typy prymitywów dla ARM (a nawet dobrą lekką bibliotekę spinlock / semephore)?
#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 jest używany w następujący sposób:
/* lock interrupts */
ARM_INT_KEY_TYPE key;
ARM_INT_LOCK(key);
<access registers, shared globals, etc...>
ARM_INT_UNLOCK(key);
Ideą „klucza” jest umożliwienie zagnieżdżenia sekcji krytycznych, które są używane na początku i na końcu funkcji w celu utworzenia funkcji ponownego wydania.
Dzięki!
źródło
ldrex
istrex
zrobić to poprawnie. Oto strona internetowa pokazująca, jak używaćldrex
istrex
wdrażać blokadę.Odpowiedzi:
Najtrudniejszą częścią obsługi sekcji krytycznej bez systemu operacyjnego nie jest tworzenie muteksu, ale raczej zastanawianie się, co powinno się stać, jeśli kod chce użyć zasobu, który nie jest obecnie dostępny. Instrukcje wykluczające ładowanie i instrukcje warunkowe wyłączające przechowywanie sprawiają, że dość łatwo jest utworzyć funkcję „zamiany”, która podając wskaźnik na liczbę całkowitą, atomowo zapisze nową wartość, ale zwróci to, co zawierała wskazana liczba całkowita:
Biorąc pod uwagę powyższą funkcję, można łatwo wprowadzić muteks za pomocą czegoś takiego
W przypadku braku systemu operacyjnego główna trudność często polega na kodzie „nie można uzyskać muteksu”. Jeśli wystąpi przerwanie, gdy zasób chroniony przez muteks jest zajęty, może być konieczne ustawienie flagi obsługującej przerwanie i zapisanie niektórych informacji wskazujących, co chce zrobić, a następnie dowolnego kodu głównego, który pobierze mutex sprawdza, ilekroć ma zamiar zwolnić muteks, aby zobaczyć, czy przerwanie chce coś zrobić, gdy muteks był trzymany, a jeśli tak, wykonaj akcję w imieniu przerwania.
Chociaż możliwe jest uniknięcie problemów z przerwaniami, które chcą korzystać z zasobów chronionych przez mutex, po prostu wyłączając przerwania (i faktycznie, wyłączenie przerwania może wyeliminować potrzebę jakiegokolwiek innego rodzaju muteksu), ogólnie pożądane jest unikanie wyłączania przerwania dłużej niż to konieczne.
Przydatnym kompromisem może być użycie flagi, jak opisano powyżej, ale mieć kod linii głównej, który ma zamiar zwolnić przerwania wyłączania muteksu i sprawdzić wyżej wspomnianą flagę tuż przed tym (ponownie włączyć przerwania po zwolnieniu muteksu). Takie podejście nie wymaga pozostawienia wyłączonych przerwań bardzo długo, ale chroni przed możliwością, że jeśli kod linii głównej przetestuje flagę przerwania po zwolnieniu muteksu, istnieje niebezpieczeństwo, że między czasem, w którym zobaczy flagę, a czasem działa na nie, może zostać zablokowany przez inny kod, który pobiera i zwalnia muteks oraz działa na flagę przerwania; jeśli kod linii głównej nie testuje flagi przerwania po zwolnieniu muteksu,
W każdym razie najważniejsze będzie posiadanie środków, dzięki którym kod, który spróbuje użyć zasobu chronionego przez muteks, gdy jest niedostępny, będzie mógł powtórzyć próbę po uwolnieniu zasobu.
źródło
Jest to ciężki sposób wykonywania krytycznych sekcji; wyłącz przerwania. Może nie działać, jeśli twój system ma / obsługuje błędy danych. Zwiększy również opóźnienie przerwań. Linux irqflags.h ma pewne makra, które zajmują się tego. The
cpsie
Icpsid
instrukcje może użyteczne; Nie zapisują jednak stanu i nie pozwalają na zagnieżdżanie.cps
nie używa rejestru.Dla Cortex-A szeregowo,
ldrex/strex
są bardziej wydajne i mogą pracować w celu utworzenia muteksu do części krytycznej lub mogą być stosowane lock-wolny algorytmów pozbyć sekcji krytycznej.W pewnym sensie
ldrex/strex
wydają się ARMv5swp
. Są jednak o wiele bardziej skomplikowane do wdrożenia w praktyce. Potrzebujesz działającej pamięci podręcznej i docelowej pamięcildrex/strex
potrzebnej w pamięci podręcznej. Dokumentacja ARM na tematldrex/strex
jest raczej mglista, ponieważ chcą mechanizmów działających na procesorach innych niż Cortex-A. Jednak w przypadku Cortex-A mechanizm utrzymywania lokalnej pamięci podręcznej procesora w synchronizacji z innymi procesorami jest taki sam, jak w przypadku implementacjildrex/strex
instrukcji. W przypadku serii Cortex-A rezerwa graniczna (wielkośćldrex/strex
zarezerwowanej pamięci) jest taka sama jak linia pamięci podręcznej; musisz również wyrównać pamięć do linii pamięci podręcznej, jeśli zamierzasz zmodyfikować wiele wartości, na przykład z podwójnie połączoną listą.Musisz upewnić się, że sekwencji nigdy nie można uprzedzić . W przeciwnym razie możesz uzyskać dwie kluczowe zmienne z włączonymi przerwaniami, a zwolnienie blokady będzie nieprawidłowe. Możesz użyć
swp
instrukcji z kluczem pamięcią aby zapewnić spójność ARMv5, ale ta instrukcja jest przestarzała na Cortex-A na korzyść,ldrex/strex
ponieważ działa lepiej w systemach wieloprocesorowych.Wszystko to zależy od tego, jaki rodzaj planowania ma Twój system. Wygląda na to, że masz tylko linie główne i zakłócenia. Często potrzebujesz sekcji krytycznej podstawowe aby mieć pewne zaczepienia w harmonogramie w zależności od poziomów (system / przestrzeń użytkownika / etc), z którymi ma pracować sekcja krytyczna.
Trudno to pisać w przenośny sposób. Tzn. Takie biblioteki mogą istnieć dla niektórych wersji procesorów ARM i dla określonych systemów operacyjnych.
źródło
Widzę kilka potencjalnych problemów z tymi krytycznymi sekcjami. Istnieją pewne zastrzeżenia i rozwiązania dla nich wszystkich, ale jako podsumowanie:
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
iMSR
wARM_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:
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ługujecpsie
icpsid
.Użyłem
ands
zamiast tylkoand
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
i1
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 naARM_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
+ wcpsie if
następujący sposób: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 obuARM_INT_LOCK
iARM_INT_UNLOCK
źródło
w przypadku stosunkowo prostych sekcji krytycznych można użyć instrukcji LDREX i STREX.
/programming/51795537/critical-sections-in-arm http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0204f/Cihbghef.html
źródło