Wstęp
W odróżnieniu od komputerów ogólnego przeznaczenia (np. typu PC), urządzenia typu embedded charakteryzują się
ściśle wyznaczoną funkcją użytkową (np. router WiFi, sterownik frezarki numerycznej, itp.).
Takie systemy zintegrowane zawierają w sobie wbudowane oprogramowanie nazywane firmwarem.
Oprogramowanie to nie podlega częstym zmianom i zazwyczaj jest "zaszyte" w urządzeniu, na którym pracuje.
Często zdarza się, że użytkownik nie ma możliwości ingerencji w to oprogramowanie i może je używać jedynie
w taki sposób, w jaki jest to wymagane do korzystania z urządzenia.
Firmware jest umieszczony w pamięci urządzenia, która przechowuje jego treść po wyłączeniu. Przykładowo w zależności od konkretnego rozwiązania może to być:
- w pamięci FLASH wlutowanej na płytce,
- na karcie CompactFlash umieszczonej w urządzeniu,
- na twardym dysku, który jest częścią urządzenia.
Zarówno w procesie produkcyjnym, jak i podczas przesyłania uaktualnień (niektóre urzadzenia pozwalaja
na wgrywanie uaktualnień firmware'u), firmware jest dostępny w postaci pojedynczego pliku lub kilku plików.
W takim pliku (nazywanym często obrazem [ang. image]) znajdują się wszystkie elementy systemu, to jest:
- główny system plików (zawiera aplikacje oraz sterowniki niezbędne do pracy urządzenia),
- jądro systemu operacyjnego [ang. kernel],
- ustawienia konfiguracyjne (niekiedy ustawienia konfiguracyjne przechowuje się oddzielnie, aby ich nie utracić
podczas aktualizacji).
Z punktu widzenia programisty i konstruktora takiego systemu, niezbędne jest posiadanie metod i narzędzi
do budowania obrazu firmware'u na podstawie plików źródłowych. Oczywiście ręczne przygotowywanie
byłoby bardzo żmudne i czasochłonne. Na szczęście istnieją narzędzia przeznaczone do wykonywania tych czynności.
Ich wykorzystanie sprowadza się do przygotowania konfiguracji, a następnie do uruchomienia procesu automatycznego
budowania firmware'u. W wyniku działania takich narzędzi powstają pliki obrazu, które należy umieścić na docelowym
urządzeniu.
Buildroot - system do budowania firmware'u
Godnym polecenia systemem do budowania obrazu firmware'u jest Buildroot.
Jest on w stanie wykonać wszystkie potrzebne czynności, niezbędne do utworzenia obrazu, czyli:
- ściąga pliki źródłowe odpowiednich pakietów z internetu,
- aplikuje poprawki na źródłach (patche),
- buduje toolchain (w tym kross-kompilator, niezbędny gdyż docelowy procesor może posiadać inną architekturę niż ten, na którym buduje się firmware),
- przygotowuje główny system plików (rootfs),
- kompiluje jądro [ang. kernel] systemu (w tym przypadku Linuxa),
- buduje bootloader,
- przygotowuje skompresowane pliki obrazu, gotowe to 'wrzucenia' na urządzenie.
Działania programisty sprowadzają się do określenia konfiguracji, w tym wyboru pakietów, które mają znaleźć się na docelowym urządzeniu.
Następnie uruchamiany jest proces budowania, który przebiega dalej automatycznie. Buildroot jest napisany w makefile'ach
oraz skryptach powłoki, dzięki czemu możliwa jest ingerencja w proces budowania i zmodyfikowanie go w celu dopasowania
do nietypowych potrzeb. Ponadto, wykorzystując zdefiniowane funkcje w Buildroocie można opisać własne pakiety, określić
jak mają być kompilowane, czy nakładać na nie poprawki oraz w jaki sposób i gdzie mają być instalowane.
Trzeba pamiętać, że Buildroot jest przeznaczony do budowania obrazów firmware'u, nie posiada on mechanizmów zarządzania
i dystrybuowania pakietów na docelowe urządzenia. Do aktualizacji firmware'u przyjęto filozofię podmiany całego obrazu.
Chociaż Buildroot śledzi wykonane etapy na kolejnych pakietach (tzn. ściągnięcie, konfiguracja, kompilacja, instalacja),
to nie sprawdza czy nie zmodyfikowano źródeł pakietu. Z tego powodu niebędne jest czasem wyczyszczenie i wykonanie budowania
od początku. Niestety pociąga to za sobą również budowanie toolchaina, co staje się uciążliwe, ponieważ wydłuża całą procedurę.
Na szczęście, Buildroot umożliwia 'podczepienie' gotowego, zewnętrznego toolchaina. Można więc skorzystać z jednej z poniższych opcji:
- użyć toolchain zbudowany przez Buildroota jako zewnętrzny (zbudować toolchain za pomocą Buildroota, a potem skopiować go do zewnętrznego
katalogu, co zapobiegnie wykasowaniu podczas czyszcenia, i wskazać jako zewnętrzny),
- użyć samodzielnie zbudowany toolchain (np. przez crosstool-NG)
- użyć inny toolchain pobrany z internetu.
Użycie zewnętrznego toolchaina poza oszczędnością czasu daje dodatkowe korzyści. Toolchain budowany przez Buildroota
jest ściśle związany z biblioteką C w postaci uClibc, która może okazać się zbyt uproszczona w niektórych zastosowaniach
(w szczególności pojawią się problem gdy potrzebne jest C++). Z powyższego powodu podczas budowania własnego toolchaina
uzyskuje się więcej możliwości i swobody. Jednocześnie, nie stanowi to dużo większego nakładu pracy, gdyż toolchain przygotowuje się
zwykle raz, na samym początku a potem już tylko używa.
Trzeba pamiętać, że jeśli nie określono tego inaczej w konfiguracji, Buildroot umieszcza w docelowym systemie Busybox - program obsługujący
zestaw poleceń powłoki w uproszczonej, kompaktowej formie. Może się więc okazać, że dla niektórych poleceń dostępna jest tylko
część funkcjonalności lub wystepują drobne niezgodności w opcjach. Warto w takim przypadku zajrzeć do konfiguracji samego BusyBoxa
(make busybox-menuconfig), co ustawia się oddzielnie od konfiguracji Buildroota.
Drzewo katalogów w Buildroocie
Po sciągnięciu i rozpakowaniu pakietu Buildroot widoczne są nastepujące katalogi, zawierające odpowiednio:
+ board - zawiera pliki specyficzne dla platformy
+ boot - zawiera opcje konfiguracji dla różnych bootloaderów
+ configs - zawiera domyślne konfiguracje Buildroota dla znanych platform, po skonfigurowaniu można zapisać tam własną
+ docs - dokumentacja Buildroota (format HTML oraz PDF)
+ fs - konfiguracje do generacji różnych systemów plików
   + skeleton - szkielet głównego systemu plików (rootfs)
+ linux - konfiguracja oraz makefile do generacji kernela Linuxa (również rozszeżeń RT, takich jak RTAI, Xenomai)
+ package - konfiguracje i makefile wielu pakietów userspace'owych
+ support - różne elementy niezbędne do budowania (skrypty, kconfig - obsługa menu konfiguracyjnego)
+ target - katalog prawie nieużywany, zawiera tylko domyślną tabelę urządzeń (device table)
+ toolchain - konfiguracje do budowania lub importowania zewnętrznego toolchaina
Po rozpoczęciu budowania obrazu pojawią się podkatalogi:
+ dl - gdzie są ściągane wszelkie pliki zewnętrzne (zwykle spakowane pakiety w postaci źródłowej),
+ output - katalog, w którym są rozpakowywane źródła, budowane pakiety oraz gdzie znajdą się pliki wynikowe i obrazy
Patrząc na drzewo Buildroota, warto zwrócić uwagę na oddzielenie opisu pakietu (konfiguracja i makefile określajace jak należy postępować z
pakietem aby go zbudować) od treści pakietów (źródeł, makefile'i i plików wynikowych). Z tego powodu konfiguracje pakietów w Buildrootcie
mogą być traktowane jako meta-opis.
Drzewo katalogów: z lewej - główny katalog Buildroota, z prawej - zawartość katalogu output
W podkatalogu output można znaleźć następujące katalogi:
+ build - zawiera podkatalogi pakietów, a w nich źródła; w tych podkatalogach będzie następowało budowanie,
+ host - zawiera narzędzia używane na hoście do budowania,
   + host/usr/<nazwa>/sysroot - podkatalog zawierający sysroot dla toolchaina, biblioteki i nagłówki nięzbędne do budowania
aplikacji na docelową platformę (*),
+ images - wynikowe, końcowe pliki obrazów (firmware, bootloader, itp.),
+ staging - link do [*] - sysroot (patrz powyżej w katalogu host),
+ stamps - pliki-stemple czasowe do określania co zostało zrobione,
+ target - docelowy, główny system plików bez tablicy urządzeń,
+ toolchain - katalog, w którym jest budowany toolchain (źródła kompilatora, biblioteki C, nagłówki Linuxa).
Trzeba pamiętać, że wszelkie zmiany w katalogu output są tracone po wykonaniu
make clean
Jest to szczególnie ważne, jeśli dokonuje się poprawek (tuningu) docelowego systemu plików w katalogu output/target.
Może to być np. dodanie opisu interfejsów sieciowych (plik /etc/network/interfaces), nadania hasła poprzez wpis do
/etc/shadow czy włączenia konsoli na porcie szeregowym ttyS0 (plik /etc/inittab).
Po skasowaniu i rozpoczęciu ponownego budowania, Buildroot wykorzystuje szkielet fs/skeleton, który (i) można zmodyfikować
dopasowując do własnych potrzeb, (ii) stworzyć własny szkielet albo (iii) skorzystać ze skryptu uruchamianego po zakończeniu
tworzenia głównego systemu plików (ta trzecia opcja jest wygodna gdy zmian jest niewiele). Użycie własnego szkieletu,
utworzonego np. przez skopiowanie fs/skeleton może spowodować niepodziankę: po uruchomieniu docelowego systemu, bootuje się on
ale nie pojawia się konsola - nie ma możliwości zalogowania się. Powodem takiego zachowania jest zakomentowana konsola
w /etc/inittab:
# Put a getty on the serial port
# ttyS0::respawn:/sbin/getty -L ttyS0 115200 vt100 # GENERIC_SERIAL
A dlaczego działało przy użyciu domyślnego szkieletu? Przecież nowy szkielet został utworzony jako kopia? Okazuje się, że
gdy wybrany jest domyślny szkielet, wykonywane jest automatyczne odkomentowanie tej linii poprzez jeden ze skryptów
na podstawie opcji w konfiguracji. Z resztą, wystarczy zaobserwować, że wybranie własnego szkieletu w konfiguracji
powoduje ukrycie opcji dotyczących konsoli na ttyS0.
Konfiguracja Buildroota
Buildroot jest konfigurowany za pośrednictwem dobrze znanego interfejsu z konfiguracji kernela, po wydaniu polecenia:
make menuconfig
pojawia się okno konfiguracji:
Pierwsze dwa podmenu służą do wybrania rodziny oraz odmiany architektury procesora na docelowej platformie.
Dalsze pozycje menu zostały opisane poniżej (podany został tylko opis ważniejszych opcji):
1. Build options
Number of jobs to run simultaneously - określa zrównoleglenie procesu budowania (szybkość vs.
obciążenie komputera). Nie należy używać opcji make -jN, tylko w tym miejscu podać liczbę zadań (próba użycia make -jN może doprowadzić do błedów).
strip - określa czy usuwać z plików wykonywalnych sekcje zawierające informacje dla debugera, głównie symbole, powiązania nr. linii źródeł i kodu binarnego.
Stripując można znacznie zmniejszyć rozmiar plików wykonywalnych.
gcc optimization level - stopień i typ optymalizacji kodu generowanego przez kompilator GCC
2. Toolchain
Toolchain type - pozwala wybrać czy Buildroot ma budować toolchain samodzielnie czy używać zewnętrzny toolchain
Kernel Headers - ustala wersję nagłówków Linuxa z jakimi ma być budowany toolchain,
jeśli nie ma na liście wersji, którą używamy, trzeba wybrać "Linux 2.6 (manually specified version)" i w "linux version" wpisać numer
wymaganej wersji.
Nie należy się przejmować numerem 2.6 na liście, istotny będzie wpisany przez nas numer.
Enable C++ - włącza obsługę C++ (uwaga uClibc nie zawiera standardowej biblioteki stdlibc++,
aby budować prorgamy C++ należy użyć zewnętrznego toolchaina zbudowanego z inną biblioteką (np. glibc).
3. System configuration
System hostname - nazwa komputera (docelowej platformy), domyślnie buildroot
System banner - teskt powitalny na konsoli
/dev management - sposób tworzenia tablicy urządzeń (może być statyczna wkompilowana w obraz albo
tworzona przez udev)
Root FS skeleton - szkielet systemu plików, domyślny to fs/skeleton
Port to run a getty (login prompt) on - port, na którym pojawi się konsola, domyślnie ttyS0,
opcja dostępna tylko dla domyślnego szkieletu
Baudrate to use - szybkość transmisji portu szeregowego, domyślnie ustawione na 115200
Custom script to run before creating filesystem images - skrypt, który będzie uruchamiany
przed tworzeniem końcowego pliku obrazu, można go używać do wprowadzania zmian w głównym systemie plików (dopasowanie).
4. Package selection for the target - pakiety, które mają być zbudowane i zainstalowane w obrazie dla docelowej platformy.
5. Filesystem images - określenie w jakich formatach ma być wygenerowany obraz (np. .tag.gz, jffs2, itd.)
6. Bootloaders - wybór bootloadera (Barebox, grub, syslinux, U-Boot) i ustawienie jego opcji
7. Kernel
Linux Kernel - należy zaznaczyć jeśli ma być budowany
Kernel Version - określenie wersji kernela, dobrze żeby był zgodny z wersją określoną do budowy toolchaina
Custom kernel patches - patche nakładane na kernel, można podać katalog, wtedy patche będą nakładane
w kolejnosci nazw - jest to przydatne gdy patche są wygenerowane przez gita, wtedy ich nazwy rozpoczynają się od kolejnych liczb, np. 0001-, 0002-, itd.).
kernel configuration/configuration file path - określa konfigurację kernela, która ma być użyta do
budowania.
Uwaga
a. Jeśli używana jest konfiguracja domyślna (defconfig) należy podać jej nazwę (np. i386, jeśli chce się użyć konfiguracji z pakietu
kernela arch/x86/configs/i386_defconfig), w przeciwnym przypadku pojawi się błąd:
linux/linux.mk:228 *** No kernel defconfig name specified. check your BR2_LINUX_KERNEL_DEFCONFIG setting. Stop.
b. Można jednak utworzyć własną konfigurację w oddzielnym katalogu (również poprzez make menuconfig, po rozpakowaniu źródeł kernela),
a następnie wybrać w konfiguracji Buildroota konfigurację nietypową (custom) i wskazać plik .config z katalogu kernela. Trzeba pamiętać
aby konfiguracja była poza katalogiem output, inaczej zostanie skasowana przy czyszceniu buildroota (wykonanie make clean).
Install kernel image to /boot in target - pozwala umieścić kernel w obrazie rootfs w katalogu /boot.
Wykorzystanie tej opcji zależy od wybranej struktury systemu, czasem kernel jest w odrębnej partycji niż rootfs.
Ten sam interfejs konfiguracji jest wykorzystywany do ustawienia parametów innych głównych elementów Buildroota, odpowiednio Busyboxa, kernela Linuxa
oraz biblioteki C typu uClibc:
make busybox-menuconfig
make linux-menuconfig
make uclibc-menuconfig
Więcej opcji można wyświetlić uruchamiając:
make help
a także uzyskać wiecej informacji przeglądając manual Buildroota.
Jak umieścić obraz na urządzeniu?
Gotowy plik obrazu firmware'u, który jest wynikiem budowania pod Buildrootem należy umieścić na docelowym urządzeniu.
W zależności od przyjętego rozwiązania konstrukcyjnego docelowej platformy, firmware może być tam przechowywany:
- we wlutowanym na płytce układzie pamięci FLASH,
- na karcie pamięci nieulotnej (np. Compact Flash), którą można wyjąć z urządzenia i zaprogramować w zewnętrznym czytniku,
podłączonym do innego komputera
- na twardym dysku, który można "przepiąć" do działającego systemu jako dysk dodatkowy (niesystemowy).
Urządzenie może też być w różnym stanie wynikającym z procesu produkcyjnego, np.:
- może nie mieć żadnego bootloadera, w takim przypadku konieczny jest sprzętowy JTAG (alternatywą może być też
uruchomienie pomocniczego systemu z pendrive'a usb czy dystrybucji LiveCD, jeśli tylko platforma pozwala na
boot z takich lokalizacji - będzie tak dla urządzeń z procesorami x86 posiadającymi BIOS),
- urządzenie posiada bootloader, wtedy pozwala on na 'wciągnięcie' i zapisanie nowego obrazu przez łacze szeregowe lub sieć,
- urządzenie posiada działający system z programem do uaktualniania obrazu.
Typowe problemy
1. po zaznaczeniu budowania kernela, proces budowania zatrzymuje się i pojawia się bład:
linux/linux.mk:228 *** No kernel defconfig name specified. check your BR2_LINUX_KERNEL_DEFCONFIG setting. Stop.
Oznacza to, że nie wskazano konfiguracji kernela. Należy wskazać nazwę konfiguracji standardowej z pakietu kernela albo własny
plik .config gdy jest to konfiguracja nietypowa. Konfiguracji standardowych można szukać w arch/<arch&gr/configs/,
przykładowo dla arch/x86/configs/i386_defconfig należy w konfiguracji buildroota podać i386.
2. po zakończeniu bootowania urządzenia nie pojawia się tekst zachęcający do zalogowania, nie można zalogować się na konsolę po porcie szeregowym.
Trzeba sprawdzić czy konsola ttyS0 jest włączona (i czy nie jest zakomentowana) w pliku /etc/inittab:
# Put a getty on the serial port
ttyS0::respawn:/sbin/getty -L ttyS0 115200 vt100 # GENERIC_SERIAL
3. po uruchomieniu nie ma interfejsu sieciowego eth0
Należy dodać odpowiedni opis w pliku /etc/network/interfaces, np.:
auto eth0
iface eth0 inet static
address 192.168.0.100
netmask 255.255.255.0
gateway 192.168.0.1
Pierwsza linijka odpowiada za automatyczne podniesienie interfejsu po uruchomieniu systemu. Jeśli nie chce się tego robić
automatycznie trzeba ją usunąć i korzystać z ifconfig.
4. zmiany wprowadzone w plikach w katalogu target zniknęły
Jest to naturalne, stało się tak zapewne w wyniku wyczyszczenia buildroota (przez make clean), wtedy system plików jest budowany ponownie.
5. zmiany wprowadzone w fs/skeleton nie przeniosły się do docelowego obrazu plików
Zmiany przeniosą się (do katalogu target i obrazu rootfs) podczas następnego budowania systemu plików. Należy wykonać make clean Buildroota.
6. Nie można zalogować się przez SSH do urządzenia, chociaż pakiet dropbear został dodany
Trzeba sprawdzić czy użytkownik ma nadane hasło. Domyslnie buildroot posiada użytkownika root bez hasła a pakiet dropbear odrzuca logowanie
użytkowników bez hasła. Należy nadać mu hasło przez passwd.
Materiały na temat Buildroota
Na zakończenie warto dodać, że Buildroot jest wykorzystywany w innym projekcie: OpenWRT, który z kolei jest przeznaczony
do tworzenia firmware'u dla routerów.