Lütfen exec () işlevini ve ailesini açıklayın


102

Nedir exec()fonksiyonu ve onun ailesi? Bu işlev neden kullanılır ve nasıl çalışır?

Lütfen bu işlevleri herkes açıklasın.


4
Stevens'ı tekrar okumaya çalışın ve anlamadığınız şeyin ne olduğunu netleştirin.
vlabrecque

Yanıtlar:


250

Basitçe, UNIX'te süreçler ve programlar kavramına sahipsiniz. Süreç, bir programın yürütüldüğü ortamdır.

UNIX "yürütme modeli" nin arkasındaki basit fikir, yapabileceğiniz iki işlemin olmasıdır.

Birincisi fork(), durumu da dahil olmak üzere mevcut programın bir kopyasını (çoğunlukla) içeren yepyeni bir süreç yaratır. Hangisinin ebeveyn, hangisinin çocuk olduğunu anlamalarına izin veren iki süreç arasında birkaç farklılık vardır.

İkincisi, exec()mevcut süreçte programı yepyeni bir programla değiştiren.

Bu iki basit işlemden, tüm UNIX yürütme modeli oluşturulabilir.


Yukarıdakilere biraz daha ayrıntı eklemek için:

Yeni süreçleri başlatmak için çok basit bir yol sağladığı için UNIX'in ruhunu kullanmak fork()ve exec()örneklendirmektedir.

fork()Çağrı neredeyse her şekilde (değil özdeş mevcut sürecin yakın bir kopyasını yapar her şeyi , örneğin kaynak bazı uygulamalarda sınırlar, ama fikir mümkün mertebe yakın bir kopyasını oluşturmak, üzerinde kopyalanır). Yalnızca bir işlem çağrısı fork() ancak bu çağrıdan iki işlem geri dönüyor - kulağa tuhaf geliyor ama gerçekten oldukça zarif

Yeni süreç (çocuk olarak adlandırılır) farklı bir işlem kimliği (PID) alır ve eski sürecin (ana) PID'sini ana PID'si (PPID) olarak alır.

İki süreç artık tam olarak aynı kodu çalıştırdığı için, hangisinin hangisi olduğunu - dönüş kodu fork()bu bilgiyi sağlar - çocuk 0 alır, ebeveyn çocuğun PID'sini alır ( fork()başarısız olursa, hayır çocuk oluşturulur ve ebeveyn bir hata kodu alır).

Bu şekilde, ebeveyn çocuğun PID'sini bilir ve onunla iletişim kurabilir, onu öldürebilir, bekleyebilir vb. (Çocuk her zaman ebeveyn sürecini bir çağrı ile bulabilir getppid()).

exec()Çağrı yeni bir program ile sürecin tüm geçerli içeriğini değiştirir. Programı mevcut işlem alanına yükler ve giriş noktasından çalıştırır.

Yani, fork()ve exec()genellikle mevcut bir sürecin alt öğesi olarak çalışan yeni bir program elde etmek için sırayla kullanılır. Kabuklar bunu genellikle find- kabuk çatalları gibi bir programı çalıştırmaya çalıştığınızda yapar , ardından çocuk findprogramı belleğe yükler, tüm komut satırı argümanlarını, standart G / Ç vb.

Ancak birlikte kullanılmaları gerekli değildir. Örneğin, program hem ana hem de alt kod içeriyorsa, bir programın fork()aşağıdakiler olmadan çağırması tamamen kabul edilebilir bir exec()durumdur (yaptığınız işe dikkat etmeniz gerekir, her uygulamanın kısıtlamaları olabilir).

Bu, yalnızca bir TCP bağlantı noktasını dinleyen ve ebeveyn dinlemeye geri dönerken belirli bir isteği işlemek için kendilerinin bir kopyasını çatallayan artalan süreçleri için oldukça fazla kullanıldı (ve hala da öyle). Bu durum için, program hem ana hem de alt kodu içerir.

Benzer şekilde, bitmiş olduğunu biliyorum ve programlar sadece gerekmez başka bir programı çalıştırmak istiyorum fork(), exec()ve sonra wait()/waitpid()çocuk için. Çocuğu doğrudan mevcut işlem alanlarına yükleyebilirler exec().

