څنګه په یونیکس کې پایپ لاینونه پلي کیږي

څنګه په یونیکس کې پایپ لاینونه پلي کیږي
دا مقاله د یونیکس کرنل کې د پایپ لاین پلي کول تشریح کوي. زه یو څه مایوسه وم چې یوه وروستۍ مقاله چې عنوان یې دی "په یونیکس کې پایپ لاینونه څنګه کار کوي؟"پاتې شو نه د داخلي جوړښت په اړه. زه لیواله شوم او د ځواب موندلو لپاره زړو سرچینو ته لاړم.

موږ د څه په اړه خبرې کوو؟

پایپ لاینونه، "شاید په یونیکس کې ترټولو مهم اختراع" د وړو برنامو سره د یوځای کولو لپاره د یونکس د اصلي فلسفې یوه مشخصه ځانګړتیا ده، په بیله بیا د کمانډ لاین کې یو پیژندل شوی نښه:

$ 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)، او نه هم په نامکمل سرچینې کوډ کې دویمه نسخه (جون ۱۹۷۲).

TUHS وايي د یونیکس دریمه نسخه (فبروري 1973) د لیږدونکو سره لومړۍ نسخه شوه:

د یونیکس دریمه نسخه وروستۍ نسخه وه چې د اسمبلۍ په ژبه لیکل شوې کرنل سره ، مګر د پایپ لاینونو سره لومړۍ نسخه هم وه. د 1973 په جریان کې، د دریمې نسخې د ښه کولو لپاره کار ترسره شو، کرنل په C کې بیا لیکل شوی و، او په دې توګه د یونکس څلورم نسخه ښکاره شوه.

یو لوستونکي د یو سند سکین وموند چې په کې ډګ مکیلروي د "د باغ نلی په څیر د برنامو سره وصل" نظر وړاندیز کړی.

څنګه په یونیکس کې پایپ لاینونه پلي کیږي
د براین کیرنیګن په کتاب کېیونیکس: یو تاریخ او یادونه"، د لیږدونکو د راڅرګندیدو په تاریخ کې، دا سند هم ذکر شوی: "... دا د 30 کلونو لپاره په بیل لابراتوار کې زما د دفتر په دیوال کې ځړول شوی و." دلته له McIlroy سره مرکه، او بله کیسه له دې څخه د McIlroy کار، په 2014 کې لیکل شوی:

کله چې یونیکس راووت ، د کورټینونو سره زما علاقه د دې لامل شوه چې د OS لیکوال ، کین تامپسن څخه وپوښتم ، ترڅو یوې پروسې ته لیکل شوي ډیټا اجازه ورکړي چې نه یوازې وسیلې ته لاړ شي ، بلکه بلې پروسې ته د تولید لپاره هم. کین پریکړه وکړه چې دا ممکنه وه. په هرصورت، د لږترلږه په توګه، هغه غوښتل چې د هر سیسټم فعالیت یو مهم رول ولوبوي. ایا مستقیم د پروسو ترمینځ لیکل واقعیا د مینځنۍ فایل لیکلو په پرتله لویه ګټه ده؟ دا یوازې هغه وخت و چې ما د زړه راښکونکي نوم "پائپ لاین" سره یو ځانګړی وړاندیز وکړ او د پروسو ترمینځ د متقابل عمل لپاره د ترکیب توضیحات چې کین په پای کې وویل: "زه به یې وکړم!"

او وکړل. یو بدمرغه ماښام، کین کرنل او شیل بدل کړ، ډیری معیاري پروګرامونه یې تنظیم کړل ترڅو معیاري کړي چې څنګه دوی ان پټ (کوم چې د پایپ لاین څخه راځي)، او د فایل نومونه هم بدل کړي. بله ورځ، پایپ لاینونه په غوښتنلیکونو کې په پراخه کچه کارول پیل شول. د اونۍ په پای کې، سکرترانو دوی د کلمې پروسس کونکو څخه پرنټر ته د اسنادو لیږلو لپاره کارول. یو څه وروسته ، کین د کلینر کنوانسیونونو سره د پایپ لاینونو کارولو لپاس کولو لپاره اصلي API او ترکیب بدل کړ ، کوم چې له هغه وخت راهیسې کارول کیږي.

