Techniczne uwagi na temat zestawu narzędziowego

W tym rozdziale spróbujemy wyjaśnić niektóre szczegóły techniczne i uzasadnienia ogólnie naszego sposobu tworzenia systemu. Nie musisz koniecznie tego wszystkiego od razu zrozumieć. W większości powyjaśnia się to w trakcie pracy. Zawsze możesz tu wracać.

Ogólnie biorąc, celem Rozdziału 5 jest stworzenie porządnego środowiska na tymczasem, abyśmy mogli się do niego chroot'ować i stamtąd stworzyć czysty, wolny od kłopotów docelowy system LFS w Rozdziale 6. Przez cały czas staramy się rozstać z systemem macierzystym jak najbardziej i dlatego tworzymy oddzielny zestaw narzędzi. Zauważyć trzeba, że proces budowania tak został pomyślany, aby zminimalizować ryzyko dla początkujących i zarazem dostarczyć jak najwięcej walorów edukacyjnych. Inaczej mowiąc, można by budować nasz system przy użyciu bardziej zaawansowanych technik.

Ważne: Zanim przejdziesz dalej, naprawdę powinnaś uświadomić sobie, jakiej platformy używasz, często określanej jako target triplet ("docelowa trójka"). Dla większości czytelników tą "trójką" będzie, na przykład: i686-pc-linux-gnu. Prostym sposobem na określenie trójki docelowej jest wykonanie skryptu config.guess który przychodzi wraz ze źródłami wielu pakietów. Rozpakuj źródła Binutils i uruchom ten skrypt: ./config.guess i zapisz wynik.

Aby nie zaskoczył cię standardowy linker ld, który jest częścią Binutils, musisz też znać nazwę dynamicznego linkera twojej aktualnej platformy, często określanego jako dynamiczny loader. Ten dynamiczny linker jest wraz z Glibca jego zadaniem jest znalezienie i załadowanie tych bibliotek, których wymaga dany program, przygotowanie programu do działania i jego uruchomienie. U większości czytelników dynamiczny linker to ld-linux.so.2. Na mniej popularnych platformach może to być ld.so.1 a nowsze platformy 64-bitowe mogą mieć coś jeszcze innego. Prawdopodobnie możesz określić nazwę dynamicznego linkera na twojej platformie zaglądając do katalogu /lib. Pewnym sposobem jest sprawdzenie dowolnego binarium w twoim systemie macierzystym przez wykonanie: 'readelf -l <nazwa binarium> | grep interpreter' i zapisanie wyniku. Właściwym odniesieniem na wszystkich platformach jest plik shlib-versions w katalogu drzewa źródeł Glibc.

Oto kilka punktów technicznych na temat działania metody budowania przedstawionej w Rozdziale 5:

Binutils instaluje się najpierw, ponieważ i GCC i Glibc wykonują różne testy (feature tests) asemblera i linkera przy uruchamianiu ich ./configure, żeby określić jakie cechy tych programów włączyć lub wyłączyć. Jest to ważniejsze, niż mogłoby się komuś wydawać z początku. Nieprawidłowo skonfigurowany GCC lub Glibc może spowodować uszkodzenie narzędzi zauważalne dopiero pod koniec budowy całej dystrybucji. Na szczęście błędy z zestawu testowego zaalarmują nas zanim zmarnujemy zbyt wiele czasu.

Binutils instaluje swój asembler i linker w dwóch miejscach /tools/bin i /tools/$TARGET_TRIPLET/bin. W rzeczywistości narzędzia z jednego miejsca źle się linkują z tymi z drugiego. Ważną cechą linkera jest kolejność przeszukiwania bibliotek. Szczegółowe informacje można uzyskać z komendy ld wykonując ją z flagą --verbose. Na przykład 'ld --verbose | grep SEARCH' pokaże aktualne ścieżki przeszukiwania i ich kolejność. Możesz sprawdzić, które właściwie pliki linkuje ld kompilując "lipny" (dummy) program i wykonując tą komendę z opcją --verbose. Na przykład: 'gcc dummy.c -Wl,--verbose 2>&1 | grep succeeded' pokaże ci wszystkie pliki otwierane podczas linkowania.

