Linuxシステムコールの勉強(その9)

Linuxシステムコール

Linuxシステムコール

前回はこちら


今回は、ioctl()の第2,第3引数の正体とioctlの使い方について説明します。


まずはioctl()の引数について。
第1引数は前回説明したとおり、制御するファイルディスクリプタです。
で、問題の第2引数は該当端末に対して要求する処理を示す整数値です。この指定可能な整数値はioctls.hに定義されています。内容はこんな↓感じ。

itotto@itotto > cat /usr/include/asm/ioctls.h

#define TCGETS    0x5401
#define TCSETS    0x5402

...

itotto@itotto > wc -l /usr/include/asm/ioctls.h

 83 /usr/include/asm/ioctls.h

itotto@itotto >

ファイルの行数だけで83行ありますので、要求に使用する設定値も結構な数が定義されているようです。ひとまず今回は以下の二つだけを使用することにします。

記号定数 意味
TCGETA 端末の属性を取得する
TCSETAF 端末の属性を設定する


最後に第3引数についてですが、これは端末の属性値を格納する構造体へのポインタを指定します。



itotto@itotto > cat /usr/include/asm/termios.h

...

#define NCC 8
struct termio 
{
    unsigned short c_iflag  ;  /* input mode flags   */
    unsigned short c_oflag  ;  /* output mode flags  */
    unsigned short c_cflag  ;  /* control mode flags */
    unsigned short c_lflag  ;  /* local mode flags   */
    unsigned short c_line   ;  /* line discripline   */
    unsigned short c_cc[NCC];  /* control characters */
}

...

itotto@itotto >


各メンバーについての説明は以下のとおりです。

メンバー 保持する値 説明
c_iflag 入力についてのフラグ  
 IGNBRK 0000001 入力中のBREAK信号を無視.
 BRKINT 0000002 "IGNBRK" 設定時はBREAK信号に対して "SIGINT" を発生.
未設定時はBREAKを文字\0として読む.
 IGNPAR 0000004 フレーム,パリティエラーを無視.
 PARMRK 0000010 "IGNPAR" 未設定時はパリティ,フレームエラーの発生した文字の前に\377 \0.を付加.
"IGNPAR" も "PARMRK" 共に未設定時は パリティ,フレームエラーの発生した文字を\0として読む.
 INPCK 0000020 入力のパリティチェックを有効化.
 ISTRIP 0000040 ビット目を落とす.
 INLCR 0000100 入力のNLをCRに置換.
 IGNCR 0000200 入力のキャリッジリターン(以下CR)を無視.
 ICRNL 0000400 "IGNCR" が未設定時のみ,入力のCRを改行に置換.
 IUCLC 0001000 入力の大文字を小文字に置換.
 IXON 0002000 出力のXON/XOFFフロー制御を有効化.
 IXANY 0004000 任意の文字を受信した時に再出力を実施.
 IXOFF 0010000 入力のXON/XOFFフロー制御を有効化.
 IMAXBEL 0020000 入力キューが一杯の時にベルを鳴らす.
 IUTF8 0040000 文字コードとしてUTF8を使用.
メンバー 保持する値 説明
c_oflag 出力についてのフラグ  
 OPOST 0000001 実装に依存した出力処理を有効化.
 OLCUC 0000002 出力時に小文字を大文字に変換.
 ONLCR 0000004 出力のNLをCR-NLに置換.
 OCRNL 0000010 出力のCRをNLに置換.
 ONOCR 0000020 0桁目でCRを出力しない.
 ONLRET 0000040 CRを出力しない.
 OFILL 0000100 転送時間を遅らせるのではなく、補填文字(fill character)を送信.
 OFDEL 0000200 補填文字としてASCII DELを送信する
(OFDEL未設定時はASCII NUL)
 NLDLY 0000400 改行の遅延を設定.
(設定値は "NL0"[遅延なし] および "NL1")
  NL0 0000000 NLDLYの設定値
  NL1 0000400  〃 
 CRDLY 0003000 CRの遅延を設定.
(設定値は "CR0"[遅延なし], "CR1", "CR2", "CR3")
  CR0 0000000 CRDLYの設定値
  CR1 0001000  〃 
  CR2 0002000  〃 
  CR3 0003000  〃 
 TABDLY 0014000 水平タブの遅延を設定.(設定値は "TAB0"[遅延なし], "TAB1", "TAB2", "TAB3", "XTABS")
