يونڪس ۾ پائپ لائنون ڪيئن لاڳو ٿين ٿيون

يونڪس ۾ پائپ لائنون ڪيئن لاڳو ٿين ٿيون
هي آرٽيڪل بيان ڪري ٿو يونڪس ڪنييل ۾ پائپ لائنن تي عمل درآمد. مون کي ڪجهه مايوس ٿي ويو ته هڪ تازو مضمون عنوان "يونڪس ۾ پائپ لائنون ڪيئن ڪم ڪن ٿيون؟"ٻاهرايو نه اندروني جوڙجڪ جي باري ۾. مون کي تجسس پيدا ٿيو ۽ جواب ڳولڻ لاءِ پراڻن ذريعن کي ڳوليائين.

اسان ڇا ڳالهائي رهيا آهيون؟

پائپ لائنون، "شايد يونڪس ۾ سڀ کان اهم ايجاد"، ننڍڙي پروگرامن کي هڪٻئي سان ڳنڍڻ جي بنيادي يونڪس فلسفي جي هڪ خاص خصوصيت آهي، انهي سان گڏ ڪمان لائن تي هڪ واقف نشاني:

$ 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, "hellon", 6)    = 6
[pid 2604796] dup2(3, 0)                = 0
[pid 2604796] execve("/usr/bin/wc", ["wc", "-c"], …)
[pid 2604796] read(0, "hellon", 16384) = 6
[pid 2604796] write(1, "6n", 2)        = 2

والدين جي عمل کي سڏي ٿو pipe()نصب ٿيل فائل بيان ڪندڙ حاصل ڪرڻ لاء. هڪ ٻار جو عمل هڪ هينڊل ڏانهن لکندو آهي، ۽ ٻيو عمل ساڳئي ڊيٽا کي ٻئي هينڊل مان پڙهي ٿو. شيل stdin ۽ stdout کي ملائڻ لاءِ ڊُپ 2 کي ”نام بدلائڻ“ جي وضاحت ڪندڙ 3 ۽ 4 کي استعمال ڪري ٿو.

بغير پائپ جي، شيل کي هڪ عمل جو نتيجو فائل ۾ لکڻو پوندو ۽ فائل مان ڊيٽا پڙهڻ لاء ان کي ٻئي پروسيس ڏانهن منتقل ڪرڻو پوندو. نتيجي طور، اسان وڌيڪ وسيلن ۽ ڊسڪ اسپيس کي ضايع ڪنداسين. بهرحال، پائپ لائنون سٺيون آهن نه صرف ڇو ته اهي توهان کي عارضي فائلن جي استعمال کان بچڻ جي اجازت ڏين ٿيون:

جيڪڏهن هڪ عمل هڪ خالي پائپ لائن مان پڙهڻ جي ڪوشش ڪري رهيو آهي ته پوء read(2) بلاڪ ڪيو ويندو جيستائين ڊيٽا دستياب ٿي وڃي. جيڪڏهن هڪ عمل مڪمل پائپ لائن تي لکڻ جي ڪوشش ڪري ٿو، پوء write(2) بلاڪ ڪيو ويندو جيستائين ڪافي ڊيٽا پائپ لائن مان پڙهيل لکت کي انجام ڏيڻ لاءِ.

POSIX جي گهرج وانگر، هي هڪ اهم ملڪيت آهي: پائپ لائن تائين لکڻ PIPE_BUF بائيٽ (گهٽ ۾ گهٽ 512) ايٽمي هجڻ لازمي آهي ته جيئن پروسيس هڪ ٻئي سان پائپ لائن ذريعي رابطو ڪري سگھن ٿيون جيئن باقاعده فائلون (جيڪي اهڙيون ضمانتون فراهم نه ڪن) نه ٿي سگهن.

جڏهن هڪ باقاعده فائل استعمال ڪندي، هڪ عمل ان جي سڀني پيداوار کي لکي سگهي ٿو ۽ ان کي ٻئي پروسيس تي منتقل ڪري ٿو. يا عمل هڪ انتهائي متوازي موڊ ۾ ڪم ڪري سگهن ٿا، هڪ خارجي سگنلنگ ميکانيزم استعمال ڪندي (جهڙوڪ سيمفور) هڪ ٻئي کي اطلاع ڏيڻ لاءِ جڏهن لکڻ يا پڙهڻ مڪمل ٿي چڪو آهي. Conveyors اسان کي هن تمام پريشاني کان بچائي.

