前のページ 次のページ 目次

3. プログラム例

全ての例は miniterm.c から取り出したものです。先行入力のバッ ファは 255 文字に制限されています。これは、カノニカル入力処理の最大文 字長と同じです (<linux/limits.h> あるいは <posix1_lim.h>を参照)。

それぞれの入力モードの使い方についてはプログラム中のコメントを見てくだ さい。プログラムは十分読みこなせると思います。カノニカル入力モードの例 は一番詳しいコメントを付けています。違いを強調するために、他の例につい てはカノニカル入力モードとの相違点だけを書いています。

説明は完全ではありませんが、勇気を出して例題で実験し、あなたの目的に最 も適した解を見つけだしてください。

使用するシリアルポートには、適切なパーミッションを与えるのを忘れないで ください(例: chmod a+rw /dev/ttyS1)。

3.1 カノニカル入力処理

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <stdio.h>

/* <asm/termbits.h> で定義されているボーレートの設定。これは
<termios.h>からインクルードされる。 */
#define BAUDRATE B38400            
/* 適切なシリアルポートを指すように、この定義を変更する。*/
#define MODEMDEVICE "/dev/ttyS1"
#define _POSIX_SOURCE 1 /* POSIX 準拠のソース */

#define FALSE 0
#define TRUE 1

volatile int STOP=FALSE; 

main()
{
  int fd,c, res;
  struct termios oldtio,newtio;
  char buf[255];
/*
  読み書きのためにモデムデバイスをオープンする。ノイズによって CTRL-C 
  がたまたま発生しても接続が切れないように、tty 制御はしない。
*/

 fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY ); 
 if (fd <0) {perror(MODEMDEVICE); exit(-1); }

 tcgetattr(fd,&oldtio); /* 現在のシリアルポートの設定を待避させる*/
 bzero(newtio, sizeof(newtio)); /* 新しいポートの設定の構造体をクリアする */

/* 
  BAUDRATE: ボーレートの設定。cfsetispeed と cfsetospeed も使用できる。
  CRTSCTS : 出力のハードウェアフロー制御 (必要な結線が全てされているケー
            ブルを使う場合のみ。Serial-HOWTO の7章を参照のこと)
  CS8     : 8n1 (8 ビット、ノンパリティ、ストップビット 1)
  CLOCAL  : ローカル接続、モデム制御なし
  CREAD   : 受信文字(receiving characters)を有効にする。
*/
 newtio.c_cflag = BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD;
 
/*
  IGNPAR  : パリティエラーのデータは無視する
  ICRNL   : CR を NL に対応させる(これを行わないと、他のコンピュータで 
            CR を入力しても、入力が終りにならない)
  それ以外の設定では、デバイスは raw である(他の入力処理は行わない)
*/
 newtio.c_iflag = IGNPAR | ICRNL;
 
/*
 Raw 出力
*/
 newtio.c_oflag = 0;
 
/*
  ICANON  : カノニカル入力を有効にする
  全てのエコーを無効にし、プログラムに対してシグナルは送らせない
*/
 newtio.c_lflag = ICANON;
 
/* 
  全ての制御文字を初期化する
  デフォルト値は /usr/include/termios.h を見れば分かるが、コメントに書
  いてあるので、ここでは見る必要はない。
*/
 newtio.c_cc[VINTR]    = 0;     /* Ctrl-c */ 
 newtio.c_cc[VQUIT]    = 0;     /* Ctrl-\ */
 newtio.c_cc[VERASE]   = 0;     /* del */
 newtio.c_cc[VKILL]    = 0;     /* @ */
 newtio.c_cc[VEOF]     = 4;     /* Ctrl-d */
 newtio.c_cc[VTIME]    = 0;     /* キャラクタ間タイマを使わない */
 newtio.c_cc[VMIN]     = 1;     /* 1文字来るまで、読み込みをブロックする */
 newtio.c_cc[VSWTC]    = 0;     /* '\0' */
 newtio.c_cc[VSTART]   = 0;     /* Ctrl-q */ 
 newtio.c_cc[VSTOP]    = 0;     /* Ctrl-s */
 newtio.c_cc[VSUSP]    = 0;     /* Ctrl-z */
 newtio.c_cc[VEOL]     = 0;     /* '\0' */
 newtio.c_cc[VREPRINT] = 0;     /* Ctrl-r */
 newtio.c_cc[VDISCARD] = 0;     /* Ctrl-u */
 newtio.c_cc[VWERASE]  = 0;     /* Ctrl-w */
 newtio.c_cc[VLNEXT]   = 0;     /* Ctrl-v */
 newtio.c_cc[VEOL2]    = 0;     /* '\0' */

/* 
  モデムラインをクリアし、ポートの設定を有効にする
*/
 tcflush(fd, TCIFLUSH);
 tcsetattr(fd,TCSANOW,&newtio);