له بده مرغه، د دریمې نسخې یونیکس کرنل لپاره د سرچینې کوډ ورک شوی. او که څه هم موږ د کرنل سرچینې کوډ لرو چې په C کې لیکل شوی څلورمه نسخه، د نومبر په 1973 کې خپور شو ، مګر دا د رسمي خوشې کیدو څخه څو میاشتې دمخه راپورته شو او د پایپ لاین پلي کول پکې شامل ندي. دا د شرم ځای دی چې د دې افسانوي یونیکس فعالیت لپاره د سرچینې کوډ ورک شوی، شاید د تل لپاره.

موږ لپاره متن اسناد لرو pipe(2) د دواړو خپرونو څخه، نو تاسو کولی شئ د اسنادو لټون پیل کړئ دریمه نسخه (د ځینو کلمو لپاره، "په لاسي ډول" لاندې لیکل شوي، د لغوي تورو ^H، وروسته یو انډر سکور!). دا پروتو-pipe(2) د اسمبلۍ په ژبه لیکل شوی او یوازې یو فایل توضیح کونکی بیرته راګرځوي، مګر دمخه د متوقع لومړني فعالیت چمتو کوي:

د سیسټم زنګ پائپ د پایپ لاین په نوم د ننوتلو/آؤټ پټ میکانیزم رامینځته کوي. د بیرته راګرځیدونکي فایل تشریح کونکی د لوستلو او لیکلو عملیاتو لپاره کارول کیدی شي. کله چې پایپ لاین ته یو څه ولیکل شي، تر 504 بایټ پورې ډاټا بفر کیږي، وروسته له دې چې د لیکلو پروسه وځنډول شي. کله چې د پایپ لاین څخه لوستل کیږي، بفر شوي ډاټا لیرې کیږي.

په راتلونکي کال کې دانه په C ، او کې بیا لیکل شوې وه پایپ (2) په څلورم چاپ کې خپل عصري بڼه د پروټوټایپ سره ترلاسه کړه "pipe(fildes)»:

د سیسټم زنګ پائپ د پایپ لاین په نوم د ننوتلو/آؤټ پټ میکانیزم رامینځته کوي. د بیرته راګرځیدونکي فایل تشریح کونکي د لوستلو او لیکلو عملیاتو کې کارول کیدی شي. کله چې پایپ لاین ته یو څه ولیکل شي، په r1 (resp. fildes[1]) کې بیرته راستانه شوي لاستی کارول کیږي، د معلوماتو 4096 بایټ ته بفر شوي، وروسته له دې چې د لیکلو پروسه وځنډول شي. کله چې د پایپ لاین څخه لوستل کیږي، لاستی r0 ته بیرته راستانه شوي (resp. fildes[0]) ډاټا اخلي.

داسې انګیرل کیږي چې یوځل چې پایپ لاین تعریف شي ، دوه (یا ډیر) د مخابراتو پروسې (د راتلونکو تلیفونونو لخوا رامینځته کیږي. رنګ) به د تلیفونونو په کارولو سره د پایپ لاین څخه ډیټا لیږدوي پاتې برخه и ولیکي.

شیل د پایپ لاین لخوا وصل شوي پروسو د خطي لړۍ تعریف کولو لپاره ترکیب لري.

د خالي پایپ لاین څخه د لوستلو لپاره زنګونه (بفر شوي ډیټا نلري) چې یوازې یو پای لري (د ټولو لیکلو فایل توضیح کونکي تړل شوي) د "د فایل پای" بیرته راګرځي. په ورته حالت کې د لیکلو غوښتنې له پامه غورځول کیږي.