Bazı UNIX uygulamaları, fork()yazma üzerine kopyalama dedikleri şeyi kullanan optimize edilmiş bir uygulamaya sahiptir . Bu, fork()program o alandaki bir şeyi değiştirmeyi deneyene kadar işlem alanının kopyalanmasını geciktirmek için yapılan bir hiledir . Bu, yalnızca kullanarak bu programlar için yararlıdır fork()ve exec()bunlar bütün bir süreç alanını kopyalamak gerekmez ki. Linux altında, fork()sadece sayfa tablolarının bir kopyasını oluşturur ve yeni bir görev yapısı, exec()iki işlemin belleğini "ayırma" gibi homurdanma işini yapar.

Eğer exec edilir Aşağıdaki denilen fork(ve bu çoğunlukla böyle olur), bu süreç uzaya bir yazma neden olur ve modifikasyonlar izin verilmeden önce o zaman, çocuk süreç için kopyalanır.

Linux ayrıca, iki işlem arasında vfork()hemen hemen her şeyi paylaşan, daha da optimize edilmiş bir özelliğe sahiptir . Bu nedenle, çocuğun yapabileceklerinde belirli kısıtlamalar vardır ve ebeveyn, çocuk exec()ya da arayana kadar durur _exit().

Üst öğenin durdurulması gerekir (ve alt öğenin mevcut işlevden dönmesine izin verilmez) çünkü iki süreç aynı yığını paylaşsa bile. fork()Hemen ardından gelen klasik kullanım durumu için bu biraz daha etkilidir exec().

Bütün bir aile olduğunu Not execaramaları ( execl, execle, execvevb) ama execbağlamda burada bunlardan herhangi biri anlamına gelir.

Aşağıdaki şema fork/exec, bashkabuğun lskomutla bir dizini listelemek için kullanıldığı tipik işlemi göstermektedir :

+--------+
| pid=7  |
| ppid=4 |
| bash   |
+--------+
    |
    | calls fork
    V
+--------+             +--------+
| pid=7  |    forks    | pid=22 |
| ppid=4 | ----------> | ppid=7 |
| bash   |             | bash   |
+--------+             +--------+
    |                      |
    | waits for pid 22     | calls exec to run ls
    |                      V
    |                  +--------+
    |                  | pid=22 |
    |                  | ppid=7 |
    |                  | ls     |
    V                  +--------+
+--------+                 |
| pid=7  |                 | exits
| ppid=4 | <---------------+
| bash   |
+--------+
    |
    | continues
    V

12
Böyle ayrıntılı açıklama için teşekkürler :)
Faizan

2
Find programındaki kabuk referansı için teşekkürler. Tam olarak anlamam gereken şey.
Kullanıcı

execYardımcı program neden mevcut sürecin GÇ'sini yeniden yönlendirmek için kullanılıyor? Yürütmeyi bağımsız değişken olmadan çalıştıran "boş" durumu bu kural için nasıl kullanıldı?
Ray

@Ray, bunu hep doğal bir uzantı olarak düşünmüşümdür. Eğer düşünürseniz execaraçlar daha sonra, diğeriyle bu süreçte mevcut programı (kabuk) yerine olarak değil kutu ile değiştirmek diğer programı belirterek basitçe ne demek yok değiştirmek istiyor.
paxdiablo

"Doğal genişleme" ile "organik büyüme" çizgisinde bir şeyi kastediyorsan ne demek istediğini anlıyorum. Görünüşe göre yeniden yönlendirme, program değiştirme işlevini desteklemek için eklenmiş olabilir ve bu davranışın execbir program olmadan çağrılmanın dejenere durumda kaldığını görebiliyorum . Ancak bu senaryoda biraz tuhaf, çünkü yeni bir program için yeniden yönlendirmenin orijinal kullanışlılığı - aslında kullanılan bir program exec- ortadan kalkıyor ve mevcut programı yeniden yönlendiren yararlı bir yapınız var - ki bu kullanılmıyor execveya başlatılmıyor herhangi bir şekilde - bunun yerine.
Ray

36

Exec () ailesindeki işlevler farklı davranışlara sahiptir:

  • l: bağımsız değişkenler dizelerin listesi olarak main () öğesine iletilir
  • v: argümanlar, main () öğesine dizeler dizisi olarak iletilir
  • p: yeni çalışan programı aramak için yol / lar
  • e: ortam, arayan tarafından belirlenebilir

