यूनिक्स पाइपलाइन कैसे लागू की जाती हैं


यह आलेख यूनिक्स कर्नेल में पाइपलाइनों के कार्यान्वयन का वर्णन करता है। हाल ही में " यूनिक्स पर पाइपलाइन कैसे काम करते हैं " शीर्षक लेख से मैं कुछ निराश था। "था नहीं आंतरिक डिवाइस के बारे में। मुझे दिलचस्पी हो गई, और मैंने जवाब खोजने के लिए खुद को पुराने स्रोतों में दफन कर लिया।

हम किस बारे में बात कर रहे हैं?


पाइपलाइन - "शायद यूनिक्स पर सबसे महत्वपूर्ण आविष्कार" - यूनिक्स के अंतर्निहित दर्शन को एक साथ छोटे कार्यक्रमों के संयोजन के साथ-साथ परिचित कमांड लाइन की परिभाषित विशेषता है:

$ echo hello | wc -c
6

यह कार्यक्षमता कर्नेल द्वारा प्रदान किए गए सिस्टम कॉल पर निर्भर करती है pipe, जिसे पाइप (7) और पाइप (2) प्रलेखन पृष्ठों पर वर्णित किया गया है :

कन्वेयर एक अप्रत्यक्ष इंटरप्रोसेस संचार चैनल प्रदान करते हैं। पाइपलाइन में एक इनपुट (राइट एंड) और एक आउटपुट (रीड एंड) है। पाइपलाइन के इनपुट को लिखे गए डेटा को पढ़ा जा सकता है।

पाइपलाइन एक कॉल का उपयोग करके बनाई गई है pipe(2)जो दो फ़ाइल डिस्क्रिप्टर लौटाती है: एक पाइपलाइन के इनपुट को संदर्भित करता है, दूसरा आउटपुट को।

उपरोक्त कमांड के ट्रेस परिणाम एक पाइपलाइन के निर्माण और इसके माध्यम से डेटा के प्रवाह को एक प्रक्रिया से दूसरी प्रक्रिया में प्रदर्शित करते हैं:

$ strace -qf -e execve,pipe,dup2,read,write \
    sh -c 'echo hello | wc -c'

execve("/bin/sh", ["sh", "-c", "echo hello | wc -c"], …)
pipe([3, 4])                            = 0
[pid 2604795] dup2(4, 1)                = 1
[pid 2604795] write(1, "hello\n", 6)    = 6
[pid 2604796] dup2(3, 0)                = 0
[pid 2604796] execve("/usr/bin/wc", ["wc", "-c"], …)
[pid 2604796] read(0, "hello\n", 16384) = 6
[pid 2604796] write(1, "6\n", 2)        = 2

अभिभावक प्रक्रिया pipe()संलग्न फाइल डिस्क्रिप्टर प्राप्त करने के लिए कॉल करती है। एक बच्चे की प्रक्रिया एक डिस्क्रिप्टर को लिखती है, और दूसरी प्रक्रिया दूसरे डिस्क्रिप्टर से एक ही डेटा पढ़ती है। स्टेप 2 और 4 का वर्णन स्टेप 2 और 4 से मिलान करने के लिए स्टेप 2 और 4 का उपयोग करते हुए रैपर का उपयोग करता है।

पाइपलाइनों के बिना, शेल को एक फ़ाइल में एक प्रक्रिया का परिणाम लिखना होगा और इसे दूसरी प्रक्रिया में स्थानांतरित करना होगा ताकि वह फ़ाइल से डेटा पढ़ ले। परिणामस्वरूप, हम अधिक संसाधन और डिस्क स्थान खर्च करेंगे। हालांकि, पाइपलाइनें न केवल अच्छी हैं क्योंकि वे अस्थायी फ़ाइलों के उपयोग से बचती हैं:

, read(2) , . , write(2) , .

पोसिक्स-आवश्यकता की तरह, यह एक महत्वपूर्ण संपत्ति है: PIPE_BUFबाइट्स (कम से कम 512) तक पाइपलाइन को लिखना परमाणु होना चाहिए ताकि प्रक्रियाएं एक दूसरे के साथ पाइप लाइन के माध्यम से उसी तरह से बातचीत कर सकें जैसे कि नियमित फाइलें (जो ऐसी गारंटी प्रदान नहीं कर सकती हैं)।

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

हम क्या खोज कर रहे हैं?


