信号驱动io

来源:互联网 发布:身份证找回淘宝账号 编辑:程序博客网 时间:2024/04/29 16:41

5.4 信号驱动I/O
使用信号驱动I/O时,当网络套接字可读后,内核通过发送SIGIO信号通知应用进程,于是应用可以开始读取数据。有时也称此方式为异步I/O。但是严格讲,该方式并不能算真正的异步I/O,因为实际读取数据到应用进程缓存的工作仍然是由应用自己负责的。
5.4.1 信号驱动I/O模型

图5-5 信号驱动I/O

首先允许套接字使用信号驱动I/O模式,并且通过sigaction系统调用注册一个SIGIO信号处理程序。当有数据到达后,系统向应用进程交付一个SIGIO信号,然后既可以如图中所示那样在信号处理程序中读取套接字数据,然后通知主循环处理逻辑,也可以直接通知主循环逻辑,让主程序进行读取操作。
无论采用上面描述的哪种方式读取数据,应用进程都不会因为尚无数据达到而被阻塞,应用主循环逻辑可以继续执行其他功能,直到收到通知后去读取数据或者处理已经在信号处理程序中读取完毕的数据。

5.4.2 设置套接字允许信号驱动I/O
为了让套接字描述符可以工作于信号驱动I/O模式,应用进程必须完成如下三步设置:
1.注册SIGIO信号处理程序。(安装信号处理器)
2.使用fcntl的F_SETOWN命令,设置套接字所有者。(设置套接字的所有者)
3.使用fcntl的F_SETFL命令,置O_ASYNC标志,允许套接字信号驱动I/O。(允许这个套接字进行信号输入输出)
注意,必须保证在设置套接字所有者之前,向系统注册信号处理程序,否则就有可能在fcntl调用后,信号处理程序注册前内核向应用交付SIGIO信号,导致应用丢失此信号。下面的程序片段描述了怎样为套接字设置信号驱动I/O:
sigaction 函数:
    int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact)
该函数会按照参数signum指定的信号编号来设置该信号的处理函数,signum可指定SIGK
ILL和SIGSTOP以外的所有信号,如果参数act不是NULL指针,则用来设置新的信号处理方式。
sigaction 结构体:
    struct sigaction
    {
        void (*sa_handler)(int);//信号处理函数
        sigset_t sa_mask;//用来设置在处理该信号时暂时将sa_mask指定的信号搁置
        int sa_flags;//用来设置信号处理的其他相关操作
        void (*sa_restorer)(void);//这个参数没有使用
    }
sa_flags的值为:
    A——NOCLDSTOP 如果参数signum为SIGCHILD,则当子进程暂停时并不会通知父进程
    SA_ONESHOT/SA——RESETHAND:但调用新的信号处理函数前,将这个信号处理方式改
    为系统默认方式。
    SA——NOMASK/SA——NODEFER:在处理这个信号没有结束前,不理会这个信号的再次到
    来。
以上的几个信号可以用“|”or运算组合起来使用。
struct tm
{
    int tm_sec;
    int tm_min;
    int tm_hour;
    int tm_mday;
    int tm_mon;
    int tm_year;
    int tm_wday;
    int tm_yday;
    int tm_isdst;
#ifdef __USE_BSD
    long int __tm_gmtoff;
    __const char *__tm_zone;
#endif
};

sigmptyset函数:
#include
    int sigemptyset(sigset_t *set);
    用来将参数set信号集初始化并清空;
    执行成功则返回0,如果有错误则返回-1;
    函数的实现:
static inline void sigemptyset(sigset_t,*set)
{
    switch(_NSIG_WORDS){
        default:
            memset(set,0,sizeof(sigse_t));
            break;
        case 2:set->sig[1]=0;
        case 1:set->sig[0]=0;
               break;
    }
}

sigset_t
信号集级信号集操作函数:信号集被定义为一种数据类型:
typedef struct
{
    unsigned long sig[_NSIG_WORDS];
}sigset_t;

