Ćwiczenie 1.
Kompilacja jądra i wywołania systemowe
A. Cel ćwiczenia
Celem ćwiczenia jest zapoznanie się z mechanizmem realizacji wywołań
systemowych (ang. system calls, tłumaczone również jako funkcje systemowe). W
trakcie ćwiczenia należy dodać do systemu 2 nowe wywołania systemowe, które
mają przeglądać listę deskryptorów procesów i zwracać informacje o procesie
bądź procesach. Zakres zwracanych informacji jest uszczegóławiany przez
prowadzącego laboratoria.
B. Katalog możliwych uszczegółowień zadania przez prowadzącego. Prowadzący może
także zaproponować własne odmienne uszczegółowienie spoza listy.
Do opracowania 2 wywołania systemowe realizujące następującą funkcjonalność:
1. Zwrócić pid procesu mającego najwięcej dzieci, zwrócić liczbę dzieci takiego
procesu.
2. Zwrócić pid procesu mającego najdłuższą ścieżkę potomków prowadzącą do
procesu niemającego dzieci, zwrócić długość tej ścieżki. Pominąć proces o
podanym w parametrze identyfikatorze pid.
3. Zwrócić pid procesu mającego największą liczbę potomków (dzieci, wnuków, itp.),
zwrócić liczbą potomków dla tego procesu. Pominąć proces o podanym w parametrze
identyfikatorze pid.
4. Zwrócić pid procesu mającego największą liczbę potomków (dzieci, wnuków, itp.)
mieszczącą się w przedziale (A i B podawane jako parametry), zwrócić
liczbą potomków dla tego procesu.
5. Zwrócić pid procesu mającego największą liczbę potomków na kolejnych 3
poziomach (dzieci, wnuków, prawnuków), zwrócić liczbą potomków dla tego
procesu. Pominąć proces o podanym w parametrze identyfikatorze pid.
6. Zwrócić pid procesu mającego największą liczbę potomków na kolejnych 3
poziomach (dzieci, wnuków, prawnuków) mieszczącą się w przedziale (A i B
podawane jako parametry), zwrócić liczbą potomków dla tego procesu.
7. Zwrócić pid procesu mającego największą liczbę potomków na kolejnych N (N
podawane jako parametr) poziomach (dla N równego 3 będą to: dzieci, wnukowie,
prawnukowie), zwrócić liczbą potomków dla tego procesu.
C. Wiadomości ogólne
Wywołania systemowe są to funkcje realizowane przez jądro systemu
operacyjnego w kontekście danego procesu. Jest to jedyny dopuszczalny
dla procesów użytkowych sposób wejścia do jądra systemu. Przykładowymi
wywołaniami systemowymi są: READ, WRITE, FORK, EXIT. Należy odróżnić
wywołania systemowe od realizujących je funkcji bibliotecznych: read(),
write(), fork(), exit().
W systemie MINIX wywołania systemowe są realizowane wewnątrz jednego z
modułów-serwerów: MM lub FS, w zależności od rodzaju samego wywołania.
Wszystkie wywołania dotyczące szeroko rozumianych operacji plikowych
(np. OPEN, READ, WRITE, IOCTL) są obsługiwane przez FS. Pozostałe
wywołania (np. GETPID, FORK, EXIT) są obsługiwane przez MM. W obu
modułach, w plikach table.c, znajdują się tablice call_vec, które
określają, które wywołania systemowe są w danym module obsługiwane i
jaka funkcja tym się zajmuje. Procesy użytkowe maja do swojej
dyspozycji odpowiednie procedury biblioteczne (np. open(), fork() itd.).
Zapoznaj się z plikami źródłowymi potrzebnymi do generacji
jądra systemu Minix 2.0 (SM). Pliki znajdują się w następujących
katalogach:
/usr/src/kernel- źródła wszystkich sterowników oraz kodu jądra
/usr/src/mm - źródła modułu Memory Menager (MM)
/usr/src/fs - źródła modułu File System (FS)
/usr/src/tools - źródła programów służących do łączenia części SM w
jeden bootowalny program oraz do inicjacji systemu.
D. Dodanie do systemu nowego wywołania systemowego (przykładowa nazwa
nowego wywołania: NEWSYSCALL)
Należy przeanalizować jak zdefiniowana jest jedna z istniejących funkcji
systemowych i wykonać analogiczne rozszerzenia kodu jądra dla nowej funkcji.
Poniżej opisano kroki niezbędne dla dodania nowego wywołania systemowego
(funkcji systemowej) NEWSYSCALL:
1. W pliku /usr/include/minix/callnr.h dodać identyfikator nowego
wywołania systemowego NEWSYSCALL i ewentualnie zwiększyć o jeden
stałą N_CALLS zwiększając w ten sposób rozmiar tablicy wywołań systemowych.
2. Napisać właściwą procedurę obsługi do_newsyscall i umieścić ją na przykład
w pliku /usr/src/mm/main.c Na rzecz obsługi sytuacji błędnych nowa funkcja
powinna korzystać ze stałych o błędach (np. ENOENT) zdefiniowanych w pliku
/usr/include/errno.h
Prototyp funkcji umieścić w pliku /usr/src/mm/proto.h.
_PROTOTYPE( int do_newsyscall, (void) );
PUBLIC int do_newsyscall()
{
...
}
W zdefiniowanej przez nas procedurze do_newsyscall mamy możliwość odwoływania
się do zmiennych typu message:
mm_in - zawiera dane przekazywane do wywołania,
mm_out - zawiera dane zwracane do procesu wywołującego.
W pliku /usr/src/mm/param.h znajdują się definicje ułatwiające odwołania do
elementów wyżej wymienionych struktur. Zapoznaj się ze znaczeniem pola
mp_flags struktury mproc, zwróć uwagę na flagę IN_USE.
3. W pliku /usr/src/mm/table.c w tablicy call_vec w odpowiednim miejscu
wstawić adres (nazwę) funkcji do_newsyscall, zaś w pliku /usr/src/fs/table.c w
tym samym miejscu umieścić adres pusty funkcji, no_sys. MM i FS mają zdublowaną
tablicę call_vec. Rejestrujemy nową funkcję tylko w jednej z nich.
4. Dokonać rekompilacji i przeładowania systemu Minix z nowym jądrem.
Procedura ta ma różny przebieg w zależności od tego, czy ćwiczenie wykonywane
jest w systemie Minix zainstalowanym na twardym dysku, w systemie Minix
pracującym pod kontrolą emulatora Qemu, czy też wreszcie wykonywane jest w
specjalnie przygotowanej dystrybucji systemu Minix w całości ładowanej do
RAM-dysku.
Przykładowa rekompilacja i restart systemu Minix:
a. przejście do katalogu /usr/src/tools
b. zapoznanie się z akceptowalnymi zleceniami dla programu make (czyli
z celami w pliku konfiguracyjnym Makefile) oraz z zawartością skryptu
mkboot,
c. kompilacja nowego jądra wraz z utworzeniem dyskietki startowej
z nowym jądrem:
$ make hdboot
(inne cele, możliwości m.in.: "make all", "make fdboot", "make clean" itp)
d. Po zachowaniu zmienionych wersji plików źródłowych należy
wykonać restart systemu z wykorzystaniem utworzonego jądra.
$ cd
$ shutdown (lub wciśnięcie )
$ boot
e. Testowanie własności nowego jądra. Ewentualne załadowanie z
zewnątrz uprzednio zachowanych zmian źródeł systemu, a w
szczególności zawartości plików nagłówkowych, które powinny być
spójne dla jądra i przygotowywanych pod system z nowym jądrem
aplikacji.
E. Demonstracja rezultatów
1. Wywoływanie w procesie funkcji systemowej
Predefiniowane funkcje systemowe wywoływane są za pośrednictwem funkcji
opatulających zdefiniowanych w bibliotece standardowej stdlib.h (np. fork(), wait(),
itp.).
Dodawane w tym zadaniu niestandardowe wywołanie systemowe nie ma takiej funkcji
opatulającej. Aby bezpośrednio wywołać konkretną funkcję systemową należy
skorzystać z funkcji bibliotecznej _syscall (/usr/include/lib.h). Jej pierwszym
argumentem jest numer serwera (MM lub FS), drugim numer wywołania systemowego,
trzecim wskaźnik na strukturę message, w której umieszczamy dane dla wywołania.
Deklaracja struktury message znajduje się w pliku /usr/include/minix/type.h. Do
przekazania numeru PID procesu zaleca się użycie pola m1_i1 tej struktury.
Funkcję _syscall() zadeklarowano w pliku /usr/src/lib/others/syscall.c
2. Oczekiwana demonstracja
Należy opracować i zrealizować demonstrację zaimplementowanych funkcji
systemowych w postaci skryptu shella uruchamiającego własne programy o
zapotrzebowanych cechach w scenariuszu potwierdzającym prawidłowość
implementacji funkcjonalności użytych wywołań systemowych zgodnie z
uszczegółowieniem prowadzącego laboratorium.