在 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 也不会立即返回.

    1. /*************************************************************************
    2. > File Name: t_select.c
    3. > Author: liuxingen
    4. > Mail: liuxingen@nsfocus.com
    5. > Created Time: 2014年08月11日 星期一 21时22分32秒
    6. ************************************************************************/
    7. #include<stdio.h>
    8. #include<unistd.h>
    9. #include<sys/types.h>
    10. #include<sys/time.h>
    11. #include<sys/select.h>
    12. #include<string.h>
    13. #include<errno.h>
    14. int main(int argc, char *argv[])
    15. {
    16. struct timeval timeout;
    17. char buf[10];
    18. fd_set readfds;
    19. int nread, nfds, ready, fd;
    20. while(1)
    21. {
    22. timeout.tv_sec = 20L;
    23. timeout.tv_usec = 0;
    24. fd = 0; //stdin
    25. nfds = fd + 1;
    26. FD_ZERO(&readfds);
    27. FD_SET(fd, &readfds);
    28. ready = select(nfds, &readfds, NULL, NULL, &timeout);
    29. if(ready == -1 && errno == EINTR)
    30. {
    31. continue;
    32. }else if(ready == -1)
    33. {
    34. fprintf(stderr, "select error:%s\n", strerror(errno));
    35. }
    36. for(fd = 0; fd < nfds; fd++)
    37. {
    38. if(FD_ISSET(fd, &readfds))
    39. {
    40. nread = read(fd, buf, 9);
    41. buf[nread] = '\0';
    42. puts(buf);
    43. }
    44. }
    45. }
    46. return 0;
    47. }

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

    lxg@remoter:~/station$ ./t_select 
    ni hao ma ,wo hen hao a ,ni ne ???
    ni hao ma
     ,wo hen 
    hao a ,ni
     ne ???
    

    边缘触发

    /*************************************************************************
    > File Name: demo_sigio.c
    > Author: liuxingen
    > Mail: liuxingen@nsfocus.com 
    > Created Time: 2014年08月14日 星期四 21时32分03秒
    ************************************************************************/
    
    #include<stdio.h>
    #include<unistd.h>
    #include<string.h>
    #include<errno.h>
    #include<ctype.h>
    #include<signal.h>
    #include<fcntl.h>
    
    static int g_fd;
    
    static void sigio_handler(int signum)
    {
        char buf[8] = {0};
    
        if(read(g_fd, buf, 7) < 0)
        {
            fprintf(stderr, "read error:%s\n", strerror(errno));
        }else
        {
            printf("sigio recv:%s\n", buf);
        }
    }
    int main(int argc, char *argv[])
    {
        struct sigaction act;
        int flags, i = 1, fds[2];
        pid_t pid;
    
        if(pipe(fds) < 0)
        {
            fprintf(stderr, "pipe error:%s\n", strerror(errno));
            return 1;
        }
        if((pid = fork()) > 0)
        {
            close(fds[1]);
            dup2(fds[0], g_fd);
    
            sigemptyset(&act.sa_mask);
            act.sa_flags = SA_RESTART;
            act.sa_handler = sigio_handler;
            if(sigaction(SIGIO, &act, NULL) == -1)
            {
                fprintf(stderr, "sigaction error:%s\n", strerror(errno));
                return 1;
            }
    
            if(fcntl(g_fd, F_SETOWN, getpid()) == -1)
            {
                fprintf(stderr, "fcntl F_SETOWN error:%s\n", strerror(errno));
                return 1;
            }
    
            flags = fcntl(g_fd, F_GETFL);
            if(fcntl(g_fd, F_SETFL, flags | O_ASYNC | O_NONBLOCK) == -1)
            {
                fprintf(stderr, "fcntl F_GETFL error:%s\n", strerror(errno));
                return 1;
            }
            while(1)
            {
                sleep(10);
            }
        }else
        {
            char buf[20] = {0};
            close(fds[0]);
            for(i = 0; i < 3; i++)
            {
                snprintf(buf, 20, "this is loop %d", i);
                write(fds[1], buf, strlen(buf));
                printf("loop %d\n", i);
                sleep(3);
            }
        }
    
        return 0;
    }
    

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

    lxg@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