Epollモデルの説明


まず、ファイル、socket、pipeなどI/O操作が可能なカーネルオブジェクトであるストリームの概念を定義します.
ファイルであれ、ソケットであれ、パイプであれ、私たちは彼らをストリームと見なすことができます.
その後I/Oの動作について議論し,readによりストリームからデータを読み込むことができる.writeにより、ストリームにデータを書き込むことができます.次に、ストリームからデータを読む必要があると仮定しますが、ストリームにはデータがありません(典型的な例では、クライアントはsocketからデータのように読みますが、サーバはまだデータを返していません).この場合どうすればいいですか?
ブロック:ブロックとはどんな概念ですか.例えば、ある时、あなたは宅配便を待っていますが、宅配便がいつ来るか分かりません.そして、他にできることはありません(あるいは、次のことは宅配便が来るのを待っていなければなりません).では、あなたは寝てもいいです.宅配便が荷物を送ってきたときに必ず電話をかけることを知っていますから(必ず起こしてくれると仮定します).
非ブロック忙しいポーリング:上の宅配便の例を待って、忙しいポーリングの方法を使うならば、宅配便員の携帯番号を知って、それから毎分彼に電話を切る必要があります:“あなたは着きましたか?”
明らかに普通の人は第2の方法を使うことができなくて、とても頭がないだけではなくて、通話料を浪費して言わないで、また速達員の大量の時間を占有しました.
ほとんどのプログラムも第2の方法ではありません.第1の方法は経済的で簡単です.経済とは少ないCPU時間を消費することです.スレッドが睡眠をとると、システムのスケジューリングキューが落ちて、しばらくCPUの貴重なタイムスライスを分けません.
ブロックがどのように行われているかを理解するために,バッファとカーネルバッファについて議論し,最終的にI/Oイベントを明確に説明した.バッファの導入は、頻繁なI/O操作を減らすために頻繁なシステム呼び出し(遅いことを知っています)を引き起こし、ストリームを操作すると、ユーザ空間に比べてバッファ単位で操作することが多くなります.カーネルにもバッファが必要です.
パイプがあると仮定し、プロセスAはパイプの書き込み方、Bはパイプの読み出し方である.
最初はカーネルバッファが空であると仮定し,Bは読み出し側としてブロックされている.そして、まずAがパイプに書き込まれると、カーネルバッファが空の状態から非空の状態に変化し、カーネルがBに目が覚めるべきだと告げるイベントが発生し、このイベントを一応「バッファ非空」と呼ぶ.
しかし、「バッファ非空」イベントがBに通知された後、Bはまだデータを読み出していない.また、カーネルがパイプラインに書き込まれたデータを捨てることができないと約束した場合、Aが書き込まれたデータはカーネルバッファに滞留し、カーネルもバッファがいっぱいになった場合、Bはまだデータの読み取りを開始していないため、最終的にカーネルバッファが満たされ、この場合、I/Oイベントが発生し、プロセスAに伝えることができます.このイベントを「バッファフル」と定義します.
仮にBがやっとデータを読み始めたとして、カーネルのバッファが空になったとしたら、カーネルはAに、カーネルバッファに空席があることを教えて、あなたは長い眠りの中から目が覚めて、データを書き続けることができて、私达はこの事件を“バッファがいっぱいではありません”と言います
イベントY 1がAに通知されたかもしれないが、Aにもデータが書き込まれず、Bはデータを読み出し続け、カーネルバッファが空であることを知る.この時カーネルはBにブロックが必要だと教えてくれました!、私たちはこの時間を「バッファ空」にします.
この4つのケースは、4つのI/Oイベント、バッファがいっぱい、バッファが空ではなく、バッファがいっぱいではありません(注はすべてカーネルバッファと呼ばれ、この4つの用語はすべて私が作ったもので、その原理を説明するために作られたものです).この4つのI/Oイベントはブロック同期の根本である.(「同期」という概念が理解できない場合は、オペレーティングシステムのロック、信号量、条件変数などのタスク同期に関する知識を学習してください).
そしてI/Oをブロックする欠点についてお話しします.ただし、ブロックI/Oモードでは、1スレッドで1ストリームのI/Oイベントしか処理できません.複数のストリームを同時に処理するには、マルチプロセス(fork)、またはマルチスレッド(pthread_create)のどちらも効率的ではありません.
そこで,非ブロックビジーポーリングのI/O方式を考慮すると,複数のストリームを同時に処理できることが分かった(1つのストリームをブロックモードから非ブロックモードに切り替えることは議論しない):
[java] view plain copy 
while true {  
    for i in stream[]; {  
        if i has data  
        read until unavailable  
    }  
}

  • 私たちはすべての流れを最初から最後まで聞いて、また最初から始めます.これにより、複数のストリームを処理することができるが、すべてのストリームにデータがなければ、CPUを無駄にするだけであるため、このようなやり方は明らかによくない.ここで補足すると、ブロックモードでは、カーネルのI/Oイベントに対する処理はブロックまたは起動であり、ブロックモードではなくI/Oイベントを他のオブジェクト(後述するselectおよびepoll)に渡す処理は直接無視される.
    CPUのアイドリングを回避するために,エージェント(最初はselectというエージェントがあり,後にpollというエージェントがあるが,両者の本質は同じである)を導入することができる.このエージェントは比較的強力で、同時に多くのストリームのI/Oイベントを観察することができ、暇な時、現在のスレッドをブロックし、1つ以上のストリームにI/Oイベントがあると、ブロック状態から目が覚め、私たちのプログラムはすべてのストリームをポーリングします(そこで、「忙しい」の字を削除することができます).コード長:
    [java] view plain copy 
    while true {  
        select(streams[])  
        for i in streams[] {  
            if i has data  
            read until unavailable  
        }  
    }

  • すると、I/Oイベントが発生しなければ、私たちのプログラムはselectにブロックされます.しかし、依然として問題があります.私たちはselectからI/Oイベントが発生したことを知っていますが、そのいくつかのストリーム(1つ、複数、さらにはすべて)であることを知りません.私たちはすべてのストリームを無差別にポーリングし、データを読み出すか、データを書き込むことができるストリームを見つけて、彼らを操作するしかありません.
    しかしselectを用いると,O(n)の無差別ポーリングの複雑さがあり,同時に処理されるストリームが多ければ多いほど,一度も無差別ポーリング時間が長くなることはない.再び
    こんなにたくさん言って、やっとepollをちゃんと説明できるようになりました
    epollはevent pollと理解でき、忙しいポーリングと無差別ポーリングとは異なり、epollがどのストリームにどのI/Oイベントが発生したかを通知します.このとき,我々はこれらのストリームの操作に意味がある.(複雑度がO(1)に低下した)
    epollの実装の詳細を議論する前に、epollに関する操作をリストします.
    [plain] view plain copy 
    epoll_create     epoll  ,  epollfd = epoll_create()  
      
    epoll_ctl (epoll_add/epoll_del   ), epoll     /              
        
    epoll_ctl(epollfd, EPOLL_CTL_ADD, socket, EPOLLIN);//         ,        
    epoll_ctl(epollfd, EPOLL_CTL_DEL, socket, EPOLLOUT);//         ,         
    epoll_wait(epollfd,...)             
    ( :                      ,write/read   -1,   errno=EAGAIN。 epoll                )。

  • epollモードのコードの概略は:
    [html] view plain copy 
    while true {  
        active_stream[] = epoll_wait(epollfd)  
        for i in active_stream[] {  
            read or write till  
        }  
     }

  • 紙面に限られていますが、原理的なものを明らかにするには、epollの使用の詳細については、manとgoogleを参照して、実装の詳細については、linux kernel sourceを参照してください.