信号驱动io

本文详细介绍了信号驱动I/O的工作原理及其在UDP编程中的应用场景。包括如何设置信号驱动I/O,信号处理函数的设计,以及一个完整的信号驱动I/O示例程序。

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 使用信号驱动


转自:http://blog.csdn.net/yskcg/article/details/6021275

<think>嗯,用户想了解事件驱动IO信号驱动IO的区别,还提到五大IO模型中并没有事件驱动IO。我需要先理清楚这两个概念以及它们在IO模型中的位置。首先,得回忆一下五大IO模型,分别是阻塞IO、非阻塞IOIO多路复用、信号驱动IO和异步IO。确实,标准分类里没有事件驱动IO这个术语,但用户可能在哪里看到过混淆的说法? 事件驱动IO通常是指一种编程模型,可能结合了IO多路复用,比如使用select、poll或epoll这些机制。而信号驱动IO则是通过SIGIO信号通知应用程序数据就绪,属于不同的机制。需要明确两者的区别,避免混淆。 用户可能对事件驱动和IO多路复用的关系不太清楚,或者有些资料里把事件驱动和IO多路复用混为一谈。需要解释清楚事件驱动通常属于应用层设计模式,而IO多路复用是底层机制的一种,属于同步IO模型。 还要注意,信号驱动IO虽然使用信号通知,但处理信号时仍然可能涉及阻塞操作,所以它和异步IO不同,异步IO是整个操作完成后才通知,数据已经准备好,可以直接使用,无需再次读取。 可能需要举例说明,比如事件驱动框架如Node.js或Nginx,它们基于IO多路复用实现高并发,而信号驱动IO则较少见,常用于特定场景如网络socket设置SIGIO信号。 另外,用户可能存在的疑问是为什么事件驱动不在五大模型中,需要指出事件驱动更多是架构设计,而五大模型是操作系统级别的IO处理方式,属于不同层次的概念。确保回答结构清晰,逐步解释每个概念,并对比它们的异同。</think>好的,我将逐步解释这两个概念及其与五大IO模型的关系: --- ### 一、五大IO模型的标准分类 根据UNIX网络编程中的经典定义,五大IO模型为: 1. **阻塞IO** - 进程持续等待数据就绪,期间无法执行其他任务 2. **非阻塞IO** - 进程轮询检查数据状态,需主动消耗CPU资源 3. **IO多路复用 (IO Multiplexing)** - 通过`select/poll/epoll`等机制监控多个描述符 4. **信号驱动IO (Signal-Driven IO)** - 内核通过信号(如`SIGIO`)通知进程数据就绪 5. **异步IO (Async IO)** - 内核完成所有操作后通知进程(如Linux的`io_uring`) --- ### 二、关于"事件驱动IO" #### 1. 概念辨析 - **事件驱动**是编程模型,非标准IO模型 $$ \text{事件驱动} \approx \text{IO多路复用} + \text{回调机制} $$ - 典型实现: - `epoll`(Linux)或`kqueue`(BSD)作为底层多路复用器 - 框架如Node.js、Nginx通过事件循环处理IO #### 2. 与标准模型的对应 - 事件驱动本质属于**IO多路复用**模型 - 区别在于抽象层级: | 层级 | IO多路复用 | 事件驱动 | |------|------------|----------| | 实现 | 系统调用 | 编程框架 | | 关注点 | 描述符状态 | 事件响应 | --- ### 三、信号驱动IO详解 #### 1. 工作原理 $$ \text{进程注册信号处理函数} \rightarrow \text{内核发送SIGIO} \rightarrow \text{进程处理数据} $$ - 设置步骤: ```c fcntl(fd, F_SETOWN, pid); // 设置接收信号的进程 fcntl(fd, F_SETFL, O_ASYNC); // 启用信号驱动 signal(SIGIO, handler_function); // 注册信号处理函数 ``` #### 2. 关键特征 - **通知阶段**:数据到达内核缓冲区时触发信号 - **数据阶段**:仍需进程调用`recvfrom`同步读取数据 - 与异步IO的核心区别: $$ \text{信号驱动IO} \in \text{同步IO} \quad vs \quad \text{异步IO} \in \text{异步操作} $$ --- ### 四、对比总结 | 特征 | 事件驱动(IO多路复用) | 信号驱动IO | |---------------------|-------------------------------|------------------------| | **通知机制** | 就绪事件列表(可读/可写等) | SIGIO信号 | | **编程复杂度** | 需管理事件循环 | 需处理信号竞争 | | **典型应用场景** | Web服务器、高并发网络编程 | 实时设备监控 | | **数据操作阶段** | 同步读取 | 同步读取 | | **平台兼容性** | 依赖具体实现(epoll/kqueue) | POSIX标准支持 | --- ### 五、常见误解澄清 1. **为什么常听说"事件驱动模型"?** - 这是应用层设计模式的概念迁移,实际对应IO多路复用 2. **信号驱动IO的缺陷** - 信号队列溢出可能导致丢失通知 - 信号处理函数中不能调用非异步安全函数 3. **异步IO vs 事件驱动** - 异步IO是真正的全异步(如Windows IOCP) - 事件驱动本质仍是同步非阻塞模式 --- ### 六、扩展理解 - **Proactor模式**:真正的异步IO实现(如Windows IOCP) - **Reactor模式**:事件驱动的基础(如libevent库) - 现代发展趋势: $$ \text{io_uring(Linux)} \rightarrow \text
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值