Unix でのパむプラむンの実装方法

Unix でのパむプラむンの実装方法
この蚘事では、Unix カヌネルでのパむプラむンの実装に぀いお説明したす。 最近の「」ずいうタむトルの蚘事に少しがっかりしたした。Unix ではパむプラむンはどのように機胜したすか?" 刀明した ノヌ 内郚構造に぀いお。 私は興味を持ち、叀い情報源を調べお答えを芋぀けたした。

䜕それ

パむプラむンは「おそらく Unix で最も重芁な発明」です。これは、小さなプログラムをたずめるずいう Unix の根底にある哲孊の特城であり、よく知られたコマンドラむンのスロヌガンです。

$ echo hello | wc -c
6

この機胜は、カヌネルが提䟛するシステム コヌルに䟝存したす。 pipe、ドキュメントペヌゞで説明されおいたす パむプ(7) О パむプ(2):

パむプラむンは、プロセス間通信のための䞀方向チャネルを提䟛したす。 パむプラむンには入力 (曞き蟌み偎) ず出力 (読み取り偎) がありたす。 パむプラむンの入力に曞き蟌たれたデヌタは、出力で読み取るこずができたす。

パむプラむンは呌び出しによっお䜜成されたす pipe(2)、これは XNUMX ぀のファむル蚘述子を返したす。XNUMX ぀はパむプラむンの入力を参照し、XNUMX ぀目は出力を参照したす。

䞊蚘のコマンドからのトレヌス出力は、パむプラむンの䜜成ず、それを通るあるプロセスから別のプロセスぞのデヌタの流れを瀺しおいたす。

$ 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()添付ファむル蚘述子を取埗したす。 2 ぀の子プロセスが 3 ぀の蚘述子に曞き蟌み、別のプロセスが別の蚘述子から同じデヌタを読み取りたす。 シェルは、stdin ず stdout に䞀臎するように、dup4 を䜿甚しお蚘述子 XNUMX ず XNUMX を「名前倉曎」したす。

パむプラむンがなければ、シェルは XNUMX ぀のプロセスの出力をファむルに曞き蟌み、それを別のプロセスにパむプしおファむルからデヌタを読み取る必芁がありたす。 その結果、より倚くのリ゜ヌスずディスク領域が無駄になりたす。 ただし、パむプラむンは䞀時ファむルを回避するだけではありたせん。

プロセスが空のパむプラむンから読み取ろうずするず、 read(2) デヌタが利甚可胜になるたでブロックされたす。 プロセスが完党なパむプラむンに曞き蟌もうずするず、 write(2) 曞き蟌みを完了するのに十分なデヌタがパむプラむンから読み取られるたでブロックされたす。

POSIX 芁件ず同様、これは重芁なプロパティです。パむプラむンぞの曞き蟌みは次のずおりです。 PIPE_BUF 通垞のファむル (そのような保蚌はありたせん) では䞍可胜な方法でプロセスがパむプラむンを介しお盞互に通信できるように、バむト (少なくずも 512) はアトミックである必芁がありたす。

通垞のファむルを䜿甚するず、プロセスはすべおの出力をそのファむルに曞き蟌み、それを別のプロセスに枡すこずができたす。 たたは、プロセスは、倖郚信号メカニズム (セマフォなど) を䜿甚しお、曞き蟌みたたは読み取りの完了を盞互に通知するハヌド䞊列モヌドで動䜜するこずもできたす。 コンベダヌを䜿えば、こうした煩わしさから解攟されたす。

私たちは䜕を探しおいるのでしょうか

コンベアの仕組みをむメヌゞしやすいように指を䜿っお説明したす。 メモリ内にバッファず䜕らかの状態を割り圓おる必芁がありたす。 バッファヌにデヌタを远加したり、バッファヌからデヌタを削陀したりする関数が必芁になりたす。 ファむル蚘述子の読み取りおよび曞き蟌み操䜜䞭に関数を呌び出すための䜕らかの機胜が必芁になりたす。 たた、䞊蚘の特別な動䜜を実装するにはロックが必芁です。

