कैसे प्रक्रिया प्रतिस्थापन बैश में कार्यान्वित किया जाता है?


12

मैं दूसरे प्रश्न पर शोध कर रहा था , जब मुझे एहसास हुआ कि मुझे समझ नहीं आ रहा है कि हुड के नीचे क्या हो रहा है, वे /dev/fd/*फाइलें क्या हैं और कैसे आती हैं बाल प्रक्रियाएं उन्हें खोल सकती हैं।


क्या उस सवाल का जवाब नहीं दिया गया है?
फॉक्स

जवाबों:


21

वैसे, इसके कई पहलू हैं।

फ़ाइल विवरणक

प्रत्येक प्रक्रिया के लिए, कर्नेल खुली फ़ाइलों की एक तालिका बनाए रखता है (ठीक है, इसे अलग तरीके से लागू किया जा सकता है, लेकिन चूंकि आप इसे वैसे भी नहीं देख पा रहे हैं, आप बस इसे एक साधारण तालिका मान सकते हैं)। उस तालिका में यह जानकारी होती है कि यह किस फ़ाइल की है / जहाँ यह पाया जा सकता है, आपने इसे किस मोड में खोला है, किस स्थिति में आप वर्तमान में पढ़ रहे हैं / लिख रहे हैं, और वास्तव में उस फ़ाइल पर I / O संचालन करने के लिए और कुछ भी आवश्यक है। अब उस तालिका को पढ़ने (या यहां तक ​​कि लिखने) के लिए प्रक्रिया कभी नहीं मिलती है। जब प्रक्रिया एक फ़ाइल खोलती है, तो यह एक तथाकथित फ़ाइल डिस्क्रिप्टर को वापस मिल जाती है। जो टेबल में एक इंडेक्स मात्र है।

निर्देशिका /dev/fdऔर इसकी सामग्री

लिनक्स dev/fdपर वास्तव में एक प्रतीकात्मक लिंक है /proc/self/fd/procएक छद्म फ़ाइल प्रणाली है जिसमें कर्नेल कई आंतरिक डेटा संरचनाओं को फ़ाइल एपीआई के साथ एक्सेस करने के लिए मैप करता है (इसलिए वे सिर्फ नियमित फ़ाइलों / निर्देशिकाओं / कार्यक्रमों के लिए सहानुभूति की तरह दिखते हैं)। विशेष रूप से सभी प्रक्रियाओं (जो इसे नाम दिया गया है) के बारे में जानकारी है। प्रतीकात्मक लिंक /proc/selfहमेशा वर्तमान में चल रही प्रक्रिया से जुड़े निर्देशिका को संदर्भित करता है (अर्थात, यह प्रक्रिया इसका अनुरोध करती है; विभिन्न प्रक्रियाएँ इसलिए अलग-अलग मूल्य देखेंगी)। प्रक्रिया की निर्देशिका में, एक उपनिर्देशिका हैfd जिसके लिए प्रत्येक ओपन फाइल में एक प्रतीकात्मक लिंक होता है जिसका नाम सिर्फ फाइल डिस्क्रिप्टर (प्रक्रिया की फाइल टेबल में सूचकांक, पिछले अनुभाग देखें) का दशमलव प्रतिनिधित्व है, और जिसका लक्ष्य वह फाइल है जो इसके अनुरूप है।

बच्चे की प्रक्रिया बनाते समय फ़ाइल विवरणक

एक बच्चे की प्रक्रिया एक द्वारा बनाई गई है fork। ए forkफ़ाइल डिस्क्रिप्टर की एक प्रतिलिपि बनाता है, जिसका अर्थ है कि बनाई गई चाइल्ड प्रक्रिया में बहुत सी खुली फाइलों की सूची है जैसा कि पेरेंट प्रोसेस करता है। इसलिए जब तक बच्चे द्वारा खुली फाइलों में से एक को बंद नहीं किया जाता है, तब तक बच्चे को विरासत में दिए गए फाइल डिस्क्रिप्टर तक पहुंचना मूल फाइल डिस्क्रिप्टर को मूल प्रक्रिया में एक्सेस करने के समान ही फाइल को एक्सेस करेगा।

ध्यान दें कि एक कांटा के बाद, आपके पास शुरू में एक ही प्रक्रिया की दो प्रतियां होती हैं जो केवल कांटा कॉल से वापसी मूल्य में भिन्न होती हैं (माता-पिता को बच्चे का पीआईडी ​​मिलता है, बच्चे को 0 मिलता है)। आम तौर पर, एक कांटा एक के बाद एक execप्रतियों को दूसरे निष्पादन योग्य द्वारा प्रतिस्थापित किया जाता है । खुली फाइल के डिस्क्रिप्टर उस निष्पादन से बचे रहते हैं। यह भी ध्यान दें कि निष्पादन से पहले, प्रक्रिया अन्य जोड़तोड़ कर सकती है (जैसे फाइलें बंद करना जो नई प्रक्रिया को प्राप्त नहीं करना चाहिए, या अन्य फाइलें खोलना)।

पाइप

अनाम पाइप केवल कर्नेल द्वारा अनुरोध पर बनाई गई फ़ाइल डिस्क्रिप्टर की एक जोड़ी है, ताकि पहली फ़ाइल डिस्क्रिप्टर को लिखा गया सब कुछ दूसरे को दिया जाए। सबसे आम उपयोग पाइपिंग कंस्ट्रक्शन foo | barके लिए होता है bash, जहां मानक आउटपुट fooको पाइप के लेखन भाग द्वारा बदल दिया जाता है, और मानक इनपुट को रीड भाग द्वारा प्रतिस्थापित किया जाता है। मानक इनपुट और मानक आउटपुट फ़ाइल तालिका में केवल पहली दो प्रविष्टियाँ हैं (प्रविष्टि 0 और 1; 2 मानक त्रुटि है), और इसलिए इसे प्रतिस्थापित करने का अर्थ है कि उस तालिका प्रविष्टि को अन्य फ़ाइल विवरणक के अनुरूप डेटा के साथ फिर से लिखना (फिर से, वास्तविक कार्यान्वयन अलग हो सकता है)। चूंकि प्रक्रिया सीधे तालिका तक नहीं पहुंच सकती है, ऐसा करने के लिए एक कर्नेल फ़ंक्शन है।

प्रक्रिया प्रतिस्थापन

अब हमारे पास यह समझने के लिए सब कुछ है कि प्रक्रिया प्रतिस्थापन कैसे काम करता है:

  1. बैश प्रक्रिया बाद में बनाई गई दो प्रक्रियाओं के बीच संचार के लिए एक अनाम पाइप बनाती है।
  2. echoप्रक्रिया के लिए बैश कांटे । बाल प्रक्रिया (जो मूल bashप्रक्रिया की एक सटीक प्रति है ) पाइप के रीडिंग एंड को बंद कर देती है और पाइप के लेखन छोर के साथ अपने स्वयं के मानक आउटपुट को बदल देती है। यह देखते हुए कि echoएक शेल बिल्डिन है, कॉल को bashखुद ही छोड़ सकता है exec, लेकिन यह वैसे भी मायने नहीं रखता है (शेल बिलिन को निष्क्रिय भी किया जा सकता है, जिस स्थिति में यह निष्पादित होता है /bin/echo)।
  3. बैश (मूल, मूल एक) अनाम पाइप के रीडिंग एंड के संदर्भ <(echo 1)में छद्म फ़ाइल लिंक द्वारा अभिव्यक्ति की जगह लेता है /dev/fd
  4. बैश PHP प्रक्रिया के लिए निष्पादित करता है (ध्यान दें कि कांटा के बाद, हम अभी भी अंदर हैं [कॉपी ऑफ़ बैश])। नई प्रक्रिया अनाम पाइप के इनहेरिटेड राइट एंड को बंद कर देती है (और कुछ अन्य प्रारंभिक चरण), लेकिन रीड एंड को खुला छोड़ देती है। फिर इसने PHP को क्रियान्वित किया।
  5. PHP प्रोग्राम में नाम प्राप्त करता है /dev/fd/। चूंकि संबंधित फाइल डिस्क्रिप्टर अभी भी खुला है, यह अभी भी पाइप के रीडिंग एंड से मेल खाता है। इसलिए यदि PHP प्रोग्राम रीडिंग के लिए दी गई फाइल को खोलता है, तो यह वास्तव में क्या करता है secondअनाम पाइप के रीडिंग एंड के लिए फाइल डिस्क्रिप्टर बनाना है । लेकिन यह कोई समस्या नहीं है, यह या तो से पढ़ सकता है।
  6. अब PHP प्रोग्राम नई फ़ाइल डिस्क्रिप्टर के माध्यम से पाइप के रीडिंग एंड को पढ़ सकता है, और इस प्रकार echoकमांड का मानक आउटपुट प्राप्त करता है जो उसी पाइप के राइटिंग एंड पर जाता है।

ज़रूर, मैं आपके प्रयास की सराहना करता हूँ। लेकिन मैं कई मुद्दों पर बात करना चाहता था। सबसे पहले, आप phpपरिदृश्य के बारे में बात कर रहे हैं, लेकिन phpपाइप को अच्छी तरह से संभालता नहीं है । इसके अलावा, कमांड पर विचार करते हुए cat <(echo test), यहां अजीब बात यह है कि bashकांटे एक बार के लिए हैं cat, लेकिन दो बार के लिए echo test
x- यूरी

13

के 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

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.