Quindi, ho pensato di avere una buona comprensione di questo, ma ho appena eseguito un test (in risposta a una conversazione in cui non ero d'accordo con qualcuno) e ho scoperto che la mia comprensione è imperfetta...
Nel modo più dettagliato possibile cosa succede esattamente quando eseguo un file nella mia shell? Quello che voglio dire è, se digito:./somefile some arguments
nella mia shell e premi Invio (e somefile
esiste nel cwd e ho i permessi di lettura+esecuzione su somefile
) allora cosa succede sotto il cofano?
pensavo la risposta è stata:
- La shell effettua una syscall a
exec
, passando il percorso asomefile
- Il kernel esamina
somefile
ed esamina il numero magico del file per determinare se si tratta di un formato che il processore può gestire - Se il numero magico indica che il file è in un formato che il processore può eseguire, allora
- viene creato un nuovo processo (con una voce nella tabella dei processi)
somefile
viene letto/mappato in memoria. Viene creato uno stack e l'esecuzione salta al punto di ingresso del codice disomefile
, conARGV
inizializzato su un array di parametri (unchar**
,["some","arguments"]
)
- Se il numero magico è uno shebang allora
exec()
genera un nuovo processo come sopra, ma l'eseguibile utilizzato è l'interprete a cui fa riferimento lo shebang (ad es./bin/bash
o/bin/perl
) esomefile
viene passato aSTDIN
- Se il file non ha un numero magico valido, viene visualizzato un errore come "file non valido (numero magico errato):errore di formato Exec"
Tuttavia qualcuno mi ha detto che se il file è di testo normale, la shell prova a eseguire i comandi (come se avessi digitato bash somefile
). Non ci credevo, ma l'ho appena provato ed era corretto. Quindi ho chiaramente alcune idee sbagliate su ciò che effettivamente accade qui e vorrei capire i meccanismi.
Cosa succede esattamente quando eseguo un file nella mia shell? (in tutti i dettagli è ragionevole...)
Risposta accettata:
La risposta definitiva a "come vengono eseguiti i programmi" su Linux è la coppia di articoli su LWN.net intitolati, sorprendentemente, Come vengono eseguiti i programmi e Come vengono eseguiti i programmi:binari ELF. Il primo articolo affronta brevemente gli script. (A rigor di termini la risposta definitiva è nel codice sorgente, ma questi articoli sono più facili da leggere e forniscono collegamenti al codice sorgente.)
Una piccola sperimentazione mostra che hai praticamente capito bene e che l'esecuzione di un file contenente un semplice elenco di comandi, senza shebang, deve essere gestita dalla shell. La manpage execve(2) contiene il codice sorgente per un programma di test, execve; lo useremo per vedere cosa succede senza una shell. Per prima cosa, scrivi uno script di test, testscr1
, contenente
#!/bin/sh
pstree
e un altro, testscr2
, contenente solo
pstree
Rendili entrambi eseguibili e verifica che entrambi vengano eseguiti da una shell:
chmod u+x testscr[12]
./testscr1 | less
./testscr2 | less
Ora riprova, usando execve
(supponendo che tu l'abbia compilato nella directory corrente):
./execve ./testscr1
./execve ./testscr2
testscr1
funziona ancora, ma testscr2
produce
execve: Exec format error
Questo mostra che la shell gestisce testscr2
diversamente. Tuttavia, non elabora lo script stesso, utilizza ancora /bin/sh
fare quello; questo può essere verificato collegando testscr2
a less
:
./testscr2 | less -ppstree
Sul mio sistema, ricevo
|-gnome-terminal--+-4*[zsh]
| |-zsh-+-less
| | `-sh---pstree
Come puoi vedere, c'è la shell che stavo usando, zsh
, che è iniziato con less
e una seconda shell, semplice sh
(dash
sul mio sistema), per eseguire lo script, che ha eseguito pstree
. In zsh
questo è gestito da zexecve
in Src/exec.c
:la shell usa execve(2)
per provare a eseguire il comando e, se fallisce, legge il file per vedere se ha uno shebang, elaborandolo di conseguenza (cosa che avrà fatto anche il kernel), e se fallisce prova a eseguire il file con sh
, purché non abbia letto zero byte dal file:
for (t0 = 0; t0 != ct; t0++)
if (!execvebuf[t0])
break;
if (t0 == ct) {
argv[-1] = "sh";
winch_unblock();
execve("/bin/sh", argv - 1, newenvp);
}
bash
ha lo stesso comportamento, implementato in execute_cmd.c
con un utile commento (come sottolineato da taliezin):
Esegui un semplice comando che si spera sia definito in un file del disco
da qualche parte.
fork ()
- collegare i tubi
- Cerca il comando
- esegui reindirizzamenti
execve ()
- Se il
execve
fallito, controlla se il file ha la modalità eseguibile impostata.
Se è così, e non è una directory, esegui il suo contenuto come
uno script di shell.
POSIX definisce un insieme di funzioni, noto come exec(3)
funzioni, che avvolgono execve(2)
e fornire anche questa funzionalità; vedere la risposta di muru per i dettagli. Su Linux almeno queste funzioni sono implementate dalla libreria C, non dal kernel.