Do spisu tresci tematu 10.


Przedostatnia instrukcja w funkcji "start_kernel()" uruchamianej przy inicjalizacji systemu jest powolanie nowego procesu, procesu o identyfikatorze 1, zwanym powszechnie procesem init. Proces ten bedac caly czas w trybie jadra uruchamia dwa demony. Pierwszy do obslugi swap'a, drugi do opoznionego zapisu buforow na dysk. Nastepnie wywoluje funkcje setup(), ktora inicjalizuje urzadzenia i podmontowywuje do korzenia glowny system plikow. Otwiera terminal i ustawia na niego swoje stdin, stdout i stderr. Nastepnie wykonuje po kolei exec("/etc/init"); exec("/bin/init");exec("/sbin/init"). Jezeli zaden sie nie udal, to proces init przechodzi do nieskonczonej petli, w ktorej: 1. uruchamia shell 2. czeka na jego zakonczenie.
Przypadek, w ktorym nie udalo sie uruchomic zadnego inita jest malo ciekawy, dlatego zajmiemy sie teraz przypadkiem, w ktorym nam sie to udalo.
Wersji "initow" jest dosyc duzo, wiecej niz wersji jadra. Zwiazane jest to z tym, ze program init nie nalezy juz do kodu jadra, jest on zwyklym programem, dzialajacym w trybie uzytkownika, tyle ze z identyfikatorem root'a. Kazdy moze latwo go sobie zmienic na inny bez zmieniania jadra, wystarczy dotychczasowy program init, ktory jest w jednym z katalogow /etc, /bin lub /sbin, zamienic na nowy o takiej samej nazwie.
Ostatnio standardem staje sie init Systemu V, dlatego dzialanie procesu init opisze na jego przykladzie.


Prace procesu init mozemy podzielic na dwie czesci. Pierwsza, ktora jest wykonywana podczas ladowania systemu, polega na wczytaniu pliku /etc/inittab i uruchomieniu procesow zwiazanych z inicjacja systemu, wczytaniu poziomu dzialania (ang. runlevel) i uruchomieniu procesow zwiazanych z tym poziomem. Druga czesc to nadzorowanie pracy systemu. Praca ta polega na wznawianiu procesow, ktorych akcja zdefiniowana w inittabie bylo "respawn" , na przechwytywaniu sygnalow o zakloceniach zasilania i na uruchamianiu zwiazanych z nimi procesow; oraz na przejsciu na nowy poziom dzialania na zyczenie administratora. Przejscie to wiaze sie z zabiciem procesow, ktore nie sa powiazane z nowym poziomem, i uruchomieniem tych, ktore sa powiazane.


algorytm init 
wejscie: brak
wyjscie: brak

{

} / * init */


Kazda linia w tym pliku ma format: id:runlevels:action:process

id to niepowtarzalny dwuliterowy identyfikator linii, runlevels to poziomy, przy ktorych ma byc wykonana dana akcja, action okresla wlasnie te akcje, a process jest linia komendy, jaka ma byc wykonana.
Mamy zdefiniowane nastepujace akcje:

Pole runlevels dla akcji boot, bootwait, sysinit, initdefault jest ignorowane.
Jezeli chodzi o znaczenie poziomow dzialania, standartowo 's' oznacza poziom dla jednego uzytkownika, 1,2,3,4,5,6 poziomy wieloztkownikowe, przy czym 6 to poziom dla X-windows. Poziom 0 to zamkniecie systemu. Podkereslam, ze jest to standartowe ustawinie, tak na prawde to znaczenie ich definiuja procesy jakie sa przy nich uruchamiane. Standartowy plik /etc/inittab jest przedstawiony tutaj. Ale kazady moze zamienic go sobie jak chce i wetedy te poziomy moga miec zupelnie inne znaczenie.

Krotko o kolejnosci wykonywania procesow podczas inicjalizowania systemu:
Pierwszym poziomem dzialania po rozpoczeciu pracy inita jest sysinit. Potem uruchamiamy procesy, ktorych akcja jest wlasnie "sysinit" i czekamy na ich zakonczenie .Nastepnie sprawdzamy, jaki ma byc docelowy poziom; sprawdzenia tego dokonuje funkcja "GetInitDefault". Jesli tym poziomem jest tryb dla jednego uzytkownika (ang. single user mode), to po zakonczeniu poziomu sysinit od razu do niego przechodzimy, wpp po sysinit, a przed poziomem docelowym mamy jeszcze poziom "boot". W tym poziomie odpalamy procesy, ktorych akcja jest "boot" lub "bootwait". Po zakonczeniu procesow o akcji "bootwait" przechodzimy juz do docelowego poziomu. Zmiana poziomow zajmuje sie funkcja "BootTransitions " .
Procesy o akcji "boot" to jedyne procesy, ktore moga dzialac we wszystkich poziomach, reszta moze dzialac tylko w poziomach okreslonych przez "runlevels".

