Kā cauruļvadi tiek ieviesti Unix

Kā cauruļvadi tiek ieviesti Unix
Å ajā rakstā ir aprakstÄ«ta konveijeru ievieÅ”ana Unix kodolā. Es biju nedaudz vÄ«lies, ka nesen tika publicēts raksts ar nosaukumu "Kā cauruļvadi darbojas sistēmā Unix?"izrādijās nē par iekŔējo struktÅ«ru. Es kļuvu ziņkārÄ«gs un rakņājos vecos avotos, lai atrastu atbildi.

Par ko mēs runājam?

Cauruļvadi, "iespējams, vissvarīgākais Unix izgudrojums", ir galvenā Unix filozofijas iezīme, kas saistīta ar mazu programmu sasaisti, kā arī pazīstama zīme komandrindā:

$ echo hello | wc -c
6

Å Ä« funkcionalitāte ir atkarÄ«ga no kodola nodroÅ”inātā sistēmas izsaukuma pipe, kas ir aprakstÄ«ts dokumentācijas lapās caurule (7) Šø caurule (2):

Cauruļvadi nodroŔina vienvirziena kanālu starpprocesu komunikācijai. Konveijeram ir ieeja (rakstīŔanas beigas) un izvade (lasīŔanas beigas). Datus, kas ierakstīti cauruļvada ieejā, var nolasīt izejā.

Cauruļvads tiek izveidots, izmantojot zvanu pipe(2), kas atgriež divus failu deskriptorus: viens attiecas uz konveijera ievadi, otrs uz izvadi.

IepriekÅ” minētās komandas izsekoÅ”anas izvade parāda cauruļvada izveidi un datu plÅ«smu caur to no viena procesa uz citu:

$ 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

Vecāku procesa zvani pipe()lai iegÅ«tu uzstādÄ«to failu deskriptorus. Viens pakārtotais process raksta vienā rokturā, bet cits process nolasa tos paÅ”us datus no cita roktura. Apvalks izmanto dup2, lai "pārdēvētu" deskriptorus 3 un 4, lai tie atbilstu stdin un stdout.

Bez caurulēm apvalkam bÅ«tu jāieraksta viena procesa rezultāts failā un jānodod citam procesam, lai nolasÄ«tu datus no faila. Rezultātā mēs iztērētu vairāk resursu un diska vietas. Tomēr cauruļvadi ir labi ne tikai tāpēc, ka tie ļauj izvairÄ«ties no pagaidu failu izmantoÅ”anas:

Ja process mēģina nolasÄ«t no tukÅ”a cauruļvada, tad read(2) bloķēs, lÄ«dz dati kļūs pieejami. Ja process mēģina rakstÄ«t pilnā konveijerā, tad write(2) bloķēs, lÄ«dz no konveijera bÅ«s nolasÄ«ts pietiekami daudz datu, lai veiktu rakstÄ«Å”anu.

Tāpat kā POSIX prasÄ«ba, arÄ« Ŕī ir svarÄ«ga Ä«paŔība: rakstÄ«Å”ana konveijerā lÄ«dz PIPE_BUF baitiem (vismaz 512) jābÅ«t atomiem, lai procesi varētu sazināties savā starpā, izmantojot konveijeru tādā veidā, kā parastie faili (kas nenodroÅ”ina Ŕādas garantijas).

Izmantojot parastu failu, process var ierakstÄ«t tajā visu savu izvadi un nodot to citam procesam. Vai arÄ« procesi var darboties ļoti paralēlā režīmā, izmantojot ārēju signalizācijas mehānismu (piemēram, semaforu), lai informētu viens otru, kad rakstÄ«Å”ana vai lasÄ«Å”ana ir pabeigta. Konveijeri mÅ«s glābj no visām Ŕīm grÅ«tÄ«bām.

Ko mēs meklējam?

Es to paskaidroÅ”u vienkārÅ”i, lai jums bÅ«tu vieglāk iedomāties, kā var darboties konveijers. Jums bÅ«s jāpieŔķir buferis un kāds stāvoklis atmiņā. Jums bÅ«s nepiecieÅ”amas funkcijas, lai pievienotu un noņemtu datus no bufera. Jums bÅ«s nepiecieÅ”ami daži lÄ«dzekļi, lai izsauktu funkcijas lasÄ«Å”anas un rakstÄ«Å”anas operāciju laikā uz failu deskriptoriem. Un jums bÅ«s nepiecieÅ”amas slēdzenes, lai Ä«stenotu iepriekÅ” aprakstÄ«to Ä«paÅ”o uzvedÄ«bu.

