یونکس میں پائپ لائنوں کو کیسے لاگو کیا جاتا ہے۔

یونکس میں پائپ لائنوں کو کیسے لاگو کیا جاتا ہے۔
یہ مضمون یونکس کرنل میں پائپ لائنوں کے نفاذ کی وضاحت کرتا ہے۔ مجھے کچھ مایوسی ہوئی کہ ایک حالیہ مضمون جس کا عنوان تھا "یونکس میں پائپ لائنز کیسے کام کرتی ہیں؟" باہر کر دیا کوئی اندرونی ساخت کے بارے میں میں نے تجسس پیدا کیا اور جواب تلاش کرنے کے لیے پرانے ذرائع کو کھود لیا۔

اس کے بارے میں کیا ہے؟

پائپ لائنز "شاید یونکس میں سب سے اہم ایجاد ہیں" - چھوٹے پروگراموں کو اکٹھا کرنے کے یونکس کے بنیادی فلسفے کی ایک واضح خصوصیت، اور مانوس کمانڈ لائن نعرہ:

$ 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 کو dup4 کے ساتھ "نام بدلتا ہے"۔

پائپ لائنوں کے بغیر، شیل کو فائل میں ایک پروسیس کا آؤٹ پٹ لکھنا ہوگا اور فائل سے ڈیٹا پڑھنے کے لیے اسے دوسرے پروسیس میں پائپ کرنا ہوگا۔ نتیجے کے طور پر، ہم مزید وسائل اور ڈسک کی جگہ ضائع کریں گے۔ تاہم، پائپ لائنز عارضی فائلوں سے گریز کرنے سے زیادہ کے لیے اچھی ہیں:

اگر کوئی عمل خالی پائپ لائن سے پڑھنے کی کوشش کرتا ہے، تو read(2) ڈیٹا دستیاب ہونے تک بلاک رہے گا۔ اگر کوئی عمل مکمل پائپ لائن پر لکھنے کی کوشش کرتا ہے، تو write(2) لکھنے کو مکمل کرنے کے لیے پائپ لائن سے کافی ڈیٹا پڑھے جانے تک بلاک رہے گا۔

POSIX کی ضرورت کی طرح، یہ ایک اہم خاصیت ہے: پائپ لائن تک لکھنا PIPE_BUF بائٹس (کم از کم 512) جوہری ہونا ضروری ہے تاکہ عمل پائپ لائن کے ذریعے ایک دوسرے کے ساتھ اس طرح بات چیت کرسکیں کہ عام فائلیں (جو اس طرح کی ضمانتیں فراہم نہیں کرتی ہیں) نہیں کرسکتی ہیں۔

ایک باقاعدہ فائل کے ساتھ، ایک عمل اپنی تمام پیداوار کو اس پر لکھ سکتا ہے اور اسے دوسرے عمل میں منتقل کر سکتا ہے۔ یا عمل ایک دوسرے کو لکھنے یا پڑھنے کی تکمیل کے بارے میں مطلع کرنے کے لیے ایک بیرونی سگنلنگ میکانزم (جیسے سیمفور) کا استعمال کرتے ہوئے سخت متوازی موڈ میں کام کر سکتے ہیں۔ کنویرز ہمیں اس تمام پریشانی سے بچاتے ہیں۔

ہم کیا ڈھونڈ رہے ہیں؟

میں اپنی انگلیوں پر وضاحت کروں گا تاکہ آپ کے لیے یہ تصور کرنا آسان ہو جائے کہ کنویئر کیسے کام کر سکتا ہے۔ آپ کو میموری میں بفر اور کچھ ریاست مختص کرنے کی ضرورت ہوگی۔ آپ کو بفر سے ڈیٹا شامل کرنے اور ہٹانے کے لیے فنکشنز کی ضرورت ہوگی۔ آپ کو فائل ڈسکرپٹرز پر ریڈ اینڈ رائٹ آپریشنز کے دوران فنکشنز کو کال کرنے کے لیے کچھ سہولت درکار ہوگی۔ اور اوپر بیان کردہ خصوصی رویے کو نافذ کرنے کے لیے تالے کی ضرورت ہے۔

اب ہم اپنے مبہم ذہنی ماڈل کی تصدیق یا غلط ثابت کرنے کے لیے روشن چراغ کی روشنی میں کرنل کے سورس کوڈ سے پوچھ گچھ کرنے کے لیے تیار ہیں۔ لیکن ہمیشہ غیر متوقع کے لیے تیار رہیں۔