Onları karıştırabilirsiniz, bu nedenle sahip olursunuz:

  • int execl (const char * yol, const char * arg, ...);
  • int execlp (const char * dosya, const char * arg, ...);
  • int execle (const char * path, const char * arg, ..., char * const envp []);
  • int execv (const char * yol, char * const argv []);
  • int execvp (const char * dosya, char * const argv []);
  • int execvpe (const char * file, char * const argv [], char * const envp []);

Hepsi için ilk argüman, yürütülecek dosyanın adıdır.

Daha fazla bilgi için exec (3) man sayfasını okuyun :

man 3 exec  # if you are running a UNIX system

1
İlginç bir şekilde, execve()POSIX tarafından tanımlanan listenizi kaçırdınız ve POSIX tarafından tanımlanmamış olanı eklediniz execvpe()(çoğunlukla tarihsel emsal nedenlerinden dolayı; işlevler kümesini tamamlar). Aksi takdirde, aile için adlandırma kuralının yararlı bir açıklaması - paxdiablo'ya yararlı bir ek , işlevlerin işleyişi hakkında daha fazla bilgi veren bir yanıt .
Jonathan Leffler

1
Ve savunmanız için, execvpe()(ve diğerleri) için Linux man sayfasının listelenmediğini görüyorum execve(); kendine ait ayrı bir kılavuz sayfasına sahiptir (en azından Ubuntu 16.04 LTS'de) - aradaki fark, diğer exec()aile işlevlerinin 3. bölümde (işlevler) execve()listelenirken 2. bölümde (sistem çağrıları) listelenmesidir. Temel olarak, ailedeki diğer tüm işlevler bir çağrı olarak gerçekleştirilir execve().
Jonathan Leffler

19

execFonksiyonların aile sizin süreci koşuyordu eski programı yerine farklı bir programı çalıştırmak olun. Yani ararsan

execl("/bin/ls", "ls", NULL);

daha sonra lsprogram, çağrılan işlemin işlem kimliği, geçerli çalışma dizini ve kullanıcı / grubu (erişim hakları) ile yürütülür execl. Daha sonra orijinal program artık çalışmıyor.

Yeni bir işlem başlatmak için forksistem çağrısı kullanılır. Orijinali değiştirmeden bir programı çalıştırmak için, gerek forko zaman, exec.


Teşekkür ederim, gerçekten yardımcı oldu. Şu anda exec () kullanmamızı gerektiren bir proje yapıyorum ve açıklamanız anlayışımı pekiştirdi.
TwilightSparkleTheGeek

7

exec işlevi ve ailesi nedir.

execFonksiyon ailesi gibi bir dosyayı yürütmek için kullanılan tüm fonksiyonlar vardır execl, execlp, execle, execv, ve execvp.Onlar için tüm önyüzleridir execveve onu çağıran farklı yöntemler sunar.

bu işlev neden kullanılıyor

Yürütme işlevleri, bir dosyayı (programı) yürütmek (başlatmak) istediğinizde kullanılır.

ve nasıl çalışıyor.

Başlattığınız mevcut işlem görüntüsünün üzerine yazarak çalışırlar. Şu anda çalışan işlemi (exec komutunu çağıran) başlatılan yeni işlemle değiştirirler (sonlandırırlar).

Daha fazla ayrıntı için: bu bağlantıya bakın .


7

execsık sık ile bağlantılı olarak kullanılıyor fork, sizin de sorduğunuzu gördüm, bu yüzden bunu aklımda tartışacağım.

execmevcut süreci başka bir programa dönüştürür. Doctor Who'yu daha önce izlediyseniz, bu kendini yenilediği zamanki gibidir - eski vücudu yeni bir bedenle değiştirilir.

Bunun programınızda meydana gelme şekli execve işletim sistemi çekirdeğinin exec, program bağımsız değişkeni (ilk bağımsız değişken) olarak ilettiğiniz dosyanın mevcut kullanıcı (işlemin kullanıcı kimliği) tarafından yürütülebilir olup olmadığını görmek için kontrol ettiği birçok kaynaktır. execçağrıyı yapmak ) ve eğer öyleyse, mevcut sürecin sanal bellek eşlemesini bir sanal bellekle değiştirir ve çağrıda iletilen argvve envpverileri execbu yeni sanal bellek haritasının bir alanına kopyalar . Burada başka şeyler de olabilir, ancak çağrılan program için açık olan dosyalar execyeni program için açık kalacak ve aynı işlem kimliğini paylaşacaklar, ancak çağrılan program execduracaktır (çalıştırma başarısız olmadıkça).

