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 .
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 .
Yanıtlar:
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/fd
ve içeriği
Linux'ta dev/fd
aslı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/self
her 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, exec
kopyalardan 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 | bar
bir bash
standart çıkış, foo
boru 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:
echo
İşlem için çatallar . Alt işlem (orijinal bash
işlemin tam bir kopyasıdır ) borunun okuma ucunu kapatır ve kendi standart çıktısını borunun yazma ucuyla değiştirir. Bunun echo
bir 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
).<(echo 1)
sözde dosya bağlantısıyla ifadenin yerini alır /dev/fd
./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ığı second
isimsiz yöneltmenin okuma sonu için bir dosya tanımlayıcı oluşturmaktır . Ama sorun değil, her ikisinden de okuyabilirdi.echo
aynı borunun yazma sonuna giden komutun standart çıktısını alabilir .php
senaryodan bahsediyorsunuz , ancak php
boruları iyi işlemiyor . Ayrıca, komut göz önüne alındığında cat <(echo test)
, burada garip olan şey bir bash
kez çatal cat
, ama iki kez çatal echo test
.
Cevabından borç almak celtschk
, /dev/fd
simgesel bir bağdır /proc/self/fd
. Ve /proc
hiyerarşik dosya benzeri bir yapıda işlemler ve diğer sistem bilgileri hakkında bilgi sunan sahte bir dosya sistemidir. İçindeki /dev/fd
dosyalar, 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/N
açmak çoğaltıcı tanımlayıcıya eşdeğerdir N
(tanımlayıcının N
açı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, bash
bir boru oluşturur ve uçlarını dosya tanımlayıcıları olarak çocuklarına iletir (sonuna okuma 1.out
ve sonuna yazma 2.out
). Ve okuma sonunu komut satırı parametresi olarak 1.out
( /dev/fd/63
) öğesine iletir . Bu şekilde 1.out
açılabilir /dev/fd/63
.