ہم کہاں دیکھ رہے ہیں؟

مجھے نہیں معلوم کہ میری مشہور کتاب کی کاپی کہاں ہے۔شیروں کی کتابیونکس 6 سورس کوڈ کے ساتھ، لیکن شکریہ یونکس ہیریٹیج سوسائٹی آن لائن تلاش کیا جا سکتا ہے سورس کوڈ یہاں تک کہ یونکس کے پرانے ورژن۔

TUHS آرکائیوز میں گھومنا ایک میوزیم کا دورہ کرنے کے مترادف ہے۔ ہم اپنی مشترکہ تاریخ کو دیکھ سکتے ہیں اور مجھے پرانی کیسٹس اور پرنٹ آؤٹس سے تھوڑا تھوڑا کرکے اس تمام مواد کو بازیافت کرنے کی برسوں کی کوششوں کا احترام ہے۔ اور میں ان ٹکڑوں سے بخوبی واقف ہوں جو ابھی تک غائب ہیں۔

پائپ لائنوں کی قدیم تاریخ کے بارے میں اپنے تجسس کو پورا کرنے کے بعد، ہم موازنہ کے لیے جدید کور کو دیکھ سکتے ہیں۔

ویسے، pipe ٹیبل میں سسٹم کال نمبر 42 ہے۔ sysent[]. اتفاق؟

روایتی یونکس دانا (1970–1974)

مجھے کوئی سراغ نہیں ملا pipe(2) نہ ہی میں PDP-7 یونکس (جنوری 1970)، اور نہ ہی میں پہلا ایڈیشن یونکس (نومبر 1971)، اور نہ ہی نامکمل سورس کوڈ میں دوسرا ایڈیشن (جون 1972)۔

TUHS کا دعویٰ ہے۔ تیسرا ایڈیشن یونکس (فروری 1973) پائپ لائنوں کے ساتھ پہلا ورژن تھا:

یونکس کا تیسرا ایڈیشن آخری ورژن تھا جس میں اسمبلر میں لکھا ہوا دانا تھا، لیکن پائپ لائنوں کے ساتھ پہلا ورژن بھی تھا۔ 1973 کے دوران، تیسرے ایڈیشن کو بہتر بنانے کے لیے کام جاری تھا، کرنل کو C میں دوبارہ لکھا گیا، اور اس طرح یونکس کے چوتھے ایڈیشن نے جنم لیا۔

ایک قارئین کو ایک دستاویز کا اسکین ملا جس میں ڈوگ میک ایلروئے نے "باغ کی نلی کی طرح پروگراموں کو جوڑنے" کا خیال پیش کیا۔

یونکس میں پائپ لائنوں کو کیسے لاگو کیا جاتا ہے۔
برائن کرنیگھن کی کتاب میںیونکس: ایک تاریخ اور ایک یادداشت"، کنویرز کی ظاہری شکل کی تاریخ میں بھی اس دستاویز کا ذکر ہے: "... یہ 30 سال سے بیل لیبز میں میرے دفتر کی دیوار پر لٹکا ہوا تھا۔" یہاں McIlroy کے ساتھ انٹرویواور سے ایک اور کہانی McIlroy کا کام، 2014 میں لکھا گیا۔:

جب یونکس نمودار ہوا، کوروٹائنز کے لیے میرے شوق نے مجھے OS کے مصنف، کین تھامسن سے یہ کہنے پر مجبور کیا کہ وہ کچھ پروسیس پر لکھے گئے ڈیٹا کو نہ صرف ڈیوائس پر جانے کی اجازت دے، بلکہ دوسرے پروسیس سے باہر جانے کے لیے بھی۔ کین نے سوچا کہ یہ ممکن ہے۔ تاہم، ایک مرصع کے طور پر، وہ چاہتا تھا کہ سسٹم کی ہر خصوصیت ایک اہم کردار ادا کرے۔ کیا عمل کے درمیان براہ راست لکھنا واقعی انٹرمیڈیٹ فائل پر لکھنے سے بڑا فائدہ ہے؟ اور صرف اس وقت جب میں نے دلکش نام "پائپ لائن" اور عمل کے تعامل کے نحو کی وضاحت کے ساتھ ایک مخصوص تجویز پیش کی، کین نے آخر میں کہا: "میں یہ کروں گا!"۔