static inline void sigaddset(sigset_t *set ,int _sig)
{
    asm ("bfset %0{%1,#1}"
        :"+od" (*set)
        :"id"((_sig-1^31)
        :"cc"));
}//没有看懂这段代码的意思,函数的功能是将_sig 代表的信号
加入到set信号集里面。

int sigprocmask(int how,sigset_t *set,sigset_t *oldset)
{
    int error;
    spin_lock_irq(¤t->sighand->siglock);
    if(oldset)
        *oldset=current->blocked;
    error=0;
    switch(how)
    {
        case SIG_BLOCK:
            sigorsets(¤t->blocked,¤t->blocked,set);
            break;
        case SIG_UNBLOCK:
            signandsets(¤t->blocked,¤t->blocked,set);
            break;
        case SIG_SETMASK:
            current->blocked=*set;
            break;
        default:
            error=-EINVAL;

    }
    recalc_sigpending();
    spin_unlock_irq(¤t->sighand->siglock);
    return error;
}
//该函数用来设置信号掩码,指定那些信号需要被阻塞。

void do_sigio(int sig)
{
   /* SIGIO处理 */
}
...
int sockfd;  /* 套接字 */
struct sigaction sigio_action;
...
sigio_action.sa_handler = do_sigio; /* 信号处理程序,这是个指针调用函数 */
if (sigaction (SIGIO, &sigio_action, NULL) == -1)
    perror ("Failed to set SIGIO");

fcntl (sockfd, F_SETOWN, getpid());  /* 设置套接字所有者以接收SIGIO */
if ((flags = fcntl (sockfd, F_GETFL, 0)) < 0)
   bail("F_GETFL error");
flags |= O_ASYNC | O_NONBLOCK;  /* 设置信号驱动和非阻塞模式 */
if (fcntl (sockfd, F_SETFL, flags) < 0)
   bail ("F_SETFL error");

通常,在UDP编程中使用信号驱动I/O,此时SIGIO信号产生于下面两种情况:
套接字收到一个数据报。
套接字上发生了异步错误。
因此,当应用因为收到一个UDP数据报而产生的SIGIO时,要么可以调用recvfrom读取该数据报,要么得到一个异步错误。
对于TCP编程,信号驱动I/O就没有太大意义了,因为对于流式套接字而言,有很多情况都可以导致SIGIO产生,而应用又无法区分是什么具体情况导致该信号产生的。例如:
监听套接字完成了一个连接请求。
收到了一个断连请求。
断连操作完成。
套接字收到数据。
有数据从套接字发出。

5.4.3信号驱动I/O示例
本节使用信号驱动I/O模式重写数据报套接字一节中的日期时间程序。本例通过信号处理程序读取套接字收到的UDP数据报,然后将数据报存入一队列,再通知应用主循环从队列中取出数据报并进行处理。图5-6描述了本例程序的工作模式。

图5-6 信号驱动I/O的应用

由图5-6可知,SIGIO信号处理程序负责从套接字接收缓存中将已收到的UDP数据报拷贝到应用自身的一个队列,而应用主程序从该队列中取出下一个数据报进行处理。为了简化设计,这里用一个数组req_queue来表示该数据报队列,数组元素都是如下结构体实例:
   struct request {          /* 客户端请求 */
      char *reqstr;         /* 客户端请求日期时间字符串指针 */
      size_t reqlen;         /* 客户端请求日期时间字符串长度 */
      struct sockaddr_in *peer;  /* 客户端internet地址指针 */
      socklen_t sklen;
   };

图5-7 信号驱动I/O的数据报应用队列

