Procedura exec ma za zadanie zmienic kontekst poziomu uzytkownika danego procesu na kopie programu wykonywalnego umieszczonego w pliku dyskowym. Tym samym, statyczny kod, jakim jest program na dysku, staje sie dynamicznym, dzialajacym w pamieci operacyjnej, procesem (byc moze nie jest to jedyny proces odpowiadajacy danemu programowi). Jesli procedura powiedzie sie, to poprzedni kontekst procesu zostanie bezpowrotnie stracony.
Uwaga: nie jest tworzony zaden nowy proces, a jedynie zmienia sie kontekst procesu juz istniejacego. W szczegolnosci nie zmienil sie identyfikator i jego miejsce w hierarchii procesow. Do tworzenia nowych procesow w Unixie wykorzystuje sie funkcje fork.
Wywolanie funkcji exec wystepuje na ogol wraz z wywolaniami
funkcji fork i wait,
co pozwala realizowac w Unixie mechanizm typu spawn dostepny np.
w systemie MS-DOS. Mechanizm ten polega na chwilowym przekazaniu sterowania
do uruchamianego programu, a po jego zakonczeniu, kontynuowaniu wykonywania
procesu wywolujacego bez ustraty zadnych danych. Schematyczny kod realizujacy
opisany mechanizm w Unixie moglby wygladac nastepujaco:
if( fork() == 0 ) exec... /* tu nalezy wstawic wywolania wlasciwej funckji bibliotecznej jezyka C */ else wait( ... )
W C, typowym dla srodowyska unixowego jezyku programowania, istnieje az szesc roznych sposobow wywolania funkcji systemowej exec. Sa to rozne funckje biblioteczne jezyka, ktore w rzeczywistosci odwoluja sie do jednej tylko funkcji systemowej. W tym punkcie przedstawionych zostanie wymienione szesc funkcji bibliotecznych, a w dalszej czesci opracowania zajmiemy sie tylko wewnetrzym ich odpowiednikiem tj. funkcja systemowa.
Parametry wywolania nowego programu (napisy, ktore umieszcza sie zwykle za nazwa programu w linii komend systemu operacyjnego) mozana podac badz jako kolejne argumenty wywolania funkcji (odpowiada tej mozliwoasci kod l w nazwie funkcji) badz tez, jako tablice wskaznikow na napisy (kod: v). W kazdym z wymienionych przypadkow ciag argumentow musi byc zakonczony zerem (a nie napisem pustym, ktory stanowi pelnoprawny argument).
Srodowisko jest pomocniczym zbiorem napisow zorganizowanym wg stalego wzorca: "nazwa-zmiennej=wartosc-zmiennej". Mozliwe jest jawne wyspecyfikowanie srodowiska, z ktorym zostanie uruchomiony nowy program (kod: e). Jesli tego nie zrobimy, zostanie mu przekazane srodowisko, w jakim dziala proces wywolujacy (jest ono dostepne poprzez zmienna globalna jezeka C o nazwie environ).
Mozemy wreszcie wybierac pomiedzy pelna nazwa sciezkowa programu, a tylko ostatnim jej czlonem. W tym drugim przypadku (kod: p) wykorzystana zostanie zmienna srodowiskowa PATH.
lista argumentow | tablica wskaznikow | |
wykorzystanie zmiennej PATH |
execlp |
execvp |
wywolanie zwykle |
execl |
execv |
jawne okreslenie srodowiska |
execle |
execve |
W systemie Linux istnieje funkcja sys_execve (do ktorej odwoluja sie funkcje biblioteczne jezyka C) dostepna dla programow uzytkownika, ktora po dodkonaniu drobnych, technicznych manipulacji wywoluje do_execve. Ta ostatnia realizuje wlasciwy algorytm exec.
Glowne kroki wykonywane przez algorytm exec to:
Trzeba w tym miejscu wyjasnic, ze Linux obsluguje, w pelni automatycznie, wiele formatow plikow wykonywalnych (np. pliki zapisane w typowym dla starszych wersji Unixa standardzie a.out i nowoczesnym, coraz powszechniejszym standardzie elf). Co waznejsze jednak, budowa wewnetrzna jadra pozwala niezwykle latwo usuwac istniejace lub dodawac nowe formaty przy minimalnych zmianach istniejacego kodu systemu. Formaty plikow wykonywalnych i procedury ich obslugi zostaly omowione dakladniej w pukcie 2.2.4.1; tutaj zajmiemy sie jedynie wyborem formatu odpowiadajacego ladowanemu plikowi.
W czasie ladowania programu wykonywalnego wykorzystuje sie strukture linux_binprm
struct linux_binprm { char buf[128]; /* niewielki bufor pozwalajacy zaladowac poczatkowy fragment pliku, ktory okresla jego format */ unsigned long page[MAX_ARG_PAGES]; /* tablica stron zawierajacych argumenty wywolania i srodowisko (aktualnie stala MAX_ARG_PAGES ustawiona jest w ten sposob, ze argumenty wraz ze srodowiskiem moga zajmowac do 128kB, czyli calkiem sporo) */ unsigned long p; /* adres w pamieci zawierajacej argumenty i srodowisko; adresy wyzsze sa zajete, nizsze - moga nawet nie miec przydzielonych stron */ int sh_bang; /* znacznik wlaczenia iterpretowania; opisany w pukcie 2.2.4.1 */ struct inode * inode; /* i-wezel pliku wykonywalnego */ int e_uid, e_gid; /* obowiazujacy identyfikator uzytkownika i grupy dla nowego programu */ int argc, envc; /* liczba napisow, odpowiednio: argumentow i srodowiska */ char * filename; /* nazwa sciezkowa pliku wykonywalnego */ unsigned long loader, exec; /* pomocnicze adresy uzywane w czasie ladowania */ int dont_iput; /* znacznik zwolnienia i-wezla */ };
Do odszukania i-wezla zwiazanego z danym plikem wykonywalnym wykorzystuje sie procedure open_namei. Nastepnie, po przypisaniu wartosci poczatkowych niektorym polom struktury linux_binprm wywoluje sie prepare_binprm, ktora to procedura wykonuje wlasciwe testy.
Przeprowadza sie nastepujace kontrole:
Wyjatek od powyzszych regul: administratorowi wolno uruchomic dowolny program z pominieciem wspomnianych zasad bezpieczenstwa.
Na koniec odczytywane jest pierwsze 128 bajtow pliku zawierajace na ogol informacje potrzebna do rozpoznania formatu pliku wykonywalnego. Na poczatku pliku znajduja sie najczesciej tzw. liczby magiczne okreslajace jego rodzaj.
Ladowanie nowego programu powoduje wymiane kontekstu poziomu uzytkownika procesu z usunieciem poprzedniej jego wersji. Istnieje jednak moment, w ktorym obie przestrzenie adresowe (a przynajmniej okreslone ich fragmenty) musza wspolistniec w pamieci. Bez tego nie bylo by mozliwe przeniesienie napisow oznaczajacych argumenty wywolania i srodowiska nowego programu.
Linux radzi sobie z tym problemem przydzielajac nowe strony pamieci, na ktorych umieszcza kopie odpowiednich danych (tablica page w linux_binprm). Po udanym ladowaniu strony te zostana dolaczone do nowej przestrzeni adresowej, a jesli ladowanie nie powiedzie sie, zwolni sie je. Ponizej przedstawiono uklad pamieci po skopiowaniu w procedurze do_execve: nazwy pliku, srodowiska i argumentow wywolania.
Uwaga: nie sa tworzone tablice wskaznikow do kolejnych napisow srodowiska ani argumentow wywolania. Ta ostatnia lista (jak sie przekonamy w punkcie 2.1.4.1dotyczacym formatow plikow wykonywalnych) moze jeszcze zostac rozszerzona.
Jako ciekawostke odnotowac mozna fakt, iz adresy poczatkow kolejnych napisow nie sa nawet przechowywane. W odpowiednim momencie Linux przeglada pamiec wiedzac jedynie ile napisow napotka i, ze kazdy z nich zakonczony jest znakiem pustym. Na tej podstawie tworzone sa dopiero odpowiednie talbice wskaznikow.
Po pomyslnym zakonczeniu ladowania programu opisywany obszar pamieci zostanie przylaczony jako stos na koncu wirtualnej przestrzeni adresowej procesu przez funkcje setup_arg_pages.
Jak wspomniano wczesniej, Linux wspiera wiele roznorodnych formatow plikow wykonywalnych. Ladowanie pliku w kazdym z tych formatow przebiega nieco inaczej, choc czesc czynnosci jest wspolna. W szczegolnosci, wszystkie opisane wczesniej kontrole wstepne i przenoszenie danych miedzy przestrzeniami adresowymi odbywa sie zanim jadro podejmie probe rozpoznania formatu pliku. Wynika z tego, ze sa to czynnosci na tyle uniwersalne, iz powinny zostac wykonane niezaleznie od formatu ladowanego pliku.
Rozpoznanie formatu dokonywane jest na podstawie odczytanego wczesniej poczatkowego fragmentu pliku. Kolejno sprawdzane formaty zapisane sa na liscie wskazywanej przez zmienna globalna jadra formats w strkturach linux_binfmt w procedurze search_binary_handler. Jesli nie uda sie znalezc formatu odpowiadajacego ladowanemu plikowi, a zachodzi podejrzenie, ze modul jadra obslugujacy dany format moze zostac zaladowany dynamicznie, to po takim ladowaniu ponownie podejmuje sie probe odnalezienia pasujacego formatu. Jesli i ta proba nie powiedzie sie, to funkcja systemowa exec zasygnalizuje blad (nieznany format pliku) i wroci do wolajacego ja procesu.
Ostatnia opisana cecha czyni Linuxa systemem bardzo oszczednym. Jesli zezwolimy na dynamiczne ladowanie modulow jadra odpowiedzialnych za poszczegolne formaty, to beda one sprowadzane do pamieci tylko wtedy, gdy zajdzie potrzeba zaladowania programu w okreslonym formacie. Jesli wiec dysponujemy modulem jadra zwiazanym z bardzo rzadko wystepujacym formatem, to smialo mozemy go dolaczyc bez obawy o utrate miejsca w pamieci lub urzadzeniu wymiany - program ladujacy pojawi sie w niej w odpowiednim momencie i do tego - na krotko.
Procedury rozpoznajace formaty potrafia, oprocz identyfikacji obslugiwanych przez siebie plikow, dokonczyc dziela ladowania programu do pamieci. Zostaly one opisane w punkcie 2.2.4.1
Mimo, ze od momentu rozpoznania formatu pliku wykonywalnego cala inicjatywa przekazana zostaje funkcjom zwiazanym z danym formatem, sa jednak pewne typowe czynnosci, ktore powinny zostac wykonane zawsze. Do czynnosci tych nalezy przede wszystkim usuniecie, niepotrzebnych juz teraz, fragmentow kontekstu procesu dotyczacych poprzednio dzialajacego programu. Czynnosci te powinny zostac zrealizowane przy kazdorazowym ladowaniu programu, choc o najwlasciwszym momencie ich uruchomienia zadecuduje procedura specyfyczna dla formatu.
Usuniecie poprzedniego kontekstu realizowane jest w procedurze flush_old_exec, ktorej dzialanie sprowadza sie glownie do wywolania, omowionych dalej, specjalistycznych procedur odpowiedzialnych za usuwanie rozmaitych elementow kontesktu. Oprocz tego, wpisuje ona ostatnia czesc nazwy sciezkowej uruchamianego programu do pola comm umieszczonego w strukturze task_struct, ktore jest uzywane do celow rozliczeniowych i zrzucania na dysk obrazu procesu przerwanego sygnalem.
Segmenty pamieci danych, kodu i stosu poprzedniego procesu nie beda juz potrzebne. Zwolni je procedura exec_mmap pozostawiajac kontekst procesu bez zadnych przylaczonych segmentow. Warto zwrocic uwage, ze nie ma w tym miejscu rozroznienia pomiedzy segmentami prywatnymi, ktore nalezy obowiazkowo zwalniac, a segementami dzielonymi (czy to danych, czy kodu), ktore powinny pozostac w pamieci tak dlugo, jak istnieje choc jeden korzystajacy z nich proces. Problemem tym zajma sie wlsciwe procedury obslugi pamieci wywolywane przez exec_mmap.
Inna ciekawa obserwacja jest fakt, ze w Linuxie nie ma (opisywanego np. przez Bacha na stronie 242) bitu lepkosci (ang. sticky bit). Bit taki, zwiazany z kazdym segmentem kodu umieszczonym w pamieci, mialby zapewniac wieksza efektywnosc dzialania systemu osiagana poprzez stale przewchowywanie kodu czesto uruchamianych programow. Zastosowane w Linuxie mechanizmy stronicowania zwalniaja administratora z koniecznosci decydowania o tym, ktore programy trzeba przechowywac w pamieci, a ktore nie.
Moze zdarzyc sie sytuacja, w ktorej proces zmieni standardowa obsluge pewnych sygnalow, a nastepnie wywola inny program poleceniem exec. Nie zawsze pozostawienie niezmienionej obslugi sygnalow przy zmianie kontekstu ma sens (kod obslugi sygnalow mogl przestac istniec). Z drugiej strony, nachalne przywrocenie standardowej obslugi wszystkich sygnalow pozbawiloby tworcow oprogramowania silnego narzedzia programistycznego.
Wybrane w Unixie rozwiazanie kompromisowe przywraca standardowa obsluge tylko tych sygnalow, dla ktorych ustawiono wczesniej nowa procedure obslugi. Oznacza to, ze sygnaly, ktore byly ignorowane w programie wywolujacym, pozostana takimi w programie wywolywanym. Za zmiane sposobu reagowania na sygnaly odpowiada procedura flush_old_signals.
Z kazdym deskryptorem otwartego pliku zwiazana jest w procesie flaga informujaca, czy dany plik powinien zostac automatycznie zamkniety przy odwolaniu do exec (ang. close on exec). Dzieki tej mozliwosci, pewne pliki, co do ktorych mamy pewnosc, ze nie beda potrzebne nowemu programowi, mozna zaznaczyc jako przeznaczone do automatycznego zamkniecia. Ich deskryptory zostana w odpowiednim momencie zwolnione, udostepniajac wiecej zasobow kolejnemu zadaniu. Zamykanie plilow odbywa sie w procedurze flush_old_files, a zmiane sposobu ich traktowania uzyskujemy za pomoca funkcji fcntl.
M. J. Bach, WNT Warszawa 1995,
rozdzial 7.5: Wywolywanie innych programow, strony 234-244
W. R. Stevens WNT Warszwa 1996,
rozdzial 2.5.3: Funkcja systemowa exec, strony 82-84