मैं अपनी उंगलियों पर यह समझाता हूं कि आपके लिए यह कल्पना करना आसान है कि कन्वेयर कैसे काम कर सकता है। आपको मेमोरी में एक बफर और कुछ राज्य आवंटित करने की आवश्यकता होगी। आपको बफर से डेटा जोड़ने और हटाने के लिए कार्यों की आवश्यकता होगी। इसे पढ़ने और लिखने के लिए कार्यों को कॉल करने के लिए कुछ साधन लेने होंगे ताकि विवरणों को दर्ज किया जा सके। और ऊपर वर्णित विशेष व्यवहार को लागू करने के लिए ताले की आवश्यकता होती है।

अब हम अपने अस्पष्ट मानसिक मॉडल की पुष्टि या खंडन करने के लिए कर्नेल के स्रोत कोड के उज्ज्वल प्रकाश में पूछताछ करने के लिए तैयार हैं। लेकिन हमेशा अप्रत्याशित के लिए तैयार रहें।

हम कहां देख रहे हैं?


मुझे पता नहीं है कि यूनिक्स 6 स्रोत कोड के साथ प्रसिद्ध पुस्तक " लायंस बुक " की मेरी प्रति कहाँ स्थित है, लेकिन यूनिक्स हेरिटेज सोसाइटी के लिए धन्यवाद आप स्रोत कोड में यूनिक्स के पुराने संस्करणों के लिए भी ऑनलाइन खोज कर सकते हैं

टीयूएचएस अभिलेखागार के चारों ओर घूमते हुए एक संग्रहालय का दौरा करने के समान है। हम अपने सामान्य इतिहास पर एक नज़र डाल सकते हैं, और मैं इन सभी सामग्रियों को पुराने कैसेट और प्रिंटआउट से थोड़ा-थोड़ा करके पुनर्प्राप्त करने के कई वर्षों का सम्मान करता हूं। और मैं उन टुकड़ों के बारे में गहराई से जानता हूं जो अभी भी गायब हैं।

कन्वेयर के प्राचीन इतिहास के बारे में हमारी जिज्ञासा को संतुष्ट करते हुए, हम तुलना के लिए आधुनिक कोर को देख सकते हैं।

वैसे, pipeटेबल में एक सिस्टम कॉल नंबर 42 है sysent[]संयोग?

पारंपरिक यूनिक्स कर्नेल (1970-1974)


मुझे पीडीपी -7 यूनिक्स (जनवरी 1970), या यूनिक्स के पहले संस्करण (नवंबर 1971) में, या दूसरे संस्करण के अधूरे स्रोत कोड (जून 1972) pipe(2)में भी कोई निशान नहीं मिला TUHS का दावा है कि यूनिक्स का तीसरा संस्करण (फरवरी 1973) पाइपलाइनों के साथ पहला संस्करण था:



यूनिक्स का तीसरा संस्करण असेंबली भाषा में लिखे गए कर्नेल के साथ नवीनतम संस्करण था, लेकिन पाइपलाइनों के साथ पहला संस्करण। 1973 के दौरान, तीसरे संस्करण को बेहतर बनाने के लिए काम किया गया था, कोर को सी में फिर से लिखा गया था, और इसलिए यूनिक्स का चौथा संस्करण दिखाई दिया।

पाठकों में से एक को एक दस्तावेज़ का स्कैन मिला, जिसमें डौग मैकलारियो ने "एक बगीचे की नली के सिद्धांत से कार्यक्रमों को जोड़ने" के विचार का प्रस्ताव रखा।


ब्रायन कार्निघन की पुस्तक " यूनिक्स: ए हिस्ट्री एंड ए मेमॉयर " में, कन्वेयर की उपस्थिति के इतिहास में, इस दस्तावेज़ का भी उल्लेख किया गया है: "... यह मेरे कार्यालय में बेल लैब्स में 30 वर्षों तक दीवार पर लटका रहा।" यहाँ McIlroy के साथ एक साक्षात्कार है , और 2014 में लिखे गए McIlroy के काम की एक और कहानी :

Unix, , , , - , , . , . , , , . ? «» , , -, : « !».

. , , ( ), . . . API , .

दुर्भाग्य से, यूनिक्स के तीसरे संस्करण के लिए कर्नेल स्रोत कोड खो गया है। और यद्यपि हमारे पास C में लिखित चौथे संस्करण कर्नेल के लिए स्रोत कोड है , जिसे नवंबर 1973 में जारी किया गया था, यह आधिकारिक रिलीज़ से कुछ महीने पहले जारी किया गया था और इसमें पाइपलाइन कार्यान्वयन शामिल नहीं हैं। यह अफ़सोस की बात है कि पौराणिक यूनिक्स फ़ंक्शन का स्रोत कोड संभवतः हमेशा के लिए खो जाता है।

