Saya sedang meneliti pertanyaan lain , ketika saya menyadari saya tidak mengerti apa yang terjadi di bawah tenda, /dev/fd/*
file apa itu dan bagaimana proses anak bisa membukanya.
Saya sedang meneliti pertanyaan lain , ketika saya menyadari saya tidak mengerti apa yang terjadi di bawah tenda, /dev/fd/*
file apa itu dan bagaimana proses anak bisa membukanya.
Jawaban:
Ya, ada banyak aspek untuk itu.
Penjelas file
Untuk setiap proses, kernel memelihara tabel file yang terbuka (well, itu mungkin diimplementasikan secara berbeda, tetapi karena Anda tidak dapat melihatnya, Anda bisa menganggap itu adalah tabel sederhana). Tabel itu berisi informasi tentang file mana itu / di mana ia dapat ditemukan, dalam mode apa Anda membukanya, di posisi mana Anda sedang membaca / menulis, dan apa pun yang diperlukan untuk benar-benar melakukan operasi I / O pada file itu. Sekarang prosesnya tidak pernah membaca (atau bahkan menulis) tabel itu. Ketika proses membuka file, itu akan kembali disebut file descriptor. Yang hanya merupakan indeks ke dalam tabel.
Direktori /dev/fd
dan isinya
Di Linux dev/fd
sebenarnya tautan simbolis ke /proc/self/fd
. /proc
adalah sistem file pseudo di mana kernel memetakan beberapa struktur data internal untuk diakses dengan file API (sehingga mereka hanya terlihat seperti file biasa / direktori / symlink ke program). Terutama ada informasi tentang semua proses (yang memberi nama itu). Tautan simbolik /proc/self
selalu merujuk ke direktori yang terkait dengan proses yang sedang berjalan (yaitu, proses memintanya; proses yang berbeda akan melihat nilai yang berbeda). Di direktori proses, ada subdirektorifd
yang untuk setiap file yang terbuka berisi tautan simbolik yang namanya hanyalah representasi desimal dari deskriptor file (indeks ke dalam tabel file proses, lihat bagian sebelumnya), dan yang targetnya adalah file yang sesuai dengan itu.
Deskriptor file saat membuat proses anak
Proses anak dibuat oleh a fork
. A fork
membuat salinan deskriptor file, yang berarti proses anak yang dibuat memiliki daftar file terbuka yang sama dengan proses induk. Jadi kecuali salah satu file yang terbuka ditutup oleh anak, mengakses deskriptor file yang diwarisi pada anak akan mengakses file yang sama seperti mengakses deskriptor file asli dalam proses induk.
Perhatikan bahwa setelah garpu, Anda awalnya memiliki dua salinan dari proses yang sama yang hanya berbeda dalam nilai balik dari panggilan garpu (orang tua mendapat PID anak, anak mendapat 0). Biasanya, sebuah garpu diikuti oleh exec
untuk menggantikan salah satu salinan oleh executable lain. Deskriptor file terbuka bertahan dari eksekutif itu. Perhatikan juga bahwa sebelum eksekutif, proses tersebut dapat melakukan manipulasi lain (seperti menutup file yang seharusnya tidak didapat oleh proses baru, atau membuka file lain).
Pipa yang tidak disebutkan namanya
Pipa tanpa nama hanyalah sepasang deskriptor file yang dibuat berdasarkan permintaan oleh kernel, sehingga semua yang ditulis ke deskriptor file pertama diteruskan ke yang kedua. Penggunaan yang paling umum adalah untuk membangun pipa foo | bar
dari bash
, di mana output standar foo
diganti dengan menulis bagian dari pipa, dan input standar Menggantikan oleh bagian dibaca. Input standar dan output standar hanyalah dua entri pertama dalam tabel file (entri 0 dan 1; 2 adalah kesalahan standar), dan karenanya menggantinya berarti hanya menulis ulang entri tabel tersebut dengan data yang sesuai dengan deskriptor file lainnya (sekali lagi, implementasi aktual mungkin berbeda). Karena proses tidak dapat mengakses tabel secara langsung, ada fungsi kernel untuk melakukan itu.
Substitusi proses
Sekarang kita memiliki segalanya bersama untuk memahami bagaimana proses substitusi bekerja:
echo
proses. Proses anak (yang merupakan salinan tepat dari bash
proses asli ) menutup ujung pembacaan pipa dan mengganti output standar sendiri dengan ujung penulisan pipa. Mengingat bahwa itu echo
adalah builtin shell, bash
mungkin mengampuni exec
panggilan itu sendiri , tetapi toh itu tidak masalah (shell builtin juga dapat dinonaktifkan, dalam hal ini ia menjalankan /bin/echo
).<(echo 1)
dengan tautan file pseudo yang /dev/fd
merujuk pada ujung bacaan dari pipa yang tidak disebutkan namanya./dev/fd/
. Karena deskriptor file yang sesuai masih terbuka, itu masih sesuai dengan ujung pipa pembacaan. Oleh karena itu jika program PHP membuka file yang diberikan untuk dibaca, apa yang sebenarnya dilakukannya adalah membuat second
deskriptor file untuk ujung bacaan dari pipa yang tidak disebutkan namanya. Tapi itu tidak masalah, bisa dibaca dari keduanya.echo
perintah yang menuju akhir penulisan dari pipa yang sama.php
skenario, tetapi php
tidak menangani pipa dengan baik . Juga, mengingat perintah cat <(echo test)
, yang aneh di sini adalah bahwa bash
garpu sekali untuk cat
, tetapi dua kali untuk echo test
.
Meminjam dari celtschk
jawaban, /dev/fd
adalah tautan simbolis ke /proc/self/fd
. Dan /proc
adalah sistem file pseudo, yang menyajikan informasi tentang proses dan informasi sistem lainnya dalam struktur seperti file hirarkis. File dalam /dev/fd
berhubungan dengan file, dibuka oleh suatu proses dan memiliki deskriptor file sebagai nama mereka dan file itu sendiri sebagai target mereka. Membuka file /dev/fd/N
sama dengan duplikasi deskriptor N
(dengan asumsi deskriptor N
terbuka).
Dan berikut ini adalah hasil penyelidikan saya tentang cara kerjanya ( strace
output menghilangkan detail yang tidak perlu dan dimodifikasi untuk mengekspresikan apa yang terjadi dengan lebih baik):
$ 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)
Pada dasarnya, bash
buat sebuah pipa dan memberikan ujung-ujungnya kepada anak-anaknya sebagai deskriptor file (baca akhir 1.out
, dan tuliskan akhir 2.out
). Dan melewati baca sebagai parameter baris perintah ke 1.out
( /dev/fd/63
). Cara 1.out
ini bisa terbuka /dev/fd/63
.