اور کیا۔ ایک خوفناک شام، کین نے کرنل اور شیل کو تبدیل کیا، کئی معیاری پروگراموں کو معیاری بنانے کے لیے طے کیا کہ وہ ان پٹ کو کیسے قبول کرتے ہیں (جو کہ پائپ لائن سے آتے ہیں)، اور فائل کے نام تبدیل کر دیے۔ اگلے دن، پائپ لائنوں کو ایپلی کیشنز میں بہت وسیع پیمانے پر استعمال کیا گیا تھا. ہفتے کے آخر تک، سیکرٹریوں نے انہیں ورڈ پروسیسرز سے پرنٹر پر دستاویزات بھیجنے کے لیے استعمال کیا۔ کچھ دیر بعد، کین نے پائپ لائنوں کے استعمال کو کلینر کنونشنوں کے ساتھ لپیٹنے کے لیے اصل API اور نحو کو بدل دیا جو تب سے استعمال ہو رہے ہیں۔

بدقسمتی سے، تیسرے ایڈیشن یونکس کرنل کا سورس کوڈ کھو گیا ہے۔ اور اگرچہ ہمارے پاس کرنل سورس کوڈ C میں لکھا ہوا ہے۔ چوتھا ایڈیشن، جو نومبر 1973 میں جاری کیا گیا تھا ، لیکن یہ سرکاری ریلیز سے چند ماہ قبل سامنے آیا تھا اور اس میں پائپ لائنوں کے نفاذ پر مشتمل نہیں ہے۔ یہ افسوس کی بات ہے کہ اس افسانوی یونکس فیچر کا سورس کوڈ شاید ہمیشہ کے لیے کھو گیا ہے۔

ہمارے پاس دستاویزات کا متن ہے۔ pipe(2) دونوں ریلیز سے، لہذا آپ دستاویزات کو تلاش کرکے شروع کر سکتے ہیں۔ تیسری اشاعت (کچھ الفاظ کے لیے، "دستی طور پر" انڈر لائن کیا جاتا ہے، ^H لٹریلز کی ایک تار جس کے بعد انڈر سکور ہوتا ہے!) یہ پروٹو-pipe(2) اسمبلر میں لکھا گیا ہے اور صرف ایک فائل ڈسکرپٹر واپس کرتا ہے، لیکن پہلے سے ہی متوقع بنیادی فعالیت فراہم کرتا ہے:

سسٹم کال۔ پائپ ایک I/O میکانزم بناتا ہے جسے پائپ لائن کہتے ہیں۔ واپس آنے والی فائل ڈسکرپٹر کو پڑھنے اور لکھنے کی کارروائیوں کے لیے استعمال کیا جا سکتا ہے۔ جب پائپ لائن پر کچھ لکھا جاتا ہے، تو یہ 504 بائٹس تک ڈیٹا بفر کرتا ہے، جس کے بعد لکھنے کا عمل معطل ہو جاتا ہے۔ پائپ لائن سے پڑھتے وقت، بفرڈ ڈیٹا لیا جاتا ہے۔

اگلے سال تک، دانا کو C میں دوبارہ لکھا گیا تھا، اور پائپ (2) چوتھا ایڈیشن پروٹو ٹائپ کے ساتھ اپنی جدید شکل حاصل کی"pipe(fildes)"

سسٹم کال۔ پائپ ایک I/O میکانزم بناتا ہے جسے پائپ لائن کہتے ہیں۔ واپس آنے والے فائل ڈسکرپٹرز کو پڑھنے اور لکھنے کے کاموں میں استعمال کیا جا سکتا ہے۔ جب پائپ لائن پر کچھ لکھا جاتا ہے، تو r1 (resp. fildes[1]) میں واپس آنے والا ڈسکرپٹر استعمال کیا جاتا ہے، 4096 بائٹس تک ڈیٹا بفر کیا جاتا ہے، جس کے بعد لکھنے کا عمل معطل ہو جاتا ہے۔ پائپ لائن سے پڑھتے وقت، ڈسکرپٹر r0 پر واپس آتا ہے (resp. fildes[0]) ڈیٹا لیتا ہے۔

یہ فرض کیا جاتا ہے کہ ایک بار پائپ لائن کی وضاحت ہو جانے کے بعد، دو (یا اس سے زیادہ) بات چیت کے عمل (بعد کی درخواستوں کے ذریعے تخلیق کیے گئے) کانٹا) کالز کا استعمال کرتے ہوئے پائپ لائن سے ڈیٹا پاس کرے گا۔ پڑھیں и لکھنا.

