Shoeisha Technology Media

CodeZine(コードジン)

特集ページ一覧

ファイルディスクリプタについて(7)
~シグナル駆動I/Oの紹介

第7回

  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加
2010/03/12 14:00

 ファイルディスクリプタは、プログラムの外部との入出力を行う為の抽象的なインタフェースです。Unix/Linux におけるファイルディスクリプタは、一般的なファイルだけではなく、デバイスやソケットやパイプも対象としております。当レポートは、ファイルディスクリ プタにまつわる色んな機能と管理方法等を提示したいと思っております。

目次

はじめに

 前回は、各種多重I/Oの紹介や性能値を測定し、傾向を紹介しました。今回は、ディスクリプタの入出力イベントで発生するシグナルイベントを補足することでデータを取得する入出力モデルについて説明していきたいと思います。

連載概要

 サンプルプログラムは100行前後程度までは画面に記載します。全プログラムは圧縮してページ上部よりダウンロード可能にしています。makeコマンドでコンパイルできます。i386/x86_64環境で動作確認済みです。

 プログラムのボリューム上、エラー処理や引数チェックなどを省いているので、あらかじめご了解ください。また使用法を誤るとシステムに重大な影響を与える可能性があります。利用する場合は責任のとれる環境において実行するよう、お願いします。

 当トピックでは、実際にプログラムを通して動作確認や性能測定を行うことで、個人的な見解を述べさせていただきます。あくまで個人的な感想に基づいているので、反論や指摘などあるかと思います。指摘や質問などは大歓迎なので、その際はぜひご連絡ください。可能な限りの対応に努めます。

シグナル駆動I/Oについて

 シグナル駆動I/Oとは、ディスクリプタ上で発生する入出力イベントとして発生するSIGIOを捕捉するためのシグナルハンドラを用意し、ハンドラ内でファイルディスクリプタからデータを取得するI/Oです。

 シグナルハンドラでは複雑で時間のかかる処理はなるべく回避しなくてはいけません。複数のファイルディスクリプタが存在する時、シグナルハンドラ内では、どのファイルディスクリプタからデータを吸い上げるのか調べる手間がかかってしまいます。さらに、TCPソケットの場合はコネクションを行った時や切断時にもSIGIOが発生し、かつハンドラ内ではどのような理由で呼ばれたのかまでは分かりません。

 そこで利用する局面としては、プロセス中に使用するファイルディスクリプタはなるべく少なく(できれば1個だけ)、かつコネクションレスなディスクリプタである必要があります。

 もちろんシグナルハンドラを使用することによるデメリットも、そのまま適用されてしまいます。

 また、シグナルはキューイングされないので、ハンドラ内でデータ処理中に同じシグナルが2回連続で発生すると、処理されるデータは1回だけになってしまい、2回目のデータが読みだされません。

 それを回避するため、ディスクリプタは非ブロッキング設定を行う必要があります。

 以上のような縛りもあるので、使用できる条件がかなり限られますが、メインプロセスとは別の場所でデータの送受信を行いたい場合には利用できるでしょう。

サンプルプログラム

 サンプルプログラムは次のとおりです。

sigio_sample.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>
#include <errno.h>
#include <sys/ioctl.h>
in_port_t make_udp_sock( );
void regist_signal( int signo, void ( *func )( int sig ));
void sig_io( int signo );
void * client_threaed( void * arg );
int  sock = 0;

int main( ) {

    in_port_t port = make_udp_sock( );
    regist_signal( SIGIO, sig_io );

    pthread_t pid;
    pthread_create( &pid, 0, &client_threaed, &port );
    pthread_join( pid, 0 );

    close( sock );
    return 0;
}

in_port_t make_udp_sock( ) {

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons( 0 );
    addr.sin_addr.s_addr = INADDR_ANY;
    sock = socket( AF_INET, SOCK_DGRAM, 0 );
    bind( sock, ( struct sockaddr *)&addr, sizeof( addr ));

    fcntl( sock, F_SETOWN, getpid( ));
    fcntl( sock, F_SETFL,  O_ASYNC | O_NONBLOCK );

    socklen_t len = sizeof( addr );
    getsockname( sock, ( struct sockaddr *)&addr, &len );
    return addr.sin_port;
}

void regist_signal( int signo, void ( *func )( int sig )) {

    struct sigaction sa = {
        .sa_handler = func,
        .sa_flags   = SA_RESTART,
    };
    sigemptyset( &sa.sa_mask );
    sigaddset( &sa.sa_mask, signo );
    sigaction( signo, &sa, 0 );
}

void sig_io( int signo ) {
    ( void )signo;

    char getline[1024];
    struct sockaddr_in udpaddr;
    socklen_t len = sizeof( udpaddr );
    while( 1 ) {
        ssize_t rtn = recvfrom( sock, getline, sizeof( getline ), 0,
            ( struct sockaddr * )&udpaddr, &len );
        if( rtn < 0 && errno == EAGAIN ) {
            break;
        }
        printf( "sig_io:[%zd] [%.*s]\n", rtn, ( int )rtn, getline );
    }
}

void * client_threaed( void * arg ) {
    int port = *( unsigned short int * )arg;

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons( ntohs( port ));
    addr.sin_addr.s_addr = htonl( INADDR_LOOPBACK );
    int sock = socket( AF_INET, SOCK_DGRAM, 0 );

    int i;
    for( i = 0; i < 3; i ++ ) {
        sleep( 1 );
        sendto( sock, "hello", 5, 0, ( struct sockaddr *)&addr, sizeof( addr ));
    }
    close( sock );
    return 0;
}

 実装する際のポイントとしては、下記の通りです。

  • SIGIOシグナルイベントに対するハンドラを用意すること
  • プロセス内で管理するディスクリプタは1個、かつグローバルであること
  • ディスクリプタのオーナを設定すること
    (fcntlのF_SETOWN)
  • ディスクリプタでシグナル駆動I/Oを許可すること
    (fcntl F_SETFLでO_ASYNC)
  • ディスクリプタの非ブロッキング設定をすること
    (fcntl F_SETFLでO_NONBLOCK)

  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

  • 赤松 エイト(エイト)

    (株)DTSに勤てます。 WebアプリやJavaやLL等の上位アプリ環境を密かに憧れつつも、ず~っとLinuxとかHP-UXばかり、ここ数年はカーネル以上アプリ未満のあたりを行ったり来たりしています。 mixiもやってまして、こちらは子育てとか日々の日記メインです。

バックナンバー

連載:ファイルディスクリプタについて
All contents copyright © 2005-2019 Shoeisha Co., Ltd. All rights reserved. ver.1.5