Neden kabuklar çatal çağırır ()?


32

Bir işlem bir kabuktan başlatıldığında, kabuk işlemden önce neden kendini çatallar?

Örneğin, kullanıcı girdiğinde grep blabla foo, neden kabuk exec()bir çocuk kabuğu olmadan sadece grep'i arayamıyor?

Ayrıca, bir kabuk bir GUI terminal emülatörü içinde kendisini çatalladığında, başka bir terminal emülatörü başlatır mı? ( pts/13başlangıç gibi pts/14)

Yanıtlar:


34

Bir execaile yöntemini çağırdığınızda yeni bir işlem oluşturmaz, bunun execyerine mevcut işlem belleğini ve komut kümesini vb. Çalıştırmak istediğiniz işlemle değiştirir.

Örnek olarak, grepexec kullanarak çalıştırmak istersiniz . bashbir işlemdir (ayrı belleğe, adres alanına sahip). Şimdi aradığınızda exec(grep), exec mevcut işlemin hafızasını, adres alanını, komut setini vs. grep'sveri ile değiştirecektir. Bu bashsüreç artık varolmayacak demektir. Sonuç olarak, grepkomutu tamamladıktan sonra terminale geri dönemezsiniz. Bu nedenle exec aile yöntemleri asla geri dönmez. Çalıştırdıktan sonra herhangi bir kodu çalıştıramazsınız; ulaşılamaz.


Neredeyse tamam --- Terminal'i bash ile değiştirdim. ;-)
Rmano

2
BTW, yapabilirsiniz komutunu kullanarak, ilk çatallama olmadan grep yürütmek için bash söyle exec grep blabla foo. Tabii ki, bu özel durumda, çok faydalı olmayacak (terminal pencereniz grep biter bitmez kapanacak çünkü), ancak zaman zaman kullanışlı olabilir (örneğin, başka bir kabuk başlatıyorsanız, belki ssh ile) / sudo / screen ve orjinaline geri dönmeyi düşünmüyorsunuz ya da üzerinde çalıştığınız kabuk işlemi, hiçbir zaman birden fazla komut çalıştırmak istemeyen bir alt kabuk ise).
Ilmari Karonen

7
Komut Seti'nin çok özel bir anlamı vardır. Ve içinde kullandığınız anlam değil.
Andrew Savinykh

@IlmariKaronen Bir komut için argümanlar ve ortamlar hazırlamak istediğiniz sarmalayıcı komut dosyalarında yararlı olur. Ve bash aslında birden fazla komutu çalıştırmak anlamına asla Bahsettiğiniz durum, bash -c 'grep foo bar've çağıran exec optimizasyonu Bash formu sizin için otomatik olarak yapar yoktur
Sergiy Kolodyazhnyy

3

Başı olarak pts, kendin kontrol et: bir kabuğun içinde, koş

echo $$ 

işlem kimliğinizi (PID) bilmek için, örneğin

echo $$
29296

Sonra örneğin çalıştırın sleep 60ve sonra başka bir terminalde