Tagad mēs esam gatavi nopratināt kodola pirmkodu spilgtā lampas gaismā, lai apstiprinātu vai atspēkotu mūsu neskaidro garīgo modeli. Bet vienmēr esiet gatavs negaidītam.

Kur mēs skatāmies?

Es nezinu, kur atrodas mans slavenās grāmatas eksemplārs "Lauvas grāmata"ar Unix 6 pirmkodu, bet pateicoties Unix mantojuma biedrÄ«ba varat meklēt tieÅ”saistē vietnē avota kods pat vecākas Unix versijas.

KlÄ«st pa TUHS arhÄ«vu ir kā muzeja apmeklējums. Mēs varam aplÅ«kot mÅ«su kopÄ«go vēsturi, un es cienu daudzu gadu pÅ«les, lai pamazām atgÅ«tu visu Å”o materiālu no vecām lentēm un izdrukām. Un es ļoti labi apzinos tos fragmentus, kuru joprojām trÅ«kst.

ApmierinājuÅ”i savu zinātkāri par konveijera seno vēsturi, salÄ«dzinājumam varam aplÅ«kot mÅ«sdienu kodolus.

Starp citu, pipe tabulā ir sistēmas izsaukuma numurs 42 sysent[]. NejauŔība?

Tradicionālie Unix kodoli (1970ā€“1974)

Es neatradu nekādas pēdas pipe(2) ne iekŔā PDP-7 Unix (1970. gada janvāris), ne arÄ« in pirmais Unix izdevums (1971. gada novembris), ne arÄ« nepilnÄ«gajā pirmkodā otrais izdevums (1972. gada jÅ«nijs).

TUHS norāda, ka treÅ”ais Unix izdevums (1973. gada februāris) kļuva par pirmo versiju ar konveijeriem:

Unix 1973rd Edition bija pēdējā versija ar kodolu, kas rakstÄ«ts montāžas valodā, bet arÄ« pirmā versija ar konveijeriem. XNUMX. gadā tika veikts darbs pie treŔā izdevuma uzlaboÅ”anas, kodols tika pārrakstÄ«ts C valodā, un tā parādÄ«jās ceturtais Unix izdevums.

Kāds lasÄ«tājs atrada skenētu dokumentu, kurā Dags Makilrojs ierosināja ideju "savienot programmas kā dārza Ŕļūteni".

Kā cauruļvadi tiek ieviesti Unix
Braiena Kernigana grāmatāUnix: vēsture un memuāri", konveijeru raÅ”anās vēsturē ir minēts arÄ« Å”is dokuments: "... tas karājās pie sienas manā birojā Bell Labs 30 gadus." Å eit intervija ar Makilroju, un vēl viens stāsts no Makilroja darbs, uzrakstÄ«ts 2014. gadā:

Kad iznāca Unix, mana aizrauÅ”anās ar korutÄ«nām lika man lÅ«gt operētājsistēmas autoram Kenam Tompsonam ļaut datiem, kas ierakstÄ«ti procesā, nonākt ne tikai ierÄ«cē, bet arÄ« izvadÄ«t citā procesā. Kens nolēma, ka tas ir iespējams. Tomēr kā minimālists viņŔ vēlējās, lai katrai sistēmas funkcijai bÅ«tu nozÄ«mÄ«ga loma. Vai tieŔām rakstÄ«Å”ana tieÅ”i starp procesiem ir liela priekÅ”rocÄ«ba salÄ«dzinājumā ar rakstÄ«Å”anu starpfailā? Tikai tad, kad es izteicu konkrētu priekÅ”likumu ar āķīgo nosaukumu ā€œcauruļvadsā€ un procesu mijiedarbÄ«bas sintakses aprakstu, Kens beidzot iesaucās: ā€œEs to izdarÄ«Å”u!ā€

