Debugowanie procesorów wielordzeniowych

| Technika

W procesorach wielordzeniowych przetwarzanie kodu programu rozdzielone jest pomiędzy kilka rdzeni. Jest to przyczyną komplikacji, które mogą wystąpić na etapie projektowania, programowania oraz debugowania urządzeń z procesorami tego typu. Aby uniknąć ewentualnych problemów, należy dobrze poznać specyfikę takich układów. W artykule przedstawiamy ich krótką charakterystykę oraz wyjaśniamy, co na etapie wyszukiwania błędów może sprawić najwięcej trudności.

Debugowanie procesorów wielordzeniowych

Wydajność przetwarzania procesora można poprawić, zwiększając częstotliwość jego zegara. Takie podejście, stosowane w elektronice od lat, z czasem zaczęło jednak przysparzać coraz więcej problemów na etapie produkcji układów elektronicznych. Największym z nich był wzrost poboru mocy, proporcjonalny do zmiany częstotliwości oraz związane z tym zwiększone wydzielanie ciepła.

Tymczasem wraz z rozpowszechnieniem się urządzeń przenośnych uzyskanie długiego czasu pracy na zasilaniu bateryjnym stało się dla producentów równie ważne, jak zapewnienie dużej mocy obliczeniowej. By jednocześnie uzyskać oba te parametry, zaczęto wykorzystywać procesory wielordzeniowe.

Ograniczanie poboru mocy jest w ich przypadku realizowane na kilka sposobów. Można m.in.: poszczególne segmenty układu zasilać różnymi napięciami, wyłączać te aktualnie nieużywane lub zmieniać częstotliwość pracy w zależności od aktualnego obciążenia procesora.

Procesory wielordzeniowe

Rys. 1. Każdy z rdzeni w procesorze wielordzeniowym może być obsługiwany przez jeden (SMP) lub wiele (AMP) systemów operacyjnych

Procesory wielordzeniowe składają się z kilku rdzeni, które mogą równocześnie odczytywać oraz wykonywać instrukcje programu. Zwykle każdy z nich ma jeden lub dwa lokalne cache, z których może korzystać tylko dany rdzeń. Cache wyższego poziomu mogą być również używane przez wszystkie rdzenie. Razem z główną pamięcią RAM cache stanowi pamięć globalną (wspólną).

Dane między rdzeniami mogą być wymieniane poprzez zapis oraz odczyt wspólnych zmiennych w tej pamięci. Jest to łatwy sposób komunikacji, jednak by zapewnić niezawodną wymianę danych, wymagana jest odpowiednia przepustowość sieci połączeń między rdzeniami. Dlatego zwykle pamięć globalna jest wykorzystywana w systemach tylko z kilkoma rdzeniami.

Układy homo- i heterogeniczne

Procesory wielordzeniowe budowane są jako układy homogeniczne lub heterogeniczne. W pierwszym przypadku składają się z wielu identycznych rdzeni, natomiast w drugim z wielu różnych, specjalizowanych rdzeni. Obie architektury mają wady i zalety. Przykładowo programowanie, a później również debugowanie układów homogenicznych jest łatwiejsze ze względu na ich jednorodną strukturę.

Układy heterogeniczne z kolei lepiej sprawdzają się w aplikacjach wymagających wyspecjalizowanej funkcjonalności (przykładowo wydajnego przetwarzania określonego typu danych - dźwięku, głosu, wideo itp.). Z drugiej strony ich programowanie, a później debugowanie może być trudniejsze, zwłaszcza jeśli poszczególne rdzenie bardzo się różnią (na przykład zestawem instrukcji).

Przetwarzanie równoległe

Systemy wielordzeniowe wymagają programów, w których zaimplementowane jest przetwarzanie równoległe. Pierwszym etapem projektowania takiej aplikacji jest jej podział na różne zadania, które mogą być realizowane równocześnie. Zadania te są przypisywane procesom lub wątkom, które z kolei przydziela się fizycznym jednostkom procesora.

Za obsługę aplikacji z przetwarzaniem równoległym odpowiada system operacyjny. Może to być jeden system operacyjny w przypadku wszystkich rdzeni procesora (symmetric multiprocessing, SMP) lub różne OS dla każdego z nich (asymmetric multiprocessing, AMP) (rys. 1).

To drugie rozwiązanie jest programowym odpowiednikiem sprzętowych platform wielordzeniowych heterogenicznych, w których zresztą jest ono najczęściej wdrażane. Przetwarzanie typu SMP można zaimplementować tylko w procesorach wielordzeniowych homogenicznych.

Debugowanie systemów wielordzeniowych

Rozwiązania zastosowane w procesorach wielordzeniowych, dzięki którym pod względem wydajności przetwarzania przewyższają one układy jednordzeniowe, są równocześnie przyczyną różnych problemów na etapie ich debugowania. Przede wszystkim ponieważ w obrębie jednego urządzania zintegrowanych jest wiele rdzeni, bezpośredni dostęp do nich (jak również do sieci połączeń między nimi oraz do pamięci) za pośrednictwem zacisków układu nie zawsze jest zapewniony.