اسان ڇا ڳولي رهيا آهيون؟

مان ان کي آسان اصطلاحن ۾ بيان ڪندس ته جيئن توهان لاءِ اهو تصور ڪرڻ آسان ٿئي ته هڪ ڪنويئر ڪيئن ڪم ڪري سگهي ٿو. توهان کي ميموري ۾ بفر ۽ ڪجهه رياست مختص ڪرڻ جي ضرورت پوندي. توھان کي بفر مان ڊيٽا شامل ڪرڻ ۽ ختم ڪرڻ لاءِ افعال جي ضرورت پوندي. توهان کي فائل بيان ڪندڙن تي پڙهڻ ۽ لکڻ جي عملن دوران افعال کي ڪال ڪرڻ لاءِ ڪجهه ذريعن جي ضرورت پوندي. ۽ مٿي بيان ڪيل خاص رويي کي لاڳو ڪرڻ لاءِ توهان کي تالا جي ضرورت پوندي.

ھاڻي اسان تيار آھيون ڪرنل سورس ڪوڊ جي پڇا ڳاڇا ڪرڻ لاءِ روشن چراغ جي تحت اسان جي مبہم ذهني ماڊل جي تصديق يا غلط ثابت ڪرڻ لاءِ. پر هميشه غير متوقع لاء تيار ٿي.

اسان ڪٿي ڳولي رهيا آهيون؟

خبر ناهي منهنجي مشهور ڪتاب جي ڪاپي ڪٿي آهي“شعرن جو ڪتاب"يونڪس 6 سورس ڪوڊ سان، پر مهرباني يونڪس ورثي سوسائٽي توھان آن لائن تي ڳولي سگھو ٿا سورس ڪوڊ يونڪس جا پراڻا ورجن.

TUHS آرڪائيوز ذريعي گھمڻ هڪ ميوزيم جو دورو ڪرڻ وانگر آهي. اسان پنهنجي گڏيل تاريخ کي ڏسي سگهون ٿا، ۽ مون کي ڪيترن ئي سالن جي ڪوشش جو احترام آهي ته هي سڄو مواد پراڻن ٽيپ ۽ پرنٽس مان ٿوري دير سان ٻيهر حاصل ڪرڻ لاء. ۽ مان انهن ٽڪرن کان واقف آهيان جيڪي اڃا تائين غائب آهن.

conveyors جي قديم تاريخ بابت اسان جي تجسس کي مطمئن ڪرڻ کان پوء، اسان مقابلي لاء جديد دانا کي ڏسي سگهون ٿا.

رستي ۾، pipe ٽيبل ۾ سسٽم ڪال نمبر 42 آهي sysent[]. اتفاق؟

روايتي يونڪس ڪرنل (1970-1974)

مون کي ڪوبه نشان نه مليو pipe(2) نه ئي اندر PDP-7 يونڪس (جنوري 1970)، ۽ نه ئي يونڪس جو پهريون ايڊيشن (نومبر 1971)، ۽ نه ئي نامڪمل سورس ڪوڊ ۾ ٻيو ايڊيشن (جون 1972ع).

TUHS چوي ٿو يونڪس جو ٽيون ايڊيشن (فيبروري 1973) کنويرز سان گڏ پهريون نسخو بڻجي ويو:

يونڪس 1973rd ايڊيشن آخري ورزن هو جنهن ۾ هڪ ڪرنل اسمبلي ٻولي ۾ لکيل هو، پر پائپ لائنن سان گڏ پهريون نسخو پڻ. XNUMX جي دوران، ٽئين ايڊيشن کي بهتر ڪرڻ لاء ڪم ڪيو ويو، ڪنيل کي C ۾ ٻيهر لکيو ويو، ۽ يونڪس جو چوٿون ايڊيشن ظاهر ٿيو.

هڪ پڙهندڙ کي هڪ دستاويز جو اسڪين مليو جنهن ۾ Doug McIlroy "ڳنڍيندڙ پروگرامن جهڙوڪ باغي نلي" جو خيال پيش ڪيو.

