Cosa succede esattamente quando eseguo un file nella mia shell?


32

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 è difettosa ...

Nel modo più dettagliato possibile, cosa succede esattamente quando eseguo un file nella mia shell? Quello che voglio dire è, se scrivo : ./somefile some argumentsnella mia shell e premo somefileInvio (ed esiste nel CWD, e ho letto + esegui i permessi su somefile) allora cosa succede sotto il cofano?

Ho pensato che la risposta fosse:

  1. La shell crea un syscall a exec, passando il percorso asomefile
  2. Il kernel esamina somefilee osserva il numero magico del file per determinare se è un formato che il processore può gestire
  3. Se il numero magico indica che il file è in un formato che può essere eseguito dal processore, allora
    1. viene creato un nuovo processo (con una voce nella tabella dei processi)
    2. somefileviene letto / mappato in memoria. Viene creato uno stack e l'esecuzione passa al punto di ingresso del codice di somefile, ARGVinizializzato su un array di parametri (a char**, ["some","arguments"])
  4. Se il numero magico è una faccenda quindi exec()genera un nuovo processo come sopra, ma l'usato eseguibile è l'interprete a cui fa riferimento la shebang (ad esempio, /bin/basho /bin/perl) e somefileviene passato aSTDIN
  5. Se il file non ha un numero magico valido, si verifica un errore come "file non valido (numero magico errato): errore formato Exec"

Tuttavia qualcuno mi ha detto che se il file è di testo semplice, la shell tenta di 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 realmente accade qui e vorrei capire la meccanica.

Cosa succede esattamente quando eseguo un file nella mia shell? (in tutti i dettagli è ragionevole ...)


1
Non c'è un sostituto perfetto per guardare il codice sorgente per una comprensione completa.
Wildcard

1
@Wildcard è quello che sto facendo in questo momento, in realtà :-) Se posso, risponderò alla mia domanda
Josh

1
source somefileè molto diverso da un nuovo processo da cui viene ignorato ./somefile, però.
thrig

@thrig yes, sono d'accordo. Ma non pensavo che ./somefileavrebbe causato l'esecuzione da parte di bash dei comandi somefilese il file non avesse un numero magico. Ho pensato che avrebbe mostrato solo un errore, e invece sembra effettivamentesource somefile
Josh

Sbaglio di nuovo, posso confermare che se somefileè un file di testo, viene generata una nuova shell se provo a eseguirlo. Un file echo $$si comporta in modo diverso se eseguo vs sorgente.
Josh

Risposte:


32

La risposta definitiva a "come vengono eseguiti i programmi" su Linux è la coppia di articoli su LWN.net intitolata, abbastanza 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. Innanzitutto, scrivi un testscript testscr1contenente

#!/bin/sh

pstree

e un altro testscr2, contenente solo

pstree

Renderli 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 creato nella directory corrente):

./execve ./testscr1
./execve ./testscr2

testscr1funziona ancora, ma testscr2produce

execve: Exec format error

Questo dimostra che la shell gestisce testscr2diversamente. Tuttavia non elabora lo script stesso, ma lo usa comunque /bin/sh; questo può essere verificato effettuando il piping testscr2a less:

./testscr2 | less -ppstree