algorytm: GetInitDefault;
wejscie: brak;
wyjscie: brak;
{

} /* GetInitDefalut */


Najwazniejsza struktura danych w programie init jest CHILD; Opisuje ona kazde polecenie wczytane z inittaba oraz ewentualny proces z nim zwiazany.
struct CHILD {
int flags ; ---------/* informacje o statucie dzialania procesu */
int extsta; --------/* kod wyjscia procesu */
int pid; -----------/* identyfikator procesu, lub zero jesli nie jest uruchomiony */
time_t tm; --------/* czas ostatniego wznowienia */
int count; ---------/* liczba wznowien w ostatnich TESTTIME sekundach */
char id[8]; ---------/* pole id z inittaba tego polecenia */
char[12];----------/* poziomy, przy ktorych moze dzialac ten proces */
int action; ---------/* akcja */
char process[128]; ---/* linia komendy, jaka nalezy wykonac */
CHILD * new; ------/* pole to jest wykorzystywane przy ponownym czytaniu inittaba do zaznaczenia procesow, ktore sie powtarzaja */
CHILD * next; -----/* wskaznik na nastepny element listy dzieci */
} /* CHILD */
Mamy zdefiniowane jeszcze trzy stale zwiazane z wykonywaniem procesow:

define MAXSPAWN /* okresla maksymalna liczbe wznowien procesu w przeciagu ... */
define TESTTIME /* tylu sekund */
define SLEEPTIME /* w przypadku przekroczenia liczby wznowien, czas, jaki musi minac, aby ponownie init probowal uruchomic ten proces */

dygresja: W inicie, ktory opisuje, powyzsze stale sa zdefiniowane nastepujaco (10, 120, 300). Wystarczy wiec w przeciagu dwoch minut zalogowac sie i wylogowac, zeby zablokowac terminal na 5 minut.

Jezeli chodzi o flagi w strukturze CHILD to mamy:


Za dziecko uwazam zatem strukture, ktora powyzej przedstawilem, a nieraz takze proces z nia zwiazany ( jedynym wyjatkiem jest obsluga sygnalu SIGCHLD; tam pod pojeciem dziecko kryje sie proces, ktorego identyfikatorem ojca jest liczba 1( czyli dziecko w ujeciu Unixa)). Wiem, ze nie jest to najlepsze rozwiazanie, ale niestety nie wpadlem na lepsze, poza tym w kodzie tez bylo to tak nazwane (za przyklad daje nazwe struktury), tych, ktorych przyjecie takiej terminologii wprowadzilo w blad, z gory przepraszam.Pod pojeciem "proces" kryje sie zwykle "proces zwiazany z danym dzieckiem".


Zadanie to wykonuje funkcja ReadItab(). Jest ona wywolywana przy rozpoczeciu pracy przez inita, oraz przy przejsciu do innego poziomu dzialania.

algorytm: ReadItab
wejscie: brak;
wyjscie: brak;
{

}; /* ReadItab */



Sprawdzeniem, czy nie nalezy przejsc do innego poziomu zajmuje sie funkcja "BootTransitions()" . Jest ona wywolywana przy kazdym obrocie petli glownej, ale zmiany poziomu dokonuja sie znacznie rzadziej( przy inicjalizowaniu systemu oraz po zakonczeniu wszystkich naszych dzieci przy pracy na poziomie dla jednego uzytkownika ).
algortym: BootTransitions;
wejscie: brak;
wyjscie: brak;
{

} /* BootTransitions */


Proces init obsluguje sygnaly dwuetapowo. Pierwszy etap polega na zaznaczeniu na zmiennej globalnej "got_signlas" sygnalu, ktory przyszedl. Etap ten jest obslugiwany przez funkcje "signal_handler", ktora przechwytuje prawie wszystkie sygnaly. Wyjatkiem jest sygnal SIGCHLD (sygnal ten jest wysylany w przypadku smierci jakiegos naszego dziecka), ktory jest obslugiwany przez "chld_handler". Funkcja ta oprocz ustawienia odpowiedniego bitu w "got_signals" sprawdza ktore to bylo dziecko, i jesli byl to proces z inittaba to ustawia w jego strukturze CHILD flage ZOMBIE. Drugim, zasadniczym etapem przetwarzania sygnalu jest wykonanie zadan odpowiadajacych danemu sygnalowi. Etapem tym zajmuje sie funkcja "ProcessSignals".