これで、私たちの曖昧なメンタル モデルを確認たたは反蚌するために、明るいランプの光の䞋でカヌネルの゜ヌス コヌドを調査する準備が敎いたした。 しかし、垞に予期せぬ事態に備えおください。

私たちはどこを芋おいるのでしょうか

私の有名な本のコピヌがどこにあるかわかりたせん。ラむオンズブック« Unix 6 ゜ヌスコヌドを䜿甚しおいたすが、おかげで Unix ヘリテヌゞ協䌚 オンラむンで怜玢できたす ゜ヌスコヌド Unix の叀いバヌゞョンでも。

TUHS のアヌカむブを散策するのは、たるで博物通を蚪れおいるかのようです。 私たちは私たちの共有の歎史を振り返るこずができ、叀いカセットやプリントアりトからこの資料すべおを少しず぀埩元する長幎の努力に敬意を衚したす。 そしお、私はただ欠けおいる断片を痛感しおいたす。

パむプラむンの叀代の歎史に぀いおの奜奇心が満たされたので、比范のために珟代のコアを芋おみたしょう。

ずころで、 pipe テヌブル内のシステム コヌル番号 42 です。 sysent[]。 䞀臎

埓来の Unix カヌネル (1970  1974 幎)

痕跡は芋぀かりたせんでした pipe(2) どちらでもない PDP-7 ナニックス (1970 幎 XNUMX 月)、 初版 Unix (1971 幎 XNUMX 月)、䞍完党な゜ヌス コヌドでも 第XNUMX版 1972幎XNUMX月。

TUHSは次のように䞻匵しおいる 第 XNUMX 版 Unix (1973 幎 XNUMX 月) はパむプラむンを備えた最初のバヌゞョンでした。

Unix の第 1973 版は、アセンブラで曞かれたカヌネルを備えた最埌のバヌゞョンでしたが、パむプラむンを備えた最初のバヌゞョンでもありたした。 XNUMX 幎に第 XNUMX 版を改良する䜜業が進められ、カヌネルが C で曞き盎され、Unix の第 XNUMX 版が誕生したした。

ある読者は、ダグ・マキロむが「庭のホヌスのようにプログラムを接続する」ずいうアむデアを提案した文曞のスキャンを芋぀けた。

Unix でのパむプラむンの実装方法
ブラむアン・カヌニハンの本の䞭でUnix: 歎史ず回想録」、コンベアの出珟の歎史にもこの文曞が蚘茉されおいたす「...それはベル研究所の私のオフィスの壁に30幎間掛けられおいたした。」 ここ マキロむのむンタビュヌそしお別の話から マキロむの䜜品、2014 幎に曞かれた:

Unix が登堎したずき、コルヌチンに察する私の情熱から、あるプロセスに曞き蟌たれたデヌタをデバむスだけでなく別のプロセスの出口にも送信できるようにするよう、OS 䜜者の Ken Thompson に䟝頌したした。 ケンはそれが可胜だず考えた。 しかし、ミニマリストずしお、圌はすべおのシステム機胜が重芁な圹割を果たすこずを望んでいたした。 プロセス間の盎接曞き蟌みは、䞭間ファむルぞの曞き蟌みに比べお本圓に倧きな利点があるのでしょうか? そしお、私が「パむプラむン」ずいうキャッチヌな名前ずプロセスの盞互䜜甚の構文の説明を䜿っお具䜓的な提案をしたずき、ようやく Ken は「私がやりたす!」ず叫びたした。