Un darÄ«ja. Kādā liktenÄ«gā vakarā Kens mainÄ«ja kodolu un čaulu, laboja vairākas standarta programmas, lai standartizētu to, kā tās pieņēma ievadi (kas varētu bÅ«t no konveijera), kā arÄ« mainÄ«ja failu nosaukumus. Nākamajā dienā cauruļvadus sāka ļoti plaÅ”i izmantot lietojumos. LÄ«dz nedēļas beigām sekretāri tos izmantoja, lai nosÅ«tÄ«tu dokumentus no tekstapstrādes programmām uz printeri. Nedaudz vēlāk Kens aizstāja sākotnējo API un sintaksi cauruļvadu izmantoÅ”anas iesaiņoÅ”anai ar tÄ«rākām konvencijām, kas tiek izmantotas kopÅ” tā laika.

Diemžēl treŔā izdevuma Unix kodola pirmkods ir pazaudēts. Un, lai gan mums ir kodola pirmkods, kas rakstÄ«ts C valodā ceturtais izdevums, izlaists 1973. gada novembrÄ«, taču tas iznāca vairākus mēneÅ”us pirms oficiālās izlaiÅ”anas un nesatur konveijera implementācijas. Žēl, ka Ŕīs leÄ£endārās Unix funkcijas pirmkods tiek zaudēts, iespējams, uz visiem laikiem.

Mums ir teksta dokumentācija pipe(2) no abiem laidieniem, lai jÅ«s varētu sākt, meklējot dokumentāciju treÅ”ais izdevums (noteiktiem vārdiem, pasvÄ«trots ā€œmanuāliā€, burtu virkne ^H, kam seko pasvÄ«tra!). Å is proto-pipe(2) ir uzrakstÄ«ts montāžas valodā un atgriež tikai vienu faila deskriptoru, taču jau nodroÅ”ina paredzēto pamata funkcionalitāti:

Sistēmas zvans caurule izveido ievades/izvades mehānismu, ko sauc par cauruļvadu. Atgriezto faila deskriptoru var izmantot lasÄ«Å”anas un rakstÄ«Å”anas darbÄ«bām. Kad konveijerā kaut kas tiek ierakstÄ«ts, tiek buferēts lÄ«dz 504 baitiem datu, pēc tam rakstÄ«Å”anas process tiek apturēts. Lasot no konveijera, buferizētie dati tiek noņemti.

Nākamajā gadā kodols tika pārrakstīts C valodā un caurule(2) ceturtajā izdevumā ieguva savu moderno izskatu ar prototipu "pipe(fildes)"

Sistēmas zvans caurule izveido ievades/izvades mehānismu, ko sauc par cauruļvadu. Atgrieztos failu deskriptorus var izmantot lasÄ«Å”anas un rakstÄ«Å”anas operācijās. Kad konveijerā kaut kas tiek ierakstÄ«ts, tiek izmantots r1 atgrieztais rokturis (resp. fildes[1]), buferēts lÄ«dz 4096 datu baitiem, pēc tam rakstÄ«Å”anas process tiek apturēts. Lasot no konveijera, rokturis, kas atgriezts uz r0 (resp. fildes[0]), ņem datus.

Tiek pieņemts, ka pēc konveijera definÄ“Å”anas tiks veikti divi (vai vairāki) saziņas procesi (ko rada nākamie izsaukumi uz dakÅ”a) pārsÅ«tÄ«s datus no cauruļvada, izmantojot zvanus lasÄ«t Šø rakstÄ«t.

Apvalkam ir sintakse lineāra procesu masÄ«va definÄ“Å”anai, kas savienoti ar cauruļvadu.

Aicinājumi lasÄ«t no tukÅ”a konveijera (kas nesatur buferētus datus), kuram ir tikai viens gals (visi rakstāmā faila deskriptori ir aizvērti), atgriež "faila beigas". Aicinājumi rakstÄ«t lÄ«dzÄ«gā situācijā tiek ignorēti.

Agrākais saglabāta cauruļvada ievieÅ”ana bažas uz piekto Unix izdevumu (1974. gada jÅ«nijā), taču tas ir gandrÄ«z identisks tam, kas parādÄ«jās nākamajā laidienā. Komentāri ir tikko pievienoti, tāpēc varat izlaist piekto izdevumu.

Sestais Unix izdevums (1975)

Sāksim lasÄ«t Unix pirmkodu sestais izdevums (1975. gada maijs). Lielā mērā pateicoties Lauvas to ir daudz vieglāk atrast nekā iepriekŔējo versiju avotus:

Daudzus gadus grāmata Lauvas bija vienÄ«gais dokuments par Unix kodolu, kas pieejams ārpus Bell Labs. Lai gan sestā izdevuma licence ļāva skolotājiem izmantot tās pirmkodu, septÄ«tā izdevuma licence izslēdza Å”o iespēju, tāpēc grāmata tika izplatÄ«ta nelegālu maŔīnrakstÄ«to kopiju veidā.

Å odien var iegādāties grāmatas atkārtotu izdruku, uz kuras vāka redzami skolēni pie kopētāja. Pateicoties Vorenam Toomejam (kurÅ” uzsāka TUHS projektu), jÅ«s varat lejupielādēt PDF fails ar avota kodu sestajam izdevumam. Es vēlos jums sniegt priekÅ”statu par to, cik daudz pūļu tika ieguldÄ«ts faila izveidei:

Pirms vairāk nekā 15 gadiem es ierakstÄ«ju norādÄ«tā pirmkoda kopiju Lauvas, jo man nepatika manas kopijas kvalitāte no nezināma skaita citu kopiju. TUHS vēl neeksistēja, un man nebija piekļuves vecajiem avotiem. Bet 1988. gadā es atradu vecu 9 celiņu lenti, kurā bija dublējums no PDP11 datora. Bija grÅ«ti pateikt, vai tas darbojas, taču bija neskarts /usr/src/ koks, kurā lielākā daļa failu bija marķēti ar 1979. gadu, kas pat tad izskatÄ«jās sens. Tas bija septÄ«tais izdevums vai tā atvasinājums PWB, kā es ticēju.

Par pamatu ņēmu atradumu un manuāli rediģēju avotus lÄ«dz sestajam izdevumam. Daļa koda palika nemainÄ«gi, bet daži bija nedaudz jārediģē, mainot moderno += marÄ·ieri uz novecojuÅ”o =+. Dažas lietas tika vienkārÅ”i izdzēstas, un dažas bija pilnÄ«bā jāpārraksta, bet ne pārāk daudz.

Un Å”odien mēs varam lasÄ«t tieÅ”saistē TUHS sestā izdevuma pirmkodu no arhÄ«vs, kuram Deniss Ričijs bija pieķēries.

Starp citu, no pirmā acu uzmetiena C-koda galvenā iezÄ«me pirms Kernighan un Ritchie perioda ir tā Ä«sums. Retos gadÄ«jumos es varu ievietot koda daļas bez lielas rediģēŔanas, lai tās ietilptu salÄ«dzinoÅ”i Å”aurā manas vietnes displeja apgabalā.

Agri /usr/sys/ken/pipe.c ir paskaidrojoÅ”s komentārs (un jā, ir vēl /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

Bufera lielums nav mainÄ«jies kopÅ” ceturtā izdevuma. Taču bez publiskas dokumentācijas mēs redzam, ka konveijeri kādreiz izmantoja failus kā rezerves krātuvi!

Kas attiecas uz LARG failiem, tie atbilst inode karogs LIELS, kuru apstrādei izmanto "lielais adresācijas algoritms". netieÅ”ie bloki lai atbalstÄ«tu lielākas failu sistēmas. Tā kā Kens teica, ka labāk tos neizmantot, es ar prieku pieņemÅ”u viņa vārdu.

Šeit ir īstais sistēmas izsaukums 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;
}

Komentārs skaidri apraksta, kas Å”eit notiek. Bet saprast kodu nav tik viegli, daļēji tāpēc, ka veids "struct lietotājs uĀ» un reÄ£istri R0 Šø R1 tiek nodoti sistēmas izsaukuma parametri un atgrieÅ”anas vērtÄ«bas.

Mēģināsim ar ialloc() ielieciet diskā inode (rādÄ«tāja rokturis), un ar palÄ«dzÄ«bu falloc () - ievietojiet atmiņā divus failu. Ja viss noritēs labi, mēs iestatÄ«sim karogus, lai identificētu Å”os failus kā divus cauruļvada galus, norādÄ«sim tos uz vienu un to paÅ”u inode (kuras atsauces skaits tiks iestatÄ«ts uz 2) un atzÄ«mēsim inode kā modificētu un lietotu. Pievērsiet uzmanÄ«bu pieprasÄ«jumiem ES lieku() kļūdu ceļos, lai samazinātu atsauces skaitu jaunajā inode.

