Il Kernel e' il cuore di ogni sistema operativo: e' quel software che permette agli utenti di condividere ed utilizzare al meglio le risorse del sistema di elaborazione.
Il Kernel puo' essere visto come il principale software del Sistema Operativo che potrebbe anche includere la gestione grafica: ad esempio sotto Linux (come nella maggiorparte dei sistemi Unix-like), l'ambiente XWindow non fa parte del Kernel di Linux perche' gestisce soltanto operazioni grafiche (grazie ad istruzioni I/O eseguite in User Mode e accesso diretto alla scheda video). Com'e' noto, invece, gli ambienti Windows (Win9x, WinME, WinNT, Win2K, WinXP, e cosi' via) sono un mix tra ambiente grafico e kernel.
Molti anni fa, quando i computers erano grandi come stanze, gli utenti lanciavano le loro applicazioni con molta difficolta' e, a volte, il sistema di elaborazione andava in crash.
Per evitare tale crash si e' pensato di introdurre 2 modi di funzionamento (ancora presenti nei nuovi microprocessori):
| Applicazioni /|\ | ______________ | | | User Mode | | | ______________ | | | | Dettaglio | _______ _______ | Astrazione Implementazione| | Kernel Mode | | | _______________ | | | | | | | | | | \|/ Hardware |
Kernel Mode impedisce alle applicazioni User Mode di danneggiare il sistema o le sue caratteristiche.
I moderni microprocessori implementano in hardware almeno 2 stati differenti. Ad esempio l'architettura Intel possiede 4 stati che determinano il PL (Privilege Level) permettendo quindi di avere gli stati 0,1,2,3 , con 0 usato in modo Kernel.
I sistemi Unix-like richiedono soltanto 2 livelli di privilegio e utilizzeremo questo come punto di riferimento.
Una volta capito che ci sono 2 modi operativi differenti, dobbiamo vedere quando avviene il ''salto'' tra uno e l'altro:
Le System Calls sono come delle normali funzioni, soltanto che operano in Kernel Mode per eseguire operazioni sull'OS (in effetti le System Calls sono parte integrante dell'OS).
Una System Call puo' essere chiamata quando:
| | ------->| System Call i | (Accesso ai Devices) | | | | [sys_read()] | | ... | | | | | system_call(i) |-------- | | | [read()] | | | | ... | | | | system_call(j) |-------- | | | [get_pid()] | | | | | ... | ------->| System Call j | (Accesso alle strutture dati del kernel) | | | [sys_getpid()]| | | USER MODE KERNEL MODE Funzionamento System Calls Unix
Le System Calls teoricamente sono l'unica interfaccia disponibile in User Mode per accedere alle risorse hardware. In realta' esiste la SC ''ioperm'' che permette ad un Task di accedere direttamente ad un device (benche' senza IRQs).
NOTA: Non tutte le funzioni di libreria ''C'' sono delle system call, solo un piccolo sottoinsieme di esse.
Segue una lista delle System Calls presenti nel Linux Kernel 2.4.17, ricavabili dal file [ arch/i386/kernel/entry.S ]
.long SYMBOL_NAME(sys_ni_syscall) /* 0 - old "setup()" system call*/ .long SYMBOL_NAME(sys_exit) .long SYMBOL_NAME(sys_fork) .long SYMBOL_NAME(sys_read) .long SYMBOL_NAME(sys_write) .long SYMBOL_NAME(sys_open) /* 5 */ .long SYMBOL_NAME(sys_close) .long SYMBOL_NAME(sys_waitpid) .long SYMBOL_NAME(sys_creat) .long SYMBOL_NAME(sys_link) .long SYMBOL_NAME(sys_unlink) /* 10 */ .long SYMBOL_NAME(sys_execve) .long SYMBOL_NAME(sys_chdir) .long SYMBOL_NAME(sys_time) .long SYMBOL_NAME(sys_mknod) .long SYMBOL_NAME(sys_chmod) /* 15 */ .long SYMBOL_NAME(sys_lchown16) .long SYMBOL_NAME(sys_ni_syscall) /* old break syscall holder */ .long SYMBOL_NAME(sys_stat) .long SYMBOL_NAME(sys_lseek) .long SYMBOL_NAME(sys_getpid) /* 20 */ .long SYMBOL_NAME(sys_mount) .long SYMBOL_NAME(sys_oldumount) .long SYMBOL_NAME(sys_setuid16) .long SYMBOL_NAME(sys_getuid16) .long SYMBOL_NAME(sys_stime) /* 25 */ .long SYMBOL_NAME(sys_ptrace) .long SYMBOL_NAME(sys_alarm) .long SYMBOL_NAME(sys_fstat) .long SYMBOL_NAME(sys_pause) .long SYMBOL_NAME(sys_utime) /* 30 */ .long SYMBOL_NAME(sys_ni_syscall) /* old stty syscall holder */ .long SYMBOL_NAME(sys_ni_syscall) /* old gtty syscall holder */ .long SYMBOL_NAME(sys_access) .long SYMBOL_NAME(sys_nice) .long SYMBOL_NAME(sys_ni_syscall) /* 35 */ /* old ftime syscall holder */ .long SYMBOL_NAME(sys_sync) .long SYMBOL_NAME(sys_kill) .long SYMBOL_NAME(sys_rename) .long SYMBOL_NAME(sys_mkdir) .long SYMBOL_NAME(sys_rmdir) /* 40 */ .long SYMBOL_NAME(sys_dup) .long SYMBOL_NAME(sys_pipe) .long SYMBOL_NAME(sys_times) .long SYMBOL_NAME(sys_ni_syscall) /* old prof syscall holder */ .long SYMBOL_NAME(sys_brk) /* 45 */ .long SYMBOL_NAME(sys_setgid16) .long SYMBOL_NAME(sys_getgid16) .long SYMBOL_NAME(sys_signal) .long SYMBOL_NAME(sys_geteuid16) .long SYMBOL_NAME(sys_getegid16) /* 50 */ .long SYMBOL_NAME(sys_acct) .long SYMBOL_NAME(sys_umount) /* recycled never used phys() */ .long SYMBOL_NAME(sys_ni_syscall) /* old lock syscall holder */ .long SYMBOL_NAME(sys_ioctl) .long SYMBOL_NAME(sys_fcntl) /* 55 */ .long SYMBOL_NAME(sys_ni_syscall) /* old mpx syscall holder */ .long SYMBOL_NAME(sys_setpgid) .long SYMBOL_NAME(sys_ni_syscall) /* old ulimit syscall holder */ .long SYMBOL_NAME(sys_olduname) .long SYMBOL_NAME(sys_umask) /* 60 */ .long SYMBOL_NAME(sys_chroot) .long SYMBOL_NAME(sys_ustat) .long SYMBOL_NAME(sys_dup2) .long SYMBOL_NAME(sys_getppid) .long SYMBOL_NAME(sys_getpgrp) /* 65 */ .long SYMBOL_NAME(sys_setsid) .long SYMBOL_NAME(sys_sigaction) .long SYMBOL_NAME(sys_sgetmask) .long SYMBOL_NAME(sys_ssetmask) .long SYMBOL_NAME(sys_setreuid16) /* 70 */ .long SYMBOL_NAME(sys_setregid16) .long SYMBOL_NAME(sys_sigsuspend) .long SYMBOL_NAME(sys_sigpending) .long SYMBOL_NAME(sys_sethostname) .long SYMBOL_NAME(sys_setrlimit) /* 75 */ .long SYMBOL_NAME(sys_old_getrlimit) .long SYMBOL_NAME(sys_getrusage) .long SYMBOL_NAME(sys_gettimeofday) .long SYMBOL_NAME(sys_settimeofday) .long SYMBOL_NAME(sys_getgroups16) /* 80 */ .long SYMBOL_NAME(sys_setgroups16) .long SYMBOL_NAME(old_select) .long SYMBOL_NAME(sys_symlink) .long SYMBOL_NAME(sys_lstat) .long SYMBOL_NAME(sys_readlink) /* 85 */ .long SYMBOL_NAME(sys_uselib) .long SYMBOL_NAME(sys_swapon) .long SYMBOL_NAME(sys_reboot) .long SYMBOL_NAME(old_readdir) .long SYMBOL_NAME(old_mmap) /* 90 */ .long SYMBOL_NAME(sys_munmap) .long SYMBOL_NAME(sys_truncate) .long SYMBOL_NAME(sys_ftruncate) .long SYMBOL_NAME(sys_fchmod) .long SYMBOL_NAME(sys_fchown16) /* 95 */ .long SYMBOL_NAME(sys_getpriority) .long SYMBOL_NAME(sys_setpriority) .long SYMBOL_NAME(sys_ni_syscall) /* old profil syscall holder */ .long SYMBOL_NAME(sys_statfs) .long SYMBOL_NAME(sys_fstatfs) /* 100 */ .long SYMBOL_NAME(sys_ioperm) .long SYMBOL_NAME(sys_socketcall) .long SYMBOL_NAME(sys_syslog) .long SYMBOL_NAME(sys_setitimer) .long SYMBOL_NAME(sys_getitimer) /* 105 */ .long SYMBOL_NAME(sys_newstat) .long SYMBOL_NAME(sys_newlstat) .long SYMBOL_NAME(sys_newfstat) .long SYMBOL_NAME(sys_uname) .long SYMBOL_NAME(sys_iopl) /* 110 */ .long SYMBOL_NAME(sys_vhangup) .long SYMBOL_NAME(sys_ni_syscall) /* old "idle" system call */ .long SYMBOL_NAME(sys_vm86old) .long SYMBOL_NAME(sys_wait4) .long SYMBOL_NAME(sys_swapoff) /* 115 */ .long SYMBOL_NAME(sys_sysinfo) .long SYMBOL_NAME(sys_ipc) .long SYMBOL_NAME(sys_fsync) .long SYMBOL_NAME(sys_sigreturn) .long SYMBOL_NAME(sys_clone) /* 120 */ .long SYMBOL_NAME(sys_setdomainname) .long SYMBOL_NAME(sys_newuname) .long SYMBOL_NAME(sys_modify_ldt) .long SYMBOL_NAME(sys_adjtimex) .long SYMBOL_NAME(sys_mprotect) /* 125 */ .long SYMBOL_NAME(sys_sigprocmask) .long SYMBOL_NAME(sys_create_module) .long SYMBOL_NAME(sys_init_module) .long SYMBOL_NAME(sys_delete_module) .long SYMBOL_NAME(sys_get_kernel_syms) /* 130 */ .long SYMBOL_NAME(sys_quotactl) .long SYMBOL_NAME(sys_getpgid) .long SYMBOL_NAME(sys_fchdir) .long SYMBOL_NAME(sys_bdflush) .long SYMBOL_NAME(sys_sysfs) /* 135 */ .long SYMBOL_NAME(sys_personality) .long SYMBOL_NAME(sys_ni_syscall) /* for afs_syscall */ .long SYMBOL_NAME(sys_setfsuid16) .long SYMBOL_NAME(sys_setfsgid16) .long SYMBOL_NAME(sys_llseek) /* 140 */ .long SYMBOL_NAME(sys_getdents) .long SYMBOL_NAME(sys_select) .long SYMBOL_NAME(sys_flock) .long SYMBOL_NAME(sys_msync) .long SYMBOL_NAME(sys_readv) /* 145 */ .long SYMBOL_NAME(sys_writev) .long SYMBOL_NAME(sys_getsid) .long SYMBOL_NAME(sys_fdatasync) .long SYMBOL_NAME(sys_sysctl) .long SYMBOL_NAME(sys_mlock) /* 150 */ .long SYMBOL_NAME(sys_munlock) .long SYMBOL_NAME(sys_mlockall) .long SYMBOL_NAME(sys_munlockall) .long SYMBOL_NAME(sys_sched_setparam) .long SYMBOL_NAME(sys_sched_getparam) /* 155 */ .long SYMBOL_NAME(sys_sched_setscheduler) .long SYMBOL_NAME(sys_sched_getscheduler) .long SYMBOL_NAME(sys_sched_yield) .long SYMBOL_NAME(sys_sched_get_priority_max) .long SYMBOL_NAME(sys_sched_get_priority_min) /* 160 */ .long SYMBOL_NAME(sys_sched_rr_get_interval) .long SYMBOL_NAME(sys_nanosleep) .long SYMBOL_NAME(sys_mremap) .long SYMBOL_NAME(sys_setresuid16) .long SYMBOL_NAME(sys_getresuid16) /* 165 */ .long SYMBOL_NAME(sys_vm86) .long SYMBOL_NAME(sys_query_module) .long SYMBOL_NAME(sys_poll) .long SYMBOL_NAME(sys_nfsservctl) .long SYMBOL_NAME(sys_setresgid16) /* 170 */ .long SYMBOL_NAME(sys_getresgid16) .long SYMBOL_NAME(sys_prctl) .long SYMBOL_NAME(sys_rt_sigreturn) .long SYMBOL_NAME(sys_rt_sigaction) .long SYMBOL_NAME(sys_rt_sigprocmask) /* 175 */ .long SYMBOL_NAME(sys_rt_sigpending) .long SYMBOL_NAME(sys_rt_sigtimedwait) .long SYMBOL_NAME(sys_rt_sigqueueinfo) .long SYMBOL_NAME(sys_rt_sigsuspend) .long SYMBOL_NAME(sys_pread) /* 180 */ .long SYMBOL_NAME(sys_pwrite) .long SYMBOL_NAME(sys_chown16) .long SYMBOL_NAME(sys_getcwd) .long SYMBOL_NAME(sys_capget) .long SYMBOL_NAME(sys_capset) /* 185 */ .long SYMBOL_NAME(sys_sigaltstack) .long SYMBOL_NAME(sys_sendfile) .long SYMBOL_NAME(sys_ni_syscall) /* streams1 */ .long SYMBOL_NAME(sys_ni_syscall) /* streams2 */ .long SYMBOL_NAME(sys_vfork) /* 190 */ .long SYMBOL_NAME(sys_getrlimit) .long SYMBOL_NAME(sys_mmap2) .long SYMBOL_NAME(sys_truncate64) .long SYMBOL_NAME(sys_ftruncate64) .long SYMBOL_NAME(sys_stat64) /* 195 */ .long SYMBOL_NAME(sys_lstat64) .long SYMBOL_NAME(sys_fstat64) .long SYMBOL_NAME(sys_lchown) .long SYMBOL_NAME(sys_getuid) .long SYMBOL_NAME(sys_getgid) /* 200 */ .long SYMBOL_NAME(sys_geteuid) .long SYMBOL_NAME(sys_getegid) .long SYMBOL_NAME(sys_setreuid) .long SYMBOL_NAME(sys_setregid) .long SYMBOL_NAME(sys_getgroups) /* 205 */ .long SYMBOL_NAME(sys_setgroups) .long SYMBOL_NAME(sys_fchown) .long SYMBOL_NAME(sys_setresuid) .long SYMBOL_NAME(sys_getresuid) .long SYMBOL_NAME(sys_setresgid) /* 210 */ .long SYMBOL_NAME(sys_getresgid) .long SYMBOL_NAME(sys_chown) .long SYMBOL_NAME(sys_setuid) .long SYMBOL_NAME(sys_setgid) .long SYMBOL_NAME(sys_setfsuid) /* 215 */ .long SYMBOL_NAME(sys_setfsgid) .long SYMBOL_NAME(sys_pivot_root) .long SYMBOL_NAME(sys_mincore) .long SYMBOL_NAME(sys_madvise) .long SYMBOL_NAME(sys_getdents64) /* 220 */ .long SYMBOL_NAME(sys_fcntl64) .long SYMBOL_NAME(sys_ni_syscall) /* reserved for TUX */ .long SYMBOL_NAME(sys_ni_syscall) /* Reserved for Security */ .long SYMBOL_NAME(sys_gettid) .long SYMBOL_NAME(sys_readahead) /* 225 */
Quando arriva un IRQ, il task in esecuzione viene interrotto per far eseguire un gestore IRQ.
Dopo l'esecuzione di tale gestore il controllo ritorna tranquillamente al processo che non si accorge di nulla.
Task in esecuzione |-----------| (3) ESECUZIONE | | | [stop esecuzione] IRQ Handler NORMALE (1) | | | ------------->|---------| | \|/ | | |gestisce | IRQ (2)---->| .. |-----> | alcuni | | | |<----- | dati | RITORNO (6) | | | | | ..(4). | ALLA NORMALE | \|/ | <-------------|_________| ESECUZIONE |___________| [ritorno al codice] (5) USER MODE KERNEL MODE Transizione User->Kernel Mode causata da evento IRQ
I seguenti passi si riferiscono al diagramma precedente:
Un IRQ di particolare interesse e' quello relativo al Timer che arriva ogni tot millisecondi per gestire
La punto chiave di ogni moderno sistema operativo e' il ''Task''. Il Task e' un'applicazione che ''gira'' in memoria e codivide tutte le risorse del sistema (inclusi CPU e Memoria) con gli altri Tasks.
Questa ''condivisione di risorse'' viene gestita dal meccanismo di MultiTasking. Ogni tot (''timeslice'') millisecondi avviene il cambiamento di contesto (Task Switching'') grazie al quale gli utenti hanno l'illusione di possedere tutte le risorse; se consideriamo un solo utente si avra' invece l'illusione di poter eseguire piu' Tasks nello stesso istante.
Per implementare il Multitasking i Task utilizzano una variabile ''state'' che puo' assumere i valori:
Lo stato del Task viene gestito dalla presenza o meno dello stesso nella lista relativa:
La commutazione da un Task ad un altro si chiama ''Task Switching''. Molti elaboratori hanno una istruzione hardware che esegue automaticamente questa operazione. Il Task Switching avviene nei seguenti casi:
In entrambi i casi abbiamo bisogno di schedulare un nuovo processo pronto per l'esecuzione (dalla ''Ready List'') ed eseguirlo.
* Questo serve ad evitare la ''Busy Form Waiting'', cioe' l'esecuzione inutile di un loop del processo in attesa della risorsa.
Il Task Switching viene gestito dall'entita' ''Schedule''.
Timer | | IRQ | | Schedule | | | ________________________ |----->| Task 1 |<------------------>|(1)Scegli un nuovo Task | | | | |(2)Task Switching | | |___________| |________________________| | | | /|\ | | | | | | | | | | | | | | | | |----->| Task 2 |<-------------------------------| | | | | | |___________| | . . . . . . . . . . . . . . . | | | | | | | | ------>| Task N |<-------------------------------- | | |___________| Task Switching basato sul TimeSlice
Un tipico Timeslice per Linux e' di circa 10 ms (in alcune architetture 1 ms).
| | | | Accesso _____________________________ | Task 1 |----------->|(1) Accoda richiesta risorsa | | | Risorsa |(2) Marca Task come bloccato | | | |(3) Scegli un Task Pronto | |___________| |(4) Task Switching | |_____________________________| | | | | | | | | | Task 2 |<------------------------- | | | | |___________| Task Switching basato sull'attesa di una risorsa
Fino adesso abbiamo visto i cosiddetti OS Monolitici, ma esiste anche un'altra famiglia di OS, quella basata sui ''Microkernel''.
Un OS basato su Microkernel utilizza i Tasks, non solo per i processi in User Mode, ma anche come gestori del Kernel, come il Floppy-Task, l'HDD-Task, il Net-Task e cosi' via. Alcuni esempi sono Amoeba e Mach.
PRO:
CONTRO:
La mia opinione personale e' che gli OS Microkernel sono un buon esempio didattico (come Minix) ma non sono ''ottimali'' (cioe' partono gia' male come prestazioni) quindi non sono da considerarsi come buoni OS.
Linux utilizza alcuni Tasks, chiamati "Kernel Threads" per implementare un mini struttura microkernel, laddove e' evidente che le il tempo di accesso di un Task Switching sia notevolmente trascurabile (come ''kswapd'', che serve per recuperare pagine dalla memoria di massa).
Lo Standard ISO-OSI descrive un'architettura di rete con i seguenti livelli:
I primi 2 livelli sono di solito implementati in hardware mentre i livelli successivi in software (o in firmware per i routers).
Un OS e0 capace di gestire molti protocolli: uno di questi e' il TCP/IP (il piu' importante sui livelli 3-4).
Il Kernel non conosce nulla dei primi 2 livelli
In RX l'OS:
frames pacchetti sockets NIC ---------> Kernel ----------> Applicazione | pacchetti --------------> Instradamento - RX -
Nello stadio di TX l'OS:
sockets packets frames Application ---------> Kernel ----------> NIC packets /|\ Forward ------------------- - TX -
La Segmentazione e' il primo metodo per risolvere i problemi di allocazione di memoria: questa tecnica permette di compilare il codice sorgente senza preoccuparsi di dove i dati verranno eseguiti o memorizzati. In effetti questa feature e' di grande aiuto nello sviluppare di applicazioni in modo indipendente dall'OS e dall'Hardware.
| Stack | | | | | \|/ | | Spazio Libero | | /|\ | Segmento <---> Processo | | | | Heap | |Dati non inizializzati| | Dati inizializzati | | Codice | |______________________| Segmento
Possiamo dire che un segmento e' la trasposizione logica di un'applicazione in memoria, o anche l'immagine dell'applicazione.
Quando si programmiamo non ci interessiamo di dove esattamente i dati vadano a finire in memoria, ci preoccupiamo soltanto dell'offset all'interno del segmento (quindi dell'applicazione).
Siamo soliti attribuire quindi, un Segment ad ogni Processo e viceversa. In Linux questo non e' vero fino in fondo, in quanto vengono usati soltanto 4 segmenti per il Kernel e tutti i Processi.
____________________ ----->| |-----> | IN | Segmento A | OUT ____________________ | |____________________| | |____| | | | Segmento B | | Segmento B | | |____ | | |____________________| | |____________________| | | Segmento C | | |____________________| ----->| Segmento D |-----> IN |____________________| OUT Problema della Segmentazione
Nel diagramma vogliamo disallocare i processi A e D ad allocare il processo B.
Come si puo' vedere ci sarebbe abbastanza spazio per B, ma in pratica non possiamo splittarlo in 2 pezzi e quindi NON POSSIAMO caricarlo (manca memoria!).
La ragione di fondo di cio' e che i segmenti puri sono aree contigue proprio perche' sono aree logiche (di processo) e quindi non possono essere divise.
____________________ | Pagina 1 | |____________________| | Pagina 2 | |____________________| | .. | Segmento <---> Processo |____________________| | Pagina n | |____________________| | | |____________________| | | |____________________| Segmento
La Paginazione divide la memoria in "N" pezzi, tutti a lunghezza fissa.
Un processo puo' essere caricato in una o piu' pagine. Quando un processo viene disallocato, tutte le sue pagine vengono liberate (vedi Problema della Segmentazione, prima).
La Paginazione viene sfruttata anche per un altro importante scopo: lo "Swapping": se una pagina infatti non e' presente in memoria fisica viene generata un'ECCEZIONE , che fara' cercare al Kernel una pagina in memoria di massa. Questo meccanismo permette all'OS di caricare piu' applicazioni rispetto a quelle realmente caricabili soltanto in memoria fisica.
____________________ Pagina X | Processo Y | |____________________| | | | SPAZIO | | INUTILIZZATO | |____________________| Problema della Paginazione
Nel diagramma possiamo notare qual e' il problema della Paginazione: quando un Processo Y viene caricato nella Pagina X, TUTTO la spazio di memoria viene allocato, cosicche' il rimanente spazio in fondo alla pagina rimanga inutilizzato.
Come possiamo risolvere i problemi di segmentazione e paginazione? Utilizzando entrambe le politiche.
| .. | |____________________| ----->| Pagina 1 | | |____________________| | | .. | ____________________ | |____________________| | | |---->| Pagina 2 | | Segmento X | ----| |____________________| | | | | .. | |____________________| | |____________________| | | .. | | |____________________| |---->| Pagina 3 | |____________________| | .. |
Il Processo X, identificato dal Segmento X, viene diviso in 3 pezzi ognino poi caricato in una pagina.
Non abbiamo:
| | | | | | Offset2 | Valore | | | /|\| | Offset1 | |----- | | | /|\ | | | | | | | | | | \|/| | | | | ------>| | \|/ | | | | Indirizzo Base --------->| | | | Paginazione | ....... | | ....... | | | | | Paginazione Gerarchica