そしお、そうしたした。 ある運呜的な倜、ケンはカヌネルずシェルを倉曎し、いく぀かの暙準プログラムを修正しお入力 (パむプラむンから来る可胜性がある) の受け入れ方法を暙準化し、ファむル名を倉曎したした。 翌日、パむプラむンはアプリケヌションで非垞に広く䜿甚されるようになりたした。 週末たでに、秘曞たちは文曞をワヌプロからプリンタヌに送信するためにこれらを䜿甚したした。 少し埌、Ken はパむプラむンの䜿甚をラップするための元の API ず構文を、それ以来䜿甚されおいるよりクリヌンな芏則に眮き換えたした。

残念ながら、第 XNUMX 版 Unix カヌネルの゜ヌス コヌドは倱われおいたす。 カヌネルの゜ヌスコヌドは C で曞かれおいたすが、 第XNUMX版は 1973 幎 XNUMX 月にリリヌスされたしたが、正匏リリヌスの数か月前にリリヌスされたものであり、パむプラむンの実装は含たれおいたせん。 この䌝説的な Unix 機胜の゜ヌス コヌドが、おそらく氞久に倱われるのは残念です。

のドキュメントテキストがありたす pipe(2) 䞡方のリリヌスから提䟛されおいるため、ドキュメントを怜玢するこずから始めるこずができたす。 第XNUMX版 (特定の単語に぀いおは、「手動で」䞋線が匕かれ、^H リテラルの埌にアンダヌスコアが続きたす!)。 このプロトタむプは、pipe(2) これはアセンブラで曞かれおおり、ファむル蚘述子を XNUMX ぀だけ返したすが、期埅されるコア機胜はすでに提䟛されおいたす。

システムコヌル パむプ パむプラむンず呌ばれる I/O メカニズムを䜜成したす。 返されたファむル蚘述子は、読み取りおよび曞き蟌み操䜜に䜿甚できたす。 パむプラむンに䜕かが曞き蟌たれるず、パむプラむンは最倧 504 バむトのデヌタをバッファリングし、その埌曞き蟌みプロセスが䞀時停止されたす。 パむプラむンから読み取る堎合、バッファリングされたデヌタが取埗されたす。

翌幎たでにカヌネルは C で曞き盎され、 パむプ(2) 第 XNUMX 版 プロトタむプでモダンな倖芳を獲埗したした。」pipe(fildes)»

システムコヌル パむプ パむプラむンず呌ばれる I/O メカニズムを䜜成したす。 返されたファむル蚘述子は、読み取りおよび曞き蟌み操䜜で䜿甚できたす。 パむプラむンに䜕かが曞き蟌たれるず、r1 で返された蚘述子 (それぞれ fildes[1]) が䜿甚され、最倧 4096 バむトのデヌタがバッファリングされ、その埌曞き蟌みプロセスが䞀時停止されたす。 パむプラむンから読み取る堎合、r0 に返される蚘述子 (それぞれ fildes[0]) がデヌタを受け取りたす。

パむプラむンが定矩されるず、XNUMX ぀ (たたはそれ以䞊) の察話プロセス (埌続の呌び出しによっお䜜成される) が実行されるず想定されたす。 フォヌク) 呌び出しを䜿甚しおパむプラむンからデヌタを枡したす read О 曞きたす.

シェルには、パむプラむンを介しお接続されたプロセスの線圢配列を定矩するための構文がありたす。

゚ンドが XNUMX ぀だけ (すべおの曞き蟌みファむル蚘述子が閉じおいる) 空のパむプラむン (バッファリングされたデヌタを含たない) からの読み取りを呌び出すず、「ファむルの終わり」が返されたす。 同様の状況での曞き蟌み呌び出しは無芖されたす。

最叀の 保存されたパむプラむンの実装 適甚する Unix の第 XNUMX 版ぞ (1974 幎 XNUMX 月) ですが、次のリリヌスで登堎したものずほが同じです。 コメントを远加しただけなので、第 XNUMX 版はスキップできたす。

Unix 第 1975 版 (XNUMX)

Unix ゜ヌスコヌドを読み始める 第XNUMX版 1975幎XNUMX月。 䞻におかげで ラむオン 以前のバヌゞョンの゜ヌスよりもはるかに簡単に芋぀けるこずができたす。

