Do spisu treści tematu 3
Algorytm exec
Spis treści
Wprowadzenie
Procedury exec* zmieniają kontekst
poziomu użytkownika danego procesu na podstawie programu wykonywalnego
umieszczonego w pliku dyskowym. Tym samym, statyczny kod, jakim jest program
na dysku, staje się dynamicznym, działającym w pamięci operacyjnej, procesem.
Wywołania funkcji execlp, execvp,
execl, execv, execle, execve, różniące się składniowo, wywołują
ostatecznie funkcję do_execve z pliku exec.c.
sp
Algorytm funkcji do_execve
sp
Przenoszenie napisów
Napisy w pamięci jądra
Zanim jądro usunie z pamięci kod procesu wolającego do_execve
oraz jego dane, musi zapamiętać argumenty wywołania i zmienne środowiska
(a.w. i z.ś.). Do tego celu służy tablica page
oraz wskaźnik p.
Tablica page jest tablicą stron, do których kopiowane sa a.w. i z.ś. Strony te przydzielane są od końca dynamicznie,
w trakcie kopiowania, tzn. początkowa część tablicy page
nie odpowiada żadnym stronom. Zmienna p
wskazuje na pierwszą przydzieloną już stronę, a w jej obrębie - ostatni
wolny adres (p/PAGE_SIZE i p%PAGE_SIZE).
Jądro trzyma wszystkie napisy obok siebie pooddzielane zerami. Ponadto
pamiętana jest liczba a.w. i z.ś. Na tej podstawie można potem odtworzyć
tablicę a.w. i tak wlaśnie dzieje się w rzeczywistości. Strony z page
dołączane są do pamięci nowego procesu na końcu jego przestrzeni
adresowej. Łącznie na stronach indeksowanych tablicą page
mieści sie 128kB napisów, co wydaje sie być wielkoscią wystarczającą.
Kopiowanie napisów
Za kopiowanie napisów z tablicy napisów na strony page
odpowiada funkcja copy_strings.
Algorytm jej jest prosty:
-
obliczamy dlugość kolejnego (idąc od tyłu) napisu z tablicy
-
sprawdzamy, czy nie doszliśmy do końca (a w zasadzie początku) strony,
jeśli tak, to przydzielamy kolejną
-
kopiujemy największą część napisu, jaka mieści się na aktualnej stronie
-
jeśli nie jest to cały napis, to czynność powtarzamy
W ten sposób postępujemy ze wszystkimi napisami z tablicy.
Należy zwrócić tu uwagę na jeden dodatkowy szczegół. Funkcja copy_strings
obsługuje zasadniczo 3 różne sytuacje:
-
tablica napisów oraz napisy znajdują się w pamięci użytkownika
-
tablica napisów znajduje się w pamięci jądra, napisy w pamięci użytkownika
-
tablica napisów oraz napisy znajdują się w pamięci jądra
Osiąga się to przez warunkowe przypisania fs:=ds.
Algorytm setup_arg_pages
Funkcja setup_arg_pages przyłącza
strony z page do przestrzeni adresowej
użytkownika, na samym jej końcu:
-
obliczamy adres, pod który strony zostaną przyłączone
-
przydzielamy strukturę vm_area_struct
i wypełniamy ją
-
do przestrzeni adresowej użytkownika dołączamy strony page
funkcją put_dirty_page
W przypadku, gdy nie uda się przydzielić struktury vm_area_struct
(brak pamięci), strony page są
zwalniane.
mm
Kontrola - prepare_binprm
W funkcji prepare_binprm przeprowadzana
jest kontrola możliwości wykonania danego pliku przez proces i ustawienie
niektórych pól struktury linux_binprm
-
sprawdzenie, czy plik jest regularnym plikem, a nie np katalogiem, czy
urządzeniem zewnętrznym
-
kontrola, czy ma przynajmniej jeden bit zezwalający na wykonanie - dokładniejsza
kontrola jest robiona później
-
czy system plików pozwala na wykonywanie programów
-
czy proces ma prawo wykonać dany plik
-
czy ktoś przypadkiem nie zapisuje do tego pliku
-
czy plik ma atrybut s, jeśli tak to proces ma uzyskać później uprawnienia
administratora
-
testy bezpieczeństwa - proces nie może uzyskać praw administratora, jeśli
-
jest śledzony,
-
system plików nie pozwala na uzyskanie uprawnień administratora, jeśli
plik ma atrybut s,
-
proces dzieli niektóre swoje struktury z innymi procesami
-
na koniec wczytywane jest pierwsze 128 bajtów z pliku, w celu ustalenia
formatu pliku wykonywalnego
sp
Szukanie modułu ładującego - rozpoznanie formatu
pliku
Funkcja odpowiedzialna za szukanie modułu ładującego: search_binary_handler
Przy szukaniu przeglądana jest lista dostępnych formatów wykonywalnych
- wykonywane są dwa obroty pętli przeszukującej.
-
w pierwszym obrocie - wywoływane są po kolei funkcje ładujące dla wszystkich
formatów, aż jakaś, rozpozna format tego pliku, albo się skończy lista
-
jeśli żadna funkcja nie rozpoznała tego formatu, to podejmowana jest próba
załadowania modułu, który będzie rozpoznawał
-
na koniec następuje drugi obrót pętli, w celu sprawdzenia czy po załadowaniu
nowego modułu sytacja się zmieniła
Jeśli któraś funkcja rozpozna format tego pliku, to od razu ładuje go do
pamięci i wykonuje.
sp
Formaty plików wykonywalnych
W pliku exec.c znajdują się też
deklaracje zmiennych i definicje funkcji wykorzystywanych przez wszystkie
moduły ładujące, niezależnie od formatu.
Lista formatów
Dla każdego formatu plików wykonywalnych zdefiniowana jest struktura linux_binfmt,
w której trzymane są m.in. wskaźniki do funkcji ładujących program, ładujących
bibliotekę dzieloną oraz robiących zrzut programu (tzw. "core-dump"). Struktury
te polączone są w listę. Po starcie systemu lista ta zawiera standardowo
formaty: script, java,
a.out i elf. Można jednak
łatwo zmienić to ustawienie (także dodać nowe formaty)
Algorytm open_inode
Funkcja open_inode jest wykorzystywana w modułach ładujących przy wczytywaniu
pliku do pamięci. Działa następująco:
-
szukamy wolnego miejsca w tablicy deskryptorów plików funkcją get_unused_fd
-
w przypadku powodzenia szukamy miejsca w tablicy plików - funkcja get_empty_filp
-
wypełniamy strukturę w tablicy plików
-
próbujemy otworzyć plik o danym i-węźle
-
w przypadku powodzenia wpisujemy do tablicy deskryptorów plików procesu
wskaźnik do struktury w tablicy plików i zwiększamy liczbę odwołań do i-węzła
Algorytm read_exec
Funkcja read_exec wczytuje plik
wykonywalny do pamięci jądra lub użytkownika według następującego algorytmu:
-
wypełniamy strukturę file
-
sprawdzamy, czy plik można otworzyć
-
otwieramy go
-
sprawdzamy, czy z pliku można czytać
-
wczytujemy plik do pamięci - funkcja file.f_op->read
W przypadku, gdy wczytujemy do pamięci użytkownika, wcześniej sprawdzamy
prawa do zapisu funkcją verify_area.
mm
Usuwanie poprzedniego kontekstu
Za usuwanie poprzedniego kontekstu odpowiada funkcja flush_old_exec,
wywoływana w procedurach ładujących dla różnych formatów. Wykonuje
ona następujące czynności:
-
zwalnianie pamięci - zwalniane są segmenty danych, kodu i stosu -
funkcja exec_mmap
-
najpierw robiona jest kopia struktury mm_struct bieżącego procesu
-
dalej następuje sprawdzenie, czy tylko ten proces odwoływał się do
tej struktury, i jeśli tak, to jest ona zwalniana
-
zwalnianie sygnałów - funkcja flush_old_signals
- przywracana jest standardowa obsługa tylko tych sygnałów, dla
ktorych poprzedni proces ustawił własną procedurę obsługi. Sygnały, które
były ignorowane, pozostają nadal ignorowane.
-
zamykanie plików - funkcja flush_old_files
- z każdym deskryptorem otwartego pliku związana jest flaga informująca,
czy dany plik ma zostać zamknięty, przy wykonaniu exec. Tu następuje
przejrzenie wszystkich tych flag-bitów i zamknięcie tych plików, dla których
są ustawione.
Domena wykonywania (exec domain)
Każdy proces ma ustawioną swoją osobowość, oraz na jej podstawie ustawioną
domenę wykonywania. Ma stanowić wsparcie do wykonywania programów napisanych
na tę samą maszynę, ale pod inną wersją systemu UNIX. W jądrze jest implementowana
lista domen, z których każda ma przypisany pewien zakres osobowości. Dwie
różne domeny mogą obsługiwać ten sam typ osobowości, w szczególności do
domyślnej domeny Linux, występującej zawsze, przypisany jest pełen zakres
osobowości.
Domena wykonywania ma zapewnić poprawną obsługę sygnałów, zakładając,
że w innym systemie te same sygnały mogły występować pod innym numerami.
Ponadto domena wykonywania, może mieć własną obsługę błędu segmentacji.
Nigdzie w jądrze nie napotkałem, zarejestrowania innej domeny, niż
standardowa - Linux.
Ale np. przy procedurze ładującej program wykonywalny w formacie
ELF, dokonuję się sprawdzenia, czy nie był on napisany dla systemu UNIX
V wersja 4, i jeśli był to ustawia mu się odpowiednią osobowość i wyszukuje
domenę rozpoznającą tę osobowość.
sp
Bibliografia
-
Pliki źródłowe jądra Linuxa (wersja 2.0.32):
Autorzy: Sławek Poreda, Marcin Mucha