हमारे पास pipe(2)दोनों रिलीज़ से प्रलेखन का पाठ है , इसलिए आप दस्तावेज़ के तीसरे संस्करण (कुछ शब्दों के लिए, "मैन्युअल रूप से रेखांकित", शाब्दिक ^ एच का एक स्ट्रिंग, अंडरस्कोर द्वारा पीछा करके खोज शुरू कर सकते हैं !)। इस प्रोटो को असेम्बलर में pipe(2)लिखा गया है और केवल एक फाइल डिस्क्रिप्टर लौटाता है, लेकिन पहले से ही अपेक्षित बुनियादी कार्यक्षमता प्रदान करता है:

पाइप सिस्टम कॉल एक आउटपुट इनपुट तंत्र बनाता है जिसे पाइपलाइन कहा जाता है। लौटे फ़ाइल डिस्क्रिप्टर का उपयोग पढ़ने और लिखने के संचालन के लिए किया जा सकता है। जब पाइप लाइन के लिए कुछ लिखा जाता है, तो 504 बाइट तक के डेटा बफ़र किए जाते हैं, जिसके बाद लेखन प्रक्रिया को रोक दिया जाता है। पाइपलाइन से पढ़ते समय, बफ़र्ड डेटा लिया जाता है।

अगले साल तक, कर्नेल को सी में फिर से लिखा गया, और चौथे संस्करण में पाइप (2) ने प्रोटोटाइप के साथ अपना आधुनिक रूप पाया pipe(fildes): ":

pipe , . . - , , r1 (. fildes[1]), 4096 , . , r0 (. fildes[0]), .

, ( ) ( fork) read write.

शेल में एक पाइपलाइन के माध्यम से जुड़ी प्रक्रियाओं के रैखिक सरणी को परिभाषित करने के लिए सिंटैक्स है।

एक खाली पाइपलाइन से कॉल पढ़ें (बफर डेटा नहीं है) जिसमें केवल एक छोर है (सभी लेखन फ़ाइल डिस्क्रिप्टर बंद हैं) "फ़ाइल का अंत" लौटाएं। एक समान स्थिति में रिकॉर्डिंग कॉल की अनदेखी की जाती है।

जल्द से जल्द जीवित पाइपलाइन कार्यान्वयन दिनांकों वापस यूनिक्स के पांचवें संस्करण के लिए (जून 1974), लेकिन यह एक है कि अगली फिल्म में दिखाई दिया करने के लिए लगभग समान है। केवल टिप्पणियां जोड़ी गईं, इसलिए पांचवें संस्करण को छोड़ दिया जा सकता है।

यूनिक्स का छठा संस्करण (1975)


हम छठे संस्करण यूनिक्स स्रोत कोड (मई 1975) को पढ़ना शुरू करते हैं बड़े पैमाने पर शेरों के लिए धन्यवाद , इसे खोजना पहले के संस्करणों के स्रोत कोड की तुलना में बहुत आसान है:

कई वर्षों के लिए, लायंस बेल लैब्स की दीवारों के बाहर उपलब्ध एकमात्र यूनिक्स कोर दस्तावेज रहा है। हालांकि छठे संस्करण के लाइसेंस ने शिक्षकों को अपने स्रोत कोड का उपयोग करने की अनुमति दी, सातवें संस्करण के लाइसेंस ने इस संभावना को बाहर कर दिया, इसलिए पुस्तक को अवैध टाइपराइटर प्रतियों के रूप में वितरित किया गया था।

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

15 , Lions, . TUHS , . 1988- 9 , PDP11. , , /usr/src/, 1979- , . PWB, .

. , , += =+. - , - , .

और आज हम टीयूएचएस पर संग्रह से छठे संस्करण के स्रोत कोड को ऑनलाइन पढ़ सकते हैं , जिसमें डेनिस रिची का हाथ था

वैसे, पहली नज़र में, कार्निगन और रिची काल से पहले सी-कोड की मुख्य विशेषता इसकी संक्षिप्तता है । ऐसा अक्सर नहीं होता है, मैं अपनी साइट पर अपेक्षाकृत संकीर्ण प्रदर्शन क्षेत्र को फिट करने के लिए व्यापक संपादन के बिना कोड स्निपेट्स एम्बेड करने का प्रबंधन करता हूं। /Usr/sys/ken/pipe.c

की शुरुआत में एक व्याख्यात्मक टिप्पणी है (और हाँ, वहाँ भी है / usr / sys / dmr ):

/*
 * Max allowable buffering per pipe.
 * This is also the max size of the
 * file created to implement the pipe.
 * If this size is bigger than 4096,
 * pipes will be implemented in LARG
 * files, which is probably not good.
 */
#define    PIPSIZ    4096