長幎にわたり、その本は ラむオン これは、ベル研究所以倖で入手可胜な Unix カヌネルに関する唯䞀の文曞でした。 第 XNUMX 版のラむセンスでは教垫がその゜ヌス コヌドを䜿甚するこずが蚱可されおいたしたが、第 XNUMX 版のラむセンスではその可胜性が陀倖されおいたため、この本は違法にタむプ打ちされたコピヌで配垃されたした。

珟圚、この本の再版版を賌入できたす。衚玙にはコピヌ機に向かう孊生が描かれおいたす。 そしお、Warren Toomey (TUHS プロゞェクトを始めた人) のおかげで、ダりンロヌドできるようになりたした。 第 XNUMX 版の゜ヌス PDF。 ファむルの䜜成にどれだけの劎力がかかったのかを説明したいず思いたす。

15 幎以䞊前、私は、提䟛されおいる゜ヌス コヌドのコピヌを入力したした。 ラむオンなぜなら、他の数え切れないほどのコピヌのうち、自分のコピヌの品質が気に入らなかったからです。 TUHS はただ存圚しおおらず、私は叀い情報源にアクセスできたせんでした。 しかし 1988 幎に、PDP9 コンピュヌタからバックアップされた 11 トラックの叀いテヌプを芋぀けたした。 それが機胜するかどうかを知るのは困難でしたが、無傷の /usr/src/ ツリヌが存圚し、ほずんどのファむルには 1979 幎ずマヌクされおおり、圓時から芋おも叀いものに芋えたした。 それは第 XNUMX 版、たたは PWB の掟生版だず思いたした。

私はその発芋を基瀎ずしお゜ヌスを第 XNUMX 版の状態に手動で線集したした。 コヌドの䞀郚は同じたたですが、䞀郚を少し線集する必芁があり、最新のトヌクン += を廃止された =+ に倉曎したした。 䜕かが単玔に削陀され、䜕かを完党に曞き盎す必芁がありたしたが、倧したこずはありたせんでした。

そしお今日、私たちはTUHSでオンラむンで、第XNUMX版の゜ヌスコヌドを読むこずができたす。 デニス・リッチヌが関わったアヌカむブ.

ずころで、カヌニハンずリッチヌの時代以前の C コヌドの䞻な特城は、䞀芋するず次のずおりです。 簡朔。 サむト䞊の比范的狭い衚瀺領域に合わせお倧芏暡な線集を行わずにコヌドのスニペットを挿入できるこずは、あたりありたせん。

早い /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

バッファサむズは第 XNUMX 版から倉曎されおいたせん。 しかし、ここでは、公開文曞は䜕もありたせんが、パむプラむンがか぀おファむルをフォヌルバック ストレヌゞずしお䜿甚しおいたこずがわかりたす。

LARG ファむルに関しおは、以䞋に察応したす。 inode フラグ 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;
}

コメントはここで䜕が起こっおいるかを明確に説明しおいたす。 しかし、コヌドを理解するのはそれほど簡単ではありたせん。その理由の XNUMX ぀は「構造䜓ナヌザヌu» ずレゞスタ R0 О R1 システムコヌルパラメヌタず戻り倀が枡されたす。

詊しおみたしょう ialloc() ディスクに眮く i ノヌド (i ノヌド)、そしお助けを借りお falloc() - XNUMX぀を保管 ファむル。 すべおがうたくいけば、これらのファむルをパむプラむンの䞡端ずしお識別するフラグを蚭定し、同じ i ノヌド (参照カりントが 2 になりたす) を指し、その i ノヌドを倉曎枈みで䜿甚䞭ずしおマヌクしたす。 リク゚ストに泚意しおください 眮いた ゚ラヌ パスで新しい inode の参照カりントをデクリメントしたす。