(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

Yani hayır, genel olarak süreçle ilgili aynı tty'ye sahipsiniz. (Bunun sizin olduğuna dikkat edin sleepçünkü ebeveyni olarak üst kabuğuna sahiptir)


2

TL; DR : Çünkü bu, yeni süreçler oluşturmak ve kontrolü etkileşimli kabukta tutmak için en uygun yöntemdir.

Çatal () işlemleri ve borular için gereklidir

Eğer bu sorunun belirli bir bölümünü cevaplamak için grep blabla fooçağrılacak olan exec()doğrudan ebeveyn de, ebeveyn varlığını ele geçirmek ve kaynaklar tarafından ele alınacağını tüm ile PID olur grep blabla foo.

Ancak, genel olarak exec()ve hakkında konuşalım fork(). Böyle bir davranışın ana nedeni fork()/exec(), Unix / Linux'ta yeni bir süreç oluşturmanın standart yöntemi olmasıdır ve bu, net bir şey değildir; bu yöntem başlangıçtan beri uygulanmış ve aynı yöntemden zamanın mevcut işletim sistemlerinden etkilenmiştir. Biraz paraphrase için goldilocks'un ilgili bir soruya vereceği cevaba göre , fork()yeni süreç oluşturmak daha kolaydır çünkü çekirdeğin kaynakları ayırmak için yapması gereken daha az işi vardır ve çoğu özellik (örneğin dosya tanımlayıcıları, çevre, vb.) ana süreçten miras alınması (bu durumdabash ).

İkincisi, etkileşimli mermiler gittiğinde, çatal olmadan harici bir komut çalıştıramazsınız. Diskte yaşayan bir yürütülebilir dosyayı başlatmak için (örneğin, /bin/df -h), üst öğeyi yeni işlemle değiştirecek olan exec()aile işlevlerinden birini çağırmanız gerekir; örneğin, execve()PID'sini ve mevcut dosya tanımlayıcılarını vb. Etkileşimli kabuk için, kontrolün kullanıcıya geri dönmesini ve ana etkileşimli kabuğun devam etmesini istersiniz. Bu nedenle, en iyi yol, bir alt işlem aracılığıyla oluşturmak fork()ve bu işlemin ele geçirilmesine izin vermektir execve(). Bu nedenle etkileşimli kabuk PID 1156 fork(), PID 1157 ile bir çocuğu doğurur , sonra çağrı execve("/bin/df",["df","-h"],&environment)yapar, bu da PID 1157 /bin/df -hile çalıştırılır.

İki veya daha fazla komut arasında bir boru oluşturmak zorunda kalmanız durumunda df | grep, iki dosya tanımlayıcısı ( pipe()sistem çağrısından gelen borunun sonunu okuyan ve yazan ) oluşturmak için bir yönteme ihtiyacınız var, o halde bir şekilde iki yeni işlemin miras almasına izin verin. Bu işlem yeni bir işlem gerektiriyor ve sonra borunun yazma ucunu aka fd 1 dup2()çağrısıyla kopyalayarak yapılır stdout(eğer yazma ucu fd 4 ise, yaparız dup2(4,1)). exec()Yumurtlama zamanı geldiğinde df, çocuk süreci hiçbir şey düşünmeyecek stdoutve çıktısının gerçekten bir boruya girdiğini bilmeden (aktif olarak kontrol etmediği sürece) yazacaktır. Aynı işlem grep, biz hariç fork(), fd 3 ile borunun sonunu okur ve dup(3,0)yumurtlamadan önce grepolur.exec(). Tüm bu zaman ana süreci hala orada, boru hattı tamamlandıktan sonra kontrolü tekrar kazanmayı bekliyor.

Yerleşik komutlarda, genellikle kabuk, komut fork()hariç , olmaz source. Deniz kabukları gerektirir fork().

Kısacası, bu gerekli ve kullanışlı bir mekanizmadır.

Çatal ve dezavantajların dezavantajları

Şimdi, bu gibi etkileşimli olmayan kabuklar için farklıbash -c '<simple command>' . fork()/exec()Birçok komutu işlemeniz gereken en uygun yöntem olmasına rağmen , yalnızca bir tek komutunuz olduğunda bu bir kaynak israfıdır. Alıntı Stéphane Chazelas gelen bu yazı :

Forking pahalıdır, CPU zamanında, hafızaya, tahsis edilmiş dosya tanımlayıcılarına sahiptir ... Çıkmadan önce sadece başka bir işlemi beklemekten bahseden bir kabuk işlemine sahip olmak sadece bir kaynak israfıdır. Ayrıca, komutu uygulayacak olan ayrı işlemin çıkış durumunun doğru bir şekilde rapor edilmesini zorlaştırır (örneğin, işlem öldüğü zaman).

Bu nedenle, birçok kabuk (sadece değil bash) bu basit komutla ele geçirilmesine exec()izin vermek bash -c ''için kullanılır. Ve tam olarak yukarıda belirtilen nedenlerden dolayı, kabuk komut dosyalarındaki boru hatlarının en aza indirilmesi daha iyidir. Genellikle yeni başlayanların böyle bir şey yaptığını görebilirsiniz:

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

Tabii ki, bu fork()3 süreç olacak . Bu basit bir örnektir, ancak Gigabyte aralığında büyük bir dosya düşünün. Tek bir işlemle çok daha verimli olurdu:

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

Kaynakların israfı aslında bir Hizmet Reddi saldırısı olabilir ve özellikle de kendilerini bombalayan ve kendilerine ait birden fazla kopyasını alan kabuk bombalarıyla çatal bombaları yaratılır. Günümüzde, bu Ubuntu'nun 15.04 sürümünden bu yana kullandığı sistem gruplarındaki gruplardaki maksimum işlem sayısını sınırlamak suretiyle azaltılmaktadır .

Tabii ki bu çatallama demek sadece kötü değil. Daha önce tartışıldığı gibi hala faydalı bir mekanizmadır, ancak daha az işlemle ve art arda daha az kaynakla ve dolayısıyla daha iyi performansla kurtulmanız fork()durumunda, mümkünse kaçınmalısınız .

Ayrıca bakınız


1

Bash komut isteminde verdiğiniz her komut için (örnek: grep), aslında yeni bir işlem başlatmayı ve yürütmeden sonra bash komut istemine dönmeyi düşünüyorsunuz.

Kabuk işlemi (bash) grep'i çalıştırmak için exec () işlevini çağırırsa, kabuk işlemi grep ile değiştirilecektir. Grep iyi çalışacaktır ancak yürütmeden sonra, kontrol işlemi kabuğa geri dönemez çünkü bash işlemi zaten değiştirilmiştir.

Bu sebeple bash, mevcut işlemin yerine geçmeyen çatal () işlevini çağırıyor.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.