تر ټولو مخکې ساتل شوي پایپ لاین پلي کول تطبیق کیږي د یونیکس پنځم نسخه ته (جون 1974)، مګر دا تقریبا ورته ورته دی چې په راتلونکی ریلیز کې راڅرګند شو. تبصرې یوازې اضافه شوي ، نو تاسو کولی شئ پنځم نسخه پریږدئ.

د یونیکس شپږمه نسخه (1975)

راځئ چې د یونیکس سرچینې کوډ لوستل پیل کړو شپږم نسخه (مۍ ۱۹۷۵). په لویه کچه مننه زمريان د پخوانیو نسخو سرچینو په پرتله موندل خورا اسانه دي:

د ډیرو کلونو لپاره کتاب زمريان د بیل لابراتوار څخه بهر د یونیکس کرنل یوازینی سند شتون درلود. که څه هم د شپږم نسخې جواز ښوونکو ته اجازه ورکړه چې د خپل ماخذ کوډ وکاروي، د اوومې نسخې جواز دا امکان لرې کړ، نو کتاب د غیرقانوني ډول لیکل شوي کاپي په بڼه وویشل شو.

نن ورځ تاسو کولی شئ د کتاب بیا چاپ واخلئ، چې پوښ یې د کاپي ماشین کې زده کونکي ښیې. او د وارن تومي څخه مننه (چې د TUHS پروژه یې پیل کړې) تاسو کولی شئ ډاونلوډ کړئ د شپږمې نسخې لپاره د سرچینې کوډ سره PDF فایل. زه غواړم تاسو ته یو نظر درکړم چې د فایل په جوړولو کې څومره هڅې شوي:

له 15 څخه ډیر کاله دمخه ، ما د ورکړل شوي سرچینې کوډ یوه کاپي ټایپ کړه زمريانځکه چې ما د خپل کاپي کیفیت د نامعلوم شمیر نورو کاپيونو څخه خوښ نه کړ. TUHS تراوسه شتون نه درلود او ما پخوانیو سرچینو ته لاسرسی نه درلود. مګر په 1988 کې، ما یو زوړ 9-ټریک ټیپ وموند چې د PDP11 کمپیوټر څخه بیک اپ لري. دا ستونزمنه وه چې ووایاست چې دا کار کوي که نه، مګر دلته یو باثباته /usr/src/ ونه وه چې ډیری فایلونه یې د 1979 کال سره لیبل شوي وو، کوم چې حتی لرغونی ښکاري. دا اوومه نسخه وه یا د هغې مشتق PWB، لکه څنګه چې ما باور درلود.

ما موندنه د اساس په توګه واخیسته او په لاسي ډول ما سرچینې شپږم نسخه ته ایډیټ کړه. ځینې ​​​​کوډونه ورته پاتې وو، مګر ځینې یې باید لږ څه ترمیم شي، عصري += زوړ =+ ته بدل کړي. ځینې ​​​​شیان په ساده ډول حذف شوي ، او ځینې باید په بشپړ ډول بیا لیکل شوي وي ، مګر ډیر نه.

او نن موږ کولی شو په TUHS کې د شپږمې نسخې سرچینې کوډ آنلاین ولولئ آرشیف، کوم چې ډینس ریچي لاس درلود.

د لارې په توګه، په لومړي نظر کې، د کارنیغان او ریچي دورې دمخه د C-code اصلي ځانګړتیا دا ده. لنډیز. دا اکثرا نه وي چې زه د دې توان لرم چې پرته له پراخ ترمیم پرته د کوډ ټوټې داخل کړم ترڅو زما په سایټ کې د نسبتا تنګ نندارې ساحې فټ کړي.

په پیل کې /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;
}