चौथे संस्करण के बाद से बफर का आकार नहीं बदला है। लेकिन यहाँ, बिना किसी सार्वजनिक दस्तावेज़ के, हम देखते हैं कि एक बार पाइपलाइनों ने बैकअप स्टोरेज के रूप में फाइलों का उपयोग किया था!

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

यहाँ वास्तविक सिस्टम कॉल है pipe:

/*
 * The sys-pipe entry.
 * Allocate an inode on the root device.
 * Allocate 2 file structures.
 * Put it all together with flags.
 */
pipe()
{
    register *ip, *rf, *wf;
    int r;

    ip = ialloc(rootdev);
    if(ip == NULL)
        return;
    rf = falloc();
    if(rf == NULL) {
        iput(ip);
        return;
    }
    r = u.u_ar0[R0];
    wf = falloc();
    if(wf == NULL) {
        rf->f_count = 0;
        u.u_ofile[r] = NULL;
        iput(ip);
        return;
    }
    u.u_ar0[R1] = u.u_ar0[R0]; /* wf's fd */
    u.u_ar0[R0] = r;           /* rf's fd */
    wf->f_flag = FWRITE|FPIPE;
    wf->f_inode = ip;
    rf->f_flag = FREAD|FPIPE;
    rf->f_inode = ip;
    ip->i_count = 2;
    ip->i_flag = IACC|IUPD;
    ip->i_mode = IALLOC;
}

टीका स्पष्ट रूप से बताती है कि यहां क्या हो रहा है। लेकिन कोड को समझना आसान नहीं है, आंशिक रूप से « एक संरचना उपयोगकर्ता यू » की मदद से और रजिस्टर R0और R1सिस्टम कॉल और वापसी मूल्यों के हस्तांतरित पैरामीटर।

आइए डिस्क पर एक आईनोड (इंडेक्स ) रखने के लिए आईलोक () का उपयोग करने का प्रयास करें , और फैलोक () का उपयोग करके , हम मेमोरी में दो फाइलें रख सकते हैं । यदि सब कुछ ठीक हो जाता है, तो हम इन फ़ाइलों को पाइपलाइन के दो सिरों के रूप में परिभाषित करने के लिए झंडे स्थापित करेंगे, उन्हें एक ही इनोड (जिसका संदर्भ संख्या 2 होगा) इंगित करें, और इनोड को परिवर्तित और उपयोग के रूप में चिह्नित करें। Iput पर कॉल पर ध्यान दें ()नए इनोड में संदर्भ संख्या को कम करने के लिए त्रुटि पथ में। पढ़ने और लिखने के लिए फ़ाइल डिस्क्रिप्टर नंबर के

pipe()माध्यम से R0और R1वापस करना चाहिए falloc()फ़ाइल संरचना के लिए एक संकेतक लौटाता है, लेकिन u.u_ar0[R0]फ़ाइल विवरणक के माध्यम से "रिटर्न" भी करता है । यही है, कोड rपढ़ने के लिए एक फ़ाइल डिस्क्रिप्टर पर सहेजता है और u.u_ar0[R0]दूसरी कॉल के बाद सीधे लिखने के लिए एक डिस्क्रिप्टर असाइन करता है falloc()पाइपलाइन बनाते समय हम जो

झंडा लगाते हैंFPIPE वह sys2.c में rdwr () फ़ंक्शन के व्यवहार को नियंत्रित करता है , जिसे विशिष्ट I / O I / O रूटीन कहते हैं:

/*
 * common code for read and write calls:
 * check permissions, set base, count, and offset,
 * and switch out to readi, writei, or pipe code.
 */
rdwr(mode)
{
    register *fp, m;

    m = mode;
    fp = getf(u.u_ar0[R0]);
        /* … */

    if(fp->f_flag&FPIPE) {
        if(m==FREAD)
            readp(fp); else
            writep(fp);
    }
        /* … */
}

तो समारोह readp()में pipe.cपाइपलाइन से डेटा पढ़ता है। लेकिन कार्यान्वयन का पता लगाना बेहतर है writep()एक बार फिर, तर्क हस्तांतरण समझौते की बारीकियों के कारण कोड अधिक जटिल हो गया, लेकिन कुछ विवरणों को छोड़ा जा सकता है।

