UNIX 6th code reading - 対話型端末

はじめに

今回は対話型端末の処理方法を見ていきます。

Lions本で言うと24章にあたります。

対話型端末とは

対話型端末とは何なのか、まずは調べてみました。

テレタイプ端末というのがそれにあたるようです。

http://ja.wikipedia.org/wiki/%E3%83%86%E3%83%AC%E3%82%BF%E3%82%A4%E3%83%97%E7%AB%AF%E6%9C%AB

上記リンク先に載っている写真のように、キーボード(タイプライタ)とディスプレイ(プリンタ)が1つになったような装置です。

YouTubeにテレタイプ端末の1つであるASR-33という端末を使ってPDP11/40を操作している動画がありました。

この動画で、テレタイプ端末がどういうものかわかったと思います。

ttyというキーワードが出てきますが、これはテレタイプ端末(tele type writer)から来ているようです。今でもUNIX系のコマンドにttyコマンドというものがあります。

インターフェイス

テレタイプ端末とPDP11の間のインターフェイスに当時使用されていたのがKLやDLです。

端末、インターフェイス、PDP11の関係を絵で描くとこのような感じになります。


tty構造体

ここからカーネルの実装を見ていきます。

7926行目からtty構造体が定義されていて、この構造体を使用して端末制御のコードが実装されています。

struct tty
{
    struct clist t_rawq;
    struct clist t_canq;
    struct clist t_outq;
    int    t_flags;
    int    *t_addr;
    char   t_delct;
    char   t_col;
    char   t_erase;
    char   t_kill;
    char   t_state;
    char   t_char;
    int    t_speeds;
    int    t_dev;
};

最初の3つのclistはデータ転送に使用するキューです。各キューの使い方は、次回のエントリで確認する予定です。

バイスに依存しないコードはtty.cに実装され、デバイスに依存するコードは各デバイスのドライバに実装されています。

sttyシステムコール

sttyシステムコールを使って、カーネルで使用しているtty構造体の一部データをセットすることができます。

tty構造体のうち、データをセットできるのは以下の要素です。フラグ、削除文字(デフォルトでは'#')、行クリア文字(デフォルトでは'@')、動作スピードを制御できます。

    int    t_flags;
    char   t_erase;
    char   t_kill;
    int    t_speeds;

ユーザプログラムで、以下のarg構造体に値を入れ、それをsttyシステムコールに渡すことで端末の設定を行うことができます。

struct {
    char ispeed, ospeed;
    char erase, kill;
    int  mode;
} *arg;
stty( )

stty( )(8183行目)はsttyシステムコールのハンドラです。端末の情報を設定します。

端末から情報を取得するgttyシステムコールとは、処理がほとんど共通で、処理の大部分を共通処理関数であるsgtty( )に任せています。

sgtty( )は、端末に対応したinodeを取得し(ユーザからファイルディスクリプタが渡され、u.u_ofile->file->inodeと辿ることで取得できる。inodeはキャラクタ型でないといけない)、そこからdevice Noを取得します。

device Noが取得できるとmajor番号とminor番号がわかります。major番号を使ってキャラクデバイスドライバのtty制御ハンドラ(d_sgtty( ))を呼び出し、その中でminor番号を使って適切なttyを取得し、情報の設定・取得を行います。

gttyシステムコール

gttyシステムコールを使って、カーネルで使用しているtty構造体の一部データを取得することができます。

上記arg構造体をgttyシステムコールに渡すと、現在の端末の情報が格納されて返ってきます。

gtty( )

gtty( )(8165行目)はgttyシステムコールのハンドラです。

stty( )との共通処理関数であるsgtty( )を実行して処理の大部分を任せます。

情報が取得できたら、ユーザから渡されたarg構造体に結果を格納してユーザに返します。

KLデバイスドライバ

次にKLのデバイスドライバ(unix/kl.c)を見ていきます。

KLのレジスタ

ソースを見る前に、KLが持っているレジスタを確認します。

2bytesのレジスタを4つ持っています。受信、送信でそれぞれステータスとデータを扱うレジスタがあります。

  • 受信status register(rcsr)
  • 受信data buffer register(rbuf)
  • 送信status register(tcsr)
  • 送信data buffer register(tbuf)


デバイスドライバの中では8008行目あたりでKLのアドレスが設定され、レジスタアクセス用の構造体が定義されています。

#define KLADDR 0177560
#define KLBASE 0177500