/*
  端末の設定終了。入力を処理するできるようになった。
  例では、行の先頭に 'z' を入力することでプログラムを終了させる
*/
 while (STOP==FALSE) {     /* 終了条件が満たされるまでループ */
/*
    255文字以上入力された場合でも、行終端文字が入力されるまでは、プロ
    グラムの実行は read でブロックされる。読み込んだ文字数が、実際に入
    力されている文字数より少ない場合には、次回の read で残りの文字が読
    み込まれる。変数 res には実際に読み込まれた文字数がセットされる。
*/
    res = read(fd,buf,255); 
    buf[res]=0;             /* printf で使うため、文字列の終端をセットする */
    printf(":%s:%d\n", buf, res);
    if (buf[0]=='z') STOP=TRUE;
 }
 /* ポートの設定をプログラム開始時のものに戻す */
 tcsetattr(fd,TCSANOW,&oldtio);
}

3.2 非カノニカル入力処理

非カノニカル入力処理モードでは、入力を行としてまとめることは行われず、 入力処理(erase, kill, delete 等)も行われません。このモードの制御は2つ のパラメータで行います。 c_cc[VTIME]でキャラクタタイマの設定を行い、 c_cc[VMIN]では読み込みを満足する前に受け取る最小の文字数を設定 します。

MIN > 0 で TIME = 0 の場合、MIN で読み込みが満足する前に受け取る文 字数を設定します。TIME はゼロなので、タイマは使われません。

MIN = 0 で TIME > 0 の場合には、TIME はタイムアウト値になります。読 み込みは1文字読み込まれた場合か、時間 TIME (待ち時間 = TIME * 0.1 秒) を過ぎた場合に満足されます。時間を越えた場合には、文字は返されません。

MIN > 0 で TIME > 0 の場合には、TIME はキャラクタ間タイマの設定 になります。読み込みは MIN 文字受け取った場合か、2つの文字を受け取る間 の時間が TIME を越えた場合に満足されます。タイマは1文字受け取るごとに 初期化されます。また、最初の1文字を受け取るまではタイマは有効にはなり ません。

MIN = 0 かつ TIME = 0 の場合には、読み込みは即座に満足されます。現在読 み込み可能な文字数か、要求した文字数が戻されます。Antonio さんによれば (contributions 参照)、読み込みの前に fcntl(fd, F_SETFL,FNDELAY); を実行することで、同じ結果を得ることができます。

newtio.c_cc[VTIME]newtio.c_cc[VMIN] を変更するこ とで、以上のモードを全て試すことができます。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <stdio.h>

#define BAUDRATE B38400
#define MODEMDEVICE "/dev/ttyS1"
#define _POSIX_SOURCE 1 /* POSIX 準拠のソース */
#define FALSE 0
#define TRUE 1

volatile int STOP=FALSE; 

main()
{
  int fd,c, res;
  struct termios oldtio,newtio;
  char buf[255];

 fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY ); 
 if (fd <0) {perror(MODEMDEVICE); exit(-1); }

 tcgetattr(fd,&oldtio); /* 現在のポート設定を待避 */

 bzero(newtio, sizeof(newtio));
 newtio.c_cflag = BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD;
 newtio.c_iflag = IGNPAR;
 newtio.c_oflag = 0;

 /* 入力モードの設定 (非カノニカル、エコー無し、…) */
 newtio.c_lflag = 0;
 
 newtio.c_cc[VTIME]    = 0;   /* キャラクタ間タイマは未使用 */
 newtio.c_cc[VMIN]     = 5;   /* 5文字受け取るまでブロックします */

 tcflush(fd, TCIFLUSH);
 tcsetattr(fd,TCSANOW,&newtio);


 while (STOP==FALSE) {       /* 入力ループ */
   res = read(fd,buf,255);   /* 5 文字入力されたら戻ります */
   buf[res]=0;               /* printf を使うためです */
   printf(":%s:%d\n", buf, res);
   if (buf[0]=='z') STOP=TRUE;
 }
 tcsetattr(fd,TCSANOW,&oldtio);
}

3.3 非同期入力

#include <termios.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/signal.h>
#include <sys/types.h>

#define BAUDRATE B38400
#define MODEMDEVICE "/dev/ttyS1"
#define _POSIX_SOURCE 1 /* POSIX 準拠のソース */
        
#define FALSE 0
#define TRUE 1

volatile int STOP=FALSE; 

void signal_handler_IO (int status);   /* シグナルハンドラの宣言 */
int wait_flag=TRUE;                    /* シグナルを受け取っていなければ TRUE */
                        

