Jądro jest centralną częścią systemu operacyjnego, odpowiedzialną za zarządzanie zasobami maszyny.
Z założenia mikrojądro nie udostępnia usług, a jedynie mechanizmy umożliwiające zrealizowanie takich usług. Jądro zawiera jedynie niezbędne elementy:
Pozostałe zadania takie jak obsługa urządzeń i sieci czy systemów plików realizowane są w trybie użytkownika przez serwery.
Rys. 1. By Wooptoo – Own work, Public Domain, https://commons.wikimedia.org/w/index.php?curid=4265836
Zaleta mikrojądra: większa niezawodność
Wada: mniejsza wydajność
Rys. 2. By Faisal Akeel at the English language Wikipedia, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=11479076
Zadania (ang. tasks) mikrojądra:
Podstawowe serwery wchodzące w skład jądra:
fork()
, exec()
, exit()
, kill()
chdir()
, open()
, read()
, select()
, write()
(więcej o systemach plików na zajęciach 9 i 10)
Charakterystyczne dla systemu Minix:
rs
jest rodzicem uruchamianych przez siebie procesów serwerów i sterowników
Sprawdź, jakie procesy są potomkami serwera rs
.
Procesy w trybie użytkownika mają dostęp tylko do własnej przestrzeni adresowej. Nie mogą bezpośrednio odczytywać ani zapisywać danych znajdujących się pamięci innych procesów. Do wymiany danych muszą skorzystać z funkcji dostarczanych przez jądro systemu. Tradycyjnymi metodami komunikacji w systemach operacyjnych są łącza, gniazda, pamięć dzielona oraz sygnały. Minix umożliwia komunikację na dwóch poziomach – niskim (wymiana komunikatów i kontrolowane udostępnianie przestrzeni adresowej) oraz wysokim (sygnały oraz pamięć dzielona i semafory – analogiczne do pakietu IPC w systemach uniksowych).
Jądro Miniksa umożliwia przekazywanie komunikatów ustalonego rozmiaru (64-bajtowych).
Każdy komunikat jest typu message
i składa się z:
Wszystkie możliwe formaty komunikatów zadeklarowane są w /usr/src/minix/include/minix/ipc.h
Identyfikator (endpoint) jednoznacznie określa proces. Składa się z indeksu procesu w tablicy procesów oraz numeru pokolenia. Numer pokolenia zabezpiecza przed przypadkowym odczytaniem danych wysłanych do procesu, który już zakończył działanie, przez nowy proces, któremu został przydzielony ten sam indeks.
Istnieją trzy identyfikatory specjalne: ANY
, NONE
i SELF
, których znaczenia łatwo się domyśleć.
Definicję identyfikatora i szczegółowy jego opis znajdziemy w /usr/src/minix/include/minix/endpoint.h
Podstawowe operacje służące do komunikacji są synchroniczne:
ipc_send(endpoint_t dest, message *m)
– blokujące wysłanie: nadawca czeka aż odbiorca odbierze wiadomość
ipc_receive(endpoint_t src, message *m, int *status)
– blokujące odebranie wiadomości
ipc_sendrec(endpoint_t src_dest, message *m)
– wysłanie wiadomości, po którym nadawca czeka na odpowiedź od odbiorcy
Po wysłaniu wiadomości jądro kopiuje wiadomość z przestrzeni nadawcy do przestrzeni odbiorcy. Ściśle zdefiniowane zasady określają, do jakich procesów dany proces może wysłać wiadomość. Podyktowane jest to względami bezpieczeństwa i ma także zabezpieczać przed możliwością zakleszczenia.
Pozostałe funkcje nie są synchroniczne:
ipc_sendnb(endpoint_t dest, message *m)
– nieblokujące wysłanie
ipc_notify(endpoint_t dest)
– nieblokujące powiadomienie bez przekazywania wiadomości
ipc_senda(asynmsg_t *table, size_t count)
– asynchroniczne wysłanie
Przekazywanie większej ilości danych przy użyciu 64-bajtowych komunikatów nie jest efektywne. Dlatego w systemie Minix opracowano mechanizm kontrolowanego udostępniania fragmentów pamięci innym procesom: /usr/src/minix/include/minix/safecopies.h.
Więcej na ten temat dowiesz się z wiki.minix3.org/developersguide:memorygrants.
Prześledźmy działanie serwera na przykładzie serwera pm
.
Zauważmy najpierw, że znane nam z poprzednich zajęć _syscall()
polega głównie na
wywołaniu ipc_sendrec()
: /usr/src/minix/lib/libc/sys/syscall.c.
Natomiast podstawowym działaniem serwera jest oczekiwanie na przychodzące wiadomości:
/usr/src/minix/servers/pm/main.c (wiersz 62).
Funkcja sef_receive_status(ANY, &m_in, &ipc_status)
zdefiniowana w
/usr/src/minix/lib/libsys/sef.c
wywołuje ipc_receive()
.
Pole m_type
(wiersz 79) przechowuje numer wywołania systemowego. Jeśli jest on poprawny,
zostaje wywołana funkcja odpowiadająca temu numerowi zgodnie z zawartością
tablicy call_vec[]
zdefiniowanej w /usr/src/minix/servers/pm/table.c.
Na końcu (wiersz 107) serwer odpowiada procesowi, który zgłosił wywołanie systemowe, wykonując
reply()
, które wywołuje ipc_sendnb()
(wiersz 268).
Zastanów się, jak serwer pm
powinien obsłużyć funkcję wait()
, którą
wywołuje proces macierzysty w oczekiwaniu na zakończenie potomka.
wait()
wywołanie systemowe?
W Miniksie dostępne są także wysokopoziomowe mechanizmy komunikacji takie jak
semafory i pamięć dzielona. Odpowiednie wywołania systemowe obsługuje serwer ipc
.
Na podstawie analizy kodu serwera ipc
określ, jakie operacje semaforowe są dostępne.
Sprawdź, jak zrealizowane jest wstrzymywanie procesu na semaforze.