"XTABS" の値はタブをスペース何個に変換するかを示す(タブは8桁毎に止まる)
  TAB0 0000000 TABDLYの設定値
  TAB1 0004000  〃 
  TAB2 0010000  〃 
  TAB3 0014000  〃 
  XTABS 0014000  〃 
 BSDLY 0020000 後退の遅延を設定.
(設定値は "BS0"[遅延なし]あるいは "BS1")
  BS0 0000000 BSDLYの設定値
  BS1 0020000  〃 
 VTDLY 0040000 垂直タブの遅延を設定.
(設定値は "VT0P"[遅延なし]あるいは "VT1")
  VT0 0000000 VTDLYの設定値
  VT1 0040000  〃 
 FFDLY 0100000 ページ送りの遅延を設定.
(設定値は "FF0"[遅延なし]あるいは "FF1")
  FF0 0000000 FFDLYの設定値
  FF1 0100000  〃 
メンバー 保持する値 説明
c_cflag 通信プロトコルについてのフラグ
 CBAUD 0010017 不明
 B0 0000000 CIBAUDの設定値(多分)
 B50 0000001  〃 
 B75 0000002  〃 
 B110 0000003  〃 
 B134 0000004  〃 
 B150 0000005  〃 
 B200 0000006  〃 
 B300 0000007  〃 
 B600 0000010  〃 
 B1200 0000011  〃 
 B1800 0000012  〃 
 B2400 0000013  〃 
 B4800 0000014  〃 
 B9600 0000015  〃 
 B19200 0000016  〃 
 B38400 0000017  〃 
 EXTA B19200 不明
 EXTB B38400 不明
 CSIZE 0000060 文字サイズを設定.
(設定値は "CS5", "CS6", "CS7", "CS8")
 CS5 0000000 CSIZEの設定値
 CS6 0000020  〃 
 CS7 0000040  〃 
 CS8 0000060  〃 
 CSTOPB 0000100 1ストップビットではなく,2ストップビットに設定.
 CREAD 0000200 受信を有効化.
 PARENB 0000400 出力にパリティを付加し,入力のパリティチェックを実施.
 PARODD 0001000 入力および出力を奇数パリティチェック.
 HUPCL 0002000 最終プロセスがDeviceをCloseした後,モデムの制御線をlowにする(切断)
 CLOCAL 0004000 モデムの制御線を無視.
 CBAUDEX 0010000 不明
 B57600 0010001 不明
 B115200 0010002 不明
 B230400 0010003 不明
 B460800 0010004 不明
 B500000 0010005 不明
 B576000 0010006 不明
 B921600 0010007 不明
 B1000000 0010010 不明
 B1152000 0010011 不明
 B1500000 0010012 不明
 B2000000 0010013 不明
 B2500000 0010014 不明
 B3000000 0010015 不明
 B3500000 0010016 不明
 B4000000 0010017 不明
 CIBAUD 002003600000 入力速度を制御
 CMSPAR 010000000000 不明
 CRTSCTS 020000000000 フロー制御を実施
メンバー 保持する値 説明
c_lflag 文字を出力する前のローカル処理についてのフラグ  
 ISIG 0000001 INTR, QUIT, SUSP, DSUSP の文字受信時に対応するシグナルを発生.
 ICANON 0000002 canonicalモードを有効化.
(特殊キャラクタ EOF, EOL, EOL2, ERASE, KILL, REPRINT, STATUS, WERASE およびラインバッファが有効化される)
 XCASE 0000004 "ICANON" 設定時,端末は大文字のみが有効となる.
入力された文字は\が付いた文字を除いて小文字に変換.
出力時は大文字の前に\が付き,小文字は大文字に変換.
 ECHO 0000010 入力キャラクタのエコーバック有効化.
 ECHOE 0000020 "ICANON" 設定時,ERASE 文字は前の文字を削除し,WERASE 文字は前の単語を削除.
 ECHOK 0000040 "ICANON" 設定時,KILL 文字は現在の行を削除.
 ECHONL 0000100 "ICANON" 設定時,ECHO が設定されていなくても NL 文字をエコー
 NOFLSH 0000200 "SIGINT", "SIGQUIT" シグナル発生時の入力および出力キューのフラッシュを無効化.
"SIGSUSP" シグナル発生時の入力キューのフラッシュを無効化.
 TOSTOP 0000400 バックグラウンドプロセスのプロセスグループで,制御端末へ文字を出力しようとしているプロセスに対して "SIGTTOU" シグナルを送信.
 ECHOCTL 0001000 "ECHO" 設定時,TAB, NL, START, STOP の ASCII 制御文字が ^X としてエコー.