Następnym instalowanym pakietem jest GCC i podczas wykonywania jego ./configure zobaczysz, na przykład:

checking what assembler to use... /tools/i686-pc-linux-gnu/bin/as
checking what linker to use... /tools/i686-pc-linux-gnu/bin/ld

To jest ważne z przyczyn opisanych powyżej. Pokazuje to również, że skrypt konfiguracyjny GCC nie przeszukuje katalogów z $PATH w poszukiwaniu narzędzi do użycia. Jednak, nawet podczas działania samego gcc, niekoniecznie będą używane te same ścieżki przeszukiwania. Możesz sprawdzić, którego standardowego linkera użyje gcc wykonując: 'gcc -print-prog-name=ld'. Dokładniejsze informacje dostaniesz od gcc uruchamiając go z flagą -v przy kompilowaniu lipnego programu (a dummy program). Na przykład 'gcc -v dummy.c' pokaże ci szczegółowe informacje na temat preprocesora, poziomów kompilacji i asemblacji (?), włącznie ze ścieżkami przeszukiwania include z gcc i ich kolejnością.

Następnym instalowanym pakietem jest Glibc. The next package installed is Glibc. Budując Glibc bierzemy pod uwagę kompilator, źródła binarne i nagłówki kernela. The most important considerations for building Glibc are the compiler, binary tools and kernel headers. Z regóły kompilator to żaden kłopot, ponieważ Glibc zawsze będzie używać gcc znalezionego w katalogu wskazanym przez $PATH. Pozostałe dwa elementy mogą być bardziej kłopotliwe. Dlatego nie podejmujemy żadnego ryzyka i używamy dostępnych opcji konfiguracji aby wymusić prawidłowe wybory. Po uruchomieniu./configure możesz sprawdzić zawartość pliku config.make w katalogu glibc-build, by znaleźć ważne szczegóły. Zauważysz trochę ciekawych rzeczy, jak zastosowanie CC="gcc -B/tools/bin/" do kontroli, które źródła binarne mają być użyte, również zastosowanie flag -nostdinc oraz -isystem do kontroli ścieżki przeszukiwania include kompilatora. Te trzy rzeczy pomogą zaznaczyć ważny aspekt  pakietu Glibc: jest on samowystarczalny pod względem swoich mechanizmów budowania i w zasadzie nie polega na parametrach (ustawieniach) domyślnych zestawów narzędziowych.

Po zainstalowaniu Glibc trochę go dopasujemy aby zapewnić, że przeszukiwanie i linkowanie będzie miało miejsce jedynie w zakresie z prefiksem /tools. Zainstalujemy dopasowany ld, który ma wbudowaną konstrukcyjnie ścieżkę ograniczoną do /tools/lib. Następnie poprawimy plik specyfikacji gcc aby wskazywało na nasz nowy dynamiczny linker w /tools/lib. Ten ostatni krok ma żywotne znaczenie dla całego procesu. Jak wspomnieliśmy wcześniej, wbudowana na sztywno ścieżka do dynamicznego linkera jest osadzana w każdym wykonywalnym pliku (a hard-wired path to a dynamic linker is embedded into every ELF shared executable). Możesz to sprawdzić wykonując: 'readelf -l <name of binary> | grep interpreter'. Poprawienie pliku specyfikacji gcc zapewniam nas, że każdy program kompilowany odtąd, aż do końca Rozdziału 5 będzie używać naszego nowego dynamicznego linkera z katalogu /tools/lib.