writep(fp)
{
    register *rp, *ip, c;

    rp = fp;
    ip = rp->f_inode;
    c = u.u_count;

loop:
    /* If all done, return. */

    plock(ip);
    if(c == 0) {
        prele(ip);
        u.u_count = 0;
        return;
    }

    /*
     * If there are not both read and write sides of the
     * pipe active, return error and signal too.
     */

    if(ip->i_count < 2) {
        prele(ip);
        u.u_error = EPIPE;
        psignal(u.u_procp, SIGPIPE);
        return;
    }

    /*
     * If the pipe is full, wait for reads to deplete
     * and truncate it.
     */

    if(ip->i_size1 == PIPSIZ) {
        ip->i_mode =| IWRITE;
        prele(ip);
        sleep(ip+1, PPIPE);
        goto loop;
    }

    /* Write what is possible and loop back. */

    u.u_offset[0] = 0;
    u.u_offset[1] = ip->i_size1;
    u.u_count = min(c, PIPSIZ-u.u_offset[1]);
    c =- u.u_count;
    writei(ip);
    prele(ip);
    if(ip->i_mode&IREAD) {
        ip->i_mode =& ~IREAD;
        wakeup(ip+2);
    }
    goto loop;
}

हम पाइपलाइन के इनपुट को बाइट्स लिखना चाहते हैं u.u_countपहले हमें इनोड लॉक करना होगा (नीचे देखें plock/ prele)।

फिर इनकोड संदर्भ गणना की जांच करें। जबकि पाइपलाइन के दोनों छोर खुले रहते हैं, काउंटर 2 होना चाहिए। हम एक लिंक (आउट rp->f_inode) रखते हैं, इसलिए यदि काउंटर 2 से कम है, तो इसका मतलब यह होना चाहिए कि रीडिंग प्रक्रिया ने पाइप लाइन के अपने छोर को बंद कर दिया है। दूसरे शब्दों में, हम एक बंद पाइपलाइन में लिखने की कोशिश कर रहे हैं, और यह एक गलती है। त्रुटि कोड EPIPEऔर संकेत पहली SIGPIPEबार यूनिक्स के छठे संस्करण में दिखाई दिया।

लेकिन अगर कन्वेयर खुला है, तो भी यह पूर्ण हो सकता है। इस मामले में, हम लॉक को हटा देते हैं और इस उम्मीद में सो जाते हैं कि एक अन्य प्रक्रिया पाइपलाइन से पढ़ेगी और इसमें पर्याप्त जगह खाली कर दी जाएगी। जागने के बाद, हम शुरुआत में लौटते हैं, फिर से हम लॉक को ब्लॉक करते हैं और एक नया रिकॉर्डिंग चक्र शुरू करते हैं।

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

आमतौर पर i_mode, इनकोड पैरामीटर का उपयोग अनुमतियों को संग्रहीत करने के लिए किया जाता है r, wऔर x। लेकिन पाइपलाइन के मामले में, हम संकेत है कि कुछ प्रक्रिया लिखने के लिए या इंतज़ार कर बिट्स के प्रयोग पढ़ा जाता है IREADऔर, IWRITEक्रमशः। एक प्रक्रिया एक ध्वज और कॉल सेट करती है sleep(), और यह उम्मीद की जाती है कि भविष्य में कुछ अन्य प्रक्रिया कॉल करेगी wakeup()

रियल जादू में होता है sleep()और wakeup()। वे slp.c में कार्यान्वित किए जाते हैंप्रसिद्ध टिप्पणी का स्रोत, "आपको यह समझने की उम्मीद नहीं है।" सौभाग्य से, हमें कोड को समझने की आवश्यकता नहीं है, बस कुछ टिप्पणियां देखें:

/*
 * Give up the processor till a wakeup occurs
 * on chan, at which time the process
 * enters the scheduling queue at priority pri.
 * The most important effect of pri is that when
 * pri<0 a signal cannot disturb the sleep;
 * if pri>=0 signals will be processed.
 * Callers of this routine must be prepared for
 * premature return, and check that the reason for
 * sleeping has gone away.
 */
sleep(chan, pri) /* … */

/*
 * Wake up all processes sleeping on chan.
 */
wakeup(chan) /* … */

एक प्रक्रिया जो sleep()किसी विशेष चैनल के लिए आह्वान करती है , बाद wakeup()में उसी प्रक्रिया से जागृत हो सकती है जो उसी चैनल के लिए आएगी writep()और readp()ऐसे युग्मित कॉल के माध्यम से अपने कार्यों का समन्वय करें। कृपया ध्यान दें कि pipe.cहमेशा PPIPEकॉल करते समय प्राथमिकता दी जाती है sleep(), इसलिए हर कोई sleep()सिग्नल पर बाधित कर सकता है।

अब हमारे पास फ़ंक्शन को समझने के लिए सब कुछ है readp():