Bunun bu şekilde yapılmasının nedeni, yeni bir programı çalıştırmayı bunun gibi iki adıma ayırarak iki adım arasında bazı şeyler yapabilmenizdir. Yapılacak en yaygın şey, yeni programın belirli dosya tanımlayıcıları olarak açılan belirli dosyalara sahip olduğundan emin olmaktır. (burada dosya tanımlayıcılarının aynı olmadığını , ancak çekirdeğin bildiği değerler olduğunu unutmayın). Bunu yaparak şunları yapabilirsiniz: FILE *int

int X = open("./output_file.txt", O_WRONLY);

pid_t fk = fork();
if (!fk) { /* in child */
    dup2(X, 1); /* fd 1 is standard output,
                   so this makes standard out refer to the same file as X  */
    close(X);

    /* I'm using execl here rather than exec because
       it's easier to type the arguments. */
    execl("/bin/echo", "/bin/echo", "hello world");
    _exit(127); /* should not get here */
} else if (fk == -1) {
    /* An error happened and you should do something about it. */
    perror("fork"); /* print an error message */
}
close(X); /* The parent doesn't need this anymore */

Bu koşmayı başarır:

/bin/echo "hello world" > ./output_file.txt

komut kabuğundan.


5

Bir süreç fork () kullandığında, kendisinin bir kopyasını oluşturur ve bu kopyalar sürecin alt öğesi olur. Fork (), linux'ta kernelden iki kez dönen clone () sistem çağrısı kullanılarak gerçeklenir.

  • Üst öğeye sıfır olmayan bir değer (çocuğun İşlem Kimliği) döndürülür.
  • Çocuğa sıfır değeri döndürülür.
  • Düşük bellek gibi sorunlar nedeniyle alt öğenin başarıyla yaratılamaması durumunda, fork'a () -1 döndürülür.

Bunu bir örnekle anlayalım:

pid = fork(); 
// Both child and parent will now start execution from here.
if(pid < 0) {
    //child was not created successfully
    return 1;
}
else if(pid == 0) {
    // This is the child process
    // Child process code goes here
}
else {
    // Parent process code goes here
}
printf("This is code common to parent and child");

Örnekte, exec () işlevinin alt süreç içinde kullanılmadığını varsaydık.

Ancak bir ebeveyn ve çocuk, PCB (işlem kontrol bloğu) özelliklerinin bazılarında farklılık gösterir. Bunlar:

  1. PID - Hem alt hem de ebeveynin farklı bir İşlem Kimliği vardır.
  2. Bekleyen Sinyaller - Çocuk Ebeveynin bekleyen sinyallerini devralmaz. Oluşturulduğunda alt süreç için boş olacaktır.
  3. Hafıza Kilitleri - Çocuk, ebeveyninin hafıza kilitlerini devralmaz. Bellek kilitleri, bir bellek alanını kilitlemek için kullanılabilen kilitlerdir ve ardından bu bellek alanı diske takılamaz.
  4. Kayıt Kilitleri - Çocuk, ebeveyninin kayıt kilitlerini devralmaz. Kayıt kilitleri bir dosya bloğu veya tüm bir dosya ile ilişkilendirilir.
  5. İşlem kaynak kullanımı ve tüketilen CPU süresi alt öğe için sıfır olarak ayarlanır.
  6. Çocuk ayrıca ebeveynden zamanlayıcıları devralmaz.

Peki ya çocuk hafızası? Bir çocuk için yeni bir adres alanı oluşturuldu mu?

Hayır cevaplar. Fork () 'dan sonra hem ebeveyn hem de alt, ebeveynin bellek adres alanını paylaşır. Linux'ta, bu adres alanları birden çok sayfaya bölünmüştür. Yalnızca çocuk ana bellek sayfalarından birine yazdığında, çocuk için o sayfanın bir kopyası oluşturulur. Bu aynı zamanda yazma sırasında kopyalama olarak da bilinir (Üst sayfaları yalnızca çocuk yazarken kopyala).

Yazının üzerine yazıyı bir örnekle anlayalım.

int x = 2;
pid = fork();
if(pid == 0) {
    x = 10;
    // child is changing the value of x or writing to a page
    // One of the parent stack page will contain this local               variable. That page will be duplicated for child and it will store the value 10 in x in duplicated page.  
}
else {
    x = 4;
}

