Unix
C
Bitwise Operators
Puntatori
Array di Puntatori
- Si utilizzano per efficienza
- per ordinare strutture dati é piú conveniente spostare puntatori che dati
 
- Si trovano per esempio anche nel main(int argc, char *argv[]) ^
Indirezione Multipla
int i = 1;
int *ip1 = &i;
int **ipp = &ip1; // puntatore doppio
Il primo \(\star\) indica un livello di indirezione, un numero maggiore di \(\star\) aumentano i livelli di indirezione
Puntatori a funzioni
int (*func)(int a, int b){...}
(*func)(a,b);
Preprocessore
#…
- Hanno effetto da un certo punto in poi
- Sono interpretate e riscritte prima che il codice arrivi al compilatore
Il suo output puó essere visualizzato con il flag -E di gcc
include
define
La macro non comporta una allocazione di record di attivazione e non prevede context switch, ha meno overhead ed é piú efficiente
- macro predefinite
- __TIME__
- __DATE__
- __FILE__
- __LINE__
 
make
Constringe a considerare le dipendenze
- si avvale della marcatura temporale dei file
- in questo modo decide se ricompilare
 
Il makefile é costituito da
- riga di dipendenza
- riga d’azione
Esiste un target speciale: clean:
- utilizzato per rimuovere tutti i file oggetto
- rm -f *.o
Environment List
Le variabili sono passate da processo padre a processo figlio Le variabili d’ambiente sono impostate in un array di stringhe
- accoppiate per nome|valore
In C ci si puó accedere utilizzando la variabile globale
extern char **environ;
PID
System Call
- pid_t getpid(void)
- non fallisce mai
 
- pid_t getppid(void)
- restituisce parent pid
 
Memory Layout
- segmenti
- text  segments
- read-only
- sharable
 
- initialized data segment
- globali
- statiche
- variabili inizializzate esplicitamente dal programma
 
 
- uninitialized data segmemt
- prima di eseguire il programma il Sistema inizializza a 0
 
- stack
- cresce e diminuisce dinamicamente
- variabili locali (o automatiche)
- argomenti
- return value
 
- heap
- area di memoria allocata a runtime
- malloc
- calloc
 
 
- area di memoria allocata a runtime
 
- text  segments
Process Control
syscall di Crezione, Esecuzione, Terminazione di processi
- 
fork- 
crea una copia quasi identica del genitore - almeno PID diverso
- copia di
- stack
- heap
- dati
- text segment
 
 pid_t fork(void);Il figlio riprende l’esecuzione a partire dalla prima istruzione successiva alla fork che lo ha creato - la fork restituisce