يونڪس ۾ پائپ لائنون ڪيئن لاڳو ٿين ٿيون
برائن ڪرنيگن جي ڪتاب ۾يونڪس: هڪ تاريخ ۽ هڪ يادگار"، conveyors جي ظاهر ٿيڻ جي تاريخ ۾، هن دستاويز جو ذڪر پڻ ڪيو ويو آهي: "... اهو 30 سالن تائين بيل ليبز ۾ منهنجي آفيس ۾ ڀت تي ٽنگيو ويو." هتي McIlroy سان انٽرويو، ۽ ٻي ڪهاڻي کان McIlroy جو ڪم، 2014 ۾ لکيو ويو:

جڏهن يونڪس ٻاهر آيو، ڪوروٽين سان منهنجي دلچسپي مون کي او ايس جي ليکڪ، ڪين ٿامپسن کان پڇڻ لاء، هڪ پروسيس تي لکيل ڊيٽا کي نه رڳو ڊوائيس ڏانهن وڃڻ جي اجازت ڏيڻ جي اجازت ڏني، پر ٻئي پروسيس ڏانهن پڻ. ڪين فيصلو ڪيو ته اهو ممڪن هو. بهرحال، هڪ minimalist طور، هن چاهيو ته هر سسٽم جي فنڪشن کي اهم ڪردار ادا ڪرڻ لاء. ڇا سڌو سنئون عملن جي وچ ۾ لکڻ هڪ وچولي فائل تي لکڻ تي هڪ وڏو فائدو آهي؟ اهو صرف تڏهن هو جڏهن مون هڪ خاص تجويز پيش ڪئي جنهن سان دلڪش نالو ”پائپ لائن“ ۽ عملن جي وچ ۾ رابطي لاءِ نحو جي وضاحت ڪئي وئي ته ڪين آخر ۾ چيو: ”مان اهو ڪندس!

۽ ڪيو. هڪ ڀيانڪ شام، ڪين ڪينل ۽ شيل کي تبديل ڪيو، ڪيترن ئي معياري پروگرامن کي طئي ڪيو ته معيار کي ڪيئن انهن ان پٽ کي قبول ڪيو (جيڪو پائپ لائن مان اچي سگهي ٿو)، ۽ فائل جا نالا پڻ تبديل ڪيا. ٻئي ڏينهن، پائپ لائنز ايپليڪيشنن ۾ تمام وڏي پيماني تي استعمال ٿيڻ لڳو. هفتي جي آخر تائين، سيڪريٽري انهن کي استعمال ڪري رهيا هئا لفظ پروسيسرز کان دستاويز موڪلڻ لاء پرنٽر ڏانهن. ٿوري دير کان پوء، Ken کي صاف ڪرڻ واري ڪنوينشن سان پائپ لائنن جي استعمال کي لپائڻ لاء اصل API ۽ نحو کي تبديل ڪيو، جيڪي ڪڏهن کان استعمال ڪيا ويا آهن.

بدقسمتي سان، ٽيون ايڊيشن يونڪس ڪنيل جو سورس ڪوڊ گم ٿي ويو آهي. ۽ جيتوڻيڪ اسان وٽ ڪنيل سورس ڪوڊ لکيل آهي سي چوٿين ايڊيشن، نومبر 1973 ۾ رليز ٿيو، پر اهو سرڪاري رليز کان ڪيترائي مهينا اڳ نڪتو ۽ پائپ لائن تي عمل درآمد تي مشتمل ناهي. اها شرم جي ڳالهه آهي ته هن افسانوي يونڪس فنڪشن جو سورس ڪوڊ گم ٿي ويو آهي، شايد هميشه لاءِ.

اسان وٽ ٽيڪسٽ دستاويز آهن pipe(2) ٻنهي رليز مان، تنهنڪري توهان شروع ڪري سگهو ٿا دستاويز ڳولڻ سان ٽيون ايڊيشن (ڪجهه لفظن لاءِ، هيٺ ڏنل ”دستي طور“، لغوي ^H جي هڪ تار، پٺيان هڪ انڊر اسڪور!). هي پروٽو-pipe(2) اسيمبلي جي ٻولي ۾ لکيل آهي ۽ صرف هڪ فائل بيان ڪندڙ کي واپس ڪري ٿو، پر اڳ ۾ ئي متوقع بنيادي ڪارڪردگي مهيا ڪري ٿو:

سسٽم ڪال پائپ هڪ ان پٽ/آئوٽ پٽ ميڪنزم ٺاهي ٿو جنهن کي پائپ لائن سڏيو ويندو آهي. واپسي فائل بيان ڪندڙ کي پڙهڻ ۽ لکڻ جي عملن لاء استعمال ڪري سگهجي ٿو. جڏهن پائپ لائن تي ڪجهه لکيو ويندو آهي، ڊيٽا جي 504 بائيٽ تائين بفر ٿي ويندي آهي، جنهن کان پوء لکڻ جي عمل کي معطل ڪيو ويندو آهي. جڏهن پائپ لائن مان پڙهي، بفر ٿيل ڊيٽا ڪڍيو ويندو آهي.

ايندڙ سال تائين ڪرنل کي C ۾ ٻيهر لکيو ويو، ۽ پائپ (2) چوٿين ايڊيشن ۾ پروٽوٽائپ سان ان جي جديد ظاهري حاصل ڪئي "pipe(fildes)»:

سسٽم ڪال پائپ هڪ ان پٽ/آئوٽ پٽ ميڪنزم ٺاهي ٿو جنهن کي پائپ لائن سڏيو ويندو آهي. واپسي فائل بيان ڪندڙ پڙهڻ ۽ لکڻ جي عملن ۾ استعمال ڪري سگھجن ٿيون. جڏهن پائپ لائن تي ڪجهه لکيو ويندو آهي، r1 (resp. fildes[1]) ۾ موٽڻ وارو هينڊ استعمال ڪيو ويندو آهي، ڊيٽا جي 4096 بائيٽ تائين بفر ڪيو ويندو آهي، جنهن کان پوء لکڻ جي عمل کي معطل ڪيو ويندو آهي. جڏهن پائپ لائن مان پڙهي، هينڊل r0 ڏانهن موٽيو (resp. fildes[0]) ڊيٽا وٺي ٿو.

اهو فرض ڪيو ويو آهي ته هڪ ڀيرو هڪ پائپ لائن جي وضاحت ڪئي وئي آهي، ٻه (يا وڌيڪ) مواصلاتي عمل (بعد ۾ ايندڙ ڪالن ذريعي ٺاهيل) ڪٽڪ) ڪال استعمال ڪندي پائپ لائن مان ڊيٽا منتقل ڪندو پڙهڻ и لکڻ.

شيل ۾ پائپ لائن سان ڳنڍيل عملن جي لڪير واري صف کي بيان ڪرڻ لاء هڪ نحو آهي.

هڪ خالي پائيپ لائين مان پڙهڻ لاءِ ڪالون (جنهن ۾ ڪو به بفر ٿيل ڊيٽا ناهي) جنهن جي صرف هڪ پڇاڙي آهي (سڀني لکڻين جي وضاحت ڪندڙ بند ٿيل آهن) واپسي "فائل جي آخر". ساڳئي صورتحال ۾ لکڻ لاء ڪالون نظر انداز ڪيا ويا آهن.

سڀ کان پهريان محفوظ ٿيل پائپ لائن تي عملدرآمد سان لاڳاپيل آهي يونڪس جي پنجين ايڊيشن تائين (جون 1974)، پر اهو لڳ ڀڳ هڪ جهڙو آهي جيڪو ايندڙ رليز ۾ ظاهر ٿيو. تبصرا شامل ڪيا ويا آهن، تنهنڪري توهان پنجين ايڊيشن کي ڇڏي سگهو ٿا.

يونڪس جو ڇهين ايڊيشن (1975)

اچو ته يونڪس سورس ڪوڊ پڙهڻ شروع ڪريون ڇهين ايڊيشن (مئي 1975ع). جي وڏي مهرباني شعر اڳوڻي نسخن جي ذريعن جي ڀيٽ ۾ ڳولڻ تمام آسان آهي:

ڪيترن سالن کان ڪتاب شعر بيل ليبز کان ٻاهر موجود يونڪس ڪنيل تي واحد دستاويز هو. جيتوڻيڪ ڇهين ايڊيشن جي لائسنس استادن کي ان جو سورس ڪوڊ استعمال ڪرڻ جي اجازت ڏني، ستين ايڊيشن جي لائسنس ان امڪان کي خارج ڪري ڇڏيو، تنهن ڪري ڪتاب غير قانوني ٽائيپ ٿيل ڪاپين جي صورت ۾ ورهايو ويو.

اڄ توهان ڪتاب جو ٻيهر ڇپائي خريد ڪري سگهو ٿا، جنهن جو احاطو شاگردن کي ڪاپي مشين تي ڏيکاري ٿو. ۽ وارين ٽومي جي مهرباني (جنهن TUHS پروجيڪٽ شروع ڪيو) توهان ڊائون لوڊ ڪري سگهو ٿا ڇهين ايڊيشن لاءِ سورس ڪوڊ سان گڏ PDF فائل. مان توهان کي هڪ خيال ڏيڻ چاهيان ٿو ته فائل ٺاهڻ ۾ ڪيتري ڪوشش ڪئي وئي:

15 سال اڳ کان وڌيڪ، مون ڏنل سورس ڪوڊ جي ڪاپي ٽائيپ ڪئي شعر, ڇاڪاڻ ته مون کي پسند نه ڪيو منهنجي ڪاپي جي معيار کي اڻڄاتل تعداد جي ٻين نسخن مان. TUHS اڃا تائين موجود نه هو ۽ مون کي پراڻي ذريعن تائين رسائي نه هئي. پر 1988 ۾، مون کي هڪ پراڻي 9-ٽريڪ ٽيپ مليو جنهن ۾ PDP11 ڪمپيوٽر مان بيڪ اپ شامل هو. اهو ٻڌائڻ مشڪل هو ته اهو ڪم ڪري رهيو هو، پر اتي هڪ برقرار /usr/src/ وڻ هو جنهن ۾ اڪثر فائلن تي سال 1979 جو ليبل لڳل هو، جيڪو پوءِ به قديم نظر اچي رهيو هو. اهو ستين ايڊيشن يا ان جو نڪتل PWB هو، جيئن مون سمجهيو.

مون ڳولها کي بنياد طور ورتو ۽ دستي طور تي ڇهين ايڊيشن تائين ذريعن کي ايڊٽ ڪيو. ڪجھ ڪوڊ ساڳيا رھيا، پر ڪجھ کي ٿورڙو تبديل ڪرڻو پيو، جديد += ٽوڪن کي پراڻي =+ ۾ تبديل ڪرڻ. ڪجهه شيون صرف ڊهي ويون، ۽ ڪجهه مڪمل طور تي ٻيهر لکڻيون هيون، پر تمام گهڻو نه.

۽ اڄ اسان TUHS تي آن لائن پڙهي سگھون ٿا ڇهين ايڊيشن جو سورس ڪوڊ آرڪائيو، جنهن ۾ ڊينس رچي جو هٿ هو.

رستي جي ذريعي، پهرين نظر ۾، ڪرنيگن ۽ رچي جي دور کان اڳ سي-ڪوڊ جي بنيادي خصوصيت ان جي آهي. اختصار. اهو اڪثر نه آهي ته مان ڪوڊ جا ٽڪرا داخل ڪرڻ جي قابل ٿي سگهان ٿو بغير ڪنهن وسيع ترميم جي منهنجي سائيٽ تي نسبتا تنگ ڊسپلي واري علائقي کي پورو ڪرڻ لاءِ.

شروعات ۾ /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

بفر جي سائيز چوٿين ايڊيشن کان تبديل نه ڪئي وئي آهي. پر هتي اسان ڏسون ٿا، بغير ڪنهن عوامي دستاويز جي، اهي پائپ لائنون هڪ ڀيرو فائلن کي بيڪ اپ اسٽوريج طور استعمال ڪيو!

