LinuxのIOシステムの常用システムの呼び出しと分析

5376 ワード

LinuxのIOは広義には多くのクラスが含まれており、狭義にはディスクのIOにすぎない.本稿では、主にディスクのIOについて説明します.ここで私はLinuxのディスクIOの常用システム呼び出しについて深く分析して、みんながディスクIOにボトルネックが発生する時、最適化を助けることができることを望んで、同時に私も前の1篇の博文に対して総括します.この文書を転載するには、出典を明記してください.http://blog.csdn.net/jiang1st2010/article/details/8373063
一、ディスクの読み取り:
ssize_t read(int fd,void * buf ,size_t count);
        ディスクを読むときに最もよく使われるシステム呼び出しはread(またはfread)です.皆さんはそれをよく知っています.まずfopenがファイルを開き、mallocがメモリを開き、最後にread関数を呼び出してfpが指すファイルをこのメモリに読みます.実行が完了すると、読み込まれたバイトに従ってファイルの読み書き位置が移動します.
        簡単で最も一般的ですが、read関数の実行過程をよく知らない学生もいるかもしれません.このプロセスは、次の図にまとめることができます.
     
        図中の上から下までの3つの位置を順に示す.
  • ファイルのディスク内の記憶アドレス;
  • カーネルがメンテナンスするファイルのcache(page cacheとも呼ばれ、4 kは1ページで、各ページは基本的なcache単位である).
  • ユーザ状態のbuffer(read関数に割り当てられたメモリ).

  •          1回の読み取りリクエストを開始すると、カーネルは、読み取りたいファイルがカーネルのページにキャッシュされているかどうかを確認します.もしそうであれば、カーネルのbufferからユーザー状態のbufferに直接コピーします.そうでない場合、カーネルはファイルに対するIOを開始し、カーネルのcacheに読み込まれ、bufferにコピーされます.
             この行動には3つの特徴があります.
  • readの動作は、データが取得されるまでブロックされたシステム呼び出しである.
  • 以内のコアはバッファである、カーネルからユーザメモリへ一度メモリコピーを行った(メモリコピーはCPUを占有する)
  • .
  • は、ファイルのどの位置から読み取りを開始するかをユーザに表示せずに通知する.ユーザはファイルポインタを利用してlseekなどのシステム呼び出しで位置を指定する必要がある.

  •          この3つの特徴は実は多くの欠点がある(私の説明の下でみんなも体得したと信じている).第2の特徴については、direct IOを用いてこのカーネルbufferを除去するプロセス(fopenの場合、フラグビットにdirectフラグを付ける方法)を採用することができるが、cacheを利用できないという問題があり、これは明らかに良い解決策ではない.だから多くのシーンでreadを直接使うのは効率的ではありません.次に、より効率的なシステム呼び出しのいくつかを順に紹介します.
    ssize_t pread(intfd, void *buf, size_tcount, off_toffset);
            preadはreadと機能的に全く同じで、ただ1つのパラメータ:読むファイルの開始アドレスです.マルチスレッドの場合、複数のスレッドが同じファイルの異なるアドレスを同時に読む場合は、ファイルポインタをロックしてパフォーマンスに影響しますが、preadを使用するとロックする必要がなくなり、プログラムがより効率的になります.3つ目の問題を解決した.
    ssize_t readahead(int fd, off64_t offset, size_t count);

           readaheadは、カーネルにこのデータをカーネルのbufferにプリフェッチするように要求し、実行が完了するのを待たずに戻る非ブロックシステム呼び出しです.readaheadを実行してからreadを実行すると、以前はパラレルにカーネルにデータを読み込ませていたため、カーネルのbufferからユーザーのbufferに直接copyを入れることが多くなり、効率が向上します.これでreadの最初の問題が解決しました.次の例を見てみましょう.
    //original function
    while(time < Ncnts)
    {
       read(fd[i], buf[i], lens);
       process(buf[i]);   //         
    }
    
    //modified program
    while(time < Ncnts)
    {
       readahead(fd[i], buf[i], lens);
    }
    
    while(time < Ncnts)
    {
       read(fd[i], buf[i], lens);
       process(buf[i]);   //         
    }

            修正後にreadaheadを追加すると、readaheadはすぐに読み取りメッセージを送信して戻ってきますが、processの過程でreadaheadはカーネルを並列にディスクを読み取り、次のprocessの時にデータがカーネルに読み込まれ、readを待つ過程が減少します.
    void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

            先ほど述べた2つの関数では,カーネルのbufferからユーザのbufferへのコピープロセスは依然として処理されていない.さっき言ったように、このメモリコピーのプロセスはcpuを占めています.この問題を解決するために、1つの方法はmmap(以前のこのブログではどのように使用するかを紹介した)、カーネル状態のアドレスmapをユーザー状態に直接適用し、ユーザー状態はこのポインタを通じて直接アクセスすることができる(ディスクからcacheをスキップしてユーザー状態に直接アクセスすることに相当する).しかし、mmapには2つの重要な問題が同時に存在しています.
  • cache命中率はreadに及ばない.
  • スレッドモデルでの反発は非常に深刻です
  •         最初の問題の解釈は,カーネルの実現メカニズムと比較して関連している.実際にcacheにヒットした場合、mmapを呼び出すのはユーザ状態からカーネル状態への切り替えが行われていないため、LRU更新ポリシーを採用したカーネルページはこのアクセスされたページの熱を1にすることができない.つまり、このページはmmapで何度もアクセスしたかもしれないが、カーネルに記録されていないので、一度もアクセスしていないように、これでLRUは簡単に更新できます.readはカーネル状態に陥っているに違いない.この問題を解決するために,readaheadを用いて今回のカーネル状態の切り替えを開始することができる(同時にIOを事前に開始する).
            2つ目の問題の原因は、カーネルcacheにヒットしない場合に、カーネルがdiskからデータを読むにはmmレベルのロックを追加することです(カーネルが追加します).このレベルのロックを付けてIO操作を行うのは、決して効率的ではありません.readaheadはこの問題をある程度解決することができるが、より良い方法はreadaheadと同じでブロック型のシステム呼び出しを利用することである(具体的にはLinuxがこのような関数を提供しているかどうか分からない).ブロックされた方法でカーネルにディスクをプリリードさせることで、mmapのときに読むデータがカーネルにあることを保証し、できるだけこのロックを回避することができます.
    二、書き込みディスク
            readのシステム呼び出しと比較して、writeはブロックされていない呼び出し方式が多い.つまり、通常はwriteをメモリに戻せます.具体的な手順は次のとおりです.
            2つのプロシージャが書き込みディスクをトリガーします.1)dirty ration(デフォルト40%)がしきい値を超えます.writeがブロックされ、スケールが下がるまでwriteが続行されず、汚れたページの同期が開始されます.2)dirty background ration(デフォルト10%)がしきい値を超えています.writeはブロックされずに返されますが、戻る前にバックグラウンドプロセスの汚れたページが呼び出されます.汚れたページがあるとブラシをかけ始める行為です(writeの汚れたページとは限らない).10%未満の場合、いつページが汚れますか?カーネルのバックグラウンドにはスレッドがあり、ページが周期的に汚れます.このサイクルは、カーネルではデフォルトで5秒(つまり5秒ごとに起動)です.すべての汚れたページをスキャンし、最も古い汚れたページがdirtyを超える時間を見つけます.expire_centisecs(デフォルトは30秒)は、この時間に達したらブラシをかけます(このプロセスはさっきのバックグラウンドプロセスとは少し違います).
            書き込みディスクで発生する問題は、カーネル書き込みディスクの場合、この割合が適切でないと、突然書き込みディスクに占めるIOが大きすぎて、読み取りディスクのパフォーマンスが低下する可能性があります.このような問題を解決するために、書くことをもっとスムーズにすることができます.つまり、これらのパラメータを小さくすることができます.
             このように、この2つの過程を簡単に紹介しました.実際の使用例については、こちらを突き刺してください.
    この文書を転載するには、出典を明記してください.http://blog.csdn.net/jiang1st2010/article/details/8373063
    転載先:https://www.cnblogs.com/jiang1st2010/archive/2012/12/22/3076337.html