تبصره په واضح ډول تشریح کوي چې دلته څه روان دي. مګر د کوډ پوهیدل دومره اسانه ندي ، یو څه د لارې له امله "struct کارن u» او راجستر کوي 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 کې (که پایپ لاین خالي وي دا د 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 په inode کې. موږ موقعیت 0 ته بیا تنظیم کوو او هڅه کوو هر هغه پروسه راپورته کړو چې غواړي پایپ لاین ته ولیکي. موږ پوهیږو کله چې لیږدونکی ډک وي، writep() په خوب ویده شي ip+1. او اوس چې پایپ لاین خالي دی ، موږ کولی شو د لیکلو دوره بیا پیلولو لپاره دا راویښ کړو.

که تاسو د لوستلو لپاره هیڅ نه لرئ، نو readp() بیرغ ټاکلی شي IREAD او ویده شه ip+2. موږ پوهیږو چې هغه به څه ویښ کړي writep()، کله چې دا پایپ لاین ته ځینې معلومات لیکي.

په اړه تبصرې readi() او لیکلی() به تاسو سره مرسته وکړي چې پوه شي چې د دې پر ځای چې د پیرامیټونو تیرولو له لارې "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 د یونیکس شپږم نسخه لخوا اغیزمن شوی، مګر دا په عصري C کې لیکل شوی ترڅو د 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 سرچینې کوډ موندل کیدی شي. دا به لارښوونه وي چې په هغه کې د پایپ لاینونو پلي کولو مطالعه وکړي fs/pipe.c. دا د پایپ لاین د نمایندګۍ لپاره انوډ کاروي، مګر پایپ لاین پخپله په عصري C کې لیکل شوی. که تاسو د XNUMXth نسخه کوډ له لارې کار کړی وي، تاسو به دلته کومه ستونزه ونه لرئ. دا هغه څه دي چې فعالیت ورته ښکاري 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

زه په چټکۍ سره د ځینو عصري کرنلونو له لارې ورغلم. هیڅ یو یې نور د ډیسک تطبیق نلري (د حیرانتیا خبره نه ده). لینکس خپل تطبیق لري. که څه هم درې عصري BSD دانا د کوډ پراساس پلي کیږي چې د جان ډیسن لخوا لیکل شوي ، د کلونو په اوږدو کې دوی له یو بل څخه ډیر توپیر لري.

لوستل fs/pipe.c (په لینکس کې) یا sys/kern/sys_pipe.c (په *BSD کې) ، دا ریښتیني وقف غواړي. د نن ورځې کوډ د ځانګړتیاوو لپاره د فعالیت او ملاتړ په اړه دی لکه ویکتور او غیر متناسب I/O. او د حافظې تخصیص ، تالاشۍ او د کرنل تشکیلاتو توضیحات ټول خورا توپیر لري. دا هغه څه ندي چې کالجونه د ابتدايي عملیاتي سیسټم کورس لپاره اړتیا لري.

په هرصورت، زه د ځینو پخوانیو نمونو کیندلو سره علاقه لرم (لکه تولید کول SIGPIPE او بیرته راګرځي EPIPE کله چې تړل شوي پایپ لاین ته لیکل کیږي) په دې ټولو مختلف عصري کرنلونو کې. زه به شاید هیڅکله په ریښتیني ژوند کې د PDP-11 کمپیوټر ونه لیدم ، مګر لاهم د کوډ څخه د زده کولو لپاره ډیر څه شتون لري چې زما له زیږیدو څخه کلونه دمخه لیکل شوی و.

یوه مقاله د دیوی کپور لخوا په 2011 کې لیکل شوې:د پایپونو او FIFOs د لینکس کرنل پلي کول"په لینکس کې د پایپ لاینونو (لا هم) کار کولو څرنګوالي یوه عمومي کتنه وړاندې کوي. الف په لینکس کې وروستي ژمنې د متقابل عمل پایپ لاین ماډل روښانه کوي، چې وړتیا یې د لنډمهاله فایلونو څخه زیاته ده؛ او دا هم ښیي چې پایپ لاینونه د شپږم نسخه یونیکس کرنل "خورا محافظه کار تالاشي" څخه څومره لرې راغلي دي.

سرچینه: www.habr.com

Add a comment