Ładowanie systemu operacyjnego w urządzeniach wbudowanych
| TechnikaProcesor umieszczony w systemie wbudowanym musi wykonać wiele czynności po włączeniu zasilania, aby użytkownik miał możliwość uruchomienia aplikacji. Cała procedura, choć może się wydawać prosta z teoretycznego punktu widzenia, staje się złożona, gdy trzeba spojrzeć na nią okiem projektantów odpowiedzialnych za kwestie sprzętowe oraz programowe. Poziom komplikacji wzrasta jeszcze bardziej, gdy pod uwagę brane są różnorodne konfiguracje sprzętowe, na jakich system operacyjny będzie uruchamiany z możliwością wyboru kilku opcji, jakie pozostawia się zazwyczaj użytkownikowi.
Resetowanie i konfiguracja systemów wbudowanych
W prostych procesorach i większości mikrokontrolerów reset powoduje przywrócenie zawsze tych samych ustawień, zdefiniowanych przez producenta lub częściowo przez projektantów, np. za pomocą specjalnie przewidzianych do tego celu bitów konfiguracyjnych. W efekcie, po zakończeniu zerowania układu częstotliwość taktowania, adresy początkowe, ustawienia portów I/O, konfiguracja zewnętrznych interfejsów pamięci czy stan poszczególnych układów peryferyjnych będą z góry określone i znane.
Wadą takiego podejścia jest konieczność każdorazowego konfigurowania CPU i zapisywania niekiedy sporej liczby rejestrów, aby układ był gotowy do pracy zgodnie z wymaganiami aplikacji, w jakiej został umieszczony. Załadowanie domyślnych wartości po resecie jest najbardziej typowym i najczęściej spotykanym scenariuszem, który nie wymaga żadnych dodatkowych zabiegów ze strony projektantów.
Bardziej złożone procesory dysponują nieporównywalnie większą liczbą rejestrów, których każdorazowe konfigurowanie nie zawsze jest akceptowalne i wygodne. Ponadto może zachodzić konieczność skonfigurowania procesora zanim rozpocznie się wykonanie właściwego programu. Sytuacja taka ma miejsce, gdy kod jest pobierany z interfejsów wymagających wstępnej konfiguracji (np. port szeregowy czy karta pamięci).
W związku z tym powstał szereg rozwiązań wspierających wstępną konfigurację procesora przed zakończeniem resetowania. Jednym z najprostszych i najbardziej intuicyjnych sposobów zapisania wstępnej konfiguracji jest sprawdzenie stanu określonych wejść i załadowanie wybranego w ten sposób zestawu wartości początkowych.
Wejścia te są wewnętrznie podciągane do plusa zasilania lub masy i ich domyślny stan można zmienić wymuszając zewnętrznie inny stan logiczny. Po włączeniu zasilania podawany jest sygnał resetu (Power on Reset), po zaniku którego następuje odczyt stanów wejść konfiguracyjnych i rozpoczyna się ładowanie określonego zestawu wartości początkowych do rejestrów.
Do momentu zakończenia fazy ładowania wartości początkowy aktywny jest wewnętrzny sygnał resetu, który wstrzymuje pracę procesora. Jest on zwalniany dopiero po załadowaniu wartości początkowych (rys. 2). Rozwiązania tego typu zapewniają pewną elastyczność niezbędną do wyboru np. interfejsu, z jakiego ma zostać pobrany program do załadowania.
Wadą podejścia opartego na wejściach konfiguracyjnych jest ograniczona elastyczność - załadowany może być tylko jeden, ściśle określony zestaw wartości początkowych z odgórnie dostępnej puli. Tym samym projektant jest zmuszony korzystać ze scenariuszy przygotowanych przez producenta i nie może np. zmieniać dowolnie prędkości portu szeregowego.
Niemniej wady te przekładają się na szybszą inicjację i skrócenie czasu oczekiwania na gotowość procesora do pracy. Procesory wyposażone w wejścia konfiguracyjne mogą współpracować z buforami trójstanowymi takimi jak 74LVC125 - rysunek 3. Wejście enable jest aktywowane sygnałem resetu, zadając ustalone stany na czas odczytu konfiguracji z wyprowadzeń.
Zapewniony w ten sposób wpływ na proces konfiguracji daje możliwość wykorzystania układów peryferyjnych współdzielących porty z wejściami konfiguracyjnymi. Użytkownik często ma dostęp do przełącznika typu DIP switch pozwalającego wymusić pożądane stany logiczne. Tym samym pozostawia się możliwość wpływania na stan procesora po resecie z funkcją np. wskazania skąd ma być załadowany system operacyjny.
Niektóre procesory zapewniają znacznie większą elastyczność na etapie konfiguracji, umożliwiając projektantom określenie wartości ładowanych do rejestrów po resecie. Do tego celu wykorzystywane są pamięci nieulotne lub specjalizowane bity bezpieczników (fuse). Po resecie odczytywane są z nich wartości i służą one do inicjacji procesora.
Warto mieć na uwadze, że programowanie bitów bezpieczników można niekiedy zrealizować tylko raz, zazwyczaj poprzez specjalnie przygotowaną do tego celu procedurę (programową bądź sprzętową). Programowalne bity bezpieczników, oprócz wprowadzenia pewnej elastyczności w proces konfiguracji procesora, pozwalają producentom niekiedy parametryzować sprzedawane podzespoły.
Przy zachowaniu odpowiednich zabezpieczeń układy danej rodziny mogą być wytwarzane w jednym procesie technologicznym, a o ich końcowych funkcjach zdecyduje właśnie odpowiednie zaprogramowanie tych bitów. Oznacza to, że tańszy procesor nie różni się od strony fizycznej niczym od droższego, a jedynie producent inaczej zaprogramował bity bezpieczników, zapewniając tym samym dostęp do większych zasobów.
Podejście tego typu nie budzi raczej dużego zdziwienia ze względu na wszechobecną presję na redukcję niepotrzebnych kosztów. Znacznie bardziej ekonomiczna jest produkcja jednego rodzaj układu i parametryzowanie go bitami bezpieczników, niż przygotowanie niezależnych procesów produkcyjnych i narzędzi testowych dla każdego egzemplarza danej rodziny procesorów.
Innym, dającym szerokie możliwości, sposobem konfiguracji procesora jest odczyt z zewnętrznej pamięci wartości początkowych dla poszczególnych rejestrów. Jest to szczególnie korzystne, gdy projektanci potrzebują możliwości indywidualnego dostosowania procesora do potrzeb aplikacji już na etapie resetowania, a liczba dostępnych wyprowadzeń konfiguracyjnych jest ograniczona i nie ma możliwości przeznaczenia kilkunastu wejść na ten cel.
W typowym przypadku wartości rejestrów są odczytywane z pamięci przez port SPI i są zapisywane do właściwych rejestrów. Dodatkową korzyścią jest uproszczenie płytki drukowanej, a pamięć szeregowa może przy okazji przechowywać dodatkowe rzeczy, takie jak kod bootloadera.
Elementy pierwszego i drugiego stopnia
Uruchamianie systemu operacyjnego jest nierozerwalnie związane z pamięciami oraz interfejsami, z których pobierany jest kod. Rozróżnia się tu urządzenia pierwszego (primary) oraz drugiego stopnia (secondary). Przynależność do jednej z tych dwóch grup jest determinowana przez możliwość podjęcia pracy natychmiast po zakończeniu fazy resetu i związanej z nią konfiguracji procesora.
Elementy pierwszego stopnia umożliwiają natychmiastowy odczyt programu bez konieczności inicjacji urządzenia. Przykład może stanowić pamięć ROM, niekiedy zintegrowana w jednej obudowie z procesorem, z której pobierane są instrukcje po zakończeniu fazy resetu. Pobrany stąd i wykonany program przygotowuje do pracy urządzenia drugiego stopnia takie jak pamięć RAM, w której znajdzie się obraz systemu operacyjnego załadowany przez bootloader.
Pamięć ulotna (RAM) jest pamięcią drugiego stopnia, bo po włączeniu zasilania nie zawiera ona żadnego programu - musi on zostać tam uprzednio wczytany. Dopiero po zakończeniu kopiowania następuje przekazanie kontroli i wykonywanie programu z pamięci operacyjnej. Wykonywanie programu znajdującego się w RAM pozwala zwiększyć szybkość wykonywania instrukcji oraz ograniczyć zużycie energii.
Jeszcze lepsze rezultaty osiąga się, gdy stan pamięci zostaje zachowany po utracie zasilania z głównego źródła i może korzystać z zasilania awaryjnego. Wznowienie pracy nie będzie wymagało ponownego kopiowania programu do pamięci, co dodatkowo skróci czas oczekiwania na powrót urządzenia do stanu gotowości.
Współcześnie komputery PC, laptopy, smartfony oraz bardziej złożone systemy wbudowane powszechnie wykorzystują pamięci DDR SDRAM. Stanowią one niezbędny element wszędzie tam, gdzie przetwarzana są duże ilości danych, szczególnie multimedialnych. Odczyt i wykonanie programu oraz dostęp do danych odbywa się ze znacznie większą prędkością niż w klasycznych systemach wyposażonych w pamięci Flash lub SRAM.
Typową architekturę urządzenia wykonującego program pobierany z pamięci operacyjnej przedstawiono na rysunku 4. W pierwszym kroku uruchamiany jest bootloader zlokalizowany w pamięci ROM odpowiedzialny za inicjację kontrolera pamięci i skopiowanie zawartości Flash do pamięci RAM. Po zakończeniu tego etapu następuje skok pod adres zlokalizowany w przestrzeni pamięci operacyjnej, skąd pobierane będą dalsze instrukcje procesora.
Bootloader może odpowiadać za konfigurację układów peryferyjnych, aby umożliwić załadowanie docelowego programu lub systemu operacyjnego za pośrednictwem jednego z wielu dostępnych interfejsów, takich jak interfejs kart pamięci SD, pamięci Flash, Ethernet czy USB. Niekiedy bootloader znajdujący się w pamięci ROM umożliwia przywrócenie skasowanego bądź uszkodzonego systemu operacyjnego.
Najszybszy start systemu operacyjnego zapewnia pobieranie obrazu systemu przez interfejs współpracujący z zewnętrznymi pamięciami takimi jak NOR Flash. Dużą szybkość zapewnia równoległa magistrala o szerokości nawet 32 bitów, pracująca z wysoką częstotliwością. Rozwiązanie tego typu jest szczególnie korzystne, gdy ładowany system operacyjny ma duży rozmiar - przepustowość magistrali przekłada się bezpośrednio na czas oczekiwania na zakończenie kopiowania danych do pamięci.
W typowym przypadku może się on zawierać w granicach od kilku do kilkudziesięciu sekund. Pamięci NAND Flash zyskują na popularności, jednakże ich szybkość odczytu nadal jest odczuwalnie niższa niż dla NOR Flash. W ostatnich lata zwiększyła się jednakże liczba układów pozwalających ładować system z pamięci NAND Flash i traktować je jako pamięci pierwszego stopnia.
Konieczny jest do tego sprzętowy kontroler, który może w typowym przypadku wymagać podłączenia ponad dwudziestu wyprowadzeń do pamięci, przez co nie zawsze jest najbardziej pożądanym rozwiązaniem. Warto zauważyć, że producenci wyposażają swoje procesory w coraz większą liczbę sprzętowych kontrolerów, co pozwala wybrać najbardziej optymalne rozwiązanie dla potrzeb pobierania kodu systemu operacyjnego (SDHC, SPI, I²C, SATA, PCIe, USB, pamięci z interfejsem szeregowym itd.).
Wykorzystanie zewnętrznych, szeregowych pamięci do przechowania systemu operacyjnego jest korzystne w układach bazujących na procesorach z ograniczoną liczbą wyprowadzeń, a wydłużony czas uruchomienia systemu nie stanowi problemu bądź system ma niewielki rozmiar.
Schemat bootowania jest w takim przypadku zbliżony i sprowadza się do przekopiowania kodu przez wybrany interfejs do pamięci RAM, skąd jest następnie wykonywany.