جيئن ته LARG فائلن لاء، اهي ملن ٿا انوڊ پرچم LARG، جنهن کي پروسيس ڪرڻ لاءِ ”وڏي ايڊريسنگ الگورٿم“ استعمال ڪيو ويندو آهي اڻ سڌي طرح بلاڪ وڏي فائيل سسٽم کي سپورٽ ڪرڻ لاء. جيئن ته ڪين چيو آهي ته اهو بهتر آهي ته انهن کي استعمال نه ڪيو وڃي، مان خوشيء سان ان لاء سندس لفظ کڻندس.

هتي حقيقي سسٽم ڪال آهي 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;
}

تبصرو واضح طور تي بيان ڪري ٿو ته هتي ڇا ٿي رهيو آهي. پر ڪوڊ کي سمجھڻ ايترو آسان ناهي، جزوي طور تي رستو "struct استعمال ڪندڙ يو»۽ رجسٽر R0 и R1 سسٽم ڪال پيٽرولر ۽ واپسي جا قدر گذري ويا آهن.

اچو ته ڪوشش ڪريون ialloc() ڊسڪ تي رکڻ انوڊ (انڊيڪس هينڊل)، ۽ مدد سان falloc() - ياداشت ۾ ٻه رکو فائل. جيڪڏهن سڀ ڪجهه ٺيڪ ٿي وڃي ته، اسان انهن فائلن کي پائپ لائن جي ٻن سرن جي طور تي سڃاڻڻ لاءِ پرچم مقرر ڪنداسين، انهن کي هڪ ئي انوڊ ڏانهن اشارو ڪنداسين (جنهن جو حوالو ڳڻپ 2 تي مقرر ڪيو ويندو)، ۽ انوڊ کي تبديل ٿيل ۽ استعمال ۾ نشان لڳايو. درخواستن تي ڌيان ڏيو iput() نئين انوڊ ۾ ريفرنس جي ڳڻپ کي گھٽائڻ لاءِ غلطي جي رستن ۾.

pipe() ذريعي ٿيڻ گهرجي R0 и R1 پڙهڻ ۽ لکڻ لاءِ فائل بيان ڪندڙ نمبر واپس ڪريو. falloc() ھڪڙي پوائنٽر کي فائل جي جوڙجڪ ڏانھن موٽائي ٿو، پر پڻ "واپسي" ذريعي u.u_ar0[R0] ۽ هڪ فائيل وضاحت ڪندڙ. اهو آهي، ڪوڊ محفوظ ڪري ٿو اندر r پڙهڻ لاءِ فائل بيان ڪندڙ ۽ سڌو سنئون لکڻ لاءِ فائل بيان ڪندڙ کي تفويض ڪري ٿو u.u_ar0[R0] ٻئي سڏ کان پوء falloc().

پرچم FPIPE، جيڪو اسان سيٽ ڪيو جڏهن پائپ لائن ٺاهي، فنڪشن جي رويي کي سنڀاليندو آهي rdwr() sys2.c ۾مخصوص 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 inode تي (جيڪڏهن پائپ لائن خالي آهي ته اها 0 جي برابر ٿي سگهي ٿي) ڊيٽا جي پڇاڙي کي ظاهر ڪري ٿي جيڪا اڳ ۾ ئي شامل آهي. جيڪڏهن ڪافي رڪارڊنگ جي جڳهه آهي، اسان پائپ لائن ڀري سگهون ٿا i_size1 ڪرڻ PIPESIZ. ان کان پوء اسان تالا کي ڇڏي ڏيو ۽ ڪنهن به عمل کي جاڳائڻ جي ڪوشش ڪئي جيڪا پائپ لائن مان پڙهڻ جي انتظار ۾ آهي. اسان شروعات ۾ واپس وڃون ٿا ته ڏسون ته ڇا اسان لکي سگھون ٿا جيترو بائيٽ اسان کي ضرورت آھي. جيڪڏهن اهو ناڪام ٿئي ٿو، پوء اسان هڪ نئين رڪارڊنگ چڪر شروع ڪندا آهيون.

عام طور تي پيٽرولر i_mode inode اجازتن کي ذخيرو ڪرڻ لاء استعمال ڪيو ويندو آهي 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 تي ري سيٽ ڪيو ۽ ڪوشش ڪريو ڪنهن به عمل کي جاڳائڻ جي جيڪو پائپ لائن تي لکڻ چاهي ٿو. اسان کي خبر آهي ته جڏهن conveyor مڪمل آهي، writep() تي سمهي پوندو ip+1. ۽ هاڻي ته پائپ لائن خالي آهي، اسان ان کي جاڳائي سگھون ٿا ته جيئن ان جي لکڻ واري چڪر کي ٻيهر شروع ڪيو وڃي.