程序5.3列出了服务器udpsrvsig.c的源程序
    1 /*
    2  * udpserver.c --liub
    3  *
    4  * DESCRIPTION: this program act as a datagram server, which
    5  * receives datagram in SIGIO handler from a datagram client,
    6  * and the received datagram is saved in a queue, later the main
    7  * loop will fetch each datagram from this queue and process it.
    8  *
    9  * NOTE: the datagram server get the server ip address and
   10  * portnumber from the command line, otherwise it uses the default one,
   11  * namely any ip is ok.
   12  *
   13  * Usuage: udpserver ip portnumber
   14  */
   15 #include
   16 #include
   17 #include
   18 #include
   19 #include
   20 #include
   21 #include
   22 #include
   23 #include
   24 #include
   25 #include
   26 #include
   27
   28 #define QUESIZE 16          /* 队列大小 */
   29 #define BUFSIZE 512
   30
   31 struct request {                /* client request string */
   32     char *reqstr;
   33     size_t reqlen;             /* client request string len */
   34     struct sockaddr_in *peer;    /* client sending this request */
   35     socklen_t sklen;
   36 };
   37
   38 static struct request req_queue[QUESIZE];
   39
   40 static int idx_in;
   41 static int idx_out;
   42 static int nqueue;
   43
   44 int s;                          /* 服务器端套接字 */
   45 struct sockaddr_in peer_addr;      /* 客户端internet地址 */
   46 static socklen_t socklen = sizeof(peer_addr);
   47
   48 static void bail(const char *on_what){
   49     fputs(strerror(errno), stderr);
   50     fputs(": ", stderr);
   51     fputs(on_what, stderr);
   52     fputc('/n', stderr);
   53     exit(1);
   54 }
   55
   56 /* SIGIO信号处理函数*/
   57 void do_sigio (int signum)
   58 {
   59     int z;
   60     struct request *p_req;
   61     char buf[BUFSIZE];
   62
   63     for(;;){
   64         /* 保存下一个客户端请求数据报的位置索引 */
   65         p_req = &req_queue[idx_in];
   66
   67         if(nqueue >= QUESIZE){
   68             write(STDOUT_FILENO, "request queue is full!/n", 23);
   69             return;
   70         }
   71
   72         z = recvfrom(s,             /* 服务器套接字 */
   73             p_req->reqstr,          /* 接收缓存位置指针 */
   74             BUFSIZE,
   75             0,
   76             (struct sockaddr *)p_req->peer, /* 客户端地址指针 */
   77             &socklen);
   78
   79         if(z < 0) {
   80             /* 设置服务器套接字于非阻塞模式,因此当无新数据
                   * 可读时,recvfrom立刻返回,并且错误代码设置为
   81              *  EWOULDBLOCK
   82              */
   83             if(errno == EWOULDBLOCK)
   84                 break;
   85             else{
   86                 write (STDOUT_FILENO, "recvfrom error!/n", 16);
   87                 exit(1);
   88             }
   89         }
   90         p_req->reqstr[z]=0;
   91         p_req->reqlen = z;
   92         nqueue++;
   93
   94         if(++idx_in >= QUESIZE)
   95             idx_in = 0;
   96     }
   97 }
   98
   99 void init_queue()
  100 {
  101     for(int i = 0; i < QUESIZE; i++){
  102         if((req_queue[i].reqstr = malloc(BUFSIZE)) == NULL ||
  103           (req_queue[i].peer = malloc(socklen)) == NULL )
  104           bail("init_queue");
  105         req_queue[i].sklen = socklen;
  106     }
  107
  108     idx_in = idx_out = nqueue = 0;
  109 }
  110 /* 注册SIGIO信号处理程序 */
  111 static void install_sigio()
  112 {
  113   struct sigaction sigio_action;
  114
  115   memset(&sigio_action, 0, sizeof(sigio_action));
  116   sigio_action.sa_flags = 0;
  117
  118   sigio_action.sa_handler = do_sigio;
  119
  120   if (sigaction(SIGIO, &sigio_action, NULL) == -1)
  121       perror("Failed to set SIGIO");
  122 }
  123 /* 设置套接字为信号驱动I/O和非阻塞模式 */
  124 void set_sockopt(int s, int flags)
  125 {
  126     fcntl(s, F_SETOWN, getpid());
  127     if((flags = fcntl(s, F_GETFL, 0)) < 0)
  128         bail("F_GETFL error");
  129     flags |= O_ASYNC | O_NONBLOCK;
  130     if(fcntl(s, F_SETFL, flags) < 0)
  131         bail("F_SETFL error");
  132 }
  133
  134 int main(int argc, char **argv){
  135     int z;
  136     char * srvr_addr = NULL;
  137     int len_inet;
  138     int portnumber;
  139     int flags;
  140    
  141     struct sockaddr_in srvaddr;    /* 服务器internet地址 */          
  142     char dtfmt[BUFSIZE];        /* 日期-时间结果 */
  143     time_t td;                    /* 当前时间和日期 */
  144     struct tm tv;                    /* 日期时间结构体 */
  145     sigset_t zeromask, newmask, oldmask;
  146     struct request *p_req;
  147    
  148      /*
  149      * 若命令行提供了作为服务器地址和端口的参数,则使用参数
  150      * 作为地址和端口,否则使用缺省的地址和端口
  151      */
  152     if(argc > 2){
  153         srvr_addr = argv[1];
  154
  155         if((portnumber = atoi(argv[2]))<0){
  156             fprintf(stderr, "Usage: %s portnumber/a/n", argv[0]);
  157             exit(1);
  158         }
  159     }else{
  160         srvr_addr = "0";
  161         portnumber = 9000;
  162     }
  163
  164     /* 创建数据报套接字 */
  165     s = socket(AF_INET, SOCK_DGRAM, 0);
  166     if(s == -1)
  167         bail("socket()");
  168
  169     init_queue();   /* 初始化应用数据报接收队列 */
  170     install_sigio();  /* 注册SIGIO信号处理程序 */
  171     set_sockopt(s, flags); /* 设置非阻塞和SIGIO驱动I/O模式 */
  172
  173     /* 初始化套接字地址 */
  174     memset(&srvaddr, 0, sizeof srvaddr);
  175     srvaddr.sin_family = PF_INET;
  176     srvaddr.sin_port = htons(portnumber);
  177     if(!inet_aton(srvr_addr, &srvaddr.sin_addr))
  178         bail("bad address.");
  179
  180     len_inet = sizeof(srvaddr);
  181
  182     /*
  183      * 绑定套接字到指定地址和端口,于是客户端可以连接
  184      * 到该服务器
  185      */
  186     z = bind(s, (struct sockaddr *)&srvaddr, len_inet);
  187     if(z == -1)
  188         bail("bind()");
  189
  190     sigemptyset(&zeromask);
  191     sigemptyset(&newmask);
  192     sigemptyset(&oldmask);
  193     sigaddset(&newmask, SIGIO);
  194
  195     sigprocmask(SIG_BLOCK, &newmask, &oldmask);
  196     for(;;) {
  197        while(nqueue == 0)
  198            /* unblock all and waiting for any signal */
  199            sigsuspend(&zeromask);
  200
  201        /* 尽可能早解除对SIGIO 的阻塞 */
  202        sigprocmask(SIG_SETMASK, &oldmask, NULL);
  203
  204        if(idx_out > QUESIZE)
  205            idx_out = 0;
  206
  207        p_req = &req_queue[idx_out++];
  208        /* 获得当前日期和时间 */
  209        time(&td);
  210        tv =*localtime(&td);
  211
  212        /*
  213         * 根据获得的日期时间格式字符串要求,获得当前日期
  214         * 和时间并格式化结果
  215         */
  216        strftime(dtfmt,  /* formatted result */
  217             sizeof dtfmt,
  218             p_req->reqstr,      /* 输入的日期时间格式串 */
  219             &tv);
  220
  221        /* 将格式化结果返回给客户端 */
  222        z = sendto(s,            /* 服务器套接字 */
  223             dtfmt,             /* 返回结果 */
  224             strlen(dtfmt),
  225             0,
  226             (struct sockaddr *)(p_req->peer),   /* 对方地址 */
  227             p_req->sklen);
  228
  229        if(z < 0)
  230           bail("sendto()");
  231
  232        /* 更新临界变量,必须阻塞SIGIO信号 */
  233        sigprocmask(SIG_BLOCK, &newmask, &oldmask);
  234        nqueue--;
  235     }
  236
  237     close(s);
  238     return 0;
  239 }
程序5.3 使用信号驱动

原创粉丝点击