Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
vuoi
o PayPal
tutte le volte che vuoi
PROCESSO PROCESSO NORMALE
THREAD PROCESSO LEGGERO
ENTRAMBI PROCESSO o TASK
Questa piccola differenza porta ad un casino con gli identificatori. Nel POSIX avevamo un
gettid())
thread ID per identificare i thread (ottenibile con la funzione e un PID che
getpid()).
identificava il processo (ottenibile con la funzione Thread appartenenti allo
stesso processo avevano lo stesso PID.
In Linux il thread ID c'è ancora sano e salvo, ma esiste un altro numero detto TGID che
numericamente è identico al PID, ma concettualmente rappresenta l'ID del THREAD
GROUP LEADER, cioè del thread di default del processo.
void *t1(void *arg) {
printf("Thread creato:\t\til TID e' %d e il TGID/PID e' %d\n", syscall(SYS_gettid), getpid());
return NULL;
}
int main(void) {
printf("Thread principale:\til TID e' %d e il TGID/PID e' %d\n", syscall(SYS_gettid), getpid());
pthread_t tID;
pthread_create(&tID, NULL, t1, NULL);
pthread_join(tID, NULL);
return 0;
}
Nel momento in cui cambia il processo in esecuzione, si ha una COMMUTAZIONE DI
CONTESTO o CONTEXT SWITCH. Il contesto è l'insieme delle informazioni che
riguardano lo stato di un processo in un determinato istante. Perché sai, quando un
processo viene fermato poi deve ripartire, e bisogna sapere in che punto era, qual era il
contenuto dei registri, il contenuto della pila ecc...
Tutto questo è nelle mani dello scheduler, che decide quale processo mandare in
esecuzione rispettando una certa POLITICA DI SCHEDULING. Essa è spesso basata su
priorità (i processi più importanti vanno eseguiti prima di quelli meno importanti) e quando
più processi hanno la stessa priorità devono essere eseguiti in maniera equa (FAIR), per
cui un processo non deve aspettare per il proprio turno troppo tempo più degli altri. È lo
scheduler stesso inoltre ad effettuare il cambio di contesto, spostando i dati che servono
dove servono.
Adesso inizia una serie di informazioni messe alla rinfusa senza alcun ordine logico.
SISTEMI MULTI-PROCESSORE
Con l'arrivo dei multi-processori si è complicato tutto e Linux ne ha tenuto conto.
L'architettura meglio supportata da Linux per il multi-processore è il SMP (Symmetric
MultiProcessing). Si suppone che ci siano più processori collegati ad una memoria
centrale condivisa, con accesso a tutte le periferiche ed un unico sistema operativo. I core
presenti su un solo processore vengono visti come processori distinti. In questa
architettura Linux alloca un solo processo per ogni processore distinto (ALLOCAZIONE
STATICA) e ognuno di essi ha il proprio PROCESSO CORRENTE. La riallocazione
avviene solo se, con un controllo periodico, si verifica che un processore è troppo
sbilanciato rispetto agli altri (LOAD BALANCING). È comunque un'operazione costosa
perché la riallocazione da un processore all'altro comporta lo svuotamento della cache. È
il modello utilizzato nei normali PC multicore, in cui i processori e le periferiche vengono
considerati tutti sullo stesso piano senza particolari specializzazioni. Noi comunque
facciamo finta che esista un solo processore e non ci complichiamo la vita.
KERNEL NON-PREEMPTABLE
Il kernel di Linux è NON-PREEMPTABLE ovvero è proibita la preemption mentre un
processo sta eseguendo codice del kernel. In pratica ha priorità massima. È comunque
possibile compilare una versione del kernel preemptable, necessaria in alcuni sistemi real-
time, dove un servizio può essere più urgente del sistema operativo e deve essere servito
in tempo reale. Di default comunque il kernel è non-preemptable e noi considereremo
questa versione.
Linux è scritto in linguaggio C e compilato con il compilatore e linker GNU GCC. È quindi
portabile sulle piattaforme hardware dove esiste quel compilatore. Il sistema operativo è
ben legato all'hardware, con alcune funzioni dipendenti dalla specifica architettura: per
questo esistono diverse implementazioni per le varie architetture supportate, che fanno
riferimento a codici assembly diversi. L'architettura che vedremo noi è la x86-64, quella
che ho io e praticamente tutti su PC e server perché in quei settori spacca e fa tutto (per
curiosità, nel settore mobile è più diffusa l'architettura ARM).
STRUTTURA DATI DELLA GESTIONE DEI PROCESSI
Ovvero la struttura che contiene informazioni necessarie per la gestione dei processi, tra
queste anche il suo CONTESTO HARDWARE, cioè il contenuto dei registri. Queste
strutture sono il DESCRITTORE DEL PROCESSO e la PILA DI SISTEMA (sPila). Ogni
processo ha un proprio descrittore e una propria pila di sistema, e sono tutte strutture
contenute nella memoria centrale. task_struct
Il descrittore del processo è una variabile struct di tipo allocata
dinamicamente nella memoria dinamica del kernel alla creazione del processo. Questa
struct contiene un casino di informazioni: il PID, il TGIP, lo stato del processo, il contesto
thread_struct
hardware (in una sottostruttura dipendente dall'HW), il puntatore alla cima
della sPila, il puntatore alla base della sPila, variabili di scheduling, informazioni su
memoria/file system/file aperti.
E indovina un po'? Ci sono due pile! Una PILA UTENTE (uPila) e una PILA DI SISTEMA
(sPila). La prossima lezione vediamo un po' a cosa servono :D
Dicevamo che noi lavoriamo su Linux x64. Parliamo meglio di questa architettura.
I registri sono a ben 64 bit, e naturalmente ci sono un Program Counter RIP e uno Stack
Pointer RSP. La pila come di consueto sale da indirizzi alti a indirizzi bassi, ma a
push pop.
differenza di MIPS nell'x64 esistono delle apposite istruzioni di e Nel salto a
funzione i parametri e l'indirizzo di ritorno sono sempre salvati sullo stack e non nei
call ret.
registri. Le istruzioni di salto e ritorno da sottoprogramma sono e
Per far sì che le cose belle che spiegheremo funzionino, è necessario che l'hardware dia
una mano attraverso registri appositi detti STRUTTURE DATI AD ACCESSO HW. Per
esempio un registro particolare del processore è chiamato PSR (Processor State
Register) ed è il REGISTRO DI STATO contenente informazioni riguardanti lo stato
attuale del processore. Inoltre il processore può funzionare in due modalità diverse:
MODO UTENTE (USER MODE o NON PRIVILEGIATO): è la modalità con cui
– funzionano i programmi utente.
MODO SUPERVISORE (SUPERVISOR MODE o KERNEL o PRIVILEGIATO):
– modalità di esecuzione del SO.
In modo U il processore può accedere ai dati e al codice solo del processo in esecuzione,
e non di altri processi o del sistema operativo. In modo S invece il processore ha accesso
a tutta la memoria. Alcune istruzioni sono eseguibili solo in modo S e sono dette
PRIVILEGIATE. La modalità con cui opera il processore è indicata in un bit del PSR.
Tutte le istruzioni di I/O sono istruzioni privilegiate, e un programma utente può utilizzarle
solo attraverso una chiamata di sistema (system call), invocando il servizio richiesto.
Nota: l'x64 reale in realtà ha anche dei livelli di privilegio intermedi, e pure il PSR è un'astrazione
perché di registri per il controllo ce ne sono un'infinità. Visto che è un'architettura complicatissima,
noi semplifichiamo un po' le cose, perché dobbiamo CAPIRE.
SYSCALL
L'istruzione non privilegiata permette di passare dal modo U al modo S per
poter eseguire un servizio del SO. Questa istruzione salva sulla pila rispettivamente il PC
e il contenuto del PSR. Nel Program Counter e nel Registro di Stato vengono inseriti i
valori <PC, PSR> contenuti in un VETTORE DI SYSCALL. Questo vettore viene
inizializzato durante il boot di sistema e contiene in coppia l'indirizzo delle syscall con i
rispettivi bit da inserire nel PSR per consentirne l'esecuzione.
SYSRET
L'istruzione privilegiata fa l'inverso: fa tornare il controllo dal sistema operativo al
processo che aveva invocato il servizio. Di conseguenza ripristina rispettivamente il PSR e
il PC che erano memorizzati sulla pila. È un'istruzione privilegiata perché viene eseguita
dal sistema operativo al termine della funzione. Questo implica il fatto importante che non
è possibile eseguire syscall annidate.
C'è un problema però: non abbiamo specificato in quale pila vengono memorizzati PC e
PSR, e l'altra volta dicevamo che ci sono due pile. Bene. Iniziamo a dire che l'architettura
64
a 64 bit potenzialmente ammette una memoria di 2 byte, ovvero 16 EB (una roba
48
gigantesca). Nella realtà la memoria virtuale ammette “solo” 2 byte ovvero 256 TB che
47
vengono suddivisi in due blocchi da 2 byte ciascuno (128 TB). Nel primo blocco gli
indirizzi vanno da 0 a 0x0000'7FFF'FFFF'FFFF e nel secondo da 0xFFFF'8000'0000'0000
a 0xFFFF'FFFF'FFFF'FFFF. Gli indirizzi intermedi sono detti NON CANONICI e non
possono essere referenziati (il loro uso genera un errore). In modo S tutti gli indirizzi
canonici sono utilizzabili, mentre in modo U solo il primo blocco è utilizzabile.
Ora possiamo chiaramente dire che quando la CPU cambia modo di funzionamento, deve
anche cambiare pila di lavoro, cioè passare dalla uPila alla sPila, un nuovo stack a cui è
assegnato uno spazio di 8 KB. Nel passaggio da modo U a modo S la commutazione di
pila deve avvenire prima del salvataggio di dati sulla stessa. Ci servono due strutture dati
nella memoria di sistema ad accesso hardware dette USP e SSP: in USP viene salvato lo
stack pointer del modo U prima di passare in modo S, mentre SSP contiene il valore da
inserire nello stack pointer nel passaggio al modo S (ovvero la base della sPila).
Quindi la SYSCALL fa queste belle cose:
Salva SP in USP
– Carica in SP il valore di SSP (adesso punta a sPila)
– Salva su sPila l'indirizzo di ritorno
– Salva su sPila il PSR (contenente il bit di modo U)
– Carica in PC e PSR i valori presenti nel Vettore di Syscall (si passa così in modo S)
–
A questo punto la SYSRET fa queste altre cosette:
Ripristina il PSR presente sulla sPila (passando così in modo U)
– Ripristina l'indirizzo di ritorno presente sulla sPila
– Carica USP in SP (si torna a puntare su uPila)
–
Parliamo ora di INTERRUPT: sono eventi rilevati via hardware che giungono dall'esterno
e sono completamente asincroni con l'esecuzione dei processi. Ad ognuno di questi
particolari eventi è associata una funzione del SO detta gestore dell'interrupt (ISR –
Interrupt Service Routine), che è in sostanza un servizio del SO. Quando il processore
rileva