Początkowo jądra systemów były "niewielkich" rozmiarów. Było to spowodowane ograniczonością architektur (pamieć), ale także małymi wymaganiami użytkowników czy też małą różnorodnością/ilocią sprzętu który winien być obsługiwany przez system operacyjny.
Jednakże wraz ze wzrostem możliwości komputerów, wraz z dodawaną funkcjonalnością, jądra systemów rosły i stawały się coraz to bardziej skomplikowane. Przejscie z systemow 16 na 32 bitowe tylko spotęgowało efekt rozrastania się kodu źródłowego, ktorego ilość w jądrach monolitowych możemy liczyć w milionach.
Spowodowało to powstawanie wielu błędów, które, ze względu na ogrom kodu, niemożliwy do ogarnięcia przez pojedyńczą osobę, były i ciągle są trudne do wytropienia i zlikwidowania.
Szczególnie niekorzystny jest to fakt dla monolitów dla których niewielki błąd zawarty w peryferyjnym sterowniku może spowodować crash całego systemu.
Inną konsekwencją wielu linii kodu źródłowego jest bardzo pracochłonna i trudna pielęgnacja. Aby zapobiec tym skutkom zaczęto rozwijać ideę systemów opartych o mikrojądra, które minimalizują wielkosć kodu źródłowego serca SO.
Mikrojądro jak nazwa wskazuje jest to minimalne jądro, gdzie przez minimalność rozumiemy
ilość udostępnionych mechanizmów koniecznych do prawidłowego funkcjonowania systemu.
Takim minimalnym zestawem jest:
obecny w Nucleusie - jądrze systemu RC 4000 Multiprogramming System.
Jeśli zastanawiamy się co jest potrzebne w jądrze możemy posłużyć się zasadą minimalności:
Ale czy to dobra droga?
I w ogólności co możemy usunąć z jądra systemu by ten ciągle działał??
Wiemy zatem czym jest mikrojądro, ale nie zostało (explicite)
powiedziane w jaki sposób możemy zachować sprawność systemu wyrzucająć
z jądra prawie cały kod.
A co ważniejsze dlaczego chcielibyśmy chcieć to zrobić ?
Skoro nie mamy żadnej funkcjonalności jądra w jądrze to musimy się
posiłkować aplikacjami wykonywanymi w trybie użytkownika - serwerami.
Serwery są to zwykłe aplikacje, demony, ktore dodatkowo mogą posiadać
prawo do bezpośredniego dostepu do sprzętu, np. do pamięci fizycznej, a
wszelkie usługi oferowane przez serwery są udostępniane innym programom
poprzez komunikaty IPC.
Przykładami serwerów są:
Innymi słowy wszystkie funkcje jądra są udostępniane jako oddzielne procesy w trybie użytkownika.
Zanim możemy mówić o zaletach albo wadach mikrojądra powinniśmy poznać jego najważniejszego oponenta jądro monolitowe
Jądro monolitowe jest to jądro z jakim codziennie spotykamy się pożytkując nasz czas na naukę SO.
Przykładem jest oczywiście Linux.
Specyfikę jądra monolitowego poznajemy systematycznie na wykładzie,
zatem pozwolę sobie jedynie zamieścić obrazek podsumowujący cechy
systemu opartego na jądrze monolitowym.
Minix - system stworzony przez A. Tanenbauma , opublikowany w 1987r.
Został stworzony do celów edukacyjnych i rozpowszechniany razem z książką
"Operating Systems: Design and Implementation" autorstwa A.T i A.W.
Jest to pierwszy klon Uniksa z dostępnym źródłem.
W roku 2005 zostały ogłoszone prace nad trzecią wersją Miniksa, pisaną w zasadzie od podstaw.
Która w zaożeniach miała być "a serious system on resource-limited and embedded computers and for applications requiring high reliabity"
Jej najnowsza wersja to 3.1.2 z 8.05.2006
Cechy miniksa to:
Jest to jądro oraz zespół serwerów których załamanie powoduje śmierć całego systemu.
W skład wchodzą: file, reincarnation, process server oraz jądro systemu.
Załamanie jakiegokolwiek innego elementu miniksa nie powinien mieć znaczącego wpływu na kondycję systemu.
Cały TCB to około 28k lini kodu.
(...)Minix 3 (...) is not about microkernels. It is about bulding highly reliable, self-healing, operating system.
I will consider the job finished when no manufacturer anywhere makes PC with reset button
QNX jest to system czasu rzeczywistego, ktorego początki sięgają 1980 roku.
System
czasu rzeczywistego, w tym kontekscie, oznacza, że system jest
zobowiazany aby każde zadanie było wykonane w ściśle określonym
terminie.
Jest to przykład systemu komercyjnego opartego na mikrojądrze.
Jego targetem są głównie systemy wbudowane, wymagające niezawodności i dotrzymywania terminów.
QNX jest wykorzystywany w takich urządzeniach jak:
Microsoft, który wie jak trudno pielęgnować kod systemu
monolitycznego, także próbował stworzyć własny system oparty na
mikrojądrze.
Miał nim być WinNT a dokładniej wersja 3.1, jednakże zamysł koncepcyjny
spowodował powstanie tzw. jądra hybrydowego, a pomysł utworzenia
Windowsa opartego na mikrojądrze został chwilowo zarzucony.
Pojawił się ponownie w projekcie o nazwie Singularity.
Obecnie jest dostępna wersja 2.0 wydana w listopadzie 2008 roku.
Singularity Research Development Kit jest dostepny jako Shared Source,
co pozwala na niekomercyjne, naukowe wykorzystanie żródła.
Po takiej dawce informacji możemy udzielić odpowiedzi na zadane wcześniej pytania._
Zatem co wygrywa w pojedynku mono vs. mikro
| Mikrojądro | Jądro monolitowe |
|---|---|
|
|

