Mengapa shell memanggil fork ()?

32

Ketika suatu proses dimulai dari shell, mengapa shell itu bercabang sendiri sebelum menjalankan proses?

Misalnya, ketika pengguna memasukkan grep blabla foo, mengapa shell tidak dapat memanggil exec()grep tanpa shell anak?

Juga, ketika shell bercabang sendiri dalam emulator terminal GUI, apakah shell memulai emulator terminal lain? (seperti pts/13memulai pts/14)

pengguna3122885
sumber

Jawaban:

34

Ketika Anda memanggil execmetode keluarga itu tidak membuat proses baru, sebagai gantinya execmenggantikan memori proses saat ini dan set instruksi dll dengan proses yang ingin Anda jalankan.

Sebagai contoh, Anda ingin menjalankan grepmenggunakan exec. bashadalah suatu proses (yang memiliki memori terpisah, ruang alamat). Sekarang ketika Anda menelepon exec(grep), eksekutif akan mengganti memori proses saat ini, ruang alamat, set instruksi dll dengan grep'sdata. Itu berarti bashproses tidak akan ada lagi. Akibatnya, Anda tidak dapat kembali ke terminal setelah menyelesaikan grepperintah. Itu sebabnya metode keluarga exec tidak pernah kembali. Anda tidak dapat mengeksekusi kode apa pun setelah exec; itu tidak terjangkau.

shantanu
sumber
Hampir ok --- saya mengganti Terminal dengan bash. ;-)
Rmano
2
BTW, Anda bisa memberi tahu bash untuk mengeksekusi grep tanpa forking terlebih dahulu, dengan menggunakan perintah exec grep blabla foo. Tentu saja, dalam kasus khusus ini, itu tidak akan sangat berguna (karena jendela terminal Anda akan menutup begitu grep selesai), tetapi kadang-kadang berguna (misalnya jika Anda memulai shell lain, mungkin melalui ssh / sudo / layar, dan jangan berniat untuk kembali ke yang asli, atau jika proses shell Anda menjalankan ini adalah sub-shell yang tidak pernah dimaksudkan untuk menjalankan lebih dari satu perintah).
Ilmari Karonen
7
Set Instruksi memiliki arti yang sangat spesifik. Dan itu bukan arti Anda menggunakannya.
Andrew Savinykh
@IlmariKaronen Ini akan berguna dalam skrip wrapper, di mana Anda ingin menyiapkan argumen dan lingkungan untuk sebuah perintah. Dan kasus yang Anda sebutkan, di mana bash tidak pernah dimaksudkan untuk menjalankan lebih dari satu perintah, itu sebenarnya bash -c 'grep foo bar'dan memanggil exec ada bentuk optimasi bash lakukan untuk Anda secara otomatis
Sergiy Kolodyazhnyy
3

Sesuai pts, periksa sendiri: di shell, jalankan

echo $$ 

untuk mengetahui id proses Anda (PID), saya punya misalnya

echo $$
29296

Kemudian jalankan misalnya sleep 60dan kemudian, di terminal lain

(0)samsung-romano:~% ps -edao pid,ppid,tty,command | grep 29296 | grep -v grep
29296  2343 pts/11   zsh
29499 29296 pts/11   sleep 60

Jadi tidak, secara umum Anda memiliki tty yang sama terkait dengan proses. (Perhatikan bahwa ini adalah milik Anda sleepkarena memiliki shell sebagai induknya).

Rmano
sumber
2

TL; DR : Karena ini adalah metode optimal untuk membuat proses baru dan menjaga kontrol dalam shell interaktif

fork () diperlukan untuk proses dan pipa

Untuk menjawab bagian spesifik dari pertanyaan ini, jika grep blabla foodipanggil via exec()langsung di induk, orangtua akan mengambil ada, dan PID dengan semua sumber daya akan diambil alih oleh grep blabla foo.

Namun, mari kita bicara secara umum tentang exec()dan fork(). Alasan utama untuk perilaku tersebut adalah karena fork()/exec()merupakan metode standar untuk menciptakan proses baru di Unix / Linux, dan ini bukan hal khusus bash; metode ini telah ada sejak awal dan dipengaruhi oleh metode yang sama dari sistem operasi yang sudah ada saat itu. Mengutip jawaban goldilocks pada pertanyaan terkait, fork()untuk membuat proses baru lebih mudah karena kernel memiliki lebih sedikit pekerjaan yang harus dilakukan sejauh mengalokasikan sumber daya, dan banyak properti (seperti deskriptor file, lingkungan, dll) - semua bisa diwarisi dari proses induk (dalam hal ini dari bash).

