水平出发和边缘出发的区别

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

边缘触发(edge-triggered)ET: 每当状态变化时,触发一个事件。

“举个读socket的例子,假定经过长时间的沉默后,现在来了100个字节,这时无论边缘触发和条件触发都会产生一个read ready notification通知应用程序可读。应用程序读了50个字节,然后重新调用api等待io事件。这时水平触发的api会因为还有50个字节可读,从而立即返回用户一个read ready notification。而边缘触发的api会因为可读这个状态没有发生变化而陷入长期等待。因此在使用边缘触发的api时,要注意每次都要读到socket返回EWOULDBLOCK为止,否则这个socket就算废了。而使用条件触发的api 时,如果应用程序不需要写就不要关注socket可写的事件,否则就会无限次的立即返回一个write ready notification。大家常用的select就是属于水平触发这一类,长期关注socket写事件会出现CPU 100%的毛病。

  1. /*************************************************************************
  2. > File Name: t_select.c
  3. > Author: liuxingen
  4. > Mail: [email protected]
  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都会立即返回.

[plain] view plaincopy

  1. [email protected]:~/station$ ./t_select
  2. ni hao ma ,wo hen hao a ,ni ne ???
  3. ni hao ma
  4. ,wo hen
  5. hao a ,ni
  6. ne ???
  7. ^C

[cpp] view plaincopy

  1. /*************************************************************************
  2. > File Name: demo_sigio.c
  3. > Author: liuxingen
  4. > Mail: [email protected]
  5. > Created Time: 2014年08月14日 星期四 21时32分03秒
  6. ************************************************************************/
  7. #include<stdio.h>
  8. #include<unistd.h>
  9. #include<string.h>
  10. #include<errno.h>
  11. #include<ctype.h>
  12. #include<signal.h>
  13. #include<fcntl.h>
  14. static int g_fd;
  15. static void sigio_handler(int signum)
  16. {
  17. char buf[8] = {0};
  18. if(read(g_fd, buf, 7) < 0)
  19. {
  20. fprintf(stderr, "read error:%s\n", strerror(errno));
  21. }else
  22. {
  23. printf("sigio recv:%s\n", buf);
  24. }
  25. }
  26. int main(int argc, char *argv[])
  27. {
  28. struct sigaction act;
  29. int flags, i = 1, fds[2];
  30. pid_t pid;
  31. if(pipe(fds) < 0)
  32. {
  33. fprintf(stderr, "pipe error:%s\n", strerror(errno));
  34. return 1;
  35. }
  36. if((pid = fork()) > 0)
  37. {
  38. close(fds[1]);
  39. dup2(fds[0], g_fd);
  40. sigemptyset(&act.sa_mask);
  41. act.sa_flags = SA_RESTART;
  42. act.sa_handler = sigio_handler;
  43. if(sigaction(SIGIO, &act, NULL) == -1)
  44. {
  45. fprintf(stderr, "sigaction error:%s\n", strerror(errno));
  46. return 1;
  47. }
  48. if(fcntl(g_fd, F_SETOWN, getpid()) == -1)
  49. {
  50. fprintf(stderr, "fcntl F_SETOWN error:%s\n", strerror(errno));
  51. return 1;
  52. }
  53. flags = fcntl(g_fd, F_GETFL);
  54. if(fcntl(g_fd, F_SETFL, flags | O_ASYNC | O_NONBLOCK) == -1)
  55. {
  56. fprintf(stderr, "fcntl F_GETFL error:%s\n", strerror(errno));
  57. return 1;
  58. }
  59. while(1)
  60. {
  61. sleep(10);
  62. }
  63. }else
  64. {
  65. char buf[20] = {0};
  66. close(fds[0]);
  67. for(i = 0; i < 3; i++)
  68. {
  69. snprintf(buf, 20, "this is loop %d", i);
  70. write(fds[1], buf, strlen(buf));
  71. printf("loop %d\n", i);
  72. sleep(3);
  73. }
  74. }
  75. return 0;
  76. }

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

[plain] view plaincopy

  1. [email protected]:~/station$ ./demo_sigio
  2. loop 0
  3. sigio recv:this is
  4. loop 1
  5. sigio recv: loop 0
  6. loop 2
  7. sigio recv:this is
  8. sigio recv: loop 1
  9. ^C
时间: 2024-11-05 16:06:27

水平出发和边缘出发的区别的相关文章

epoll 水平触发和边缘触发的区别

EPOLLLT——水平触发EPOLLET——边缘触发 epoll有EPOLLLT和EPOLLET两种触发模式,LT是默认的模式,ET是“高速”模式.LT模式下,只要这个fd还有数据可读,每次 epoll_wait都会返回它的事件,提醒用户程序去操作,而在ET(边缘触发)模式中,它只会提示一次,直到下次再有数据流入之前都不会再提示了,无 论fd中是否还有数据可读.所以在ET模式下,read一个fd的时候一定要把它的buffer读光,也就是说一直读到read的返回值小于请求值,或者 遇到EAGAIN