Tymczasem taka możliwość jest wymagana dla efektywnego debugowania, zwłaszcza procesorów heterogenicznych. Dodatkowym utrudnieniem w przypadku tych ostatnich mogą być także różne interfejsy diagnostyczne JTAG dla poszczególnych rdzeni. W debugowaniu aplikacji uruchamianych na procesorach wielordzeniowych nie sprawdzają się też standardowe metody, jak uzupełnianie kodu programu o instrukcje zwracające na przykład aktualny stan zmiennych.

Trudno bowiem zagwarantować właściwą kolejność zapisu tych danych z równocześnie kilku procesorów. Z tego powodu mogą być one niewiarygodne lub trudne w interpretacji, a w efekcie nieprzydatne. Nagromadzenie takich dodatkowych informacji może ponadto stać się źródłem różnych niepożądanych zdarzeń, na przykład wpływać na stan pamięci, rejestrów, cache itp.

Specyficzne błędy i ich naprawa

Rys. 2. W zależności od tego, który wątek zostanie wykonany wcześniej, zmienna x może mieć wartość 1, 2 lub 3 (początkowo wartość wszystkich zmiennych to 0)

W przypadku procesorów jednordzeniowych błędy mają zwykle charakter deterministyczny i powodowane są przez określony zestaw danych wejściowych. Takie błędy występują również w programach przetwarzanych równolegle w procesorach wielordzeniowych, ale oprócz nich w tym przypadku występują też nowe rodzaje błędów.

Mogą być one przykładowo skutkiem problemów w interakcji między zadaniami, procesami, wątkami, rdzeniami. Te z kolei są zazwyczaj spowodowane brakiem synchronizacji oraz komunikacji między nimi. Przykładem jest sytuacja, w której w określonych warunkach dwa rdzenie niezależnie modyfikują zmienną globalną wykorzystywaną w danej pętli (na przykład for), w taki sposób, że nigdy prawdziwy nie będzie warunek jej zakończenia.

Podobna sytuacja w przypadku współdzielenia zmiennych przez różne wątki została przedstawiona na rysunku 2. Znalezienie przyczyny, wywołanie oraz powtórzenie błędów tego typu w czasie testów jest trudne. Również rozwiązanie takich problemów nie jest proste.

Można przykładowo zaimplementować mechanizm, który będzie decydował o tym, kiedy jaka jednostka będzie mogła skorzystać ze współdzielonych zasobów. Należy przy tym uważać, by nie doprowadzić do sytuacji, w której dostęp do danego zasobu zostanie całkowicie zablokowany.

Zarządzanie breakpointami

Synchronizacja oraz śledzenie powiązań między zadaniami, wątkami, procesami oraz rdzeniami są zatem koniecznością także podczas debugowania systemów wielordzeniowych z przetwarzaniem równoległym. Gdy nie jest to zapewnione, wtedy m.in. efektywność tego etapu projektu z wykorzystaniem tzw. breakpointów będzie bardzo niska.

Przykładowo jeżeli w aplikacji wielowątkowej breakpoint zostanie ustawiony w kontekście wykonywania jednego wątku, istnieje ryzyko, że ten fragment kodu może zostać zrealizowany w innym wątku. Rozwiązaniem jest sprawdzanie identyfikatorów wątków. Jeśli identyfikator aktywnego wątku nie będzie zgodny z tym zadanym, program będzie dalej wykonywany.

Podobny problem występuje w procesorach wielordzeniowych. Jeżeli w punkcie kontrolnym wstrzymane zostanie wykonanie programu w jednym z rdzeni, pozostałe, które nie przerwą pracy, nadal będą przetwarzać dane. W rezultacie zrealizowanych zostanie wiele kolejnych instrukcji kodu, co znacząco zmniejszy dokładność oraz efektywność debugowania. By temu zapobiec, w systemach wielordzeniowych homogenicznych również wykorzystuje się identyfikatory, ale rdzeni. W momencie wstrzymania pracy jednego z nich dla pozostałych generowane są przerwania.

Podsumowanie

Procesory wielordzeniowe oraz przetwarzanie równoległe to wciąż stosunkowo nowe zagadnienia. Nie ma jednak wątpliwości, że w związku z rozwojem nowych technologii, zwłaszcza multimedialnych, układy tego typu ze względu na wydajność obliczeniową będą używane coraz powszechniej. Dlatego producenci elektroniki wciąż pracują nad narzędziami, które ułatwią konstruktorom używanie takich procesorów.

Przykładem są rozwiązania na poziomie układu w postaci bloków logicznych, których głównym zadaniem jest wsparcie na etapie debugowania oraz monitoring pracy procesora. Celem projektantów powinno być natomiast doskonalenie się w zakresie ich efektywnego wykorzystywania.

Monika Jaworowska