Qui vediamo come vengono allocati in memoria "stack" ed "heap".
FF.. | | <-- inizio dello stack /|\ | | | valori | | | | stack piu'alti| | | \|/ crescente di memoria | | XX.. | | <-- attuale puntatore stack | | | | | | 00.. |_________________| <-- fine dello stack [Stack Segment] Stack
Gli indirizzi di memoria partono da 00.. (che e' il dove il Segmento dello Stack comincia) e aumentano verso il valore FF.., mentre lo stack cresce all'opposto, cioe' dal valore FF.. al valoce 00.. verso valori bassi di memoria
XX.. e' il valore attuale dello Stack Pointer.
Lo Stack viene solitamente usato per:
Ad esempio una classica funzione
|int funzione (parametro_1, parametro_2, ..., parametro_N) { |dichiarazione variabile_1; |dichiarazione variabile_2; .. |dichiarazione variabile_N; |// Corpo della funzione |dichiarazione variabile_dinamica_1; |dichiarazione variabile_dinamica_2; .. |dichiarazione variabile_dinamica_N; |// Il Codice e' all'interno del Segmento di Codice, non nel Segmento Dati/Stack! |return (ret-type) valore; // Spesso finisce in qualche registro, per 386+: registro ''eax'' |}
utilizza uno stack del tipo
| | | 1. parametro_1 pushato| \ S | 2. parameter_2 pushato| | Prima della T | ................... | | chiamata A | N. parameter_N pushato| / C | *Indirizzo di Ritorno*| -- Chiamata K | 1. variabile_1 locale | \ | 2. variabile_2 locale | | Dopo la | ................. | | chiamata | N. variabile_N locale | / | | ... ... Stack ... ... Libero | | H | N.variabile_N dinamica| \ E | ................... | | Allocato dalle A | 2.variabile_2 dinamica| | malloc & kmalloc P | 1.variabile_1 dinamica| / |_______________________| Utilizzo tipico dello Stack
Nota: L'ordine delle variabile puo' essere differente a seconda dell'architettura hardware (come sempre qui si ipotizza l'utilizzo del 386+).
Distinguiamo 2 2 concetti:
Spesso i Processi vengono chiamati Task o Thread.
2 tipi di locks:
Il meccanismo della Copy_on_write consente di ridurre l'utilizzo di memoria, postponendo l'allocazione per un nuovo Thread fino a quando non e' strettamente necessario.
Ad esempio, quando un Task esegue la "fork()" per creare un nuovo Processo, le pagine del vecchio processo non vengono copiate, ma vengono soltanto "referenziate" in modalita' READ ONLY (sia per il padre che per il figlio) in modo che, quando qualcuno andra' a scrivervi sopra, l'eccezione generata si occupera' di creare una nuova copia della pagina marcandola, questa volta READ + WRITE.
Ecco uno schema riassuntivo:
1-) La Pagina X e' condivisa tra Il Task Padre e quello figlio Task Padre | | Accesso RO ________ | |---------->|Pagina X| |_________| |________| /|\ | Task Figlio | | | Accesso RO | | |---------------- |_________| 2-) Richiesta di Scrittura Task Figlio | | Accesso RO ________ | |---------->|Pagina X| (tentativo di scrittura) |_________| |________| | /|\ | | \|/ Task Figlio | ECCEZIONE | | Accesso RO | | |---------------- |_________| 3-) Configurazione Finale: il Task Padre e quello Figlio hanno ognuno una copia indipendente della Pagina, X e Y Task Padre | | Accesso RW ________ | |---------->|Pagina X| |_________| |________| Task Figlio | | Accesso RW ________ | |---------->|Pagina Y| |_________| |________|