readp(fp)
int *fp;
{
    register *rp, *ip;

    rp = fp;
    ip = rp->f_inode;

loop:
    /* Very conservative locking. */

    plock(ip);

    /*
     * If the head (read) has caught up with
     * the tail (write), reset both to 0.
     */

    if(rp->f_offset[1] == ip->i_size1) {
        if(rp->f_offset[1] != 0) {
            rp->f_offset[1] = 0;
            ip->i_size1 = 0;
            if(ip->i_mode&IWRITE) {
                ip->i_mode =& ~IWRITE;
                wakeup(ip+1);
            }
        }

        /*
         * If there are not both reader and
         * writer active, return without
         * satisfying read.
         */

        prele(ip);
        if(ip->i_count < 2)
            return;
        ip->i_mode =| IREAD;
        sleep(ip+2, PPIPE);
        goto loop;
    }

    /* Read and return */

    u.u_offset[0] = 0;
    u.u_offset[1] = rp->f_offset[1];
    readi(ip);
    rp->f_offset[1] = u.u_offset[1];
    prele(ip);
}

आपको नीचे से ऊपर तक इस फ़ंक्शन को पढ़ना आसान हो सकता है। "रीड एंड रिटर्न" शाखा का उपयोग आमतौर पर तब किया जाता है जब पाइपलाइन में कुछ डेटा होता है। इस मामले में, हम रीडी () को पढ़ने के लिए उतना ही उपयोग करते हैं जितना कि वर्तमान f_offsetरीड से शुरू होने वाला डेटा उपलब्ध है , और फिर संबंधित ऑफसेट के मूल्य को अपडेट करें।

बाद में पढ़ने पर, पाइपलाइन खाली हो जाएगी यदि रीड ऑफ़सेट i_size1इनोड के मूल्य तक पहुंच गया है । हम स्थिति को 0 पर रीसेट करते हैं और किसी भी प्रक्रिया को जगाने की कोशिश करते हैं जिसे वह पाइपलाइन में लिखना चाहता है। हम जानते हैं कि जब कन्वेयर भरा हुआ है, तो यह writep()सो जाएगा ip+1। और अब जब पाइपलाइन खाली है, तो हम इसे जगा सकते हैं ताकि यह अपने रिकॉर्डिंग चक्र को फिर से शुरू करे।

यदि पढ़ने के लिए कुछ नहीं है, तो यह readp()एक झंडा लगा सकता है IREADऔर सो सकता हैip+2हम जानते हैं कि writep()जब वह पाइपलाइन के लिए कुछ डेटा लिखता है तो उसे क्या जगाएगा रीडी () और राइटी ()

पर टिप्पणियों को समझने में मदद मिलेगी कि " " के माध्यम से मापदंडों को पारित करने के बजाय , हम उन्हें सामान्य I / O फ़ंक्शन की तरह व्यवहार कर सकते हैं, जो फ़ाइल, स्थिति, मेमोरी में बफर, और पढ़ने या लिखने के लिए बाइट्स की संख्या की गणना करते हैं। ।u

/*
 * Read the file corresponding to
 * the inode pointed at by the argument.
 * The actual read arguments are found
 * in the variables:
 *    u_base        core address for destination
 *    u_offset    byte offset in file
 *    u_count        number of bytes to read
 *    u_segflg    read to kernel/user
 */
readi(aip)
struct inode *aip;
/* … */

/*
 * Write the file corresponding to
 * the inode pointed at by the argument.
 * The actual write arguments are found
 * in the variables:
 *    u_base        core address for source
 *    u_offset    byte offset in file
 *    u_count        number of bytes to write
 *    u_segflg    write to kernel/user
 */
writei(aip)
struct inode *aip;
/* … */

"रूढ़िवादी" लॉक के लिए, तब तक readp()और writep()इनोड को ब्लॉक करें जब तक वे एक नौकरी खत्म कर देते हैं या परिणाम प्राप्त नहीं करते हैं (अर्थात कारण wakeup)। plock()और वे prele()बस काम करते हैं: कॉल के एक अलग सेट का उपयोग करते हुए sleepऔर wakeupहमें किसी भी प्रक्रिया को जागृत करने की अनुमति देता है जिसे एक लॉक की आवश्यकता होती है जिसे हमने अभी हटा दिया है:

/*
 * Lock a pipe.
 * If its already locked, set the WANT bit and sleep.
 */
plock(ip)
int *ip;
{
    register *rp;

    rp = ip;
    while(rp->i_flag&ILOCK) {
        rp->i_flag =| IWANT;
        sleep(rp, PPIPE);
    }
    rp->i_flag =| ILOCK;
}

/*
 * Unlock a pipe.
 * If WANT bit is on, wakeup.
 * This routine is also used to unlock inodes in general.
 */
prele(ip)
int *ip;
{
    register *rp;

    rp = ip;
    rp->i_flag =& ~ILOCK;
    if(rp->i_flag&IWANT) {
        rp->i_flag =& ~IWANT;
        wakeup(rp);
    }
}