Ama neden yazma sırasında kopya gerekli?

Tipik bir süreç oluşturma fork () - exec () kombinasyonu ile gerçekleşir. Öncelikle exec () ne yaptığını anlayalım.

Exec () işlev grubu, çocuğun adres alanını yeni bir programla değiştirir. Bir çocuk içinde exec () çağrıldığında, çocuk için ebeveyninkinden tamamen farklı olan ayrı bir adres alanı yaratılacaktır.

Fork () ile ilişkili yazma mekanizmasında herhangi bir kopya olmasaydı, alt öğe için yinelenen sayfalar oluşturulur ve tüm veriler çocuğun sayfalarına kopyalanırdı. Yeni bellek ayırmak ve verileri kopyalamak çok pahalı bir işlemdir (işlemcinin zamanını ve diğer sistem kaynaklarını alır). Ayrıca çoğu durumda çocuğun exec () 'i çağıracağını ve bunun çocuğun hafızasını yeni bir programla değiştireceğini biliyoruz. Dolayısıyla, yazılan kopya orada olmasaydı, yaptığımız ilk kopya boşa giderdi.

pid = fork();
if(pid == 0) {
    execlp("/bin/ls","ls",NULL);
    printf("will this line be printed"); // Think about it
    // A new memory space will be created for the child and that   memory will contain the "/bin/ls" program(text section), it's stack, data section and heap section
else {
    wait(NULL);
    // parent is waiting for the child. Once child terminates, parent will get its exit status and can then continue
}
return 1; // Both child and parent will exit with status code 1.

Ebeveyn neden bir çocuk süreci bekler?

  1. Ebeveyn, çocuğuna bir görev atayabilir ve görevini tamamlayana kadar bekleyebilir. O zaman başka işler yapabilir.
  2. Çocuk sona erdiğinde, süreç kontrol bloğu dışında çocukla ilişkili tüm kaynaklar serbest bırakılır. Şimdi, çocuk zombi durumunda. Wait () kullanarak ebeveyn, çocuğun durumunu sorgulayabilir ve ardından çekirdekten PCB'yi serbest bırakmasını isteyebilir. Ebeveyn beklemeyi kullanmazsa, çocuk zombi durumunda kalacaktır.

Exec () sistem çağrısı neden gereklidir?

Fork () ile exec () kullanmak gerekli değildir. Çocuğun yürüteceği kod, üst öğe ile ilişkili programın içindeyse, exec () gerekli değildir.

Ancak çocuğun birden fazla program çalıştırması gereken durumları düşünün. Kabuk programı örneğini ele alalım. Find, mv, cp, date vb. Gibi birden çok komutu destekler. Bu komutlarla ilişkili program kodunu tek bir programa eklemek veya gerektiğinde bu programları belleğe çocuk yüklemek doğru olur mu?

Hepsi kullanım durumunuza bağlıdır. İstemcilere 2 ^ x döndüren bir x girdisi veren bir web sunucunuz var. Her istek için web sunucusu yeni bir çocuk yaratır ve hesaplama yapmasını ister. Bunu hesaplamak ve exec () kullanmak için ayrı bir program yazacak mısınız? Yoksa sadece üst programın içine hesaplama kodu yazacaksınız?

Genellikle, bir süreç oluşturma fork (), exec (), wait () ve exit () çağrılarının bir kombinasyonunu içerir.


4

exec(3,3p)İşlevleri yerine başka geçerli süreci. Yani, mevcut süreç durur ve bunun yerine orijinal programın sahip olduğu kaynakların bir kısmını devralan bir başkası çalışır.


6
Pek değil. Mevcut işlem görüntüsünü yeni bir işlem görüntüsü ile değiştirir. İşlem aynı pid, aynı ortam ve aynı dosya tanımlayıcı tablosu ile aynı süreçtir. Değişen, tüm sanal bellek ve CPU durumudur.
JeremyP

@JeremyP "Aynı dosya tanımlayıcısı" burada önemliydi, yeniden yönlendirmenin kabuklarda nasıl çalıştığını açıklıyor! Exec her şeyin üzerine yazarsa yeniden yönlendirmenin nasıl çalışacağı konusunda şaşkınım! Teşekkürler
FUD
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.