pipe() 期限たでに R0 О R1 読み取りおよび曞き蟌み甚のファむル蚘述子番号を返したす。 falloc() ファむル構造ぞのポむンタを返したすが、経由で「返す」こずもできたす。 u.u_ar0[R0] そしおファむル蚘述子。 ぀たり、コヌドは次のように保存されたす。 r 読み取り甚のファむル蚘述子ず、盎接曞き蟌み甚の蚘述子を割り圓おたす。 u.u_ar0[R0] XNUMX回目の通話埌 falloc().

Ѐлаг FPIPEパむプラむンの䜜成時に蚭定し、関数の動䜜を制埡したす sys2.cのrdwr()、特定の 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。 たず、i ノヌドをロックする必芁がありたす (以䞋を参照) plock/prele).

次に、inode 参照数を確認したす。 パむプラむンの䞡端が開いたたたである限り、カりンタは 2 になるはずです。 XNUMX ぀のリンクを保持したす (から rp->f_inode) なので、カりンタヌが 2 未満の堎合は、読み取りプロセスがパむプラむンの終端を閉じたこずを意味したす。 蚀い換えれば、閉じたパむプラむンに曞き蟌もうずしおいるのですが、これは間違いです。 最初の゚ラヌコヌド EPIPE そしお信号 SIGPIPE Unix の第 XNUMX 版に登堎したした。

ただし、コンベアが開いおいおも、満杯の堎合がありたす。 この堎合、別のプロセスがパむプラむンから読み取り、パむプラむン内に十分なスペヌスを解攟するこずを期埅しお、ロックを解攟しおスリヌプ状態に入りたす。 目芚めるず、最初に戻り、再びロックを解陀し、新しい曞き蟌みサむクルを開始したす。

パむプラむンに十分な空き領域がある堎合は、次を䜿甚しおそこにデヌタを曞き蟌みたす。 writei()。 パラメヌタ i_size1 inode'a (空のパむプラむンでは 0 に等しくおもよい) は、すでに含たれおいるデヌタの終わりを指したす。 曞き蟌むのに十分なスペヌスがある堎合は、次からパむプラむンを埋めるこずができたす。 i_size1 ЎП PIPESIZ。 次に、ロックを解攟し、パむプラむンからの読み取りを埅機しおいるプロセスを起動しようずしたす。 最初に戻っお、必芁なだけのバむトを曞き蟌むこずができたかどうかを確認したす。 倱敗した堎合は、新しい蚘録サむクルを開始したす。

通垞パラメヌタ i_mode i ノヌドは暩限を保存するために䜿甚されたす 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 i ノヌドで。 䜍眮を 0 にリセットし、パむプラむンに曞き蟌みたいプロセスをりェむクアップしようずしたす。 コンベアがいっぱいになるず、 writep() に眠りたす ip+1。 パむプラむンが空になったので、パむプラむンを起動しお曞き蟌みサむクルを再開できたす。

読むものがない堎合は、 readp() フラグを立おるこずができる IREAD そしお眠りに぀く ip+2。 䜕が圌を目芚めさせるのか私たちは知っおいたす writep()パむプラむンにデヌタを曞き蟌むずき。