पहले तो मैं समझ नहीं पाया कि readp()कॉल करने prele(ip)से पहले फोन क्यों नहीं किया wakeup(ip+1)। पहली बात जो writep()इसके पाश में होती है वह यह है कि यह plock(ip)गतिरोध की ओर जाता है यदि readp()उसने अभी तक अपने ब्लॉक को नहीं हटाया है, तो कोड को किसी तरह सही तरीके से काम करना होगा। यदि आप इसे देखते हैं wakeup(), तो यह स्पष्ट हो जाता है कि वह केवल नींद की प्रक्रिया को निष्पादन के लिए तैयार के रूप में चिह्नित करता है, ताकि भविष्य में यह sched()वास्तव में इसे लॉन्च कर सके। तो यह चक्र को फिर से शुरू करने से पहले , इसका readp()कारण बनता है wakeup(), अनलॉक, सेट IREADऔर कॉल करता है। यह छठे संस्करण में कन्वेयर का वर्णन पूरा करता है। सरल कोड, दूरगामी परिणाम। यूनिक्स का सातवां संस्करणsleep(ip+2)writep()



(जनवरी 1979) नई प्रमुख रिलीज़ (चार साल बाद) थी, जिसमें कई नए एप्लिकेशन और कर्नेल गुण दिखाई दिए। साथ ही, संरचनाओं के प्रकार कास्टिंग, Union'ov और टाइप किए गए पॉइंटर्स के उपयोग के संबंध में महत्वपूर्ण परिवर्तन हुए हैं। हालांकि, पाइपलाइन कोड बहुत ज्यादा नहीं बदला है। हम इस संस्करण को छोड़ सकते हैं।

Xv6, एक सरल यूनिक्स के आकार का गिरी


यूनिक्स के छठे संस्करण ने Xv6 कोर के निर्माण को प्रभावित किया, लेकिन इसे x86 प्रोसेसर पर चलाने के लिए आधुनिक सी में लिखा गया है। कोड को पढ़ना आसान है, यह स्पष्ट है। इसके अलावा, टीयूएचएस के साथ यूनिक्स स्रोतों के विपरीत, आप इसे संकलित कर सकते हैं, इसे संशोधित कर सकते हैं, और इसे पीडीपी 11-70 के अलावा किसी और चीज़ पर चला सकते हैं। इसलिए, यह कोर विश्वविद्यालयों में व्यापक रूप से ऑपरेटिंग सिस्टम पर शैक्षिक सामग्री के रूप में उपयोग किया जाता है। सूत्र जीथुब पर हैं

कोड में पाईप . c का एक स्पष्ट और सुविचारित कार्यान्वयन है , जो डिस्क पर इनोड के बजाय मेमोरी में बफर द्वारा समर्थित है। यहाँ मैं केवल "संरचनात्मक पाइपलाइन" और फ़ंक्शन की परिभाषा देता हूं pipealloc():

#define PIPESIZE 512

struct pipe {
  struct spinlock lock;
  char data[PIPESIZE];
  uint nread;     // number of bytes read
  uint nwrite;    // number of bytes written
  int readopen;   // read fd is still open
  int writeopen;  // write fd is still open
};

int
pipealloc(struct file **f0, struct file **f1)
{
  struct pipe *p;

  p = 0;
  *f0 = *f1 = 0;
  if((*f0 = filealloc()) == 0 || (*f1 = filealloc()) == 0)
    goto bad;
  if((p = (struct pipe*)kalloc()) == 0)
    goto bad;
  p->readopen = 1;
  p->writeopen = 1;
  p->nwrite = 0;
  p->nread = 0;
  initlock(&p->lock, "pipe");
  (*f0)->type = FD_PIPE;
  (*f0)->readable = 1;
  (*f0)->writable = 0;
  (*f0)->pipe = p;
  (*f1)->type = FD_PIPE;
  (*f1)->readable = 0;
  (*f1)->writable = 1;
  (*f1)->pipe = p;
  return 0;

 bad:
  if(p)
    kfree((char*)p);
  if(*f0)
    fileclose(*f0);
  if(*f1)
    fileclose(*f1);
  return -1;
}

pipealloc()बाकी कार्यान्वयन की स्थिति सेट करता है, जिसमें फ़ंक्शंस शामिल हैं piperead(), pipewrite()और pipeclose()वास्तविक सिस्टम कॉल sysfile.csys_pipe में लागू किया गया एक आवरण है मैं इसके सभी कोड को पढ़ने की सलाह देता हूं। जटिलता छठे संस्करण के स्रोत स्तर पर है, लेकिन यह पढ़ने में बहुत आसान और सुखद है।

लिनक्स 0.01


