Powrót do strony głównej exec

 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.



Opracowanie:
Marcin Mędelski-Guz

Komentarze w pliku binfmt_aout.c:
Marcin Mędelski-Guz i Marcin Mucha.