मैं दूसरे प्रश्न पर शोध कर रहा था , जब मुझे एहसास हुआ कि मुझे समझ नहीं आ रहा है कि हुड के नीचे क्या हो रहा है, वे /dev/fd/*
फाइलें क्या हैं और कैसे आती हैं बाल प्रक्रियाएं उन्हें खोल सकती हैं।
मैं दूसरे प्रश्न पर शोध कर रहा था , जब मुझे एहसास हुआ कि मुझे समझ नहीं आ रहा है कि हुड के नीचे क्या हो रहा है, वे /dev/fd/*
फाइलें क्या हैं और कैसे आती हैं बाल प्रक्रियाएं उन्हें खोल सकती हैं।
जवाबों:
वैसे, इसके कई पहलू हैं।
फ़ाइल विवरणक
प्रत्येक प्रक्रिया के लिए, कर्नेल खुली फ़ाइलों की एक तालिका बनाए रखता है (ठीक है, इसे अलग तरीके से लागू किया जा सकता है, लेकिन चूंकि आप इसे वैसे भी नहीं देख पा रहे हैं, आप बस इसे एक साधारण तालिका मान सकते हैं)। उस तालिका में यह जानकारी होती है कि यह किस फ़ाइल की है / जहाँ यह पाया जा सकता है, आपने इसे किस मोड में खोला है, किस स्थिति में आप वर्तमान में पढ़ रहे हैं / लिख रहे हैं, और वास्तव में उस फ़ाइल पर I / O संचालन करने के लिए और कुछ भी आवश्यक है। अब उस तालिका को पढ़ने (या यहां तक कि लिखने) के लिए प्रक्रिया कभी नहीं मिलती है। जब प्रक्रिया एक फ़ाइल खोलती है, तो यह एक तथाकथित फ़ाइल डिस्क्रिप्टर को वापस मिल जाती है। जो टेबल में एक इंडेक्स मात्र है।
निर्देशिका /dev/fd
और इसकी सामग्री
लिनक्स dev/fd
पर वास्तव में एक प्रतीकात्मक लिंक है /proc/self/fd
। /proc
एक छद्म फ़ाइल प्रणाली है जिसमें कर्नेल कई आंतरिक डेटा संरचनाओं को फ़ाइल एपीआई के साथ एक्सेस करने के लिए मैप करता है (इसलिए वे सिर्फ नियमित फ़ाइलों / निर्देशिकाओं / कार्यक्रमों के लिए सहानुभूति की तरह दिखते हैं)। विशेष रूप से सभी प्रक्रियाओं (जो इसे नाम दिया गया है) के बारे में जानकारी है। प्रतीकात्मक लिंक /proc/self
हमेशा वर्तमान में चल रही प्रक्रिया से जुड़े निर्देशिका को संदर्भित करता है (अर्थात, यह प्रक्रिया इसका अनुरोध करती है; विभिन्न प्रक्रियाएँ इसलिए अलग-अलग मूल्य देखेंगी)। प्रक्रिया की निर्देशिका में, एक उपनिर्देशिका हैfd
जिसके लिए प्रत्येक ओपन फाइल में एक प्रतीकात्मक लिंक होता है जिसका नाम सिर्फ फाइल डिस्क्रिप्टर (प्रक्रिया की फाइल टेबल में सूचकांक, पिछले अनुभाग देखें) का दशमलव प्रतिनिधित्व है, और जिसका लक्ष्य वह फाइल है जो इसके अनुरूप है।
बच्चे की प्रक्रिया बनाते समय फ़ाइल विवरणक
एक बच्चे की प्रक्रिया एक द्वारा बनाई गई है fork
। ए fork
फ़ाइल डिस्क्रिप्टर की एक प्रतिलिपि बनाता है, जिसका अर्थ है कि बनाई गई चाइल्ड प्रक्रिया में बहुत सी खुली फाइलों की सूची है जैसा कि पेरेंट प्रोसेस करता है। इसलिए जब तक बच्चे द्वारा खुली फाइलों में से एक को बंद नहीं किया जाता है, तब तक बच्चे को विरासत में दिए गए फाइल डिस्क्रिप्टर तक पहुंचना मूल फाइल डिस्क्रिप्टर को मूल प्रक्रिया में एक्सेस करने के समान ही फाइल को एक्सेस करेगा।
ध्यान दें कि एक कांटा के बाद, आपके पास शुरू में एक ही प्रक्रिया की दो प्रतियां होती हैं जो केवल कांटा कॉल से वापसी मूल्य में भिन्न होती हैं (माता-पिता को बच्चे का पीआईडी मिलता है, बच्चे को 0 मिलता है)। आम तौर पर, एक कांटा एक के बाद एक exec
प्रतियों को दूसरे निष्पादन योग्य द्वारा प्रतिस्थापित किया जाता है । खुली फाइल के डिस्क्रिप्टर उस निष्पादन से बचे रहते हैं। यह भी ध्यान दें कि निष्पादन से पहले, प्रक्रिया अन्य जोड़तोड़ कर सकती है (जैसे फाइलें बंद करना जो नई प्रक्रिया को प्राप्त नहीं करना चाहिए, या अन्य फाइलें खोलना)।
पाइप
अनाम पाइप केवल कर्नेल द्वारा अनुरोध पर बनाई गई फ़ाइल डिस्क्रिप्टर की एक जोड़ी है, ताकि पहली फ़ाइल डिस्क्रिप्टर को लिखा गया सब कुछ दूसरे को दिया जाए। सबसे आम उपयोग पाइपिंग कंस्ट्रक्शन foo | bar
के लिए होता है bash
, जहां मानक आउटपुट foo
को पाइप के लेखन भाग द्वारा बदल दिया जाता है, और मानक इनपुट को रीड भाग द्वारा प्रतिस्थापित किया जाता है। मानक इनपुट और मानक आउटपुट फ़ाइल तालिका में केवल पहली दो प्रविष्टियाँ हैं (प्रविष्टि 0 और 1; 2 मानक त्रुटि है), और इसलिए इसे प्रतिस्थापित करने का अर्थ है कि उस तालिका प्रविष्टि को अन्य फ़ाइल विवरणक के अनुरूप डेटा के साथ फिर से लिखना (फिर से, वास्तविक कार्यान्वयन अलग हो सकता है)। चूंकि प्रक्रिया सीधे तालिका तक नहीं पहुंच सकती है, ऐसा करने के लिए एक कर्नेल फ़ंक्शन है।
प्रक्रिया प्रतिस्थापन
अब हमारे पास यह समझने के लिए सब कुछ है कि प्रक्रिया प्रतिस्थापन कैसे काम करता है:
echo
प्रक्रिया के लिए बैश कांटे । बाल प्रक्रिया (जो मूल bash
प्रक्रिया की एक सटीक प्रति है ) पाइप के रीडिंग एंड को बंद कर देती है और पाइप के लेखन छोर के साथ अपने स्वयं के मानक आउटपुट को बदल देती है। यह देखते हुए कि echo
एक शेल बिल्डिन है, कॉल को bash
खुद ही छोड़ सकता है exec
, लेकिन यह वैसे भी मायने नहीं रखता है (शेल बिलिन को निष्क्रिय भी किया जा सकता है, जिस स्थिति में यह निष्पादित होता है /bin/echo
)।<(echo 1)
में छद्म फ़ाइल लिंक द्वारा अभिव्यक्ति की जगह लेता है /dev/fd
।/dev/fd/
। चूंकि संबंधित फाइल डिस्क्रिप्टर अभी भी खुला है, यह अभी भी पाइप के रीडिंग एंड से मेल खाता है। इसलिए यदि PHP प्रोग्राम रीडिंग के लिए दी गई फाइल को खोलता है, तो यह वास्तव में क्या करता है second
अनाम पाइप के रीडिंग एंड के लिए फाइल डिस्क्रिप्टर बनाना है । लेकिन यह कोई समस्या नहीं है, यह या तो से पढ़ सकता है।echo
कमांड का मानक आउटपुट प्राप्त करता है जो उसी पाइप के राइटिंग एंड पर जाता है।php
परिदृश्य के बारे में बात कर रहे हैं, लेकिन php
पाइप को अच्छी तरह से संभालता नहीं है । इसके अलावा, कमांड पर विचार करते हुए cat <(echo test)
, यहां अजीब बात यह है कि bash
कांटे एक बार के लिए हैं cat
, लेकिन दो बार के लिए echo test
।
के celtschk
उत्तर से उधार लेना , /dev/fd
एक प्रतीकात्मक कड़ी है /proc/self/fd
। और /proc
एक छद्म फाइलसिस्टम है, जो एक पदानुक्रमित फ़ाइल जैसी संरचना में प्रक्रियाओं और अन्य सिस्टम जानकारी के बारे में जानकारी प्रस्तुत करता है। फ़ाइलों के /dev/fd
अनुरूप फाइलें, एक प्रक्रिया द्वारा खोली गई हैं और उनके विवरण के रूप में फाइल डिस्क्रिप्टर है और अपने लक्ष्य के रूप में खुद को फाइल करते हैं। फ़ाइल खोलना, /dev/fd/N
वर्णनकर्ता को डुप्लिकेट करने के बराबर है N
(यह मानते हुए कि वर्णनकर्ता N
खुला है)।
और यहां मेरी जांच के परिणाम हैं कि यह कैसे काम करता है ( strace
आउटपुट अनावश्यक विवरणों से छुटकारा पाता है और बेहतर व्यक्त करने के लिए संशोधित होता है कि क्या हो रहा है):
$ 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)
मूल रूप से, bash
एक पाइप बनाता है और अपने बच्चों को फाइल डिस्क्रिप्टर (अंत तक पढ़ें 1.out
और अंत लिखें 2.out
) के रूप में अपने बच्चों को पास करता है । और एक कमांड लाइन पैरामीटर 1.out
( /dev/fd/63
) के रूप में रीड एंड पास करता है । यह रास्ता 1.out
खुलने में सक्षम है /dev/fd/63
।