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 foo
dipanggil 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 -h
dijalankan 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 stdout
alias fd 1 (jadi jika akhir penulisan adalah fd 4, kita lakukan dup2(4,1)
). Kapan exec()
akan muncul df
proses anak tidak akan memikirkan apa-apa stdout
dan 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 grep
denganexec()
. Selama ini proses induk masih ada, menunggu untuk mendapatkan kembali kontrol setelah pipa selesai.
Dalam kasus perintah bawaan, umumnya shell tidak fork()
, dengan pengecualian source
perintah. 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