جيڪڏهن توهان وٽ پڙهڻ لاء ڪجهه ناهي، پوء readp() پرچم قائم ڪري سگهي ٿو IREAD ۽ سمهڻ ip+2. اسان ڄاڻون ٿا ته هن کي ڇا جاڳندو writep()، جڏهن اهو پائپ لائن ڏانهن ڪجهه ڊيٽا لکي ٿو.

ڏانهن تبصرو readi() and writei() توھان کي سمجھڻ ۾ مدد ڏيندو ته پيٽرول پاس ڪرڻ بدران "u"اسان انھن کي عام I/O افعال وانگر علاج ڪري سگھون ٿا جيڪي ھڪڙي فائل، ھڪڙي پوزيشن، ميموري ۾ بفر، ۽ پڙھڻ يا لکڻ لاء بائيٽ جو تعداد شمار ڪن ٿا.

/*
 * 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)، جيڪو تعطل ڏانهن وٺي وڃي ٿو if readp() اڃا تائين منهنجو بلاڪ نه هٽايو آهي، تنهنڪري ڪنهن به طرح ڪوڊ صحيح ڪم ڪرڻ گهرجي. جيڪڏهن توهان ڏسندا wakeup()، پوءِ اهو واضح ٿئي ٿو ته اهو صرف سمهڻ واري عمل کي عمل ڪرڻ لاءِ تيار طور نشانو بڻائيندو آهي ، انهي ڪري ته مستقبل ۾ sched() واقعي ان کي شروع ڪيو. سو readp() سبب wakeup()، تالا هٽائي ٿو، سيٽ IREAD ۽ ڪالون sleep(ip+2)- اهو سڀ ڪجهه اڳ writep() سائيڪل کي ٻيهر شروع ڪري ٿو.

هي ڇهين ايڊيشن ۾ conveyors جي وضاحت مڪمل ڪري ٿو. سادي ڪوڊ، دور رس نتيجا.

يونڪس جو ستين ايڊيشن (جنوري 1979) هڪ نئين وڏي رليز هئي (چار سال بعد) جنهن ۾ ڪيتريون ئي نيون ايپليڪيشنون ۽ ڪنيل خاصيتون متعارف ڪرايون ويون. ان ۾ ٽائيپ ڪاسٽنگ، يونينز ۽ ٽائپ ٿيل پوائنٽرز جي استعمال جي سلسلي ۾ پڻ اهم تبديليون آيون. بهرحال conveyor ڪوڊ عملي طور تي اڻڄاتل. اسان ھن ايڊيشن کي ڇڏي سگھون ٿا.

Xv6، ھڪڙو سادو يونڪس جھڙو دانا

ڪنيل ٺاهڻ لاءِ Xv6 يونڪس جي ڇهين ايڊيشن کان متاثر ٿيو، پر اهو x86 پروسيسرز تي هلائڻ لاءِ جديد سي ۾ لکيل آهي. ڪوڊ پڙهڻ ۽ سمجھڻ ۾ آسان آهي. ان سان گڏ، TUHS سان يونڪس ذريعن جي برعڪس، توهان ان کي گڏ ڪري سگهو ٿا، ان کي تبديل ڪري سگهو ٿا، ۽ ان کي هلائي سگهو ٿا PDP 11/70 کان سواءِ ڪنهن ٻئي تي. تنهن ڪري، هي ڪرنل وڏي پيماني تي يونيورسٽين ۾ تعليمي مواد طور آپريٽنگ سسٽم تي استعمال ڪيو ويندو آهي. ذريعا Github تي آهن.

ڪوڊ هڪ واضح ۽ سوچيل سمجهيل عمل تي مشتمل آهي پائپ سي، ڊسڪ تي انوڊ جي بدران ميموري ۾ بفر جي مدد سان. هتي مان صرف "ساختي پائپ لائن" جي تعريف ۽ فنڪشن مهيا ڪريان ٿو 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(). حقيقي سسٽم ڪال sys_pipe ۾ لاڳو ٿيل هڪ چادر آهي sysfile.c. مان هن جي پوري ڪوڊ پڙهڻ جي صلاح ڪريان ٿو. پيچيدگي ڇهين ايڊيشن جي سورس ڪوڊ جي سطح تي آهي، پر اهو پڙهڻ لاء تمام آسان ۽ وڌيڪ مزو آهي.

لينڪس 0.01

لينڪس 0.01 سورس ڪوڊ ڳولي سگھجي ٿو. هن ۾ پائيپ لائينن جي عمل درآمد جو مطالعو ڪرڻ لاءِ سبق آموز هوندو fs/pipe.c. اهو پائپ لائن جي نمائندگي ڪرڻ لاءِ هڪ انوڊ استعمال ڪري ٿو، پر پائپ لائن پاڻ جديد C ۾ لکيل آهي. جيڪڏهن توهان 6هين ايڊيشن ڪوڊ ذريعي ڪم ڪيو آهي، ته توهان کي هتي ڪا به تڪليف نه ٿيندي. اھو اھو آھي جيڪو فنڪشن وانگر ڏسڻ ۾ اچي ٿو 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 ايترو اجنبي نه ٿو لڳي.

جديد لينڪس ڪنيلز، فري بي ايس ڊي، نيٽ بي ايس ڊي، اوپن بي ايس ڊي

مون تڪڙو تڪڙو ڪجهه جديد ڪنلن ذريعي ڀڄي ويو. انهن مان ڪو به ڊسڪ تي عمل درآمد نه آهي (عجب جي ڳالهه ناهي). لينڪس ان جو پنهنجو عمل آهي. جيتوڻيڪ ٽي جديد BSD ڪنيلز ڪوڊ جي بنياد تي لاڳو ٿين ٿا جيڪي جان ڊيسن پاران لکيل هئا، ڪيترن سالن کان اهي هڪ ٻئي کان بلڪل مختلف ٿي چڪا آهن.

پڙهڻ fs/pipe.c (لينڪس تي) يا sys/kern/sys_pipe.c (*BSD تي)، اهو حقيقي وقف وٺندو آهي. اڄ جو ڪوڊ ڪارڪردگي ۽ سپورٽ جي باري ۾ آهي خاصيتن لاءِ جيئن ته ویکٹر ۽ هم وقت ساز I/O. ۽ ميموري مختص ڪرڻ جا تفصيل، لاڪ ۽ ڪنييل ترتيب سڀ مختلف آهن. اهو نه آهي ته ڪاليجن کي هڪ تعارفي آپريٽنگ سسٽم ڪورس جي ضرورت آهي.

بهرحال، مون کي ڪجهه پراڻن نمونن (جهڙوڪ پيدا ڪرڻ SIGPIPE ۽ واپسي EPIPE جڏهن هڪ بند پائپ لائن ڏانهن لکندو آهي) انهن سڀني مختلف جديد ڪنلن ۾. مان شايد ڪڏهن به PDP-11 ڪمپيوٽر کي حقيقي زندگي ۾ نه ڏسندس، پر ڪوڊ مان سکڻ لاءِ اڃا گهڻو ڪجهه آهي جيڪو منهنجي ڄمڻ کان سال اڳ لکيو ويو هو.

ديوي ڪپور پاران 2011 ۾ لکيل هڪ مضمون:لينڪس ڪنييل پائپس ۽ FIFOs جو نفاذلينڪس ۾ پائپ لائنز (اڃا تائين) ڪم ڪرڻ جو هڪ جائزو پيش ڪري ٿو. اي لينڪس ۾ تازو انجام رابطي جي پائپ لائن ماڊل کي بيان ڪري ٿو، جن جون صلاحيتون عارضي فائلن کان وڌيڪ آهن؛ ۽ اهو پڻ ڏيکاري ٿو ته پائپ لائنون ڇهين ايڊيشن يونڪس ڪنيل جي ”ڏاڍي قدامت پسند لاڪنگ“ کان ڪيتري پري آيون آهن.

جو ذريعو: www.habr.com

تبصرو شامل ڪريو