pipe() jāiziet cauri R0 Šø R1 atgriezt faila deskriptora numurus lasÄ«Å”anai un rakstÄ«Å”anai. falloc() atgriež rādÄ«tāju uz faila struktÅ«ru, bet arÄ« "atgriež" caur u.u_ar0[R0] un faila deskriptors. Tas ir, kods tiek saglabāts r faila deskriptors lasÄ«Å”anai un pieŔķir faila deskriptoru rakstÄ«Å”anai tieÅ”i no u.u_ar0[R0] pēc otrā zvana falloc().

AtzÄ«mēt FPIPE, ko iestatÄ«jām, veidojot konveijeru, kontrolē funkcijas darbÄ«bu rdwr() sistēmā sys2.cspecifisku I/O rutÄ«nu izsaukÅ”ana:

/*
 * 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);
    }
        /* ā€¦ */
}

Pēc tam funkcija readp() Š² pipe.c nolasa datus no konveijera. Bet labāk ir izsekot ievieÅ”anai, sākot no writep(). Atkal kods ir kļuvis sarežģītāks argumentu nodoÅ”anas konvenciju dēļ, taču dažas detaļas var izlaist.

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;
}

Mēs vēlamies rakstīt baitus cauruļvada ievadei u.u_count. Vispirms mums ir jābloķē inode (skatiet zemāk plock/prele).

Pēc tam mēs pārbaudām inode atsauces skaitÄ«tāju. Kamēr abi cauruļvada gali paliek atvērti, skaitÄ«tājam jābÅ«t vienādam ar 2. Mēs turam vienu saiti (no rp->f_inode), tādēļ, ja skaitÄ«tājs ir mazāks par 2, tas nozÄ«mē, ka nolasÄ«Å”anas process ir slēdzis cauruļvada galu. Citiem vārdiem sakot, mēs mēģinām rakstÄ«t slēgtā konveijerā, un tā ir kļūda. Pirmo reizi kļūdas kods EPIPE un signāls SIGPIPE parādÄ«jās Unix sestajā izdevumā.

Bet pat tad, ja konveijers ir atvērts, tas var bÅ«t pilns. Å ajā gadÄ«jumā mēs atlaižam slēdzeni un dodamies gulēt, cerot, ka kāds cits process nolasÄ«s no cauruļvada un atbrÄ«vos tajā pietiekami daudz vietas. PamoduÅ”ies atgriežamies sākumā, atkal noliekam slēdzeni un sākam jaunu ierakstÄ«Å”anas ciklu.

Ja cauruļvadā ir pietiekami daudz brÄ«vas vietas, mēs tajā ierakstām datus, izmantojot rakstÄ«t (). Parametrs i_size1 inode (ja konveijers ir tukÅ”s, tas var bÅ«t vienāds ar 0) norāda tajā jau esoÅ”o datu beigas. Ja ir pietiekami daudz vietas ierakstÄ«Å”anai, mēs varam aizpildÄ«t cauruļvadu no i_size1 lÄ«dz PIPESIZ. Pēc tam atlaižam slēdzeni un mēģinām pamodināt jebkuru procesu, kas gaida nolasÄ«Å”anu no konveijera. Mēs atgriežamies pie sākuma, lai redzētu, vai mēs spējām ierakstÄ«t tik daudz baitu, cik mums nepiecieÅ”ams. Ja tas neizdodas, mēs sākam jaunu ierakstÄ«Å”anas ciklu.

Parasti parametrs i_mode inode tiek izmantots atļauju glabāŔanai r, w Šø x. Bet cauruļvadu gadÄ«jumā mēs signalizējam, ka kāds process gaida rakstÄ«Å”anu vai lasÄ«Å”anu, izmantojot bitus IREAD Šø IWRITE attiecÄ«gi. Process nosaka karogu un zvanus sleep(), un sagaidāms, ka nākotnē izraisÄ«s kāds cits process wakeup().

ÄŖstā maÄ£ija notiek iekŔā sleep() Šø wakeup(). Tie tiek ieviesti slp.c, avots slavenajam komentāram ā€œTu to nesapratÄ«siā€. Par laimi mums nav jāsaprot kods, vienkārÅ”i apskatiet dažus komentārus:

/*
 * 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) /* ā€¦ */

