Background

Contesto e multiprogrammazione

Nel nostro sistema si adotta la multiprogrammazione, ovvero la possibilità di avere più processi attivi contemporaneamente oltre al kernel del sistema operativo residente. Quando avviamo un programma (tramite un comando da linea di comando o con un doppio clic su un’icona), l’eseguibile viene caricato dal disco in memoria affinché possa essere eseguito. Spesso, infatti, ci sono diversi programmi attivi in parallelo (basti pensare alle molte app aperte su uno smartphone o su un qualunque PC).

Memoria RAM e differenze con la memoria di massa

Per “memoria” in questo contesto si intende la memoria volatile (RAM), che si distingue dalla memoria di massa (dischi e file system). La memoria RAM è detta “volatile” perché perde i dati allo spegnimento del computer, mentre i dischi conservano le informazioni in modo persistente.

Dal punto di vista della CPU, la RAM esiste perché garantisce tempi di accesso più veloci rispetto al disco, anche se rimane più lenta rispetto ai registri interni alla CPU. In genere, le dimensioni della RAM possono variare da gigabyte a decine/centinaia di gigabyte, a seconda del sistema: i registri, invece, sono molto meno numerosi (spesso poche decine o, in rari casi, poche centinaia).

Accesso alla memoria e bus

Per accedere alla memoria, la CPU utilizza indirizzi e dati attraverso i relativi bus (l’address bus, il data bus e il control bus). L’accesso ai registri è molto rapido (spesso avviene in un solo ciclo di clock), mentre l’accesso alla memoria RAM può essere più lento: la CPU opera a frequenze dell’ordine di GHz (tempi dell’ordine dei nanosecondi), mentre la RAM può introdurre ritardi maggiori.

Cache e prestazioni

La cache è una memoria intermedia che cerca di offrire tempi di accesso più simili a quelli della CPU ma con una capacità maggiore rispetto ai registri, anche se comunque inferiore alla RAM. In questo modo, si riducono i tempi di attesa (o “stall”) dovuti ai ritardi di accesso alla memoria principale.

Protezione della memoria

Un altro aspetto importante è la protezione: un sistema deve impedire che un processo possa accedere o modificare la memoria riservata ad altri processi o, ancora peggio, la memoria del sistema operativo. Nei sistemi di qualche decennio fa (come i primissimi PC con MS-DOS), queste misure di protezione erano assenti o molto limitate.

The Model of Run Time

image.png

La figura mostra il meccanismo di creazione di un programma eseguibile in ambiente Unix. In particolare, mostra come, partendo dai file sorgente scritti in C (in questo esempio, main.c, help.c e other.c), si arrivi alla produzione di un unico file eseguibile (per impostazione predefinita chiamato a.out in Unix).

Il processo avviene in due passaggi principali:

  1. Il preprocessore e il compilatore generano per ciascun file sorgente un file oggetto (estensione .o), tenendo conto anche dei file header (come defs.h e mac.h). In questa fase, il preprocessore sostituisce le direttive (#include, #define, ecc.), mentre il compilatore traduce il codice C in linguaggio macchina, producendo i corrispondenti file .o.
  2. Il linker unisce poi tutti i file oggetto insieme alle librerie necessarie (qui vediamo la libc.a, che contiene le funzioni standard del C), per creare l’eseguibile finale. Oltre a combinare i file oggetto, il linker collega le chiamate di funzione del programma alle relative implementazioni nella libreria, consentendo di usare le funzioni standard del C (printf, scanf, ecc.).

Nel mondo Unix, se non si specifica un nome diverso per l’eseguibile, il risultato prenderà il nome di a.out. Naturalmente, è possibile rinominarlo o assegnargli un nome differente (ad esempio con l’opzione -o usando gcc).

Da questa descrizione risulta chiaro che il flusso di compilazione è costituito da: