Apa yang sebenarnya terjadi ketika saya menjalankan file di shell saya?

32

Jadi, saya pikir saya memiliki pemahaman yang baik tentang ini, tetapi hanya menjalankan tes (dalam menanggapi percakapan di mana saya tidak setuju dengan seseorang) dan menemukan bahwa pemahaman saya cacat ...

Sedetail mungkin apa yang sebenarnya terjadi ketika saya menjalankan file di shell saya? Apa yang saya maksud adalah, jika saya mengetik: ./somefile some argumentske dalam shell saya dan tekan kembali (dan somefileada di cwd, dan saya telah membaca + mengeksekusi izin pada somefile) lalu apa yang terjadi di bawah tenda?

Saya pikir jawabannya adalah:

  1. Shell melakukan syscall to exec, melewati path tosomefile
  2. Kernel memeriksa somefiledan melihat nomor ajaib file untuk menentukan apakah itu adalah format yang dapat ditangani prosesor
  3. Jika angka ajaib menunjukkan bahwa file tersebut dalam format yang dapat dijalankan prosesor, maka
    1. proses baru dibuat (dengan entri dalam tabel proses)
    2. somefiledibaca / dipetakan ke memori. Tumpukan dibuat dan eksekusi melompat ke titik masuk kode somefile, dengan ARGVdiinisialisasi ke array parameter (a char**, ["some","arguments"])
  4. Jika angka ajaib adalah shebang maka exec()memunculkan proses baru seperti di atas, tetapi executable yang digunakan adalah interpreter yang dirujuk oleh shebang (misalnya /bin/bashatau /bin/perl) dan somefilediteruskan keSTDIN
  5. Jika file tidak memiliki angka ajaib yang valid, maka kesalahan seperti "file tidak valid (angka sihir buruk): Kesalahan format Exec" terjadi

Namun seseorang mengatakan kepada saya bahwa jika file tersebut adalah teks biasa, maka shell mencoba mengeksekusi perintah (seolah-olah saya telah mengetik bash somefile). Saya tidak percaya ini, tetapi saya hanya mencobanya, dan itu benar. Jadi saya jelas memiliki kesalahpahaman tentang apa yang sebenarnya terjadi di sini, dan ingin memahami mekanika.

Apa yang sebenarnya terjadi ketika saya menjalankan file di shell saya? (Dalam banyak detail masuk akal ...)

Josh
sumber
Tidak ada pengganti yang sempurna untuk melihat kode sumber untuk pemahaman mendalam.
Wildcard
1
@Wildcard itulah yang saya lakukan sekarang, sebenarnya :-) Jika saya bisa, saya akan menjawab pertanyaan saya sendiri
Josh
1
source somefilesangat berbeda dari proses baru yang dipotong oleh ./somefile.
thrig
@ thrig ya, saya setuju. Tapi saya tidak berpikir itu ./somefileakan menyebabkan bash untuk mengeksekusi perintah somefilejika file tidak memiliki angka ajaib. Saya pikir itu hanya akan menampilkan kesalahan, dan sebaliknya itu muncul secara efektifsource somefile
Josh
Saya salah lagi, saya dapat mengkonfirmasi bahwa jika somefileini adalah file teks, maka shell baru akan muncul jika saya mencoba untuk mengeksekusinya. File echo $$berperilaku berbeda jika saya mengeksekusi vs sumbernya.
Josh

Jawaban:

31

Jawaban pasti untuk "bagaimana program dijalankan" di Linux adalah sepasang artikel di LWN.net berjudul, cukup mengejutkan, Bagaimana program dijalankan dan Bagaimana program dijalankan: ELF binari . Artikel pertama membahas skrip secara singkat. (Sebenarnya, jawaban pasti ada dalam kode sumber, tetapi artikel ini lebih mudah dibaca dan memberikan tautan ke kode sumber.)

Sebuah eksperimen kecil menunjukkan bahwa Anda sudah melakukannya dengan benar, dan bahwa eksekusi file yang berisi daftar perintah sederhana, tanpa shebang, perlu ditangani oleh shell. Halaman execve (2) berisi kode sumber untuk program pengujian, execve; kami akan menggunakannya untuk melihat apa yang terjadi tanpa shell. Pertama, tulis skrip test testscr1,, yang berisi

#!/bin/sh

pstree

dan satu lagi testscr2, hanya berisi

pstree

Jadikan keduanya dapat dieksekusi, dan verifikasi bahwa keduanya berjalan dari shell:

chmod u+x testscr[12]
./testscr1 | less
./testscr2 | less

Sekarang coba lagi, menggunakan execve(dengan asumsi Anda membuatnya di direktori saat ini):

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

testscr1masih berjalan, tetapi testscr2menghasilkan

execve: Exec format error

Ini menunjukkan bahwa shell menangani secara testscr2berbeda. Itu tidak memproses skrip itu sendiri, ia masih menggunakan /bin/shuntuk melakukan itu; ini dapat diverifikasi dengan memipis testscr2ke less:

./testscr2 | less -ppstree

Di sistem saya, saya mengerti

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

Seperti yang Anda lihat, ada shell yang saya gunakan, zshyang dimulai less, dan shell kedua, polos sh( dashdi sistem saya), untuk menjalankan skrip, yang berjalan pstree. Dalam zshhal ini ditangani oleh zexecvedalam Src/exec.c: shell digunakan execve(2)untuk mencoba menjalankan perintah, dan jika itu gagal, ia membaca file untuk melihat apakah ia memiliki shebang, memprosesnya sesuai (yang juga akan dilakukan oleh kernel), dan jika itu gagal mencoba menjalankan file dengan sh, asalkan tidak membaca nol byte dari 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);
        }

bashmemiliki perilaku yang sama, diimplementasikan execute_cmd.cdengan komentar yang bermanfaat (seperti yang ditunjukkan oleh taliezin ):

Jalankan perintah sederhana yang mudah-mudahan didefinisikan dalam file disk di suatu tempat.

  1. fork ()
  2. menghubungkan pipa
  3. lihat perintahnya
  4. lakukan pengalihan
  5. execve ()
  6. Jika execvegagal, lihat apakah file telah menetapkan mode yang dapat dieksekusi. Jika demikian, dan itu bukan direktori, maka jalankan isinya sebagai skrip shell.

POSIX mendefinisikan satu set fungsi, yang dikenal sebagai yang exec(3)fungsi , yang membungkus execve(2)dan menyediakan fungsi ini juga; lihat jawaban muru untuk perinciannya. Di Linux setidaknya fungsi-fungsi ini diimplementasikan oleh pustaka C, bukan oleh kernel.

Stephen Kitt
sumber
Ini fantastis dan memiliki detail yang saya cari, terima kasih!
Josh
12

Sebagian, ini tergantung pada execfungsi keluarga tertentu yang digunakan. execve, seperti yang diperlihatkan oleh Stephen Kitt secara terperinci, hanya menjalankan file dalam format biner yang benar atau skrip yang dimulai dengan shebang yang tepat.

Namun , execlpdan execvpmelangkah lebih jauh: jika shebang tidak benar, file dieksekusi dengan /bin/shdi Linux. Dari 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.)

Ini agak didukung oleh POSIX (penekanan milik saya):

Salah satu sumber kebingungan potensial yang dicatat oleh pengembang standar adalah bagaimana konten file gambar proses mempengaruhi perilaku keluarga eksekutif fungsi. Berikut ini adalah deskripsi tindakan yang dilakukan:

  1. Jika file gambar proses adalah executable yang valid (dalam format yang dapat dieksekusi dan valid dan memiliki hak istimewa yang sesuai) untuk sistem ini, maka sistem mengeksekusi file tersebut.

  2. Jika file gambar proses memiliki hak istimewa yang sesuai dan berada dalam format yang dapat dieksekusi tetapi tidak valid untuk sistem ini (seperti biner yang dikenali untuk arsitektur lain), maka ini merupakan kesalahan dan errno diatur ke [EINVAL] (lihat nanti RATIONALE pada [EINVAL]).

  3. Jika file gambar proses memiliki hak istimewa yang sesuai tetapi tidak diakui:

    1. Jika ini adalah panggilan ke execlp () atau execvp (), maka mereka memanggil juru bahasa dengan asumsi bahwa file gambar proses adalah skrip shell.

    2. Jika ini bukan panggilan ke execlp () atau execvp (), maka kesalahan terjadi dan errno diatur ke [ENOEXEC].

Ini tidak menentukan bagaimana juru bahasa perintah diperoleh, jadi, tetapi tidak menentukan bahwa kesalahan harus diberikan. Saya kira, oleh karena itu, para pengembang Linux mengizinkan file-file tersebut untuk dijalankan /bin/sh(atau ini sudah menjadi praktik umum dan mereka hanya mengikuti sesuai).

FWIW, manual FreeBSD untukexec(3) juga menyebutkan perilaku serupa:

 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, bagaimanapun, tidak menggunakan shell umum execlpatau execvpsecara langsung, mungkin untuk kontrol yang lebih baik terhadap lingkungan. Mereka semua menerapkan logika yang sama menggunakan execve.

muru
sumber
3
Aku juga menambahkan bahwa setidaknya di Linux, execl, execlp, execle, execv, execvpdan execvpesemua depan berakhir dengan execvesyscall; yang pertama disediakan oleh perpustakaan C, kernel hanya tahu tentang execve(dan execveatsaat ini).
Stephen Kitt
@StephenKitt Itu menjelaskan mengapa saya tidak dapat menemukan halaman manual untuk fungsi-fungsi itu di bagian 2.
man7.org
6

Ini bisa menjadi tambahan untuk jawaban Stephen Kitt, sebagai komentar dari bashsumber dalam file execute_cmd.c:

Jalankan perintah sederhana yang mudah-mudahan didefinisikan dalam file disk di suatu tempat.

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.  

Jika demikian, dan itu bukan direktori, maka jalankan isinya sebagai skrip shell.

taliezin
sumber
0

Itu dieksekusi sebagai skrip shell, itu tidak bersumber (misalnya, variabel yang diatur dalam file yang dieksekusi tidak mempengaruhi luar). Mungkin sisa-sisa dari masa lalu yang berkabut, ketika ada satu shell dan satu format yang dapat dieksekusi. Bukan yang dapat dieksekusi, itu harus berupa skrip shell.

vonbrand
sumber
2
Anda salah mengerti pertanyaan saya. Apa yang terjadi secara detail? Paling tidak, saya perlu memahami pemeriksaan apa untuk shebang, apakah itu exec()atau kulitnya? Saya ingin lebih banyak internal
Josh