Konieczność użycia nowego dynamicznego linkera jest także powodem, dla którego stosujemy łatkę Specs przy drugim przebiegu GCC. Rezultatem niepowodzenia w tym wypadku byłoby zagnieżdżanie nazwy dynamicznego linkera z systemu macierzystego, z katalogu /lib w budowanych programach pakietu GCC, przez co nie moglibyśmy oderwac się od systemu macierzystego. (The need to use the new dynamic linker is also the reason why we apply the Specs patch for the second pass of GCC. Failure to do so will result in the GCC programs themselves having the name of the dynamic linker from the host system's /lib directory embedded into them, which would defeat our goal of getting away from the host.)

Podczas drugiego przebiegu Binutils, możemy użyć opcji (przełącznika) konfiguracji --with-lib-path aby zabezpieczyć ścieżkę poszukiwania biblioteki ld. Od tej chwili, sam zestaw narzędzi opiera się na sobie i działa sam. Pozostałe z pakietów z Rozdziału 5 są budowane na podstawie nowej biblioteki Glibc w /tools i wszystko jest w porządku.

Zaraz po przejściu do środowiska chroot w Rozdziale 6, pierwszym głównym pakietem, jaki instalujemy jest Glibc, z powodu jego samowystarczalności, o której wspominaliśmy powyżej. Gdy już ta Glibc jest zainstalowana w /usr, szybciutko "przebieramy się" w domyślny zestaw narzędziowy i działamy na poważnie, budując resztę docelowego systemu LFS w Rozdziale 6.

Uwagi o linkowaniu statycznym

Większość programów, poza swoimi szczególnymi zadaniami, musi wykonywać pewne pospolite i nieraz trywialne operacje.  Zaliczymy do nich alokację pamięci, przeszukiwanie katalogów, odczytywanie i zapisywanie plików, operacje na napisach, porównywanie danych, działania arytmetyczne i wiele innych zadań. Zamiast zmuszać każdy program do wymyślania koła na nowo, system GNU dosarcza wszystkie te podstawowe funkcje w gotowych bibliotekach. Główną biblioteką dowolnego systemu linuksowego jest Glibc.

Dwa są podstawowe sposoby dołączania (linkowania ;-) funkcji z biblioteki do programu, który ich używa: statyczne i dynamiczne. Gdyb program jest linkowany statycznie, to kod wykorzystywanych funkcji włącza się do pliku wykonywalnego, co daje raczej niezgrabny program. Gdy program linkuje się dynamicznie, to do pliku wykonywalnego wkłada się odsyłacz do linkera dynamicznego, nazwę wykorzystanej biblioteki i nazwę odpowiedniej funkcji, co daje dużo mniejszy plik. (Trzecim sposobem jest skorzystanie z programistycznego interfejsu dynamicznego linkera. Więcej informacji znajdziesz w manualu  dlopen.)

W Linuksie defaultowym (domyślnym) sposobem linkowania jest dynamiczne. Ma ono trzy główne zalety w porównaniu do statycznego. Pierwsza -- potrzebujesz tylko jednego egzemplarza kodu biblioteki wykonywalnej na swoim dysku, zamiast trzymać wiele identycznych kopii tego samego kodu włączonych w różne programy, czyli oszczędzasz miejsce na dysku. Druga -- gdy kilka programów używa tej samej funkcji bibliotecznej równocześnie, to tylko jeden egzemplarz kodu tej funkcji  jest wykonywany przez procesor, czyli oszczędzasz miejsce w pamięci operacyjnej. Trzecia -- jeśli funkcja w bibliotece jest jakoś usprawniona lub pozbawiona usterki, to wystarczy, że ponownie skompilujesz tą jedną bibliotekę, zamiast zajmować się wszystkimi programami, które korzystają z tej poprawionej funkcji.

Skoro linkowanie dynamiczne ma takie zalety, to dlaczego dwa pierwsze pakiety w tym rozdziale kompilujemy statycznie? Przyczyny są trojakie: historyczne, edukacyjne i techniczne. Historyczne, ponieważ wcześniejsze wersje LFS linkowały statycznie każdy program w tym rozdziale. Edukacyjne, ponieważ znajomość tych różnic jest pożyteczna. Techniczne, ponieważ postępując w ten sposób uzyskujemy pewną niezależność od systemu macierzystego, co oznacza, że można używać tych programów niezależnie od systemu macierzystego. Nota bene, dynamiczne linkowanie dwóch pierwszych pakietów nie przeszkodziłoby w pomyślnym zbudowaniu systemu LFS.