Process, kas izraisa sleep() konkrētam kanālam, vēlāk var tikt pamodināts ar citu procesu, kas izraisÄ«s wakeup() tam paÅ”am kanālam. writep() Šø readp() koordinēt savas darbÄ«bas, izmantojot Ŕādus pāra zvanus. pieraksti to pipe.c vienmēr dod priekÅ”roku PPIPE kad sauc sleep(), tā tas arÄ« viss sleep() var tikt pārtraukts ar signālu.

Tagad mums ir viss, lai saprastu funkciju 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);
}

Jums var bÅ«t vieglāk lasÄ«t Å”o funkciju no apakÅ”as uz augÅ”u. Filiāle "lasÄ«t un atgriezt" parasti tiek izmantota, ja ir daži dati. Å ajā gadÄ«jumā mēs izmantojam lasÄ«t () mēs nolasām tik daudz datu, cik ir pieejams, sākot no paÅ”reizējā f_offset lasÄ«Å”anu un pēc tam atjauniniet atbilstoŔās nobÄ«des vērtÄ«bu.

Turpmākajos nolasÄ«jumos konveijers bÅ«s tukÅ”s, ja ir sasniegta nolasÄ«Å”anas nobÄ«de i_size1 inodē. Mēs atiestatām pozÄ«ciju uz 0 un mēģinām aktivizēt jebkuru procesu, kas vēlas rakstÄ«t konveijerā. Mēs zinām, ka tad, kad konveijers ir pilns, writep() aizmigs tālāk ip+1. Un tagad, kad cauruļvads ir tukÅ”s, mēs varam to pamodināt, lai atsāktu tā rakstÄ«Å”anas ciklu.

Ja nav ko lasīt, tad readp() var uzstādīt karogu IREAD un aizmigt tālāk ip+2. Mēs zinām, kas viņu pamodinās writep(), kad tas ieraksta dažus datus konveijerā.

Komentāri par readi () un writei () palīdzēs jums saprast, ka tā vietā, lai nosūtītu parametrus caur "u"Mēs varam tos uzskatīt par parastajām I/O funkcijām, kas aizņem failu, pozīciju, buferi atmiņā un skaita lasāmo vai rakstāmo baitu skaitu.

/*
 * 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;
/* ā€¦ */

Kas attiecas uz "konservatÄ«vo" bloÄ·Ä“Å”anu, tad readp() Šø writep() bloķējiet inode, lÄ«dz viņi pabeidz darbu vai saņem rezultātu (tas ir, zvaniet wakeup). plock() Šø prele() strādāt vienkārÅ”i: izmantojot citu zvanu kopu sleep Šø wakeup ļauj mums pamodināt jebkuru procesu, kam nepiecieÅ”ama tikko atbrÄ«votā slēdzene:

/*
 * 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);
    }
}

Sākumā es nevarēju saprast, kāpēc readp() neizraisa prele(ip) pirms zvana wakeup(ip+1). Pirmā lieta ir writep() cēloņi savā ciklā, Å”is plock(ip), kas noved pie strupceļa, ja readp() vēl neesmu noņēmis savu bloku, tāpēc kodam kaut kā jādarbojas pareizi. Ja paskatās wakeup(), tad kļūst skaidrs, ka tas tikai atzÄ«mē gulÄ“Å”anas procesu kā gatavu izpildei, lai turpmāk sched() tieŔām to palaida. Tātad readp() cēloņi wakeup(), noņem slēdzeni, uzstāda IREAD un zvani sleep(ip+2)- tas viss iepriekÅ” writep() atsāk ciklu.

Tas pabeidz konveijeru aprakstu sestajā izdevumā. VienkārŔs kods, tālejoŔas sekas.

SeptÄ«tais Unix izdevums (1979. gada janvāris) bija jauns liels izlaidums (četrus gadus vēlāk), kas ieviesa daudzas jaunas lietojumprogrammas un kodola funkcijas. Tas arÄ« piedzÄ«voja bÅ«tiskas izmaiņas saistÄ«bā ar tipa lieÅ”anas, savienojumu un maŔīnrakstu norādes uz konstrukcijām izmantoÅ”anu. Tomēr konveijera kods praktiski nemainÄ«gs. Mēs varam izlaist Å”o izdevumu.

Xv6, vienkārŔs Unix līdzīgs kodols

Lai izveidotu kodolu Xv6 ietekmēja Unix sestais izdevums, bet tas ir rakstÄ«ts mÅ«sdienu C valodā, lai darbotos ar x86 procesoriem. Kods ir viegli lasāms un saprotams. Turklāt atŔķirÄ«bā no Unix avotiem ar TUHS jÅ«s varat to kompilēt, modificēt un palaist kaut ko citu, nevis PDP 11/70. Tāpēc Å”is kodols tiek plaÅ”i izmantots universitātēs kā izglÄ«tojoÅ”s materiāls par operētājsistēmām. Avoti atrodas vietnē Github.

Kods satur skaidru un pārdomātu ievieÅ”anu caurule.c, ko nodroÅ”ina buferis atmiņā, nevis diska inode. Å eit es sniedzu tikai "strukturālā cauruļvada" un funkcijas definÄ«ciju 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() iestata pārējās ievieÅ”anas stāvokli, kas ietver funkcijas piperead(), pipewrite() Šø pipeclose(). Faktiskais sistēmas zvans sys_pipe ir iesaiņojums, kas ieviests sysfile.c. Es iesaku izlasÄ«t visu viņa kodu. SarežģītÄ«ba ir sestā izdevuma pirmkoda lÄ«menÄ«, taču to ir daudz vieglāk un patÄ«kamāk lasÄ«t.

Linux 0.01

Var atrast Linux 0.01 pirmkodu. BÅ«s pamācoÅ”i izpētÄ«t cauruļvadu ievieÅ”anu viņa fs/pipe.c. Konveijera attēloÅ”anai tiek izmantota inode, bet pats konveijers ir rakstÄ«ts mÅ«sdienu C valodā. Ja esat strādājis lÄ«dz 6. izdevuma kodam, jums Å”eit nebÅ«s problēmu. Šādi izskatās funkcija 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;
}

Pat neapskatot struktūras definīcijas, varat noskaidrot, kā inode atsauces skaits tiek izmantots, lai pārbaudītu, vai rakstīŔanas operācijas rezultātā SIGPIPE. Papildus darbam pa baitam Ŕo funkciju ir viegli salīdzināt ar iepriekŔ aprakstītajām idejām. Pat loģika sleep_on/wake_up neizskatās tik sveŔs.

MÅ«sdienu Linux kodoli, FreeBSD, NetBSD, OpenBSD

Es ātri izskrēju cauri dažiem mÅ«sdienu kodoliem. Nevienam no tiem vairs nav diska ievieÅ”anas (nav pārsteidzoÅ”i). Linux ir sava ievieÅ”ana. Lai gan trÄ«s mÅ«sdienu BSD kodoli satur implementācijas, kuru pamatā ir Džona Daisona uzrakstÄ«tais kods, gadu gaitā tie ir kļuvuÅ”i pārāk atŔķirÄ«gi viens no otra.

LasÄ«t fs/pipe.c (uz Linux) vai sys/kern/sys_pipe.c (uz *BSD), tas prasa patiesu centÄ«bu. Å odienas kods ir par veiktspēju un atbalstu tādām funkcijām kā vektors un asinhronā I/O. Un informācija par atmiņas pieŔķirÅ”anu, bloÄ·Ä“Å”anu un kodola konfigurāciju ir ļoti atŔķirÄ«ga. Tas nav tas, kas koledžām ir vajadzÄ«gs operētājsistēmu ievadkursam.

Jebkurā gadÄ«jumā mani interesēja izrakt dažus vecus modeļus (piemēram, Ä£enerēt SIGPIPE un atgriezties EPIPE rakstot slēgtā cauruļvadā) visos Å”ajos dažādajos mÅ«sdienu kodolos. Visticamāk, es nekad dzÄ«vē neredzÄ“Å”u datoru PDP-11, taču joprojām ir daudz ko mācÄ«ties no koda, kas tika uzrakstÄ«ts gadus pirms manas dzimÅ”anas.

Divi Kapoor raksts 2011. gadā:Cauruļu un FIFO Linux kodola ievieÅ”ana" sniedz pārskatu par to, kā cauruļvadi (joprojām) darbojas operētājsistēmā Linux. A nesen veiktas darbÄ«bas operētājsistēmā Linux ilustrē mijiedarbÄ«bas konveijera modeli, kura iespējas pārsniedz pagaidu failu iespējas; un arÄ« parāda, cik tālu cauruļvadi ir nonākuÅ”i no sestā izdevuma Unix kodola "ļoti konservatÄ«vās bloÄ·Ä“Å”anas".

Avots: www.habr.com

Pievieno komentāru