水平触发和边缘触发的区别

水平触发(level-triggered,也被称为条件触发)LT:  只要满足条件,就触发一个事件(只要有数据没有被获取,内核就不断通知你) 边缘触发(edge-triggered)ET: 每当状态变化时,触发一个事件. "举个读socket的例子,假定经过长时间的沉默后,现在来了100个字节,这时无论边缘触发和条件触发都会产生一个read ready notification通知应用程序可读.应用程序读了50个字节,然后重新调用api等待io事件.这时条件触发的api会因为还有50个字节可读,

实例浅析epoll的水平触发和边缘触发,以及边缘触发为什么要使用非阻塞IO

一.基本概念                                                          我们通俗一点讲: Level_triggered(水平触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写.如果这次没有把数据一次性全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait()时,它还会通知你在上没读写完的文件描述符上继续读写,当然如果你一直不去读写,它会一直通知你!!!如果系统中有大量你不需要读写的就

2015-01-27-从实验出发理解buffer与cache区别-吴伟顺

通过du(find) 与 cat 体现buffer与cache差异实验: 实验表明: 1 通常 buffer << cache 2 "文件系统"相关内容(inode等)缓存在buffer,"文件系统"无关的(即文件内容)缓存在cache 同: 目的:提高linux的IO效率 属性:对程序是透明的 (everything is a file in linux) IO层:缓存在内存的其他慢IO层的数据. (IO : 网络 < 磁盘 < 内存 &l

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

在linux的IO多路复用中有水平触发,边缘触发两种模式,这两种模式的区别如下: 水平触发:如果文件描述符已经就绪可以非阻塞的执行IO操作了,此时会触发通知.允许在任意时刻重复检测IO的状态,没有必要每次描述符就绪后尽可能多的执行IO.select,poll就属于水平触发. 边缘触发:如果文件描述符自上次状态改变后有新的IO活动到来,此时会触发通知.在收到一个IO事件通知后要尽可能多的执行IO操作,因为如果在一次通知中没有执行完IO那么就需要等到下一次新的IO活动到来才能获取到就绪的描述符.信号

水平触发与边缘触发

水平触发(Level Triggered,也称条件触发):select()和poll()将就绪的文件描述符告诉进程后,如果进程没有对其进行IO操作,那么下次调用select()和poll()的时候将再次报告这些文件描述符,所以它们一般不会丢失就绪的消息. 边缘触发(Edge Triggered):只告诉进程哪些文件描述符刚刚变为就绪状态,它只说一遍,如果我们没有采取行动,那么它将不会再次告知,这种方式称为边缘触发. ? ? 两者都会带来一系列问题:例如当100字节到达后,应用程序只读取了一部分,

epoll的水平触发和边缘触发

水平触发(Level Triggered): 当满足条件时, 触发.  (select和poll采用该方式)边缘触发(Edge Triggered): 当状态变化时,触发.  (信号驱动[Signal-Driven I/O]采用该方式) 过程: 当数据到来的时候, 触发器满足条件,发送通知, 进程接收到通知后,请求内核读取数据. 水平触发,是因为数据就绪,可IO,满足条件,则发送通知. 边缘触发,是因为状态发生了变化, 则发送通知. 理想情况下, 两种触发方式都能满足需求. 但是当内核在读取数据

读《构建高性能Web站点》服务器并发处理能力 - 2

系统调用 进程有两种运行模式:用户态和内核态.进程通常在用户态,这时可以使用CPU和内存,而当进程需要对硬件外设进行操作的时候(如读取磁盘文件.发送网络数据),就必须切换到内核态,当在内核态的任务完成后,进程又切回到用户态. 由于系统调用涉及进程从用户态到内核态的切换,导致一定的内存空间交换,这也是一定程度上的上下文切换,所以系统调用的开销通常是比较昂贵的. 减少不必要的系统调用,也是Web服务器性能优化的一个方面. 内存分配 Apache在运行时的内存使用量是非常惊人的,这主要归咎于它的多进程

(转)Linux后台开发应该具备技能

Linux后台开发应该具备技能 分类: 杂谈 2013-04-14 12:45 741人阅读 评论(0) 收藏 举报 Linux后台开发应该具备技能 一.linux和os: 1.命令:netstat tcpdump ipcs ipcrm  这四个命令的熟练掌握程度基本上能体现实际开发和调试程序的经验 2.cpu 内存 硬盘 等等与系统性能调试相关的命令必须熟练掌握,设置修改权限 tcp网络状态查看 各进程状态 抓包相关等相关命令 必须熟练掌握 3.awk sed需掌握 4.共享内存的使用实现原理