1.07 Lezione 7 - follen99/ArchitetturaDeiCalcolatori GitHub Wiki

📅 03-19 0:24

Pseudo istruzioni di Jump

Queste pseudo istruzioni vengono utilizzate dai programmatori per velocizzare la programmazione; l'assembler ci fornisce un set di istruzioni "finte", che vengono rimpiazzate (dall'assembler) dalle istruzioni reali.

Se devo effettuare un salto a funzione, o utilizzo l'istruzione intera, oppure posso usare call e return;

  • j label
  • b label
  • jal label
  • jalr t0
  • jalr t0, -100
  • jr t0
  • jr t0, -100
  • call label: la call è una call "lontana", sovrascrive il registro t1, quindi se usiamo il registro t1 dobbiamo stare attenti a copiarlo in un altro registro!
  • ret : return, viene usata dal programmatore perchè è più comodo di scrivere l'istruzione completa di jump
  • tail label

Sincronizzazione

Argomento visto nella parte di SO.

Traduzione e setup

Come si passa dal programma scritto ad alto livello, ad esempio in C, al linguaggio macchina che viene eseguito in memoria?

Quando, su linea di comando o tramite IDE, compiliamo ed eseguiamo il codice, viene prima lanciato uno script che lancia il compilatore, l'assembler ed il linker.

Supponiamo di partire da un programma C

  1. Compilazione, eseguita dello strumento chiamato compiler. Tipicamente il compiler prevede più passi di compilazione;
  2. Il gcc tira fuori dei codici .s, che sono dei file assembly, che poi vengono assemblati da un assemblatore; il compilatore tradizionale dei sistemi UNIX si chiama cc, altri gcc, altri ancora as, ecc. Questi file .s non si vedono tanto spesso perchè quando si chiama il gcc tutti i passaggi intermedi vengono cancellati. È possibile aggiungere dei parametri che dice a gcc di non cancellare i files intermedi.

🏁 00:48

Alla fine del processo di gcc, otteniamo un file oggetto (.o); il linker ha il compito di prendere tutti i vari "pezzettini" (come ad esempio tutte le varie librerie) e di generare un unico "pezzettone" in grado da essere eseguito.

Quando usiamo una libreria C, questa non viene inserita tutta all'interno dell'eseguibile finale dal linker (perchè altrimenti questo sarebbe enorme), ma prende solo i moduli che servono al nostro programma, e ci restituisce un programma in linguaggio macchina pronto ad essere eseguito.

n.b. questo codice non si trova ancora in RAM, ma sul disco. Quindi come sappiamo (in realtà ancora no se devi ancora studiare la parte di SO), il programma dovrà essere spostato (almeno in parte) nella RAM per essere eseguito dal processore.

Per una prova pratica, vedi qui.

Vari eseguibili

Su sistemi windows, gli eseguibili hanno un'estensione ben precisa: .exe .

Sui sistemi UNIX, invece, non hanno una vera e propria estensione, infatti se un file è eseguibile o meno, non lo si determina grazie alla sua estensione. Per default il compilatore (se non riceve argomenti di nome), produce un eseguibile di default chiamato a.out (.o).

Produrre un modulo oggetto

I moduli oggetto sono quelli prodotti alla fine della catena compilatore - assemblatore. Vengono prodotte istruzioni in linguaggio macchina; oltre a questo servono delle informazioni di riferimento che serviranno nelle fasi successive.

In questi moduli oggetto si trovano:

  • Headers: descrivono i contenuti del modulo

  • Segmento text: istruzioni tradotte

  • Segmento dei dati statici: dati allocati per l'intera vita del programma

  • Tabelle dei simboli: per default sono tutti nomi delle funzioni e tutte le variabili globali le quali potrebbero usate da altri moduli; il linker unisce i vari moduli. Per nascondere le funzioni al linker si dichiarano le funzioni come static.

  • Info di debug: Associare i nomi simbolici alle variabili reali che sono state allocate in memoria; questo tipo di debug è chiamato debug simbolico.

    Linking Object Modules

Il linker è colui che produce un'immagine eseguibile, dove per immagine si intende un qualcosa che può essere montata in memoria (sarà più chiaro nella parte di SO).

Come viene prodotta l'immagine?

  • Unisce i vari segmenti.
  • Risolve i vari labels: ovvero collega i nomi simbolici di variabili e funzioni con le reali locazioni di memoria in cui esse sono andate a finire.
  • Patch (modifiche) di tutto ciò che dipende dalla locazione di memoria e dei riferimenti esterni;

Il linker non definisce la posizione in memoria dove il programma verrà caricato, proprio. perchè questa è decisa solo al momento di esecuzione (visto meglio nella parte di SO)

Ultima fase - Il loader

Il loader è un vero e proprio modulo appartenente al sistema operativo, che preleva il programma dal disco e lo carica in memoria:

  1. Accede al file
  2. legge le dimensioni dei segmenti
  3. crea uno spazio indirizzi
  4. Inizializza array, ecc
  5. prepara lo stack: pone i parametri per il main, infatti il main potrebbe prevedere dei parametri (vedi argv argc); infatti il main potrebbe prevedere dei parametri (che di solito passiamo da linea di comando quando lanciamo il programma). Questi parametri sono forniti proprio dal loader.
  6. Inizializza i registri: ad esempio inizializza il valore dello stack pointer ponendolo al valore giusto
  7. lancia una routine di startup: è un minimo di codice aggiunto automaticamente dal compilatore; la prima istruzione del main non è la prima istruzione eseguita in realtà. Prima di eseguire il "nostro" codice c'è questa routine di startup che esegue delle operazioni che servono a "preparare" il nostro programma all'esecuzione.

Come termina il programma?

Il programma (a meno che non sia terminato esternamente di cattiveria) termina comunicando aver terminato al sistema operativo.

La system call in questione è la exit() in un programma assembly, o con una return in C.

Viene quindi eseguita una routine di uscita, che come quella di entrata, pulisce tutto ciò che è stato sporcato dal programma eseguito.

🏴 fine lezione 7

⚠️ **GitHub.com Fallback** ⚠️