Caricare un programma e metterlo in esecuzione è un processo delicato che parte dalla funzione execve() della libreria standard e viene svolto dalla funzione proc_sys_exec() del kernel.
La funzione proc_sys_exec() (listato i188.9.21) del kernel è quella che svolge il compito di caricare un processo in memoria e di annotarlo nella tabella dei processi.
La funzione, dopo aver verificato che si tratti di un file eseguibile valido e che ci siano i permessi per metterlo in funzione, procede all'allocazione della memoria, dividendo se necessario l'area codice da quella dei dati, quindi legge il file e copia opportunamente le componenti di questo nelle aree di memoria allocate.
La realizzazione attuale della funzione proc_sys_exec() non è in grado di verificare se un processo uguale sia già in memoria, quindi carica la parte del codice anche se questa potrebbe essere già disponibile. |
Terminato il caricamento del file, viene ricostruita in memoria la pila dei dati del processo. Prima si mettono sul fondo le stringhe delle variabili di ambiente e quelle degli argomenti della chiamata, quindi si aggiungono i puntatori alle stringhe delle variabili di ambiente, ricostruendo così l'array noto convenzionalmente come envp[], continuando con l'aggiunta dei puntatori alle stringhe degli argomenti della chiamata, per riprodurre l'array argv[]. Per ricostruire gli argomenti della chiamata della funzione main() dell'applicazione, vanno però aggiunti ancora: il puntatore all'inizio dell'array delle stringhe che descrivono le variabili di ambiente, il puntatore all'array delle stringhe che descrivono gli argomenti della chiamata e il valore che rappresenta la quantità di argomenti della chiamata.
Fatto ciò, vanno aggiunti tutti i valori necessari allo scambio dei processi, costituiti dai vari registri da rimpiazzare.
Superato il problema della ricostruzione della pila dei dati, la funzione proc_sys_exec() predispone i descrittori di standard input, standard output e standard error, quindi libera la memoria usata dal processo chiamante e ne rimpiazza i dati nella tabella dei processi con quelli del nuovo processo caricato.
I programmi iniziano con il codice che si trova nel file applic/crt0.s
. Questo file ha delle affinità con il file kernel/main/crt0.s
del kernel, dove la prima differenza che si incontra riguarda l'impronta di riconoscimento. A parte questo, va considerato che il codice delle applicazioni viene eseguito in un momento in cui i registri di segmento sono già stati impostati e l'indice della pila è già collocato correttamente; inoltre, se la funzione main() termina e restituisce il controllo a crt0.s
, un ciclo senza fine esegue continuamente una chiamata di sistema per la conclusione del processo elaborativo corrispondente.
|
La figura mostra il confronto tra il codice iniziale contenuto nel file applic/crt0.s
, senza preamboli e senza commenti, con la dichiarazione del tipo derivato header_t, presente nel file kernel/proc.h
. Attraverso questa struttura, la funzione proc_sys_exec() è in grado di estrapolare dal file le informazioni necessarie a caricarlo correttamente in memoria.
Come già accennato, quando viene eseguito il codice di un programma applicativo, la pila dei dati è già operativa. Pertanto, dopo il simbolo startup_code si può già lavorare con questa.
|
Per prima cosa, viene estratto dalla pila il puntatore all'array noto come envp[], per poter assegnare tale valore alla variabile environ, come richiede lo standard della libreria POSIX. Tuttavia, per poter gestire poi le variabili di ambiente, si rende necessario utilizzare un array più «comodo», quando le stringhe vanno sostituite. A tale proposito, nel file lib/stdlib/environment.c
, si dichiarano _environment_table[][] e _environment[]. Il primo è semplicemente un array di caratteri, dove, utilizzando due indici di accesso, si conviene di allocare delle stringhe, con una dimensione massima prestabilita. Il secondo, invece, è un array di puntatori, per localizzare l'inizio delle stringhe contenute nel primo. In pratica, alla fine _environment[] e environ[] devono essere equivalenti. Ma per attuare questo, occorre utilizzare la funzione _environment_setup() che sistema tutti i puntatori necessari.
|
Come si vede dall'estratto del file applic/crt0.s
, si vede l'uso della funzione _environment_setup() (il registro CX contiene già il puntatore a envp[], e viene inserito nella pila proprio come argomento per la funzione). Successivamente viene riassegnata anche la variabile environ in modo da coincidere con _environment. Alla fine, viene ricostruita la pila per gli argomenti della chiamata della funzione main(), ma prima di procedere con quella chiamata, si utilizzano due funzioni, per inizializzare la gestione dei flussi di file e delle directory, sempre in forma di flussi.
|
La funzione _stdio_stream_setup(), contenuta nel file lib/stdio/FILE.c
, associa i descrittori standard ai flussi di file standard (standard input, standard output e standard error); la funzione _dirent_directory_stream_setup() compie un lavoro analogo, limitandosi però a inizializzare un array di flussi di directory.
Dopo queste preparazioni, viene chiamata la funzione main(), la quale riceve regolarmente i propri argomenti previsti. Il valore restituito dalla funzione viene poi salvato in corrispondenza del simbolo exit_value.
|
All'uscita dalla funzione main(), dopo aver salvato quanto restituito dalla funzione stessa, ci si introduce nel codice successivo al simbolo halt, nel quale si chiama la funzione sys() (chiamata di sistema), per produrre la chiusura formale del processo. Ciò che si vede è comunque l'equivalente di _exit (exit_status);.(1)
1) Va tenuto in considerazione che exit_status è un simbolo non raggiungibile dal codice C, perché dovrebbe essere esportato con un nome che inizi con il trattino basso.
«a2» 2013.11.11 --- Copyright © Daniele Giacomini -- appunti2@gmail.com http://informaticalibera.net