Kedua, sejauh shell interaktif berjalan, Anda tidak dapat menjalankan perintah eksternal tanpa forking. Untuk meluncurkan executable yang hidup pada disk (misalnya, /bin/df -h), Anda harus memanggil salah satu exec()fungsi keluarga, seperti execve(), yang akan menggantikan induk dengan proses baru, mengambil alih PID dan deskriptor file yang ada, dll. Untuk shell interaktif, Anda ingin kontrol kembali ke pengguna dan membiarkan shell interaktif induk melanjutkan. Dengan demikian, cara terbaik adalah membuat subproses via fork(), dan membiarkan proses itu diambil alih via execve(). Jadi shell interaktif PID 1156 akan menelurkan seorang anak melalui fork()dengan PID 1157, lalu panggil execve("/bin/df",["df","-h"],&environment), yang /bin/df -hdijalankan dengan PID 1157. Sekarang shell hanya perlu menunggu proses untuk keluar dan mengembalikan kontrol ke sana.

Jika Anda harus membuat pipa di antara dua perintah atau lebih, katakanlah df | grep, Anda memerlukan cara untuk membuat dua deskriptor file (yang membaca dan menulis ujung pipa yang berasal dari pipe()syscall), lalu membiarkan dua proses baru mewarisinya. Itu dilakukan forking proses baru dan kemudian dengan menyalin ujung tulis pipa melalui dup2()panggilan ke stdoutalias fd 1 (jadi jika akhir penulisan adalah fd 4, kita lakukan dup2(4,1)). Kapan exec()akan muncul dfproses anak tidak akan memikirkan apa-apa stdoutdan menulis padanya tanpa sadar (kecuali jika aktif memeriksa) bahwa outputnya benar-benar berjalan pipa. Proses yang sama terjadi grep, kecuali kita fork(), mengambil membaca ujung pipa dengan fd 3 dan dup(3,0)sebelum pemijahan grepdenganexec(). Selama ini proses induk masih ada, menunggu untuk mendapatkan kembali kontrol setelah pipa selesai.

Dalam kasus perintah bawaan, umumnya shell tidak fork(), dengan pengecualian sourceperintah. Subshell membutuhkan fork().

Singkatnya, ini adalah mekanisme yang perlu dan bermanfaat.

Kekurangan forking dan optimalisasi

Sekarang, ini berbeda untuk cangkang non-interaktif , seperti bash -c '<simple command>'. Meskipun fork()/exec()merupakan metode optimal di mana Anda harus memproses banyak perintah, itu membuang-buang sumber daya ketika Anda hanya memiliki satu perintah tunggal. Mengutip Stéphane Chazelas dari pos ini :

Forking itu mahal, dalam waktu CPU, memori, deskriptor file yang dialokasikan ... Memiliki proses shell berbohong tentang hanya menunggu proses lain sebelum keluar hanya membuang-buang sumber daya. Selain itu, sulit untuk melaporkan dengan benar status keluar dari proses terpisah yang akan mengeksekusi perintah (misalnya, ketika proses tersebut dimatikan).

Oleh karena itu, banyak cangkang (bukan hanya bash) digunakan exec()untuk membiarkannya bash -c ''diambil alih oleh satu perintah sederhana itu. Dan tepat untuk alasan yang disebutkan di atas, meminimalkan pipa dalam skrip shell lebih baik. Seringkali Anda dapat melihat pemula melakukan sesuatu seperti ini:

cat /etc/passwd | cut -d ':' -f 6 | grep '/home'

Tentu saja, ini akan fork()3 proses. Ini adalah contoh sederhana, tetapi pertimbangkan file besar, dalam kisaran Gigabytes. Akan jauh lebih efisien dengan satu proses:

awk -F':' '$6~"/home"{print $6}' /etc/passwd

Buang-buang sumber daya sebenarnya bisa menjadi bentuk serangan Denial of Service, dan khususnya bom fork dibuat melalui fungsi shell yang menyebut diri mereka sendiri dalam pipa, yang memalsukan banyak salinan dari diri mereka sendiri. Saat ini, ini dimitigasi melalui pembatasan jumlah maksimum proses dalam cgroup pada systemd , yang Ubuntu juga gunakan sejak versi 15.04.

Tentu saja bukan berarti forking itu buruk. Ini masih merupakan mekanisme yang bermanfaat seperti yang dibahas sebelumnya, tetapi jika Anda dapat pergi dengan lebih sedikit proses, dan secara berurutan lebih sedikit sumber daya dan dengan demikian kinerja yang lebih baik, maka Anda harus menghindari fork()jika mungkin.

Lihat juga

Sergiy Kolodyazhnyy
sumber
1

Untuk setiap perintah (contoh: grep) yang Anda terbitkan pada bash prompt, Anda sebenarnya bermaksud untuk memulai proses baru dan kemudian kembali ke bash prompt setelah eksekusi.

Jika proses shell (bash) memanggil exec () untuk menjalankan grep, proses shell akan diganti dengan grep. Grep akan berfungsi dengan baik tetapi setelah eksekusi, kontrol tidak dapat kembali ke shell karena proses bash sudah diganti.

Untuk alasan ini, bash memanggil fork (), yang tidak menggantikan proses saat ini.

FlowRaja
sumber