在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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53/************************************************************************* > File Name: t_select.c > Author: liuxingen > Mail: liuxingen@nsfocus.com > Created Time: 2014年08月11日 星期一 21时22分32秒 ************************************************************************/ #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:%sn", strerror(errno)); } for(fd = 0; fd < nfds; fd++) { if(FD_ISSET(fd, &readfds)) { nread = read(fd, buf, 9); buf[nread] = ''; puts(buf); } } } return 0; }
1
2
3
4
5
6
7
8lxg@remoter:~/station$ ./t_select ni hao ma ,wo hen hao a ,ni ne ??? ni hao ma ,wo hen hao a ,ni ne ??? ^C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85/************************************************************************* > 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:%sn", strerror(errno)); }else { printf("sigio recv:%sn", 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:%sn", 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:%sn", strerror(errno)); return 1; } if(fcntl(g_fd, F_SETOWN, getpid()) == -1) { fprintf(stderr, "fcntl F_SETOWN error:%sn", 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:%sn", 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 %dn", i); sleep(3); } } return 0; }
因为信号驱动IO属于边缘触发,所以上面以信号驱动来举例.从下面的输出可以得知:我们一次写入14个字节,但是一次我们每次只读取7字节,除非等到下一次数据写入不然不会再触发SIGIO信号,并且上一次未读完的数据会在下次继续被读取.
1
2
3
4
5
6
7
8
9
10lxg@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
最后
以上就是沉静板栗最近收集整理的关于水平触发,边缘触发的全部内容,更多相关水平触发内容请搜索靠谱客的其他文章。
发表评论 取消回复