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