Süreç ikamesi bash'da nasıl uygulanır?


12

Diğer soruyu araştırıyordum, davlumbazın altında neler olduğunu, bu /dev/fd/*dosyaların neler olduğunu ve çocuk süreçlerinin bunları nasıl açabileceğini anlamadığımı fark ettiğimde .


Bu soru cevaplanmadı mı?
phk

Yanıtlar:


21

Pek çok yönü var.

Dosya tanımlayıcıları

Her işlem için, çekirdek bir açık dosya tablosu tutar (farklı şekilde uygulanabilir, ancak yine de göremediğiniz için, bunun basit bir tablo olduğunu varsayabilirsiniz). Bu tablo hangi dosyanın olduğu / nerede bulunabileceği, hangi modda açtığınız, hangi konumda okuduğunuz / yazdığınız ve bu dosyadaki G / Ç işlemlerini gerçekleştirmek için başka ne gerekiyorsa hakkında bilgi içerir. Şimdi süreç asla bu tabloyu okumaz (hatta yazmaz). İşlem bir dosyayı açtığında, dosya tanımlayıcı olarak adlandırılır. Bu sadece tabloya bir dizin.

Dizin /dev/fdve içeriği

Linux'ta dev/fdaslında sembolik bir bağlantıdır /proc/self/fd. /procçekirdeğin, dosya API'sı ile erişilecek birkaç dahili veri yapısını eşleştirdiği sahte bir dosya sistemidir (bu yüzden programlara normal dosyalar / dizinler / sembolik bağlantılar gibi görünürler). Özellikle tüm süreçler hakkında bilgi var (ona adı veren şey bu). Sembolik bağlantı /proc/selfher zaman şu anda çalışan işlemle ilişkili dizine işaret eder (yani, bunu isteyen işlem; farklı işlemler farklı değerler görecektir). İşlemin dizininde bir alt dizin varfd her açık dosya için adı dosya tanımlayıcısının yalnızca ondalık gösterimi olan (işlemin dosya tablosundaki dizin, önceki bölüme bakın) ve hedefi karşılık geldiği dosya olan sembolik bir bağlantı içerir.

Alt işlemler oluştururken dosya tanımlayıcılar

Bir alt süreç a fork. A fork, dosya tanımlayıcılarının bir kopyasını oluşturur; bu, oluşturulan alt işlemin, üst işlemle aynı açık dosyalar listesine sahip olduğu anlamına gelir. Bu nedenle, açık dosyalardan biri çocuk tarafından kapatılmadığı sürece, alt öğedeki devralınan bir dosya tanımlayıcısına erişmek, üst işlemdeki orijinal dosya tanımlayıcısına erişmekle aynı dosyaya erişir.

Bir çataldan sonra, başlangıçta aynı işlemin yalnızca çatal çağrısından dönüş değerinde farklı olan iki kopyası olduğunu unutmayın (üst öğe çocuğun PID'sini alır, çocuk 0 değerini alır). Normalde, execkopyalardan birini başka bir yürütülebilir dosyayla değiştirmek için bir çatalı takip eder. Açık dosya tanımlayıcıları bu exec'de varlığını sürdürür. Ayrıca, exec'den önce işlemin başka manipülasyonlar yapabileceğini (yeni işlemin almaması gereken dosyaları kapatmak veya diğer dosyaları açmak gibi) unutmayın.

Adsız borular

Adsız bir kanal, çekirdek tarafından istek üzerine oluşturulan bir çift dosya tanımlayıcıdır, böylece ilk dosya tanımlayıcısına yazılan her şey ikincisine aktarılır. En yaygın kullanım boru yapısı için foo | barbir bashstandart çıkış, fooboru yazma parçası ile ikame edilir ve standart giriş okuma parçası yerine geçer. Standart girdi ve standart çıktı, dosya tablosundaki ilk iki girdidir (giriş 0 ve 1; 2 standart hatadır) ve bu nedenle değiştirilmesi, bu tablo girişini diğer dosya tanımlayıcısına karşılık gelen verilerle yeniden yazmak anlamına gelir (yine, gerçek uygulama farklı olabilir). İşlem doğrudan tabloya erişemediğinden, bunu yapmak için bir çekirdek işlevi vardır.

Proses ikamesi

Şimdi süreç ikamesinin nasıl çalıştığını anlamak için her şeyimiz var:

  1. Bash süreci, daha sonra oluşturulan iki süreç arasında iletişim için isimsiz bir boru oluşturur.
  2. echoİşlem için çatallar . Alt işlem (orijinal bashişlemin tam bir kopyasıdır ) borunun okuma ucunu kapatır ve kendi standart çıktısını borunun yazma ucuyla değiştirir. Bunun echobir kabuk yerleşimi olduğu göz önüne alındığında bash, execçağrıyı yedekleyebilir, ancak yine de önemli değildir (kabuk yerleşimi de devre dışı bırakılabilir, bu durumda yürütülür /bin/echo).
  3. Bash (orijinal, üst öğe) , adlandırılmamış borunun okuma ucuna atıfta bulunarak <(echo 1)sözde dosya bağlantısıyla ifadenin yerini alır /dev/fd.
  4. Bash, PHP işlemi için çalışır (çataldan sonra hala bash'ın bir kopyasının içinde olduğumuzu unutmayın). Yeni işlem, adlandırılmamış borunun devralınan yazma ucunu kapatır (ve diğer hazırlık adımlarını gerçekleştirir), ancak okuma ucunu açık bırakır. Sonra PHP yürüttü.
  5. PHP programı ismini alır /dev/fd/. Karşılık gelen dosya tanımlayıcı hala açık olduğundan, yine de borunun okuma ucuna karşılık gelir. Bu nedenle, PHP programı verilen dosyayı okumak için açarsa, aslında yaptığı secondisimsiz yöneltmenin okuma sonu için bir dosya tanımlayıcı oluşturmaktır . Ama sorun değil, her ikisinden de okuyabilirdi.
  6. Şimdi PHP programı, yeni dosya tanımlayıcı aracılığıyla borunun okuma ucunu okuyabilir ve böylece echoaynı borunun yazma sonuna giden komutun standart çıktısını alabilir .

Tabii, çaban için teşekkür ederim. Ama birkaç konuya değinmek istedim. İlk olarak, phpsenaryodan bahsediyorsunuz , ancak phpboruları iyi işlemiyor . Ayrıca, komut göz önüne alındığında cat <(echo test), burada garip olan şey bir bashkez çatal cat, ama iki kez çatal echo test.
x-yuri

13

Cevabından borç almak celtschk, /dev/fdsimgesel bir bağdır /proc/self/fd. Ve /prochiyerarşik dosya benzeri bir yapıda işlemler ve diğer sistem bilgileri hakkında bilgi sunan sahte bir dosya sistemidir. İçindeki /dev/fddosyalar, bir işlem tarafından açılan dosyalara karşılık gelir ve adları ve dosya hedefleri olarak dosya tanımlayıcıları vardır. Dosyayı /dev/fd/Naçmak çoğaltıcı tanımlayıcıya eşdeğerdir N(tanımlayıcının Naçık olduğu varsayılarak ).

Ve işte nasıl çalıştığımı araştırmamın sonuçları ( straceçıktı gereksiz ayrıntılardan kurtuldu ve neler olduğunu daha iyi ifade etmek için değiştirildi):

$ cat 1.c
#include <unistd.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
    char buf[100];
    int fd;
    fd = open(argv[1], O_RDONLY);
    read(fd, buf, 100);
    write(STDOUT_FILENO, buf, n_read);
    return 0;
}
$ gcc 1.c -o 1.out
$ cat 2.c
#include <unistd.h>
#include <string.h>

int main(void)
{
    char *p = "hello, world\n";
    write(STDOUT_FILENO, p, strlen(p));
    return 0;
}
$ gcc 2.c -o 2.out
$ strace -f -e pipe,fcntl,dup2,close,clone,close,execve,wait4,read,open,write bash -c './1.out <(./2.out)'
[bash] pipe([3, 4]) = 0
[bash] dup2(3, 63) = 63
[bash] close(3) = 0
[bash] clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f7c211fb9d0) = p2
Process p2 attached
[bash] close(4) = 0
[bash] clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f7c211fb9d0) = p1
Process p1 attached
[bash] close(63) = 0
[p2] dup2(4, 1) = 1
[p2] close(4) = 0
[p2] close(63) = 0
[bash] wait4(-1, <unfinished ...>
Process bash suspended
[p1] execve("/home/yuri/_/1.out", ["/home/yuri/_/1.out", "/dev/fd/63"], [/* 31 vars */]) = 0
[p2] clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f7c211fb9d0) = p22
Process p22 attached
[p22] execve("/home/yuri/_/2.out", ["/home/yuri/_/2.out"], [/* 31 vars */]) = 0
[p2] wait4(-1, <unfinished ...>
Process p2 suspended
[p1] open("/dev/fd/63", O_RDONLY) = 3
[p1] read(3,  <unfinished ...>
[p22] write(1, "hello, world\n", 13) = 13
[p1] <... read resumed> "hello, world\n", 100) = 13
Process p2 resumed
Process p22 detached
[p1] write(1, "hello, world\n", 13) = 13
hello, world
[p2] <... wait4 resumed> [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = p22
[p2] --- SIGCHLD (Child exited) @ 0 (0) ---
[p2] wait4(-1, 0x7fff190f289c, WNOHANG, NULL) = -1 ECHILD (No child processes)
Process bash resumed
Process p1 detached
[bash] <... wait4 resumed> [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = p1
[bash] --- SIGCHLD (Child exited) @ 0 (0) ---
Process p2 detached
[bash] wait4(-1, 0x7fff190f2bdc, WNOHANG, NULL) = 0
--- SIGCHLD (Child exited) @ 0 (0) ---
[bash] wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], WNOHANG, NULL) = p2
[bash] wait4(-1, 0x7fff190f299c, WNOHANG, NULL) = -1 ECHILD (No child processes)

Temel olarak, bashbir boru oluşturur ve uçlarını dosya tanımlayıcıları olarak çocuklarına iletir (sonuna okuma 1.outve sonuna yazma 2.out). Ve okuma sonunu komut satırı parametresi olarak 1.out( /dev/fd/63) öğesine iletir . Bu şekilde 1.outaçılabilir /dev/fd/63.

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.