Sul mio sistema, ottengo

    |-gnome-terminal--+-4*[zsh]
    |                 |-zsh-+-less
    |                 |     `-sh---pstree

Come puoi vedere, c'è la shell che stavo usando, zshche è iniziata less, e una seconda shell, semplice sh( dashsul mio sistema), per eseguire lo script, che è stato eseguito pstree. In zshquesto è gestito da zexecvein Src/exec.c: la shell usa execve(2)per provare a eseguire il comando, e se fallisce, legge il file per vedere se ha un shebang, elaborandolo di conseguenza (cosa che anche il kernel avrà fatto), e se quello non riesce tenta di eseguire il file con sh, purché non legga alcun byte zero 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);
        }

bashha lo stesso comportamento, implementato execute_cmd.ccon un commento utile (come sottolineato da taliezin ):

Esegui un semplice comando che si spera sia definito da qualche parte in un file su disco.

  1. fork ()
  2. collegare i tubi
  3. cerca il comando
  4. fare reindirizzamenti
  5. execve ()
  6. In caso execvecontrario, vedere se nel file è impostata la modalità eseguibile. In tal caso, e non è una directory, quindi esegui il suo contenuto come uno script di shell.

POSIX definisce un insieme di funzioni, noto come le exec(3)funzioni , che si avvolgono execve(2)e fornisce questa funzionalità anche; vedere la risposta di Muru per i dettagli. Su Linux almeno queste funzioni sono implementate dalla libreria C, non dal kernel.


Questo è fantastico e ha i dettagli che stavo cercando, grazie!
Josh

12

In parte, questo dipende dalla particolare execfunzione familiare utilizzata. execve, come ha mostrato Stephen Kitt in dettaglio, esegue solo file nel formato binario corretto o script che iniziano con un vero shebang.

Tuttavia , execlpe execvpfai un ulteriore passo: se lo shebang non era corretto, il file viene eseguito con /bin/shsu Linux. Da man 3 exec:

Special semantics for execlp() and execvp()
   The execlp(), execvp(), and execvpe() functions duplicate the actions
   of the shell in searching for an executable file if the specified
   filename does not contain a slash (/) character.
   …

   If the header of a file isn't recognized (the attempted execve(2)
   failed with the error ENOEXEC), these functions will execute the
   shell (/bin/sh) with the path of the file as its first argument.  (If
   this attempt fails, no further searching is done.)

Questo è in qualche modo supportato da POSIX (l'enfasi è mia):

Una potenziale fonte di confusione rilevata dagli sviluppatori standard riguarda il modo in cui il contenuto di un file immagine di processo influisce sul comportamento della famiglia di funzioni exec. Di seguito è una descrizione delle azioni intraprese:

  1. Se il file immagine di processo è un eseguibile valido (in un formato eseguibile e valido e con privilegi appropriati) per questo sistema, il sistema esegue il file.

  2. Se il file immagine di processo ha i privilegi appropriati ed è in un formato eseguibile ma non valido per questo sistema (come un file binario riconosciuto per un'altra architettura), si tratta di un errore e errno è impostato su [EINVAL] (vedere più avanti RATIONALE su [EINVAL]).

  3. Se il file immagine di processo ha i privilegi appropriati ma non viene altrimenti riconosciuto:

    1. Se si tratta di una chiamata a execlp () o execvp (), invocano un interprete di comandi assumendo che il file di immagine del processo sia uno script di shell.

    2. Se questa non è una chiamata a execlp () o execvp (), si verifica un errore e errno è impostato su [ENOEXEC].

Questo non specifica come si ottiene l'interprete dei comandi, quindi, ma non specifica che debba essere dato un errore. Immagino, quindi, che gli sviluppatori Linux abbiano permesso l'esecuzione di tali file /bin/sh(o questa era già una pratica comune e hanno appena seguito l'esempio).

FWIW, la manpage di FreeBSDexec(3) menziona anche comportamenti simili:

 Some of these functions have special semantics.

 The functions execlp(), execvp(), and execvP() will duplicate the actions
 of the shell in searching for an executable file if the specified file
 name does not contain a slash ``/'' character. 
 …
 If the header of a file is not recognized (the attempted execve()
 returned ENOEXEC), these functions will execute the shell with the path
 of the file as its first argument.  (If this attempt fails, no further
 searching is done.)

AFAICT, tuttavia, nessuna shell comune utilizza execlpo execvpdirettamente, presumibilmente per un controllo più preciso sull'ambiente. Tutti implementano la stessa logica usando execve.


3
Vorrei anche aggiungere che, almeno su Linux, execl, execlp, execle, execv, execvpe execvpesono tutte front-end per la execvechiamata di sistema; i primi sono forniti dalla libreria C, il kernel conosce solo execve(e execveatoggigiorno).
Stephen Kitt,

@StephenKitt Questo spiega perché non sono riuscito a trovare una manpage per quelle funzioni nella sezione 2. di man7.org
muru

6

Questa potrebbe essere un'aggiunta alla risposta di Stephen Kitt, come un commento dalla bashfonte nel file execute_cmd.c:

Esegui un semplice comando che si spera sia definito da qualche parte in un file su disco.

1. fork ()
2. connect pipes
3. look up the command
4. do redirections
5. execve ()
6. If the execve failed, see if the file has executable mode set.  

In tal caso, e non è una directory, quindi esegui il suo contenuto come uno script di shell.


0

Viene eseguito come uno script di shell, è non di provenienza (per esempio, le variabili impostate nel file eseguito non influiscono fuori). Probabilmente vestigia del passato nebbioso, quando c'erano una shell e un formato eseguibile. Non è un eseguibile, deve essere uno script di shell.


2
Hai frainteso la mia domanda. Cosa succede in dettaglio? Come minimo, devo capire quali sono i controlli di uno shebang, è questo exec()o il guscio? Voglio molti più interni
Josh
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.