以下はこの話題についての入門的な解説ですが、もっと進んだ内容につい ては FIFO を見てください。
送信(transmitting)とは、シリアルポートを通じてコンピュータからバイ ト列を送ることです。一旦、送信を理解できれば、受信は同じようなものなの で理解するのは容易です。 ここで最初に示す例は極端に簡略化したものです。 もっと詳しい説明を後で追加します。 コンピュータが(外部ケーブルに繋がっている)シリアルポートから 1 バイト のデータを送ろうとする時、CPU はコンピュータ内部のバス上にあるそのデータをシリアルポートの I/O アド レスに送ります。シリアルポートはそのデータを受け取り、シリアルケーブルの端子の 送信ピンを使ってこれを 1 ビットずつ送ります(シリアルビットストリーム)。 あるビット(とバイト)が電気的にはどう見えるのかについては、 電圧の波形の章をご覧ください。
上記の説明をもう少し詳しくして(まだ完全には程遠いものですが)繰り返 します。シリアルポートで行われる処理のほとんどは UART (または同等のも の)が行います。1 バイトのデータを転送するために、シリアルデバイスドラ イバプログラム(CPU 上で動作)はシリアルポートの I/O アドレスに 1 バイト を送ります。このデータはシリアルポートの「送信シフトレジスタ」(大きさ が 1 バイト)に入ります。このシフトレジスタでは、このバイトデータから 1 ビットずつ取り出され、1 ビットずつシリアル線に送られます。そして最後の ビットが送信され、シフトレジスタが送るための別のバイトを必要とするよう になると、UART は別のバイトデータを送るよう CPU に求めるだけで済みます。 これなら単純な話ですが、CPU がバイトデータを即座に取得することができな いために遅延が生じるかもしれません。なにしろ CPU はシリアルポートの 処理以外にも作業をこなしているのが普通です。
このような遅延をなくす方法は、シフトレジスタがバイトデータを必要とする 前に CPU がバイトデータを取得して、これをシリアルポートの(ハードウェア) バッファに格納することです。 こうしておけば、シフトレジスタがバイトデータを送り出してしまって次に送る バイトデータが即座に必要になった時には、シリアルポートのハードウェアは 自分のバッファに入っている次の バイトデータをシフトレジスタに転送するだけです。 新しいバイトデータを取得するために CPU を呼ぶ必要はありません。
シリアルポートのバッファのサイズは元々 1 バイトしかありませんでしたが、 現在は普通 16 バイトです(高価なシリアルポートにはもっとあります)。しか しそれでも、シフトレジスタが転送するデータを必要とした時に必ずバッファ にデータがあるように、このバッファに十分なバイトデータを与え続けるとい う問題が残っています(送るデータがもう残っていない場合は除きます)。これ は割り込みを使って CPU と連絡することで行います。
まずはバッファが 1 バイトの古い方式のシリアルポートについて説明します。 というのも、バッファが 16 バイトのものも同じように動作するからです( ただし動作はより複雑です)。 シフトレジスタがバイトデータをバッファから取り出し、バッファが他のバイ トデータを必要とすると、コンピュータのバス上にある専用の配線の電圧を上 げることにより、CPU に割り込みが送られます。CPU が非常に重要な処理をし ていなければ、CPU が実行中の処理は割り込みにより強制的に中断され、シリ アルポートのバッファに別のバイトデータを与えるためのプログラムが実行さ れます。このバッファの目的は、シリアルポートのケーブルから送り出される バイトデータが途切れないように、追加のバイトデータが(送られるのを待っ て)ハードウェア内のキューに入った状態を保つことです。
いったん CPU に割り込みがかかると、CPU は誰が割り込みをかけたかを知ります。 なぜなら、各シリアルポートについて専用の割り込み線があるから です(ただし割り込みの共有をしていない場合)。それから CPU はシリアルデ バイスドライバを動作させます。ドライバは I/O アドレスにあるレジスタを チェックし、何が起きたのかを調べます。そして、シリアルポートの送信バッ ファが空になり、追加のバイトデータを待っていることを知ります。したがっ て、送るべきバイトデータがもっとあれば、CPU は次のバイトデータをシリア ルポートの I/O アドレスに送ります。このバイトは、前のバイトがまだ送信 シフトレジスタ内にあり、1 ビットずつ送られている間に届かなくてはなりま せん。
復習すると、1 バイトがシリアルポートの送信線へ完全に送り出されると、 シフトレジスタは空になり、以下の 3 つの動作がほとんど同時に起こります:
このように、シリアルポートは割り込み駆動(interrupt driven)であると言えます。 シリアルポートが割り込みを発行する度に、CPU は次のバイトを送ります。 CPU が 1 バイトを送信バッファに送ると、次の割り込みを受け取るまで、CPU は他の処理を自由に行うことができます。シリアルポートはユーザ(あるいは アプリケーションプログラム)が選択した 固定の速度でビット列を送信します。この速度はボーレートと呼ばれることも あります。シリアルポートはバイトごとに追加のビット(スタートビット、ス トップビット、場合によってはパリティビットも)も付け加えるので、多 くの場合、1 バイトごとに 10 ビットのデータが送られます。したがって、通 信レート(速度とも言われます) 19,200 ビット/秒(bps, bit per second)は 1,920 バイト/秒(1,920 割り込み/秒)ということになります。
この処理を全て行うことは CPU にとっても重い負担です。これは色々な理由 から言えます。まずは、32 ビット(64 ビットのことだってあります)のバス上 で一回に 1 バイト(8 ビット)のデータしか送らないので、バス幅をあまり有 効に使っているとは言えません。また、割り込みを送る度に大きなオーバーヘッ ドが生じます。割り込みを受け取った場合でも、デバイスドライバに分かるこ とは何かがシリアルポートで割り込みを起こしたことだけであり、文字が送ら れたことが理由であることまでは分かりません。何が起こったかを調べるには、 デバイスドライバは色々なチェックを行わなければなりません。同じ割り込み が起こっても、文字を受け取ったのかもしれませんし、制御線の状態が変化し たのかもしれません。
重要な解決方法の一つは、シリアルポートのバッファの大きさを 1 バイトか ら 16 バイトに増やすことでした。 つまり、CPU は割り込みを受け取ると、シリアルポート に 16 バイトまでのデータを新たに送ることができます。これにより割り込み の発行は減りますが、太いバスにもかかわらず 1 バイトずつしかデータを送 れない問題はそのままです。16 バイトのバッファは実際には FIFO (First In First Out, 先入れ先出し)のキューであり、よく FIFO と呼ばれます。 FIFO の詳細については FIFO をご覧ください。 これまでの説明も一部繰り返してあります。
シリアルポートによるバイト列の受信は送信とほぼ同じで、方向が逆になっ ているだけです。受信も割り込み駆動です。受信バッファを 1 バイトしか持 たない古い型のシリアルポートでは、外部ケーブルから 1 バイトのデータ全体を受け取ると、こ のデータは(大きさが 1 バイトである)受信バッファに入ります。するとシリ アルポートは CPU に割り込みをかけてそのデータを取り込ませ、現在受信中 の次のデータを格納できる場所が空くようにします。16 バイトのバッファを持っ ている新しいシリアルポートの場合は、この(バイトデータを取得するための) 割り込みはバッファに 14 バイトのデータが蓄積した時点で発行されます。す ると CPU は現在の処理を一旦止め、14 から 16 バイトのデータをシリアルポー トから取り出します。14 番目のバイトが受け取られた時に送られた割り込み に対し、これが起きてからさらに 2 つのバイトデータが届くと、受け取るべ きバイトの個数は 16 バイトになるかもしれません。しかし(2 バイトではな く) 3 バイト届いてしまうと、16 バイトのバッファは溢れてしまいます。 14 バイト未満のデータを取り込むことも、そのように設定することや タイムアウトによりできます。詳しくは FIFO を見 てください。
今まではシリアルポートが持っている 16 バイトの小さいハード ウェアバッファについて説明しましたが、メインメモリにはもっと大きな バッファもあります。CPU はいくつかのバイトデータを ハードウェアの受信バッファから取り込むと、これらのデータをメイン メモリ中のずっと大きな(例えば 8K バイトの)バッファに入れます。したがって、 シリアルポートからデータを受け取るプログラムは、この大きなバッファから (プログラム中で "read" 文を用いて)データを取り出します。送信するデータ についても同じことが言えます。CPU がデータをいくらか送る必要がある場合、 CPU はメインメモリにある大きい(8K バイトの)送信バッファからデータを取 り出し、ハードウェアが持つ 16 バイトの小さな送信バッファに入れます。