• IO多路复用的水平触发与边缘触发


    在linux的IO多路复用中有水平触发,边缘触发两种模式,这两种模式的区别如下:

        水平触发:如果文件描述符已经就绪可以非阻塞的执行IO操作了,此时会触发通知.允许在任意时刻重复检测IO的状态,没有必要每次描述符就绪后尽可能多的执行IO.select,poll就属于水平触发.

        边缘触发:如果文件描述符自上次状态改变后有新的IO活动到来,此时会触发通知.在收到一个IO事件通知后要尽可能多的执行IO操作,因为如果在一次通知中没有执行完IO那么就需要等到下一次新的IO活动到来才能获取到就绪的描述符.信号驱动式IO就属于边缘触发.

        epoll既可以采用水平触发,也可以采用边缘触发.

        大家可能还不能完全了解这两种模式的区别,我们可以举例说明:一个管道收到了1kb的数据,epoll会立即返回,此时读了512字节数据,然后再次调用epoll.这时如果是水平触发的,epoll会立即返回,因为有数据准备好了.如果是边缘触发的不会立即返回,因为此时虽然有数据可读但是已经触发了一次通知,在这次通知到现在还没有新的数据到来,直到有新的数据到来epoll才会返回,此时老的数据和新的数据都可以读取到(当然是需要这次你尽可能的多读取).

        下面我们还从电子的角度来解释一下:

        水平触发:也就是只有高电平(1)或低电平(0)时才触发通知,只要在这两种状态就能得到通知.上面提到的只要有数据可读(描述符就绪)那么水平触发的epoll就立即返回.

        边缘触发:只有电平发生变化(高电平到低电平,或者低电平到高电平)的时候才触发通知.上面提到即使有数据可读,但是没有新的IO活动到来,epoll也不会立即返回.

    #include<stdio.h>  
    #include<unistd.h>  
    #include<sys/types.h>  
    #include<sys/time.h>  
    #include<sys/select.h>  
    #include<string.h>  
    #include<errno.h>  
      
    int main(int argc, char *argv[])  
    {  
        struct timeval timeout;  
        char buf[10];  
        fd_set readfds;  
        int nread, nfds, ready, fd;  
      
        while(1)  
        {  
            timeout.tv_sec = 20L;  
            timeout.tv_usec = 0;  
      
            fd = 0;     //stdin  
            nfds = fd + 1;  
            FD_ZERO(&readfds);  
            FD_SET(fd, &readfds);  
      
            ready = select(nfds, &readfds, NULL, NULL, &timeout);  
            if(ready == -1 && errno == EINTR)  
            {  
                continue;  
            }else if(ready == -1)  
            {  
                fprintf(stderr, "select error:%s
    ", strerror(errno));  
            }  
            for(fd = 0; fd < nfds; fd++)  
            {  
                if(FD_ISSET(fd, &readfds))  
                {  
                    nread = read(fd, buf, 9);  
                    buf[nread] = '';  
                    puts(buf);  
                }  
            }  
        }  
      
        return 0;  
    }  

    上面的示例中每次最多读取9个字节,当我们一次输入了20个字节那么分三次调用select,每次都能立即读取到数据,这也就证明了水平触发中只要数据准备好了那么select都会立即返回.

    cra@remoter:~/station$ ./t_select   
    ni hao ma ,wo hen hao a ,ni ne ???  
    ni hao ma  
     ,wo hen   
    hao a ,ni  
     ne ???  
      
    ^C  
     1 #include<stdio.h>  
     2 #include<unistd.h>  
     3 #include<string.h>  
     4 #include<errno.h>  
     5 #include<ctype.h>  
     6 #include<signal.h>  
     7 #include<fcntl.h>  
     8   
     9 static int g_fd;  
    10   
    11 static void sigio_handler(int signum)  
    12 {  
    13     char buf[8] = {0};  
    14   
    15     if(read(g_fd, buf, 7) < 0)  
    16     {  
    17         fprintf(stderr, "read error:%s
    ", strerror(errno));  
    18     }else  
    19     {  
    20         printf("sigio recv:%s
    ", buf);  
    21     }  
    22 }  
    23 int main(int argc, char *argv[])  
    24 {  
    25     struct sigaction act;  
    26     int flags, i = 1, fds[2];  
    27     pid_t pid;  
    28   
    29     if(pipe(fds) < 0)  
    30     {  
    31         fprintf(stderr, "pipe error:%s
    ", strerror(errno));  
    32         return 1;  
    33     }  
    34     if((pid = fork()) > 0)  
    35     {  
    36         close(fds[1]);  
    37         dup2(fds[0], g_fd);  
    38   
    39         sigemptyset(&act.sa_mask);  
    40         act.sa_flags = SA_RESTART;  
    41         act.sa_handler = sigio_handler;  
    42         if(sigaction(SIGIO, &act, NULL) == -1)  
    43         {  
    44             fprintf(stderr, "sigaction error:%s
    ", strerror(errno));  
    45             return 1;  
    46         }  
    47   
    48         if(fcntl(g_fd, F_SETOWN, getpid()) == -1)  
    49         {  
    50             fprintf(stderr, "fcntl F_SETOWN error:%s
    ", strerror(errno));  
    51             return 1;  
    52         }  
    53   
    54         flags = fcntl(g_fd, F_GETFL);  
    55         if(fcntl(g_fd, F_SETFL, flags | O_ASYNC | O_NONBLOCK) == -1)  
    56         {  
    57             fprintf(stderr, "fcntl F_GETFL error:%s
    ", strerror(errno));  
    58             return 1;  
    59         }  
    60         while(1)  
    61         {  
    62             sleep(10);  
    63         }  
    64     }else  
    65     {  
    66         char buf[20] = {0};  
    67         close(fds[0]);  
    68         for(i = 0; i < 3; i++)  
    69         {  
    70             snprintf(buf, 20, "this is loop %d", i);  
    71             write(fds[1], buf, strlen(buf));  
    72             printf("loop %d
    ", i);  
    73             sleep(3);  
    74         }  
    75     }  
    76   
    77     return 0;  
    78 }  

    因为信号驱动IO属于边缘触发,所以上面以信号驱动来举例.从下面的输出可以得知:我们一次写入14个字节,但是一次我们每次只读取7字节,除非等到下一次数据写入不然不会再触发SIGIO信号,并且上一次未读完的数据会在下次继续被读取

    cra@remoter:~/station$ ./demo_sigio   
    loop 0  
    sigio recv:this is  
    loop 1  
    sigio recv: loop 0  
    loop 2  
    sigio recv:this is  
    sigio recv: loop 1  
    ^C  
  • 相关阅读:
    图床
    MarkDown认识与入门
    IIS Express
    HTML中的英文缩写标记、属性
    AI-->从新建文档开始说起,串联相关色彩知识
    微软相关软件、操作系统常用下载方法(附离线方式)
    SubLime3 Emmet插件终极教程
    sublime text 3 扩展插件SideBarEnhancements用法教程
    几款前端开发工具比较
    关于VUE项目中报Error: Avoided redundant navigation to current location: 的错
  • 原文地址:https://www.cnblogs.com/crafet/p/4696528.html
Copyright © 2020-2023  润新知