شیل میں پائپ لائن کے ذریعے جڑے ہوئے عمل کی لکیری صف کی وضاحت کے لیے ایک نحو موجود ہے۔

خالی پائپ لائن سے پڑھنے کے لیے کالز (بفرڈ ڈیٹا پر مشتمل نہیں) جس کا صرف ایک سرہ ہے (تمام لکھنے والے فائل ڈسکرپٹرز بند ہیں) "فائل کا اختتام" لوٹتے ہیں۔ اسی طرح کی صورتحال میں کالیں لکھنا نظر انداز کر دیا جاتا ہے۔

سب سے جلد محفوظ پائپ لائن پر عمل درآمد لاگو ہوتا ہے یونکس کے پانچویں ایڈیشن تک (جون 1974)، لیکن یہ تقریباً ایک جیسی ہے جو اگلی ریلیز میں شائع ہوئی۔ صرف تبصرے شامل کیے، اس لیے پانچویں ایڈیشن کو چھوڑا جا سکتا ہے۔

یونکس چھٹا ایڈیشن (1975)

یونکس سورس کوڈ پڑھنا شروع کر رہا ہوں۔ چھٹا ایڈیشن (مئی 1975)۔ کا بڑی حد تک شکریہ لائنز پچھلے ورژن کے ذرائع کے مقابلے میں تلاش کرنا بہت آسان ہے:

کئی سالوں سے کتاب لائنز بیل لیبز کے باہر دستیاب یونکس کرنل پر واحد دستاویز تھی۔ اگرچہ چھٹے ایڈیشن کے لائسنس نے اساتذہ کو اس کا سورس کوڈ استعمال کرنے کی اجازت دی، لیکن ساتویں ایڈیشن کے لائسنس نے اس امکان کو خارج کر دیا، اس لیے کتاب کو غیر قانونی ٹائپ رائٹ کاپیوں میں تقسیم کر دیا گیا۔

آج آپ کتاب کی دوبارہ پرنٹ کاپی خرید سکتے ہیں، جس کا سرورق کاپیئر پر طلباء کو دکھاتا ہے۔ اور وارن ٹومی (جس نے TUHS پروجیکٹ شروع کیا) کا شکریہ، آپ ڈاؤن لوڈ کر سکتے ہیں۔ چھٹا ایڈیشن ماخذ پی ڈی ایف. میں آپ کو اندازہ دینا چاہتا ہوں کہ فائل بنانے میں کتنی محنت کی گئی:

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 فائلوں کا تعلق ہے، وہ اس کے مطابق ہیں۔ inode-flag 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 ہو جائے گا)، اور انوڈ کو بطور ترمیم شدہ اور استعمال میں نشان زد کریں گے۔ کی درخواستوں پر توجہ دیں۔ گھماؤ () نئے انوڈ میں حوالہ شمار کو کم کرنے کے لیے غلطی کے راستوں میں۔

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'a (خالی پائپ لائن کے ساتھ 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 پر دوبارہ ترتیب دیتے ہیں اور کسی بھی عمل کو بیدار کرنے کی کوشش کرتے ہیں جو پائپ لائن پر لکھنا چاہتا ہے۔ ہم جانتے ہیں کہ جب کنویئر بھر جاتا ہے، writep() سو جاؤ ip+1. اور اب جب کہ پائپ لائن خالی ہے، ہم اس کے لکھنے کے چکر کو دوبارہ شروع کرنے کے لیے اسے جگا سکتے ہیں۔

اگر پڑھنے کو کچھ نہ ہو تو readp() جھنڈا لگا سکتے ہیں۔ IREAD اور سو جاؤ ip+2. ہم جانتے ہیں کہ اسے کیا بیدار کرے گا۔ writep()جب یہ پائپ لائن پر کچھ ڈیٹا لکھتا ہے۔

پر تبصرے پڑھیں () اور لکھیں () آپ کو یہ سمجھنے میں مدد ملے گی کہ پیرامیٹرز سے گزرنے کے بجائے "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)جس کے نتیجے میں تعطل پیدا ہوتا ہے اگر readp() نے ابھی تک اپنا بلاک نہیں ہٹایا ہے، لہذا کوڈ کو کسی نہ کسی طرح صحیح طریقے سے کام کرنا چاہیے۔ اگر آپ دیکھیں wakeup()، یہ واضح ہو جاتا ہے کہ یہ صرف سونے کے عمل کو عملدرآمد کے لیے تیار کے طور پر نشان زد کرتا ہے، تاکہ مستقبل میں sched() واقعی اسے شروع کیا. تو readp() اسباب wakeup()، کھولتا ہے، سیٹ کرتا ہے۔ IREAD اور کالز sleep(ip+2)- یہ سب پہلے writep() سائیکل کو دوبارہ شروع کرتا ہے.