TDP - ( protokół / moduł ) łączący jądra systemów w sieci, powodujący że wszystkie usługi systemowe dostępne są przez ten sam mechanizm, niezależnie od tego z jakiej jednostki (maszyny) są wywoływane.
Możliwości jakie to stwarza:count = read(fd, buffer, nbytes);
return(callm1(FS, READ, fd, nbytes, 0, buffer, NIL_PTR, NIL_PTR));
PUBLIC int callm1(proc, syscallnr, int1, int2, int3, ptr1, ptr2, ptr3)
int proc; /* FS or MM */
int syscallnr; /* which system call */
int int1; /* first integer parameter */
int int2; /* second integer parameter */
int int3; /* third integer parameter */
char *ptr1; /* pointer parameter */
char *ptr2; /* pointer parameter */
char *ptr3; /* pointer parameter */
{
/* Send a message and get the response. The 'M.m_type' field of the
* reply contains a value (>= 0) or an error code (<0). Use message format m1.
*/
_M.m1_i1 = int1;
_M.m1_i2 = int2;
_M.m1_i3 = int3;
_M.m1_p1 = ptr1;
_M.m1_p2 = ptr2;
_M.m1_p3 = ptr3;
return callx(proc, syscallnr);
}
Struktura _M jest de facto zmienną globalną
message _M = {0};
SEND = 1 RECEIVE = 2 BOTH = 3 SYSVEC = 32 |*========================================================================* | send and receive * |*========================================================================* | send(), receive(), sendrec() all save bp, but destroy ax, bx, and cx. .globl _send, _receive, _sendrec _send: mov cx,*SEND | send(dest, ptr) jmp L0 _receive: mov cx,*RECEIVE | receive(src, ptr) jmp L0 _sendrec: mov cx,*BOTH | sendrec(srcdest, ptr) jmp L0 L0: push bp | save bp mov bp,sp | can't index off sp mov ax,4(bp) | ax = dest-src mov bx,6(bp) | bx = message pointer int SYSVEC | trap to the kernel pop bp | restore bp ret | return
set_vec(SYS_VECTOR, s_call, base_click);Funkcja s_call (w assemblerze) zachowuje stan procesora, po czym wywoluje sys_call
07477 /*===========================================================================*
07478 * sys_call *
07479 *===========================================================================*/
07480 PUBLIC int sys_call(call_nr, src_dst, m_ptr)
07481 int call_nr; /* system call number and flags */
07482 int src_dst; /* src to receive from or dst to send to */
07483 message *m_ptr; /* pointer to message in the caller's space */
07484 {
07489 register struct proc *caller_ptr = proc_ptr; /* get pointer to caller */
07490 int function = call_nr & SYSCALL_FUNC; /* get system call function */
07491 unsigned flags = call_nr & SYSCALL_FLAGS; /* get flags */
07492 int mask_entry; /* bit to check in send mask */
07493 int result; /* the system call's result */
07494 vir_clicks vlo, vhi; /* virtual clicks containing message to send */
07550 /* Now check if the call is known and try to perform the request. The only
07551 * system calls that exist in MINIX are sending and receiving messages.
07552 * - SENDREC: combines SEND and RECEIVE in a single system call
07553 * - SEND: sender blocks until its message has been delivered
07554 * - RECEIVE: receiver blocks until an acceptable message has arrived
07555 * - NOTIFY: nonblocking call; deliver notification or mark pending
07556 * - ECHO: nonblocking call; directly echo back the message
07557 */
07558 switch(function) {
07559 case SENDREC:
07560 /* A flag is set so that notifications cannot interrupt SENDREC. */
07561 priv(caller_ptr)->s_flags |= SENDREC_BUSY;
07562 /* fall through */
07563 case SEND:
07564 result = mini_send(caller_ptr, src_dst, m_ptr, flags);
07565 if (function == SEND || result != OK) {
07566 break; /* done, or SEND failed */
07567 } /* fall through for SENDREC */
07568 case RECEIVE:
07569 if (function == RECEIVE)
07570 priv(caller_ptr)->s_flags >= ~SENDREC_BUSY;
07571 result = mini_receive(caller_ptr, src_dst, m_ptr, flags);
07572 break;
07573 case NOTIFY:
07574 result = mini_notify(caller_ptr, src_dst);
07575 break;
07576 case ECHO:
07577 CopyMess(caller_ptr->p_nr, caller_ptr, m_ptr, caller_ptr, m_ptr);
07578 result = OK;
07579 break;
07580 default:
07581 result = EBADCALL; /* illegal system call */
07582 }
07583
07584 /* Now, return the result of the system call to the caller. */
07585 return(result);
07586 }
Funkcja generalnie upewnia się, że komunikat jest poprawny i przekazuje go dalej przez sendrec.
24075 if (call_nr < 0 || call_nr >= NCALLS) {
24076 error = ENOSYS;
24077 printf("FS, warning illegal %d system call by %d\n", call_n
24078 } else if (fp->fp_pid == PID_FREE) {
24079 error = ENOSYS;
24080 printf("FS, bad process, who = %d, call_nr = %d, slot1 = %d
24081 who, call_nr, m_in.slot1);
24082 } else {
24083
I w przypadku read() wywoła się do_read()
PUBLIC int do_read()
{
return(read_write(READING));
}
mount -t XXX /path/device /another/pathAby sprawić by w katalogu /another/path dostępna była zawartość urządzenia /path/device zinterpretowana w odpowiedni (XXX) sposób.
settrans -a /cdrom /hurd/iso9660fs /dev/hd1Gdzie /hurd/iso9660fs jest programem do obsługi tego systemu plików. Niewątpliwie idea translatorów wydaje się ogólniejsza i bardziej praktyczna - dzięki niej, możemy na przykład łatwo stworzyć translator obsługujący kopiowanie za pomocą scp, tak by dla użytkownika wyglądało jak operowanie na lokalnym systemie plików.
static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info *fi)
{
(void) offset;
(void) fi;
if(strcmp(path, "/") != 0)
return -ENOENT;
filler(buf, ".", NULL, 0);
filler(buf, "..", NULL, 0);
filler(buf, hello_path + 1, NULL, 0);
return 0;
}
static struct fuse_operations hello_oper = {
.getattr = hello_getattr,
.readdir = hello_readdir,
.open = hello_open,
.read = hello_read,
};
int main(int argc, char *argv[])
{
return fuse_main(argc, argv, &hello_oper);
}
I cieszyć się rezultatem:
~/fuse/example$ mkdir /tmp/fuse ~/fuse/example$ ./hello /tmp/fuse ~/fuse/example$ ls -l /tmp/fuse total 0 -r--r--r-- 1 root root 13 Jan 1 1970 hello ~/fuse/example$
Jądra QNXa zajmuje się obsługą przerwań, ale nie wyklucza ono obsługi przerwać przez serwery.
Funkcja InterruptAttach pozwala na zdefiniowanie funkcji obsługi przerwania.