आप लिनक्स 0.01 के लिए स्रोत कोड पा सकते हैं। यह उसके fs/ में पाइपलाइनों के कार्यान्वयन का अध्ययन करने के लिए शिक्षाप्रद होगा pipe.cयहां, पाइपलाइन का प्रतिनिधित्व करने के लिए इनोड का उपयोग किया जाता है, लेकिन पाइपलाइन स्वयं आधुनिक सी में लिखी गई है। यदि आपको छठे संस्करण कोड के माध्यम से मिला है, तो आपको कठिनाइयों का अनुभव नहीं होगा। यह इस प्रकार दिखता है write_pipe():

int write_pipe(struct m_inode * inode, char * buf, int count)
{
    char * b=buf;

    wake_up(&inode->i_wait);
    if (inode->i_count != 2) { /* no readers */
        current->signal |= (1<<(SIGPIPE-1));
        return -1;
    }
    while (count-->0) {
        while (PIPE_FULL(*inode)) {
            wake_up(&inode->i_wait);
            if (inode->i_count != 2) {
                current->signal |= (1<<(SIGPIPE-1));
                return b-buf;
            }
            sleep_on(&inode->i_wait);
        }
        ((char *)inode->i_size)[PIPE_HEAD(*inode)] =
            get_fs_byte(b++);
        INC_PIPE( PIPE_HEAD(*inode) );
        wake_up(&inode->i_wait);
    }
    wake_up(&inode->i_wait);
    return b-buf;
}

यहां तक ​​कि संरचनाओं की परिभाषाओं को देखे बिना, आप यह पता लगा सकते हैं कि इनर संदर्भ काउंटर का उपयोग यह जांचने के लिए किया जाता है कि क्या लेखन ऑपरेशन की ओर जाता है SIGPIPEबाइट कार्य के अलावा, यह फ़ंक्शन आसानी से उपरोक्त विचारों के साथ सहसंबद्ध है। यहां तक ​​कि तर्क भी sleep_on/ wake_upइतना अलग नहीं दिखता है।

आधुनिक लिनक्स, FreeBSD, NetBSD, OpenBSD गुठली


मैं जल्दी से कुछ आधुनिक गुठली पर चला गया। उनमें से किसी के पास पहले से डिस्क कार्यान्वयन नहीं है (आश्चर्यजनक रूप से नहीं)। लिनक्स का अपना कार्यान्वयन है। हालांकि तीन आधुनिक बीएसडी कर्नेल में कोड के आधार पर कार्यान्वयन होते हैं जो जॉन डायसन द्वारा लिखे गए थे, वर्षों से वे एक दूसरे से बहुत अलग हो गए हैं।

पढ़ने के लिए fs/ pipe.c(लिनक्स पर) या sys/ kern/ sys_pipe.c(* बीएसडी पर), सच समर्पण की आवश्यकता है। वेक्टर और अतुल्यकालिक I / O जैसी सुविधाओं के लिए प्रदर्शन और समर्थन आज कोड में महत्वपूर्ण हैं। और मेमोरी आवंटन, लॉक और कर्नेल कॉन्फ़िगरेशन का विवरण - यह सब बहुत अलग है। यह वह नहीं है जो विश्वविद्यालयों को ऑपरेटिंग सिस्टम पर एक परिचयात्मक पाठ्यक्रम के लिए आवश्यक है।

किसी भी मामले में, मेरे लिए कुछ पुराने पैटर्न का पता लगाना दिलचस्प था (उदाहरण के लिए, एक बंद पाइपलाइन को लिखते SIGPIPEसमय , उत्पन्न करना और वापस लौटना EPIPE), इन सभी में, इसलिए अलग, आधुनिक कोर। मैं शायद एक जीवित पीडीपी -11 कंप्यूटर नहीं देख पाऊंगा, लेकिन अभी भी उस कोड से बहुत कुछ सीखना बाकी है जो मेरे जन्म के कुछ साल पहले लिखा गया था।

2011 में देवी कपूर द्वारा लिखित, लेख " लिनक्स कर्नेल कार्यान्वयन पाइप्स और एफआईएफओ के लिए " एक अवलोकन प्रदान करता है कि कैसे (अब तक) पाइपलाइन लिनक्स पर काम करती है। और हाल ही में लिनक्स कमिट एक पाइलेटेड इंटरैक्शन मॉडल दिखाता है जिसकी क्षमता अस्थायी फ़ाइलों की क्षमताओं से अधिक है; और यह भी दर्शाता है कि छठे संस्करण यूनिक्स कर्नेल में "बहुत रूढ़िवादी लॉकिंग" से पाइपलाइनें कितनी दूर चली गई हैं।

All Articles