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.
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.
Yanıtlar:
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 find
programı 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 exec
aramaları ( execl
, execle
, execve
vb) ama exec
bağlamda burada bunlardan herhangi biri anlamına gelir.
Aşağıdaki şema fork/exec
, bash
kabuğun ls
komutla 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
exec
Yardı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ı?
exec
araç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.
exec
bir 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 exec
veya başlatılmıyor herhangi bir şekilde - bunun yerine.
Exec () ailesindeki işlevler farklı davranışlara sahiptir:
Onları karıştırabilirsiniz, bu nedenle sahip olursunuz:
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
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 .
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()
.
exec
Fonksiyonları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 ls
program, ç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 fork
sistem çağrısı kullanılır. Orijinali değiştirmeden bir programı çalıştırmak için, gerek fork
o zaman, exec
.
exec işlevi ve ailesi nedir.
exec
Fonksiyon 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 execve
ve 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 .
exec
sı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.
exec
mevcut 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 exec
ve 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 argv
ve envp
verileri exec
bu 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 exec
yeni program için açık kalacak ve aynı işlem kimliğini paylaşacaklar, ancak çağrılan program exec
duracaktı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.
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.
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:
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?
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.
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.