struct klregs {
    int klrcsr;
    int klrbuf;
    int kltcsr;
    int kltbuf;
}

KL端末は複数存在し得、各端末ごとに独立したアドレスを持っています。KLADDR, KLBASE, 端末のmajor, minor番号から適切なアドレスを決定し、addr->klrcsrなどとすることで目的のレジスタを触ることができます。

tty構造体

KLのデバイスドライバではtty構造体の配列であるkl11[ ]が定義されており(8015行目)、端末の個数分tty構造体が用意されます。各端末に対応するminor番号を使って、kl11[ ]から適切なtty構造体を取得して使用します。

open, close処理

klopen( )(8023行目), klclose( )(8055行目)が端末のopen, close処理を行うハンドラです。

端末情報は子プロセスに引き継がれる(forkではu.u_ofileとp.p_ttypはコピーされる)ので、通常は一度しかopen処理は行われません。

また、その端末を使用しているプロセスが全ていなくなるとき(ログアウト時やpower off時?)にclose処理が行われます。

klopen( )はtty構造体とKLのレジスタに適切な値をセットします。klclose( )はtty構造体を初期化します。


その他の読み、書き、読み書き割り込みハンドラは次回のエントリで見ていく予定です。

コードメモ

stty( )
  • 8187-8190 : u.u_arg[0]に上で書いたarg構造体のポインタが格納されている。u.u_arg[0]がspeedを、u.u_arg[1]がerase, killを、u.u_arg[2]がmodeを指すように設定
  • 8191 : sgtty( )を呼び出す
gtty( )
  • 8171 : tty構造体のデータを格納するためのint[3]のポインタを引数にsgtty( )を実行
  • 8172-8173 : sgtty( )実行中にエラーがあればreturn
  • 8174-8177 : 引数で渡されたarg構造体に、tty構造体から取得したデータを格納する
sgtty( )
  • arguments
    • v : set, get flag. 0 -> set, 1 -> get. d_sgtty( )の引数に使われる
  • 8206-8207 : userのr0にファイルディスクリプタが格納されているので、getf( )を使って対応するfile構造体を取得する。失敗したらエラー
  • 8208 : file構造体が指すinodeを取得
  • 8209-8211 : inodeがキャラクタ型でなかったら、ENOTTY(ttyではない)エラーでreturn
  • 8213 : cdevsw[ ].d_sgtty( )を実行。例えばklsgtty( )が呼ばれる(4669行目を参照)
klsgtty( )
  • arguments
    • dev : device No
    • v : set, get flag. ttystty( )の引数に使われる
  • 8093 : tty構造体の配列であるkl11[ ]から、KL用のtty構造体を取得する
  • 8094 : 8093行目で取得したtty構造体と引数のvを使ってttystty( )を呼ぶ
ttystty( )
  • arguments
    • atp : tty構造体へのポインタ
    • av : read, write flag
  • 8582 : 引数のflagが0でなければ(tty構造体への値セットならば)
    • 8583-8587 : tty構造体のspeed, erase, kill, flagsをセットし、1でreturn.
  • 8589 : tty構造体からデータを読み出す場合。wflushtty( )を実行し、キューを空に
  • 8590-8595 : ユーザから渡されたarg構造体のデータをtty構造体(実際はint[3]. gtty( )参照)へ格納し、0でreturn.
klopen( )
  • arguments
    • dev : device No
    • flag :
  • 8026-8028 : minor番号の妥当性チェック。問題があればENXIOエラーでreturn
  • 8030 : kl11[ ]から、minor番号を使って、扱うべきtty構造体を取得
  • 8031-8033 : 端末をopenしようとしているプロセスが端末を持っていなければ、先ほど取得したtty構造体を割り当て。tty構造体にdevice Noをセット
  • 8039-8044 : 端末のデバイスのregisterのアドレス計算
  • 8045 : 端末の状態をチェックし、オープンしていなければ
    • 8046-8049 : 端末の状態初期設定(tty構造体)
  • 8051 : 端末のregisterセット
klclose( )
  • arguments
    • dev : device No
  • 8057 : kl11[ ]から適切なtty構造体を取得
  • 8058 : wflushtty( )を使って、tty構造体が持つキューを空にする
  • 8059 : tty構造体のstateを0クリア

終わりに

今回は対話型端末処理の外枠を中心に見てきました。次回は実際に読み書き処理を見ていきます。

次回でUNIX 6th code readingのエントリも最終回の予定です。