Takže jsem si myslel, že tomu rozumím dobře, ale provedl jsem test (v reakci na konverzaci, kde jsem s někým nesouhlasil) a zjistil jsem, že moje porozumění je chybné…
Co nejpodrobněji co se přesně stane, když spustím soubor v mém shellu? Mám na mysli, když napíšu:./somefile some arguments
do mého shellu a stiskněte return (a somefile
existuje v cwd a mám oprávnění ke čtení a spouštění pro nějaký soubor
) co se potom děje pod kapotou?
Myslel jsem odpověď byla:
- Shell provede systémové volání
exec
, předá cestu knějakému souboru
- Jádro zkoumá
nějaký soubor
a podívá se na magické číslo souboru, aby zjistil, zda se jedná o formát, který procesor zvládne - Pokud magické číslo udává, že soubor je ve formátu, který může procesor spustit, pak
- je vytvořen nový proces (se záznamem v tabulce procesů)
nějaký soubor
se čte/mapuje do paměti. Vytvoří se zásobník a provedení skočí na vstupní bod kódusomefile
, sARGV
inicializován na pole parametrů (char**
,["nějaké","argumenty"]
)
- Pokud je magické číslo shebang, pak
exec()
vytvoří nový proces jako výše, ale použitým spustitelným souborem je interpret, na který odkazuje shebang (např./bin/bash
nebo/bin/perl
) anějaký soubor
je předánSTDIN
- Pokud soubor nemá platné magické číslo, objeví se chyba jako „neplatný soubor (špatné magické číslo):Chyba formátu Exec“
Někdo mi však řekl, že pokud je soubor prostý text, pak se shell pokusí provést příkazy (jako bych napsal bash somefile
). Nevěřil jsem tomu, ale prostě jsem to zkusil a bylo to správné. Takže mám zjevně nějaké mylné představy o tom, co se tu vlastně děje, a rád bych porozuměl mechanikám.
Co přesně se stane, když spustím soubor v mém shellu? (pokud jde o podrobnosti, které jsou rozumné…)
Přijatá odpověď:
Definitivní odpovědí na otázku „jak se spouštějí programy“ na Linuxu je dvojice článků na LWN.net nazvaných, překvapivě, Jak se spouštějí programy a Jak se spouštějí programy:binární soubory ELF. První článek se skriptům věnuje stručně. (Přesně vzato je definitivní odpověď ve zdrojovém kódu, ale tyto články se snáze čtou a poskytují odkazy na zdrojový kód.)
Trocha experimentování ukazuje, že jste to do značné míry vystihli správně a že spuštění souboru obsahujícího jednoduchý seznam příkazů bez shebangu musí zvládnout shell. Manuová stránka execve(2) obsahuje zdrojový kód pro testovací program, execve; použijeme to, abychom viděli, co se stane bez shellu. Nejprve napište testovací skript testscr1
, obsahující
#!/bin/sh
pstree
a další, testscr2
, obsahující pouze
pstree
Udělejte je oba spustitelnými a ověřte, že oba běží z prostředí shell:
chmod u+x testscr[12]
./testscr1 | less
./testscr2 | less
Nyní to zkuste znovu pomocí execve
(za předpokladu, že jste jej vytvořili v aktuálním adresáři):
./execve ./testscr1
./execve ./testscr2
testscr1
stále běží, ale testscr2
produkuje
execve: Exec format error
To ukazuje, že shell zpracovává testscr2
jinak. Nezpracovává však samotný skript, stále používá /bin/sh
udělat to; to lze ověřit pomocí potrubí testscr2
na méně
:
./testscr2 | less -ppstree
V mém systému dostávám
|-gnome-terminal--+-4*[zsh]
| |-zsh-+-less
| | `-sh---pstree
Jak můžete vidět, je tu shell, který jsem používal, zsh
, který začal méně
, a druhý shell, prostý sh
(pomlčka
na mém systému), ke spuštění skriptu, který spustil pstree
. V zsh
toto řeší zexecve
v Src/exec.c
:shell používá execve(2)
pokusit se spustit příkaz, a pokud se to nezdaří, přečte soubor, aby zjistil, zda má shebang, a podle toho jej zpracuje (což jádro také udělá), a pokud se to nezdaří, pokusí se spustit soubor s
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
má stejné chování, implementované v execute_cmd.c
s užitečným komentářem (jak poukázal taliezin):
Proveďte jednoduchý příkaz, který je, doufejme, definován v souboru na disku
někde.
fork ()
- připojit potrubí
- vyhledejte příkaz
- provádět přesměrování
execve ()
- Pokud
execve
selhal, zjistěte, zda má soubor nastavený spustitelný režim.
Pokud ano a nejedná se o adresář, spusťte jeho obsah jako
skript shellu.
POSIX definuje sadu funkcí, známou jako exec(3)
funkce, které obalují execve(2)
a poskytovat také tuto funkci; podrobnosti viz Muruova odpověď. Na Linuxu jsou alespoň tyto funkce implementovány knihovnou C, nikoli jádrem.