Uruchamianie pliku typu a.out - procedura "do_load_aout_binary"
Pliki a.out mogą być w jednym z wielu formatów,
z których nie wszystkie Linux potrafi uruchamiać. Kontrola zgodności pliku
z formatem przeprowadzana jest w zależności od typu badanego pliku (patrz
komentarze w kodzie). Wśród testowanych własności jest wielkość pliku wykonywalnego
- musi być równa co najmniej zadeklarowanej w nagłówku pliku wielkości
kodu plus wielkość obszaru zmiennych inicjowanych wartościami z pliku.
W przypadku plików typu ZMAGIC / QMAGIC (stronicowanych na życzenie
- "demand paged") ważne jest dodatkowo, aby przesunięcie kodu w pliku odpowiadało
krawędzi strony. Następnie sprawdzane jest, czy deklarowany rozmiar potrzebnej
pamięci nie jest większy od ustalonych limitów. Jeśli do tej pory nie wykryto
błędu, następuje "punkt bez wyjscia" - wywołujemy procedurę flush_old_exec,
która usuwa kontekst poprzednio uruchomionego programu. Teraz już w razie
błędu pozostaje nam tylko "zabić się" - wysłać do bieżącego procesu sugnał
SIGKILL (co ciekawe, procedura ładowania plików w formacie ELF używa do
tego samego celu sygnału "SEGV" - błąd segmentacji(!)). Musimy zmienić
wartoci pól struktury task_struct, wskazywanej przez zm. current,
na nowe (patrz kod procedury). Dalej następuje już wczytanie kodu programu.
Fizyczny przydział pamięci i ładowanie wykonywanie jest tylko dla programów,
które nie są typu ZMAGIC / QMAGIC. Do przydziału pamięci używana jest procedura
do_mmap, służąca do odwzorowywania plików dyskowych w pamięci
wirtualnej (sic!). Jej pierwszym parametrem winien być uchwyt FD pliku
- w tym przypadku wstawiamy na to miejsce NULL, ustawiamy flagę MAP_PRIVATE
i ... gotowe. Jest to podobny mechanizm do tego, który służy do tworzenia
segmentów pamięci dzielonej w systemach Windows 95 i NT. Do wczytywania
ciągów bajtów z pliku do segmentu kodu i danych programu służy procedura
"read_exec". (Ciekawe: w przypadku
plików ładowanych w taki sposób jest to jeden i ten sam segment. Najpierw
umieszczony jest kod, tuż za nim dane. Taka kolejność powoduje, że w przypadku
"wyjechania" poza zadeklarowany rozmiar tablicy w kierunku "+" dostaniemy
błąd stronicowania ("segmentation fault"), ale jeśli zdarzy się zrobić
to w odwrotną stronę, to zaczniemy zamazywać instrukcje programu. Ta organizacja
programu w pamięci znana jest programistom assemblerowym jako "model flat"
- ułatwia kodowanie, ale jest niebezpieczna!). Po wczytaniu kodu i danych
inicjowanych, przydzielamy pamięć na dane nieinicjowane. Brak tutaj zerowania
przydzielonej pamięci, co ma miejsce np. w procedurze ładującej programy
typu ELF.
Jeśli program jest typu ZMAGIC / QMAGIC,
to zamiast wczytywać go do pamięci, korzystamy z mechanizmu stronicowania
(odwzorowywania w pam. wirtualnej) zbiorów. Najpierw jednak sprawdzamy,
czy zamontowany system plików umożliwia taką operację. Jeśli nie, to robimy
dokładnie to samo, co dla zwykłych plików. W przeciwnym przypadku otwieramy
i-węzeł pliku, a otrzymany w ten sposób deskryptor przekazujemy procedurze
do_mmap. Proces po uruchomieniu będzie zachowywał się tak, jakby wszystkie
jego strony w pamięci wirtualnej były zrzucone na dysk. Fizyczne wczytanie
kodu (lub danych inicjowanych) odbywać się będzie w wyniku przerwań braku
strony, generowanych podczas kolejnych odwołań. Unikamy w pewnym stopniu
"zaśmiecania" pamięci fizycznej komputera kodem niektórych procedur, które
nie będą wykorzystane przez użytkownika. Pozostaje jeszcze tylko przydzielić
pamięć na dane nieinicjowane (do_mmap(NULL,...)). Na uwagę zasługuje fakt,
że w tym przypadku programiści zadbali już bezpieczeństwo kodu programu,
przydzielajšc mu osobny segment z wyzerowaną flagą PROT_WRITE.
Dopiero teraz wykonywane jest kopiowanie zmiennych
środowiskowych i parametrów wywołania programu do jego pamięci, wraz ze
zmianą struktury danych - z listy ciągów poprzedzielanych znakami NULL
(bprm->p), na dwie tablice zmiennych
typu (char*) - wykonuje to procedura "create_aout_tables". Dalej
pozistaje nam już tylko uruchomić program - "start_thread".
Na koniec, jeśli proces ma podlegać śledzeniu, blokujemy go (praca krokowa).
Wczytywanie biblioteki dzielonej typu a.out - procedura "do_load_aout_library"
Ten fragment kodu systemu Linux nie podlega
już do procedurze exec, ale opiszemy go tu po krótce ze względu na wysokie
podobieństwo do "do_load_aout_binary". Kod procedury ładujšcej
bibliotekę w formacie a.out, pominąwszy kontrolę prawidłowości typu
pliku, pokrywa się z fragmentem kodu "do_load_aout_binary", przeznaczonym
dla plików typu ZMAGIC / QMAGIC (stronicowanych na życzenie) - innych bibliotek
Linux po prostu nie obsługuje - procedura zwróci bład "-ENOEXEC" dla innych
zbiorów. Parametrem procedury ładującej bibliotekę nie jest struktura linux_binprm,
ale deskryptor już otwartego pliku. Nie musimy więc troszczyć się o sprawdzenie,
czy plik istnieje. Szczegóły - komenentarze w pliku źródłowym.
Pozostałą część pliku binfmt_aout.c stanowi
kod procedur wykonujących "core dump" - zrzut treści procesu na dysk. Ze
względu na brak związku z uruchamianiem nowych procesów, fragment ten pomijamy
w opisie.
Komentarze w pliku binfmt_aout.c:
Marcin Mędelski-Guz i Marcin Mucha.