Xは制御文字に対して ASCII コードで0x10大きな文字を表示
[例]
文字 0x28 (BS) は ^H とエコーする.
 ECHOPRT 0002000 "ICANON" および "IECHO" 設定時,削除された文字も表示.
 ECHOKE 0004000 "ICANON" 設定時, KILL が行の各文字を消去する代わりにエコーされる.
"ECHOE" および "ECHOPRT" を指定することと等価.
 FLUSHO 0010000 出力をフラッシュ.
(DISCARD 文字を入力することで切替え可能)
 PENDIN 0040000 次の文字を読み,入力キュー中の全文字を再表示.
 IEXTEN 0100000 実装依存の入力処理を有効化.
メンバー 保持する値 説明
c_cc[NCC] 制御文字の配列  
 VINTR 0 INTR 文字
 VQUIT 1 QUIT 文字
 VERASE 2 ERASE 文字
 VKILL 3 KILL 文字
 VEOF 4 EOF 文字
 VTIME 5 TIME 値(キー入力を受け付ける時間(1/10[sec])
 VMIN 6 バッファをフラッシュするデータ単位数
 VSWTC 7 不明
 VSTART 8 START 文字
 VSTOP 9 STOP 文字
 VSUSP 10 SUSP 文字
 VEOL 11 EOL 文字
 VREPRINT 12 不明
 VDISCARD 13 不明
 VWERASE 14 WERASE 文字
 VLNEXT 15 LNEXT 文字
 VEOL2 16 EOL2 文字


c_lineについては調べてみたもののよく分からなかったのでとりあえずここには載せません。あといくつか分からないフラグもあったので、それは不明としておきました。


以上がioctl()の引数です。この3つの引数を使用して端末制御を行います。


端末のエコーを止めてみる

sshなどで端末へ接続した時に、キーボードで入力した文字がそのままディスプレイに表示されます。このことを端末のエコーと呼びます。当たり前の動作なので、常時エコーしているのかと思いきや一部そのエコーが止まるときがあります。例えばパスワードを入力する時などです。それがどのように実現されているのか試してみます。

      1 #include <sys/ioctl.h>
      2 #include <asm/termbits.h>
      3 #include <stdio.h>
      4
      5 int main()
      6 {
      7     char buff[BUFSIZ];
      8     struct termio tm, tm_save;
      9
     10     // 現在の設定を取得 //
     11     ioctl(fileno(stdin), TCGETA, &tm);
     12
     13     // 現在の設定を一時保存 //
     14     tm_save = tm;
     15
     16     // ECHOを止める //
     17     tm.c_lflag &= ~ECHO;
     18
     19     // 改行だけはエコーする //
     20     tm.c_lflag |= ECHONL;
     21
     22     ioctl(fileno(stdin), TCSETAF, &tm);
     23
     24     printf("パスワード?");
     25     fgets(buff, BUFSIZ, stdin);
     26     buff[strlen(buff) - 1] = '\0';
     27
     28     printf("パスワードは %s です\n", buff);
     29
     30     // 元に戻す //
     31     ioctl(fileno(stdin), TCSETAF, &tm_save);
     32
     33     return 0;
     34 }


ioctl()で現在の設定を取得し、その一部(今回はc_lflagのECHO,ECHONLフラグ)を変更して設定しなおしています。変更の方法にはビット演算を使っているので、その部分が分からない場合はCの参考書を読んでみてください。

それで、最初にこのテストプログラムを作ったときに2点はまりました。

    1. 標準入力に対するエコーがなくなってしまった
    2. ECHO,ECHONLがundeclaredと言われる

1.についてはちなみに30行目にあるように、プログラム実行時の設定値を元に戻さないでいると、ログオフするまでずっとエコーされないという悲しい状態に陥ることが分かりました。なので必ず最後には戻すように気をつけましょう。
2.については書籍ではsys/ioctl.hをincludeすれば大丈夫と書いてありましたが、最近変わったのかこれではダメなようです。asm/termbits.hにc_lflagの定数定義があったのを覚えてたので(上の表を作るときに見つけました)これをincludeして解消しました。これについてはもう少し調べてみます。


プログラムを実行した場合はこんな感じの動きになります。

[itotto@itotto ]$ gcc -o no_echo no_echo.c
[itotto@itotto ]$ ./no_echo
パスワード?        ← HOGEHOGE と入力しても表示なし
パスワードは HOGEHOGE です

[itotto@itotto ]$


本当はCOOKEDモードとRAWモードまで説明したかったのですが、長くなったのでそれはまた次回。


(参考ページ)


次へ進む