コメント read() ず 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() i ノヌドが終了するか結果を取埗するたでロックしたす (぀たり、呌び出し 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() サむクルを再開したす。

以䞊で第 XNUMX 版のパむプラむンに぀いおの説明を終わりたす。 シンプルなコヌド、広範囲にわたる圱響。

第 XNUMX 版 Unix (1979 幎 XNUMX 月) は、倚くの新しいアプリケヌションずカヌネル機胜を導入した新しいメゞャヌ リリヌス (XNUMX 幎埌) でした。 たた、型キャスト、共甚䜓、構造䜓ぞの型付きポむンタヌの䜿甚に関連しお、倧幅な倉曎も加えられおいたす。 しかし パむプラむンコヌド 実質的には倉わりたせんでした。 この゚ディションはスキップできたす。

Xv6、単玔な Unix 颚のカヌネル

栞を䜜成するには Xv6 Unix の第 86 版の圱響を受けおいたすが、x11 プロセッサで実行できるように最新の C で曞かれおいたす。 コヌドは読みやすく、理解しやすいです。 たた、TUHS を䜿甚した Unix ゜ヌスずは異なり、PDP 70/XNUMX 以倖でコンパむル、倉曎、実行するこずができたす。 そのため、このコアはオペレヌティングシステムの教材ずしお倧孊で広く䜿甚されおいたす。 情報源 Github䞊にありたす.

コヌドには明確で思慮深い実装が含たれおいたす パむプ.c、ディスク䞊の i ノヌドではなくメモリ内のバッファヌによっおバックアップされたす。 ここでは「構造パむプラむン」の定矩ずその機胜のみを瀺したす。 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。 圌のコヌドをすべお読むこずをお勧めしたす。 耇雑さは第 XNUMX 版の゜ヌス コヌドのレベルですが、はるかに簡単で読みやすくなっおいたす。

Linux 0.01

Linux 0.01 の゜ヌス コヌドを芋぀けるこずができたす。 圌の著曞でパむプラむンの実装を研究するこずは有益です。 fs/pipe.c。 ここでは、パむプラむンを衚すために i ノヌドが䜿甚されおいたすが、パむプラむン自䜓は最新の C で曞かれおいたす。第 XNUMX 版のコヌドをハッキングしたこずがあれば、ここでは䜕の問題もありたせん。 機胜はこんな感じです 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;
}

構造䜓の定矩を芋なくおも、曞き蟌み操䜜の結果が以䞋であるかどうかを確認するために inode 参照カりントがどのように䜿甚されるかを理解できたす。 SIGPIPE。 この関数はバむト単䜍の䜜業に加えお、䞊蚘のアむデアず比范するのが簡単です。 偶数ロゞック sleep_on/wake_up そんなに異星人には芋えたせん。

最新の Linux カヌネル、FreeBSD、NetBSD、OpenBSD

私はいく぀かの最新のカヌネルを簡単に調べたした。 それらのどれも、すでにディスクベヌスの実装を持っおいたせん (圓然のこずですが)。 Linux には独自の実装がありたす。 そしお、XNUMX ぀の最新の BSD カヌネルには、John Dyson によっお曞かれたコヌドに基づく実装が含たれおいたすが、長幎の間に、それらは互いにあたりにも異なっおきたした。

読むには fs/pipe.c (Linux の堎合) たたは sys/kern/sys_pipe.c (*BSD では)、それには真の献身が必芁です。 今日のコヌドでは、パフォヌマンスずベクトルや非同期 I/O などの機胜のサポヌトが重芁です。 たた、メモリ割り圓お、ロック、カヌネル構成の詳现はすべお倧きく異なりたす。 これは倧孊がオペレヌティング システムの入門コヌスに必芁ずするものではありたせん。

いずれにせよ、いく぀かの叀いパタヌンを発掘するのは興味深いこずでした (たずえば、 SIGPIPE そしお戻る EPIPE 閉じたパむプラむンに曞き蟌む堎合)、これらすべおの非垞に異なる最新のカヌネルで。 PDP-11 コンピュヌタヌを実際に芋るこずはおそらくないでしょうが、私が生たれる数幎前に曞かれたコヌドから孊ぶべきこずはただたくさんありたす。

2011 幎にディノィ・カプヌルによっお曞かれた蚘事「Linux カヌネルのパむプず FIFO の実装は、Linux パむプラむンが (これたでのずころ) どのように動䜜するかを抂説しおいたす。 あ Linux での最近のコミット むンタラクションのパむプラむン モデルを瀺しおおり、その機胜は䞀時ファむルの機胜を超えおいたす。 たた、パむプラむンが第 XNUMX 版 Unix カヌネルの「非垞に保守的なロック」からどの皋床進んでいるのかも瀺しおいたす。

出所 habr.com

コメントを远加したす