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 entrambi
Il 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);