main()
{
  int fd,c, res;
  struct termios oldtio,newtio;
  struct sigaction saio;           /* シグナルを受けた時の動作を定義 */
  char buf[255];

  /* デバイスを非ブロッキングモードでオープンする
     (read は即座に戻ってくるようになる) */
  fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY | O_NONBLOCK);
  if (fd <0) {perror(MODEMDEVICE); exit(-1); }

  /* デバイスを非同期モードにする前に、シグナルハンドラを設定する */
  saio.sa_handler = signal_handler_IO;
  saio.sa_mask = 0;
  saio.sa_flags = 0;
  saio.sa_restorer = NULL;
  sigaction(SIGIO,&saio,NULL);
  
  /* プロセスが SIGIO を受け取れるようにする */
  fcntl(fd, F_SETOWN, getpid());
  /* ファイルデスクリプタを非同期モードにする (オンラインマニュアルに
     よれば、O_APPEND と O_NONBLOCK だけが F_SETFL で動作するとのこと… 
   */
  fcntl(fd, F_SETFL, FASYNC);

  tcgetattr(fd,&oldtio); /* 現在のポートの設定を待避する */
  /* 新しいポートの設定をカノニカル入力処理に設定する */
  newtio.c_cflag = BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD;
  newtio.c_iflag = IGNPAR | ICRNL;
  newtio.c_oflag = 0;
  newtio.c_lflag = ICANON;
  newtio.c_cc[VMIN]=1;
  newtio.c_cc[VTIME]=0;
  tcflush(fd, TCIFLUSH);
  tcsetattr(fd,TCSANOW,&newtio);
 
  /* 入力待ちの間ループします。通常はここで何らかの作業をする */
  while (STOP==FALSE) {
    printf(".\n");usleep(100000);
    /* SIGIO を受け取り、wait_flag = FALSE になっていれば入力が利用可
       能なので読み込みを行うことができる */
       
    if (wait_flag==FALSE) { 
      res = read(fd,buf,255);
      buf[res]=0;
      printf(":%s:%d\n", buf, res);
      if (res==1) STOP=TRUE; /* CR だけが入力された場合にループ終了 */
      wait_flag = TRUE;      /* 次の入力を待つ */
    }
  }
  /* プログラム開始時のポート設定を復元する */
  tcsetattr(fd,TCSANOW,&oldtio);
}

/**********************************************************************
* シグナルハンドラ。上記のループで文字を受け取ったことを示すために、  *
* wait_flag に FALSE をセットする。                                   *
***********************************************************************/

void signal_handler_IO (int status)
{
  printf("received SIGIO signal.\n");
  wait_flag = FALSE;
}

3.4 複数の入力からの入力待ち

この章は簡単に済ませます。ヒントを示すことが目的なので、サンプルのプロ グラムも短いものにしています。この仕組はシリアルポートだけでなく、全て のファイルデスクリプタの集合に対して使うことができます。

select システムコールとこれに伴うマクロでは、fd_set を使いま す。これは、全ての有効なファイルデスクリプタのエントリーが含まれるビッ ト列です。select はファイルデスクリプタに対応するビット集合 fd_setを引数とし、fd_setに入出力が可能なファイルデス クリプタ、あるいは例外が発生したファイルデスクリプタの集合をセットしま す。fd_set の操作は用意されているマクロで行います。詳しくはオ ンラインマニュアルの select(2) を参照してください。

#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

main()
{
   int    fd1, fd2;  /* 入力ソース 1, 2 */
   fd_set readfs;    /* ファイルデスクリプタの集合 */
   int    maxfd;     /* 使われているファイルデスクリプタの最大値 */
   int    loop=1;    /* ループしている間は TRUE */

   /* open_input_source ではデバイスのオープン、正しいポートの設定が行わ
      れ、ファイルデスクリプタが戻し値になっているものとする。 */
   fd1 = open_input_source("/dev/ttyS1");   /* COM2 */
   if (fd1<0) exit(0);
   fd2 = open_input_source("/dev/ttyS2");   /* COM3 */
   if (fd2<0) exit(0);
   maxfd = MAX (fd1, fd2)+1;  /* 調べるビット長の最大値 */

   /* 入力のためのループ *'/
   while (loop) {
     FD_SET(fd1, &readfs);  /* ソース 1 を調べることを指定 */
     FD_SET(fd2, &readfs);  /* ソース 1 を調べることを指定 */
     /* 入力が可能になるまでブロックされます */
     select(maxfd, &readfs, NULL, NULL, NULL);
     if (FD_ISSET(fd1))         /* ソース 1 からの入力が可能 */
       handle_input_from_source1();
     if (FD_ISSET(fd2))         /* ソース 2 からの入力が可能 */
       handle_input_from_source2();
   }

}   

この例では、どちらかのソースから入力が行われるまで、プログラムがいつま でもブロックされてしまいます。入力のタイムアウトを指定したい場合には、 select システムコールの部分を次のように書き換えます。

int res;
struct timeval Timeout;

/* 入力ループでのタイムアウト値を設定 */
Timeout.tv_usec = 0;  /* ミリ秒 */
Timeout.tv_sec  = 1;  /* 秒 */
res = select(maxfd, &readfs, NULL, NULL, &Timeout);
if (res==0)
/* 入力があったファイルデスクリプタの数が 0 ならば、タイムアウト発生 */

This example will timeout after 1 second. If a timeout occurs, select will return 0, but beware that Timeout is decremented by the time actually waited for input by select. If the timeout value is zero, select will return immedeatly.

この例では 1 秒後にタイムアウトになります。タイムアウトとなった場合に は、select は 0 を返しますが、Timeoutselectが実際に入力を待った時間の分だけ減らされることに注意し てください。タイムアウト値がゼロの場合には、select は即座に帰っ てきます。


前のページ 次のページ 目次