exec BLCDLQKKOEJX3ELCFLFHTOVDRA2BZAC665P7V7A





2.1.4 Algorytm exec




Do spisu
tresci tematu 2

2.1.4 Wywolywanie innych programow - algorytm exec





Spis tresci


Informacje ogolne

Interfejs programisty w jezyku C

Procedura exec w Linuxie

Czynnosci wstepne

Przenoszenie napisow miedzy przestrzeniami
adresowymi

Rozpoznawanie formatu pliku

Usuwanie poprzedniego kontekstu

Bibliografia





Informacje ogolne

Przeznaczenie

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.



Funkcje towarzyszace

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( ... )







Interfejs programisty w jezyku C

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.

Przekazywanie parametrow wywolania

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).

Przekazywanie srodowiska

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).

Nazwa pliku a nazwa sciezkowa

Mozemy wreszcie wybierac pomiedzy pelna nazwa sciezkowa programu, a
tylko ostatnim jej czlonem. W tym drugim przypadku (kod: p) wykorzystana
zostanie zmienna srodowiskowa PATH.

Laczenie sposobow wywolania





lista argumentow

tablica wskaznikow



wykorzystanie zmiennej PATH


execlp



execvp




wywolanie zwykle


execl



execv




jawne okreslenie srodowiska


execle



execve




Tabela przedstawia zestawienie mozliwych sposobow wywolania algorytmu
exec






Procedura exec w Linuxie

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:


odczytanie i-wezla pliku z programem wykonywalnym i wykonanie
wstepnych kontroli majacych na celu ustalenie, czy uruchomienie nowego
programu jest mozliwe

kopiowanie argumentow wywolania
i srodowiska do tymczasowego obszaru pamieci, ktory pozniej zostanie przylaczony
do przestrzeni adresowej procesu

odszuaknie procedury odpowiedzialnej za dalsze
ladowanie i uruchomienie programu


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.






Czynnosci wstepne

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:


czy podana nazwa sciezkowa reprezentuje
zwykly plik (a nie np. katalog). Tylko zwykle pliki moga byc programami
wykonywalnymi

czy choc jeden bit zezwalajacy
na wykonanie jest ustawiony. Dokladniejszy test - czy dany uzytkownik
ma prawo wykonywac ten program - przeprowadzany jest pozniej

czy caly system plikow nie zostal
zamontowany z ustawionym bitem IS_NOEXEC. Jesli tak sie stalo,
to nie wolno wykonywac zadengo pliku z tak zamontowanego systemu

za pomoca funkcji permission,
czy dozwolone jest wykonanie
programu przez aktualnego uzytkownika

nie dopuszcza sie do ladowanie
plikow aktualnie otwartych do zapisu. Modyfikacja kodu programu mialaby
katastrofalne skutki, gdyby doszlo do niej w trakcie jego ladowania

jesli jest konieczna zmiana obowiazujacego
identyfikatora uzytkownika badz grupy, to nowe wartosci wspisuje sie
do odpowiednich pol struktury linux_binprm..

Warto zwrocic uwage, ze nie zawsze ustawiony bit ustanowienia
grupy oznacza rzeczywista zmiane obowiazujacego identyfikatora grupy. Moze
mianowicie wystapic sytuacja, w ktorej przy ustawieniu wspomnianego bitu,
wyzerowany pozostal bit wykonywania dla grupy. Tak oznaczony plik podlega
zajmowaniu obowiazkowemu.

Z przyczyn bezpieczenstwa nie
zezwala sie na zmiane identyfikatora uzytkownika lub grupy jesli zachodzi
ktorykolwiek z warunkow:



system plikow zostal zamontowany z ustawionym bitem zabraniajacym zmian
identyfikatorow uzytkownika lub grupy

proces podlega sledzeniu

czesc kontekstu poziomu systemu jest dzielona z innym procesem (jest
to mozliwe poprzez specjalne wywolanie funkcji systemowej fork



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.






Przenoszenie napisow pomiedzy przestrzeniami adresowymi

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.






Rozpoznawanie formatu pliku

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




Usuwanie poprzedniego kontekstu

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.

Zwalnianie pamieci

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.

Obsluga sygnalow

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.

Zamykanie plikow

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.




Bibliografia

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




Autor: Adam Wasylewski
















Wyszukiwarka

Podobne podstrony:
04 The Fork Exec Model
ref exec
exec key
exec
function odbc exec
04 The Fork Exec Model
function pg exec
function ora exec
function ovrimos exec
exec c (5)
function odbc exec
function exec

więcej podobnych podstron