یہ چھٹے ایڈیشن میں پائپ لائنوں کی تفصیل کو مکمل کرتا ہے۔ سادہ کوڈ، دور رس اثرات۔

ساتواں ایڈیشن یونکس (جنوری 1979) ایک نئی بڑی ریلیز تھی (چار سال بعد) جس نے بہت سی نئی ایپلی کیشنز اور کرنل کی خصوصیات متعارف کروائیں۔ اس میں ٹائپ کاسٹنگ، یونینز اور ڈھانچے کے ٹائپ پوائنٹرز کے استعمال کے سلسلے میں بھی نمایاں تبدیلیاں آئی ہیں۔ البتہ پائپ لائنز کوڈ عملی طور پر تبدیل نہیں ہوا. ہم اس ایڈیشن کو چھوڑ سکتے ہیں۔

Xv6، ایک سادہ یونکس جیسا دانا

ایک کور بنانے کے لیے Xv6 یونکس کے چھٹے ایڈیشن سے متاثر، لیکن x86 پروسیسرز پر چلنے کے لیے جدید C میں لکھا گیا۔ کوڈ پڑھنے میں آسان اور قابل فہم ہے۔ نیز، 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. یہاں، پائپ لائن کی نمائندگی کرنے کے لیے ایک inode استعمال کیا جاتا ہے، لیکن پائپ لائن خود جدید 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 اتنا اجنبی نہیں لگتا

جدید لینکس کرنل، فری بی ایس ڈی، نیٹ بی ایس ڈی، اوپن بی ایس ڈی

میں نے جلدی سے کچھ جدید دانا کو عبور کیا۔ ان میں سے کسی کے پاس پہلے سے ہی ڈسک پر مبنی عمل درآمد نہیں ہے (حیرت کی بات نہیں)۔ لینکس کا اپنا نفاذ ہے۔ اور اگرچہ تین جدید بی ایس ڈی کرنل جان ڈائیسن کے لکھے ہوئے کوڈ پر مبنی نفاذات پر مشتمل ہیں، لیکن برسوں کے دوران وہ ایک دوسرے سے بہت مختلف ہو گئے ہیں۔

پڑھنے کے لئے fs/pipe.c (لینکس پر) یا sys/kern/sys_pipe.c (*BSD پر)، یہ حقیقی لگن لیتا ہے. ویکٹر اور غیر مطابقت پذیر I/O جیسی خصوصیات کے لیے کارکردگی اور معاونت آج کوڈ میں اہم ہیں۔ اور میموری کی تقسیم، تالے، اور کرنل کنفیگریشن کی تفصیلات بہت مختلف ہوتی ہیں۔ یہ وہ چیز نہیں ہے جس کی یونیورسٹیوں کو آپریٹنگ سسٹم پر تعارفی کورس کی ضرورت ہے۔

کسی بھی صورت میں، میرے لیے کچھ پرانے نمونوں کا پتہ لگانا دلچسپ تھا (مثال کے طور پر، پیدا کرنا SIGPIPE اور واپس EPIPE بند پائپ لائن پر لکھتے وقت) ان سب میں، بہت مختلف، جدید دانا۔ میں شاید کبھی بھی PDP-11 کمپیوٹر لائیو نہیں دیکھوں گا، لیکن اس کوڈ سے سیکھنے کے لیے ابھی بھی بہت کچھ ہے جو میری پیدائش سے چند سال پہلے لکھا گیا تھا۔

دیوی کپور نے 2011 میں لکھا، مضمون "پائپس اور FIFOs کے لینکس کرنل کا نفاذلینکس پائپ لائنز (اب تک) کیسے کام کرتی ہیں اس کا ایک جائزہ ہے۔ اے لینکس پر حالیہ کمٹ تعامل کے پائپ لائن ماڈل کی وضاحت کرتا ہے، جس کی صلاحیتیں عارضی فائلوں سے زیادہ ہیں۔ اور یہ بھی دکھاتا ہے کہ چھٹے ایڈیشن یونکس کرنل میں پائپ لائنز "انتہائی قدامت پسند لاکنگ" سے کتنی دور گئی ہیں۔

ماخذ: www.habr.com

نیا تبصرہ شامل کریں