- 0 al figlio
- PID del figlio al genitore se avviene con successo
 
 pid_t procID; procID = fork(); if(procID == -1) exit(1); if(procID){ // padre }else{ // if(procID == 0) // figlio }switch (procID = fork()){ case -1: exit(1); case 0: // figlio case default: // padre } // qua eseguono entrambiIl figlio riceva una copia dei riferimenti ai descrittori dei file - se il genitore apre un file prima della fork le modifiche saranno condivise tra i due
 
 
- 
- 
exit- 
libera le risorse utilizzate dal processo - in modo che il kernel possa allocarle
- status
- intero che descrive lo stato di terminazione del processo
 
 I Processi possono terminare in due modi - in maniera anomala
- segfault per esempio
- con una _exit()
- incapsulato all’interno della syscall exit
- effettua
- exit handlers
- stdio stream buffer flushed
- _exit() viene invocata utilizzando lo status
 
 
- effettua
 
- incapsulato all’interno della syscall exit
 
 
 
- 
- 
wait- sospende il chiamante di sospendere l’esecuzione fino alla terminazione di un figlio
- status
- alla terminazione del figlio qui sara' riportato l’exit status del figlio
 
- error handling
- ritorna -1
- errno = ECHILD se diverso da questo é un errore inatteso
- in caso chiamante non abbia figli
 
 
 
- 
waitpid- permette di scegliere il figlio da aspettare
- permette una nonblocking wait
- non si blocca in caso di nessuna terminazione
 
- parametri
- pid
- >0 aspetta specifico figlio
- == 0 attende un processo dello stesso gruppo del chiamante
- == -1 attende un figlio qualsiasi
 
- status
- figlio ha terminato
- con una exit
- con un segnale non gestito
- e' stato bloccato da un segnale e waitpid era chiamata con WUNTRACED
 
- <sys/wait.h>
- WIFEXITED(status)
- WIFSIGNALED(status)
- WIFSTOPPED(status)
- WIFCONTINUED(status)
 
 
- figlio ha terminato
- options
- maschera di bit
- WUNTRACED
- WCONTINUED
- WNOHANG
- se nessun figlio specificato ha cambiato stato restituisci immediatamente
 
 
 
- pid
 
- 
execve- 
carica un nuovo programma nella memoria del processo chiamante, sovrascrivendolo - i descrittori di file aperti rimangono aperti nel nuovo programma
 
- 
e' cancellato il text segment del precedente processo - sono poi ricreati heap e dati
 
- 
esistono funzioni di libreria - interfaccie alla execve
 
- 
parametri - pathname
- argv[]
- lista di puntatori a stringa
- termina con puntatore a NULL
 
- envp[]
- environment pointer
- coppie nome-valore
 
 
- 
ritorno - non ne ha se ha successo
- -1 per errore
- EACCES
- ENOENT
- ENOEXEC
- TXTBSY
- E2BIG
 
 
 
- 
- 
system- crea un processo figlio per eseguire
- restituisce lo stato di terminazione dell’ultimo comando eseguito
- é inefficente
 
orphans and zombies
in generale un padre sopravvive al figlio
- in caso di orfano il padre diventa il processo init
- per garantire una wait dalla parte del padre per conoscere lo status del figlio
- il kernel permette di trasformare il figlio in zombie
 
- 
zombies Non possono essere uccisi da un segnale - rimangono nella tabella dei processi
- se si verificano zombie potrebbe riempirsi la tabella
 
 Idealmente si devono evitare processi zombie 
- rimangono nella tabella dei processi
Signals
Cause dei Segnali
- Eccezione Hardware
- istruzioni linguaggio macchina malformate
- divisioni per 0
- riferimenti a parti di memoria inaccessibili
 
- Caratteri speciali su terminale
- interrupt
- ^c
 
- suspend
- ^z
- stop the current job (so you can put it in the background)
 
- quit
- ^\
- terminates current job; makes a core file
 
 
- interrupt
- Eventi Software
- input ora disponibile su un descrittore di file
- timer é arrivato a 0
- quanto di tempo esaurito
- figlio del processo é terminato
 
Symbolic Names and Numbers
Segnali definiti con interi unici contenuti in nomi simbolici
- in <signal.h> o <sys/signal.h>
- della forma SIGxxxx
- utilizzabili piú facilmente in quanto le implementazioni variano nei numeri assegnati ad ogni segnale
 
for (i=0; i<NSIG; i++) {
    printf("Signal #%2d: %s\n", i, strsignal(i));
}
Lifecycle
Un segnale:
- generato da qualche evento
- delivered ad un processo
- questo esegue qualche azione in risposta al segnale
 
Nel periodo di tempo intercorrente tra la generazione e l’invio al processo il segnale é pending
- viene inviato
- appena il processo é scelto per l’esecuzione
- immediatamente se il processo é in esecuzione
- nel caso il processo invii un segnale a se stesso
 
 
blocking A volte si deve assicurare che un blocco di codice non sia interrotto dalla consegna di un segnale
- aggiungere segnale alla maschera dei segnali
- insieme dei segnali la cui risezione é attualmente bloccata
 
- il segnale rimane pending fino a che non é sboccato e rimosso dalla maschera
delivery & actions Di default
- Il segnale é ignorato
- Il processo é terminato killed
- abnormal process termination
- opposta alla exit()
 
 
- abnormal process termination
- Viene generato core dump file
- virtual memory image
- utilizzata per ispezionare lo stato del processo al momento della terminazione
 
 
- virtual memory image
- Processo é bloccato stopped
- Esecuzione riprende resumed
in Unix
- 
trap Segnali generati da eventi prodotti da un processo e inviati al processo stesso - comportamenti errati possono generare trap
- divisioni per zero SIGFPE
- indirizzamento errato di array SIGEGV
- negato l’accesso a instruzioni privilegiate SIGILL
 
 
- comportamenti errati possono generare trap
- 
interrupt Segnali inviati ad un processo da un agente esterno (utente o altro processo) - User
- ^c SIGINT
- ^z SIGSTOP
- kill -s SIGNAL PID
 
- Altro processo
- kill(PID,SIGNAL)
 
 
- User
- 
disposition Impostazione della disposizione del segnale: sovrascrivere la disposizione (risposta) di default - default
- ignore
- exec signal hadler
- funzione che esegue le azioni appropriate in riposta alla ricezione di un segnale
- il segnale in questo caso é gestito handled o intercettato caught
 
 - signal() & sigaction()
 
- 
Segnali - SIGABRT
 - SIGALRM
 - SIGCHLD
 - SIGCONT
 - 
SIGINT Il terminale invia questo segnale al gruppo del processo in foreground 
 - 
SIGKILL Non puó essere bloccato, ignorato, intercettato da un hander - termina sempre un processo
 
 - 
SIGPIPE Inviato quando un processo tempa di scrivere su un pipe o FIFO il quale non ha un corrispondente lettore 
 - 
SIGSEGV O Segmentation Violation Processo tenta un riferimento in memoria non valido - la pagina non esiste
- tentata modifica a locazione read-only
- tentato accesso alla memoria del kernel
 Spesso a causa di un puntatore che contiene un bad address 
 
Facilities
System V IPC
- 
syscall - msgget()
- semget()
- shmget()
 
- 
message queues I messaggi possono essere pescati per tipo oltre che ordine 
- 
shared memory Uno degli strumenti di IPC piú veloci 
- 
semaphores set di semafori 
Sincronizzazione
- 
Semafori - blocking
- non-blocking
 Aumento del semaforo :: reso disponibile Decremento del semaforo :: accesso all’area critica - 
Syscall #include <sys/types.h> #include <sys/sem.h> int semget(key_t key, int nsems, int semflg); // returns semaphore set identifier on success , -1 on error // semflg: IPC_CREAT, IPC_EXCL int semclt(int semid, int semnum, int cmd, ... /* union semun arg */); // semun utilizzabile per l'argomento // IPC_RMID // IPC_STAT // IPC_SET // GETVAL // SETVAL // GETALL // SETALL // GETPID // GETNCNT // GETZCNT int semop(int semid, struct sembuf *sops, unsigned int nsops); // sembuf codifies the operations to do // nops is the dimension of sops struct semid_ds { struct ipc_perm sem_perm; time_t sem_otime; // timestamp of last semop (0 when created) time_t sem_ctime; unsigned short } struct sembuf { unsigned short sem_num; short sem_op; // group of operations are atomic and executed in their order // either they are all executed at once or not short sem_flg; // IPC_NOWAIT == non-blocking }Una semop bloccata fino a che: - un processo sblocca il semaforo
- viene interrotto da un segnale di Interrupt
- EINTR
 
 
 - 
binary semaphores 1: Free, 0: Reserved - Reserve
- Wait
 int initSem(); int reserveSem(int semID, int semNum); int releaseSem(int semId, int semNum);
 
Communication Facilities
- 
Distruttivitá I dati sono consumati con la lettura 
- 
Data transfer - 
byte stream Si scrivono/leggono numeri arbitrari di byte - 
Pipes Permettono l’utilizzo dell’output di un processo come input di un altro processo Il suo funzionamento é implementato utilizzando - fork()
- exec()
 Il pipe ha un verso - write end
- read end
 I processi semplicemente scrivono su fd1 e leggono da fd0 - Essendo flussi non c’é la nozione di dimensione del messaggio
- La lettura é sequenziale secondo l’ordine di scrittura
 Il pipe é semplicemente un buffer, ha una capacitá massima- quando é pieno la scrittura viene bloccata finche non sará effettuata una lettura
- generalmente non é necessario conoscere la capacitá della pipe
- una dimensione maggiore porterá a meno context switch
 - 
Protocol - carattere delimitatore
- si sceglie un carattere che non sará utilizzato nei messaggi
 
- header / body
- intestazione con lunghezza del messaggio seguente
 
- messaggi a dimensione fissa a \(n\) byte
 
- carattere delimitatore
 - 
Syscal int pipe(int filedes[2]); // ritorna 0 on success, -1 on error filedes[0]; // lettura filedes[1]; // scrittura
 - 
Chiusura fd Se il lettore non chiude il write-end alla chiusura dalla parte dello scrittore non verrá scritto EOF alla fine dello stream Quando uno scrittore scrive su una pipe senza lettori questo riceve SIGPIPE - che puó essere gestito
 Un pipe chiamato prima di una fork puó mettere in comunicazione processi imparentati 
 
 - 
FIFO Sono dotati di un nome a differenza di un pipe - possono essere utilizzati per comunicazione tra processi non imparentati
- architettura client-server
 
 Come pipe - write-end read-end
 - 
Syscall int mkfifo(const char *pathname, mode_t mode); // returns on success, -1 on error
 - 
Sincronizzazione Possibile creare una fork nella pipeline utilizzando il comando tee- duplicato dell’outut viene inviato ad un terzo processo oltre che il suo successore nella pipeline
 
 
- possono essere utilizzati per comunicazione tra processi non imparentati
 
- 
 - 
message Si scrivono/leggono messaggi interi - ogni read legge 1 solo messaggio
 - message queue
 
 
- 
Shared Memory
- meccanismo estremamente veloce
- non é mediato dal kernel
 
- va sicronizzato, tipicamente con i semafori
- evitare accessi simultanei
- evitare letture concorrenti a scritture
 
A differenza del data transfer non é distruttiva
Di norma ogni processo ha uno spazio di indirizzamento logico indipendente/separato dagli altri processi
- la memoria condivisa é fisicamente condivisa tra i processi
I figli ereditano i segmenti di SM a disposizione del genitore
- durante una exec() i segmenti sono staccati detached
- NB: non distrutti
- sono staccati anche al momento della terminazione dei processi
 
- 
lifecycle - shmget()
- creazione
- ottenere l’id di un segmento giá esistentsetxkbmap -option ctrl:swapcapse
 
- shmat()
- attach
 
- shmdt()
- detach
- avviene automaticamente alla terminazione del processo
 
 
- detach
- shmctl()
- cancellare (IPC_RMID)
- solo un processo la esegue
 
- un segmento sará effettivamente distrutto solo dopo che tutti i processi correntemente attaccati lo avranno staccato
 
- cancellare (IPC_RMID)
 
- shmget()
- 
syscalls int shmget(key_t key, size_t size, int shmflg); // funzionamento analogo alla malloc ma accessibile // a piú processi int shmat(int shmid, const void *shmaddr, int shmflg); // se il secondo parametro é NULL il kernel si // occupa di trovare una zona di memoria adatta // RETURN VALUE: indirizzo base del segmentop int shmdt(const void *shmaddr); int shmctl(int shmid, int cmd, struct shmid_ds *buf);