algortym Ustawienie przechwytywania sygnalow;
wejscie: brak;
wyjscie: brak:
{

};

Przetwarzaniem sygnalow zajmuje sie funkcja "ProcessSignals". Funkcja ta jest wywolywana przy kazdym obrocie petli glownej.

algorytm: ProcessSignals;
wejscie: brak;
wyjscie: brak:
{

};/ * ProcessSignals*/

Sprawdzeniem, czy sa procesy, ktore nalezaloby uruchomic, zajmuje sie funkcja "StartEmIfNeeded".
algorytm: StartEmIfNeeded;
wejscie: brak;
wyjscie: brak;
{

}; /* StartEmIfNeeded */

Funkcja StartUp sprawdza czy dane dziecko nalezy uruchomic, jesli tak to wywoluje funkce Spawn.

algorytm: StartUp;
wejscie: dziecko, ktore nalezy sprawdzic, czy nie nalezy go uruchomic;
wyjscie: brak;
{

}; /* StartUp */

algorytm: Spawn; /* Uruchom proces */
wejscie: dziecko, ktore nalezy uruchomic;
wyjscie: brak;
{

}; /* Spawn */

Sprawdzeniem, czy mozna juz uruchamiac procesy, ktore przekroczyly maksymalna liczbe wznowien, zajmuje sie funkcja "FailCheck()". Jest ona wywolywana przy kazdym obrocie glownej petli.
algorytm: FailCheck;
wejscie: brak;
wyjscie: brak;
{

}/ * FailCheck */


/*Poziom do jakiego mamy przejsc po inicjalizacji systemu. */
id:5:initdefault:

/*Uruchamiamy ten skrypt przy inicjalizacji systemu (standartowo wykonuje on polecenie '/sbin/fsck -a -A', ktore sprawdza czy wyszystkie systemy plikow aktualnie podlaczone sa poprawne, automatycznie usuwajac napotkane bledy ; skrypt ten podmontowywuje takze systemy plikow opisane z pliku /etc/fstab)*/
si::sysinit:/etc/rc.d/rc.S

/* Skrypt ten uruchamiamy kiedy przechodzimy do poziomu dla jednego uzytkownika. Standardowo odlacza on wszystkie systemy plikow i wylacza swap'a. */
su:S:wait:/etc/rc.d/rc.K

/* Skrypt ten uruchamiamy kiedy przechodzimy do poziomu wielouzytkownikowego. Standardowo inicjalizuje on siec, uruchamia on demona poczty, sprawdza CD i jesli jest to podlacza je do katalogu /cdrom. */
rc:123456:wait:/etc/rc.d/rc.M

/* Trzech kroli powoduje wylaczenie komputera. */
ca::ctrlaltdel:/sbin/shutdown -t3 -rf now

/* Co robic kiedy zanika napiecie. */
pf::powerfail:/sbin/shutdown -f +5 "THE POWER IS FAILING"

/* Jesli napiecie wraca to: */
pg:0123456:powerokwait:/sbin/shutdown -c "THE POWER IS BACK"

/* Uruchamianie odpowiedniej ilosci terminali: */
c1:12345:respawn:/sbin/agetty 38400 tty1
c2:12345:respawn:/sbin/agetty 38400 tty2
c3:45:respawn:/sbin/agetty 38400 tty3
c4:45:respawn:/sbin/agetty 38400 tty4
c5:45:respawn:/sbin/agetty 38400 tty5
c6:456:respawn:/sbin/agetty 38400 tty6

/* Poziom 6 to standardowo uruchomienie X-windows. Skrypt rc.6 wlasnie je uruchamia */
x1:6:wait:/etc/rc.d/rc.6

/* koniec pliku /etc/inittab */


Tak naprawde to program init dziala troche inaczej. Sa to w zasadzie dwa programy w jednym. Jeden jest wykonywany jezeli identyfikatorem procesu jest liczba 1, natomiast drugi wpp. Pierwszy program to wlasnie ten, ktory opisalem na tej stronie, natomiast drugi sluzy do powiadomienia "prawdziwego inita", ze chcemy zmienic poziom dzialania.

algorytm: Prawdziwy init;
wejsie: Poziom, na ktory chcemy przejsc, lub brak jesli to ma byc proces o id=1.
wyjsie: brak;
{

} /* Prawdziwy init */

Pozwolilem sobie na takie male oszustwo, zeby opis dzialania inita bylo zgodny z opisem przedstawianym w wielu ksiazkach np Bacha, Stevensa. Mam nadzieje, ze zostanie mi to wybaczone.




Autor: Krzysztof Micek