Per capire come sia fatto analizziamo il codice sorgente del cuore(kernel) di un sistema operativo minimale Minix.
Per semplicità di trattazione si prende in considerazione una delle prime versioni del kernel creata da Andrew S. Tanenbaum ,intel 1.1.
Le prime versioni erano fortemente legate all'architettura sottostante. Successivamente si sono resi conto che potevano utilizzare istruzioni condizionali (#if,#ifndef,#endif).
Minix implementa le strutture dati e i processi atti ad offrire servizi minimali del sistema operativo quali gestione dei thread,spazi di indirizzamento o comunicazione interprocesso.E' arrivato alla versione 3.1.3.
Personalmente analizzerò ogni singolo file,data l'ampiezza dell'argomento,anche se cercherò di essere più sintetico possibile, dividendo la descrizione in più' post.
In questa prima "puntata" definisco tutte le principali strutture dati che servono per descrivere il sistema.
type.h
Il file type.h contiene la definizione dei tipi utilizzati dal kernel e sono presenti alcune macro. In dettaglio abbiamo:
- le variabili utilizzate dal file system(numero blocchi,inode,zona,link,posizione del file,tipe,id dell'utente e del gruppo)
unshort block_nr; /* block number,assenza,max */
unshort inode_nr; /* inode number,assenza,max */
typedef unshort zone_nr; /* zone number,assenza 0,max 0177777(base ottagonale) */
typedef long zone_type; /* zone size */
typedef unshort dev_nr; /* major | minor device number ,no dev*/
typedef char links; /* number of links to an inode,max */
typedef long real_time; /* real time in seconds since Jan 1, 1980 */
typedef long file_pos; /* position in, or length of, a file */
typedef short int uid; /* user id */
typedef char gid; /* group id */
- locazioni gestite dal gestore della memoria, quali: informaizoni sull' indirizzo vituale e fisico.Un Click è 256bytes
typedef unsigned vir_bytes; /* virtual addresses and lengths in bytes */
typedef unsigned vir_clicks; /* virtual addresses and lengths in clicks */
typedef long phys_bytes; /* physical addresses and lengths in bytes */
typedef unsigned phys_clicks; /* physical addresses and lengths in clicks */
typedef int signed_clicks; /* same length as phys_clicks, but signed */

- per le comunicazioni interprocesso sono definiti 6 tipi di messaggio. La struttura del messaggio
- la struttura dati per mappare indirizzi virtuali ad indirizzi fisici e quella per la copia tra locazioni di memoria
struct mem_map {
vir_clicks mem_vir; /* virtual address */
phys_clicks mem_phys; /* physical address */
vir_clicks mem_len; /* length */
};
copy_info { /* used by sys_copy(src, dst, bytes) */ };
signal.h
signal.h contiene 16 costanti per la definizione dei segnali(
kill,bus error,float point exception,alarm clock,bad argument to system call,trace trap,Termination sw, stack fault.)
#define NR_SIGS 16 /* number of signals used */
Const.h
Definisce le costanti utilizzate dal kernel i, in particolare notiamo:
1. la memoria per i processi è divisa in 3 parti
- Text, dove vengono memorizzate tutte le istruzioni del processo
- Data, quì vengono memorizzati i dati statici e le costanti
- Stack, pila di chiamate a subroutine
#define PUBLIC /* PUBLIC is the opposite of PRIVATE */
#define HZ 60 /* clock freq (software settable on IBM-PC) */
#define BLOCK_SIZE 1024 /* # bytes in a disk block */
#define SUPER_USER (uid) 0 /* uid of superuser */
#define NR_TASKS 8 /* number of tasks in the transfer vector */
#define NR_PROCS 16 /* number of slots in proc table */
#define T 0 /* proc[i].mem_map[T] is for text */
#define D 1 /* proc[i].mem_map[D] is for data */
#define S 2 /* proc[i].mem_map[S] is for stack */
2. Il processo numero zero e' quello resposabile alla gestione della memoria.
Nella tabella dei proc, in seconda posizione(posizione 1 del vettore) troviamo il processo file system e in terza posizione quello per la gestione utenti.
Minix è un sistema multi utente.
/* Process numbers of some important processes */
#define MM_PROC_NR 0 /* process number of memory manager */
#define FS_PROC_NR 1 /* process number of file system */
#define INIT_PROC_NR 2 /* init -- the process that goes multiuser */
3. sono presenti informazioni utili al file system, in particolare per utilizzare gli inode. Sono presenti le costanti per distinguere se una operazione è di lettura o scrittura, l'ampiezza della parola, la lunghezza massima del path, i tipi di file e per i diritti su di essi.
#define LOW_USER 2 /* first user not part of operating system */
#define TO_USER 0 /* flag telling to copy from fs to user */
#define FROM_USER 1 /* flag telling to copy from user to fs */
#define READING 0 /* copy data to user */
#define WRITING 1 /* copy data from user */
#define WORD_SIZE 2 /* number of bytes per word */
#define MAX_PATH 128 /* max length of path names */
#define MAX_ISTACK_BYTES 1024 /* maximum initial stack size for EXEC */
#define ROOT_DEV (dev_nr) 256 /* major-minor device number of root dev */
#define BOOT_DEV (dev_nr) 512
/* for inode */
#define I_TYPE 0170000 /* this field gives inode type */
#define I_REGULAR 0100000 /* regular file, not dir or special */
#define I_BLOCK_SPECIAL 0060000 /* block special file */
#define I_DIRECTORY 0040000 /* file is a directory */
#define R_BIT 0000004 /* Rwx protection bit */
#define W_BIT 0000002 /* rWx protection bit */
#define X_BIT 0000001 /* rwX protection bit */
#define I_NOT_ALLOC 0000000 /* this inode is free */
com.h
In questo file sono definite le costanti e gli identificatori di funzioni utilizzati per la comunicazione tra processi.
/* Task numbers, function codes and reply codes. */
#define SEND 1 /* function code for sending messages */
#define RECEIVE 2 /* function code for receiving messages */
#define BOTH 3 /* function code for SEND + RECEIVE */
#define ANY (NR_PROCS+100) /* receive(ANY, buf) accepts from any source */
#define HARDWARE -1 /* used as source on interrupt generated msgs */
#define TTY -7 /* terminal I/O class */
#define PRINTER -8 /* printer I/O class */
#define FLOPPY -5 /* floppy disk class */
#define WINCHESTER -6 /* winchester (hard) disk class */
#define MEM -4 /* /dev/ram, /dev/(k)mem and /dev/null class */
#define CLOCK -3 /* clock class */
#define SYSTASK -2 /* internal functions */(forl,exit,copy,exec,abort,time)
error.h
Sono presenti tutti i codici di errori.
call.h
Contiene gli identificatori di tutte le system call consentite, 5 identificatori di funzioni che non sono chiamate di sistema ma vengono processate come tali.
#define NCALLS 69 /* number of system calls allowed */
#define EXIT 1
#define FORK 2
#define READ 3
#define WRITE 4
#define OPEN 5
#define CLOSE 6
#define CHROOT 61
/* The following are not system calls, but are processed like them. */
#define KSIG 64 /* kernel detected a signal */
sgtty.h
Contiene le strutture dati per le operazioni di I/0 su file(stream,tty, nastro, disco di file, ecc)
sgttyb = { char sg_ispeed; /* input speed (not used) */
char sg_ospeed; /* output speed (not used) */
char sg_erase; /* erase character */
char sg_kill; /* kill character */}
struct tchars {
char t_intrc; /* SIGINT char */
char t_quitc; /* SIGQUIT char */
char t_startc; /* start output (initially CTRL-Q) */
char t_stopc; /* stop output (initially CTRL-S) */
char t_eofc; /* EOF (initially CTRL-D) */
char t_brkc; /* input delimiter (like nl) */
};
stat.h
per le statistiche sugli inode. es. tipo del file,se è una directory, dimensione, data creazione.etc.
stdio.h
Descrive le funzioni, attraverso macro, standard I/O e definisci la struttura dati per il file.
Gli stream sono file con associato il buffer ed il puntatore che definisce il tipo.
Le funzioni di i/o più comuni prelevano o inseriscono caratteri dalla stream.
#define BUFSIZ 1024
#define NFILES 20
#define NULL 0
#define EOF (-1)
#define CMASK 0377
#define READMODE 1
#define WRITEMODE 2
#define UNBUFF 4
#define _EOF 8
#define _ERR 16
#ifndef FILE
extern struct _io_buf {
int _fd;
int _count;
int _flags;
char *_buf;
char *_ptr;
} *_io_table[NFILES];
#endif /* FILE */
#define getchar() getc(stdin)
#define putchar(c) putc(c,stdout)
#define puts(s) fputs(s,stdout)
#define fgetc(f) getc(f)
#define noperprintf(p) ((p)->_flags &= ~PERPRINTF)
#define perprintf(p) ((p)->_flags |= PERPRINTF)//If you want a stream to be flushed after each printf use:
regexp.h
Vi sono sotto routine per expressioni regolari e la strtuttra dati regexp migliora il patter matching
ctype.h
Sono definite delle macro utilizzabili per ricavare il tipo del carattere
se è alfanumerico,maiuscolo,minuscolo,compreso tra [0-9],spazio,etc.
#define isalpha(c) ((_ctype_+1)[c]&(_U|_L))
#define isupper(c) ((_ctype_+1)[c]&_U)
#define islower(c) ((_ctype_+1)[c]&_L)
#define isdigit(c) ((_ctype_+1)[c]&_N)
lib.h
Funge da unificatore dei vari header e definisce il vettore delle eccezioni
extern int begsig(); /* interrupts all vector here */
blocksize.h
Contiene solo la costante per la dimensione del blocco dati utilizzato dal fylesustem
#define BLOCK_SIZE 1024 /* file system data block size */
pwd.h
Viene definita la struct per la password
char *pw_name /* user's login name */
uid_t pw_uid /* numerical user ID */
gid_t pw_gid /* numerical group ID */
char *pw_dir /* initial working directory */
char *pw_shell /* program to use as shell */
grp.h
password per il gruppo
char gr_name /* the name of the group */
char *passwd;
gid_t gr_gid /* numerical group ID */
setjmp.h
Specifica il buffer che si occupa di salvare lo stato corrente delle CPU
#define _JBLEN 3
typedef int jmp_buf[_JBLEN];
package KERNEL
type.h
Contiene la struttura dati pc_psw, dipendente dalla macchina, che è utilizzata per salvare sullo stack, il program counter e i registri in caso di trap o interruzione per poi ripristinarli.
program status word è il registro contiene i bit che specificano il livello di privileggio con cui il processore sta eseguendo e il bit che abilità il processore a ricevere l'interruzioni
struct pc_psw {
int (*pc)(); /* storage for program counter */
phys_clicks cs; /* code segment register */
unsigned psw; /* program status word */
};
glo.h
Variabili globali utilizzati dal kernel per per il clock e il timer, processi,segnali, messaggi, il tipo della CPU, lo stack del kernel e dei task. Ogni task ha uno stack di 256 bytes ed il kernel a un solo stack di 256bytes
EXTERN real_time realtime; /* real time clock */
EXTERN int cur_proc; /* current process */
EXTERN int prev_proc; /* previous process */
EXTERN int olivetti; /* TRUE for Olivetti-style keyboard */
EXTERN int pc_at; /* PC-AT type diskette drives (360K/1.2M) */
EXTERN struct t_stack {
int stk[TASK_STACK_BYTES/sizeof(int)];
} t_stack[NR_TASKS - 1]; /* task stacks; task = -1 never really runs */
EXTERN char k_stack[K_STACK_BYTES]; /* The kernel stack. */
const.h
Costanti usati dal kernel.
- Ogni processo usa 11 registri generali. In ordine sono : ax, bx, cx, dx, si, di, bp, es, ds, cs, ss
- AX (Accumulatore): Utilizzato nelle istruzioni aritmetiche e di I/O
- BX (Base Register): Può essere utilizzato come registro base per il calcolo di indirizzi
- CX (Count Register): Utilizzato tipicamente come contatore
- DX (Data Register): Utilizzato in operazioni di I/O, moltiplicazione, divisione con numeri a 32 bit (in coppia con AX)
- SP (Stack Pointer): Puntatore alla cima dello stack
- BP (Base Pointer): Utilizzato come puntatore all’interno dello stack ma può
- essere impiegato amche come generico registro indice
- SI (Source Index): Registro sorgente o come generico registro indice.
- DI (Destination Index): Registro destinazione
- CS Segmento codice.
- DS Segmento dati.
- ES Segmento extra.
- SS Segmento stack.
- AX (Accumulatore): Utilizzato nelle istruzioni aritmetiche e di I/O
- il valore iniziale dello stack pointer è 0x0010 dato che 3 parole sono inserite dal kernel
#define INIT_SP (int*)0x0010 /* initial sp: 3 words pushed by kernel */ Stackpointer
- i seguenti identificatori di registri sono mappati nel codice assembly. Difatti un warning avvisa di non cambiarli senza cambiare il valore corrispondente nel codice assembly.
#define ES_REG 7 /* proc[i].p_reg[ESREG] is saved es */
#define DS_REG 8 /* proc[i].p_reg[DSREG] is saved ds */
#define CS_REG 9 /* proc[i].p_reg[CSREG] is saved cs */
#define SS_REG 10 /* proc[i].p_reg[SSREG] is saved ss */
- - E' definito il vettore delle interruzioni per gli interrupt generati da orologio,tastiera,floppy,stampante,system call,I/0 e divisione.Sono presenti maschere interruzioni, per abilitare e disabilitare alcuni bit
#define INT2_MASK 0xA1 /* setting bits in this port disables ints */
#define ENABLE 0x20 /* code used to re-enable after an interrupt */
#define IDLE -999 /* 'cur_proc' = IDLE means nobody is running */
- Tre differenti code per lo scheduling,una per i task, una per i processi server(offre servizi di sistema a livello utente) e l'altra per i processi utente.
#define NQ 3 /* # of scheduling queues */
#define TASK_Q 0 /* ready tasks are scheduled via queue 0 */
#define SERVER_Q 1 /* ready servers are scheduled via queue 1 */
#define USER_Q 2 /* ready users are scheduled via queue 2 */
la funzione print del kernel
#define printf printk /* the kernel really uses printk, not printf */
proc.h
In questo file è dichiarato la tabella dei processi(array di processi), con i puntatori necessari a gestirla(processo corrente, processo in testa e coda),ed i puntatori alle liste pronti relative alle tre code di scheduling. Viene fatto riferimento a codice assembly. Si noti che anche i task fanno parte della tabella.
La struttura processo contiene, nel nosto caso,
- 11 registri, con relativo mapping in memoria,
- il puntatore stack pointer,
- la struttura dati per salvare il pc e i registri in caso di interruzione,
- un flag per lo stato del processo,
- il valore più piccolo consentito dello stack,
- l'id del processo(pid),
- l'ora
- puntatore alla lista processi destinatari,
- puntatore al buffer del messaggio,
- puntatore il processo che vuole comunicare con me e puntatore al prossimo processo in stato di pronto
EXTERN struct proc {
int p_reg[NR_REGS]; /* process' registers */
int *p_sp; /* stack pointer */
struct pc_psw p_pcpsw; /* pc and psw as pushed by interrupt */
int p_flags;/* P_SLOT_FREE, SENDING, RECEIVING,etc,if =0 runnable*/
struct mem_map p_map[NR_SEGS];/* memory map */
int *p_splimit; /* lowest legal stack value */
int p_pid; /* process id passed in from MM */
...
struct proc *p_callerq;/* head of list of procs wishing to send */
message *p_messbuf; /* pointer to message buffer */
int p_getfrom; /* from whom does process want to receive? */
struct proc *p_nextready; /* pointer to next ready process */
} proc[NR_TASKS+NR_PROCS];
dmp.c
Contiene informazioni utili per il debugging. Stampa tutte le informazione sul processo in esecuzione(pid,stato,etc..)
table.c
Contiene tutti i dati definiti nei vari Header, fa il merge di tutte le strutture dati
Daniele Licari