点开工具、字典、anything


epoll

<h4>1. 什么是程序的阻塞,和 while(1) 的区别是什么</h4> <p>首先要知道操作系统运行程序的时候,是通过队列去调度的</p> <ul> <li> <p>执行(Running)状态 当进程已获得CPU,其程序正在CPU上执行,此时的进程状态称为执行状态。</p> </li> <li> <p>就绪(Ready)状态 (就绪队列) 当进程已分配到除 CPU 以外的所有必要的资源,只要获得 CPU 便可立即执行,这时的进程状态称为就绪状态,进行就绪队列。一旦获得cpu ,就会被运行。</p> </li> <li>阻塞(Blocked)状态 (等待队列) 正在执行的进程,由于等待某个事件发生而无法执行时,便放弃CPU而处于阻塞状态,进入等待队列。</li> </ul> <p>while 循环空跑的程序大多数时间处于执行状态(少数时间会被切换到就绪),所以会一直占用 cpu。</p> <p>当发生 IO 请求时,因为网络 IO 数据传输相对 CPU 运行来说很慢,所以操作系统会让程序进入到等待队列,此时就是阻塞状态,不再占用 CPU。</p> <p><br></p> <h4>2. CPU 如何知道何时通过网卡接受数据</h4> <p>首先要知道硬件中断。</p> <p>硬件中断其实就是某个程序运行到一半,系统收到硬件的中断信号,将其强行让其停止(保留现场),以此让出 CPU,然后执行某个规定好的操作(程序)。</p> <p>有数据到达网卡时,网卡向 CPU 发出一个中断信号,操作系统得知有新数据到来,停止当前其他程序的运行,再通过网卡中断程序去处理数据。</p> <p><br></p> <h4>3. socket</h4> <p>简单理解就是封装了一些细节,让应用程序和底层网卡打交道的时候可以更加简单。</p> <p>指定了 ip 和端口后,socket 就可以用 write/send 函数发送数据,使用 read/recv接收数据</p> <p>每个 socket 被创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区。网卡并不存储数据,而是放到了缓冲区里。</p> <p><br></p> <h4>4. fd 句柄</h4> <p><br></p> <h4>5. 用户态 和 内核态</h4> <p><br></p> <h4>6. 一次完整的过程</h4> <pre><code>//创建socket int s = socket(AF_INET, SOCK_STREAM, 0); //绑定 bind(s, ...) //监听 listen(s, ...) //接受客户端连接 int c = accept(s, ...) //接收客户端数据 recv(c, ...); //将数据打印出来 printf(...) </code></pre> <p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/373fff421092d3a36bbb13752c2793b0?showdoc=.jpg" alt="" /></p> <ul> <li>计算机收到了对端传送的数据</li> <li>数据经由网卡传送到内存</li> <li>网卡通过中断信号通知 CPU 有数据到达,CPU 执行中断程序</li> <li>中断程序将网络数据写入到对应 Socket 的接收缓冲区里面,再唤醒进程 A,让其处于就绪状态</li> <li>进程由就绪状态变成运行状态后,通过 socket,就可以到相应的缓冲区获取数据</li> <li>数据全部读完之后,进程又回到阻塞状态,循环往复</li> </ul> <p><br></p> <h4>7. Select 的流程</h4> <p>上面只是对一个 socket 的管理。</p> <p>假设程序需要对多个 socket 同时管理,这时候就需要用到 Select</p> <pre><code>int s = socket(AF_INET, SOCK_STREAM, 0); bind(s, ...) listen(s, ...) int fds[] = 存放需要监听的socket while(1){ int n = select(..., fds, ...) for(int i=0; i &lt; fds.count; i++){ if(FD_ISSET(fds[i], ...)){ //fds[i]的数据处理 } } } </code></pre> <p>select 的实现思路很直接,程序 A 与相应的 socket 1 2 3 进行绑定 (加入资源等待队列)</p> <p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/44917abe94488ffd32871d485cd98b9b?showdoc=.jpg" alt="" /></p> <p>数据到达的时候,网卡中断程序执行,根据 socket 等待队列定位到程序A要处理,于是唤醒A,让 A 进入就绪状态,获得 CPU 后运行。</p> <p>数据全部读取完毕后,进程重回阻塞状态。</p> <p>之所以 Select 效率不高,是因为 进程A 被唤醒后,想知道是哪个 socket 处于活跃状态,要遍历所有 socket,判断其 fd_set。</p> <p>这里的 socket 只有 3个,但是当 socket 有成千上万个的时候,遍历的开销就变的巨大。</p> <p>另外,句柄会有多次拷贝:</p> <p>首先,创建 socket 句柄后,需要从向内核态(内存)拷贝</p> <p>然后,要进行内容读取,select会将之前传递给内核的文件句柄再次从内核传到用户态</p> <p>最后,fd_set 被修改后,想要再次复位,句柄需要再次从用户态向内核态进行拷贝</p> <p><br></p> <h4>8. Epoll 的设计思路</h4> <pre><code>int s = socket(AF_INET, SOCK_STREAM, 0); bind(s, ...) listen(s, ...) int epfd = epoll_create(...); epoll_ctl(epfd, ...); //将所有需要监听的socket添加到epfd中 while(1){ int n = epoll_wait(...) for(接收到数据的socket){ //处理 } } </code></pre> <p>具体 Epoll 的思路如下:</p> <p>首先创建一个 eventpool 对象</p> <p>多维护一个列表,只代表当前需要处理的,就不需要全部遍历, 叫做 rdllist。</p> <p>通过 epoll_ctl 添加要处理的 socket (底层实现应该是依赖于事件回调)</p> <p>当收到数据,网卡中断程序执行,将要处理的句柄加入 rdllist。然后唤醒进程 A。</p> <p>进程 A 只需要检查 rdlist 即可,不需要检查全部。</p> <p>rdllist 为空时,进程又回到阻塞状态。</p> <p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/706046e5dd12ed218a1c7a0e090573f0?showdoc=.jpg" alt="" /></p> <p>Epoll 的事件通知原理</p> <p>epoll_ctl 向内核注册了该文件句柄的回调函数,中断时调用回调函数,回调函数里将该文件句柄放到就绪链表。 不需要像 select 一样修改 fd_set。</p> <p><br></p> <h4>9. epoll高效的本质</h4> <ul> <li> <p>减少用户态和内核态之间的文件句柄拷贝,epoll 句柄就是建立在内核中的,加上是通过事件进行通知,这样就减少了内核和用户态的拷贝。</p> </li> <li>通过rdllist, 减少了对可读可写文件句柄的遍历。</li> </ul> <p><br></p> <h4>10. epoll的两种触发模式</h4> <ul> <li>lt</li> </ul> <p>内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表</p> <ul> <li>et </li> </ul> <p>当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知。请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)</p> <p><br></p> <h4>11. 就绪列表的数据结构 \ 文件句柄列表</h4> <p>就绪列表 : 数量相对较少,操作大多是遍历,又需要快速地增加和删除,所以采用双向列表。</p> <p>文件句柄列表: 量大,并且方便搜索,增加和减少不能太慢,那就是红黑树了。</p> <p><br></p> <h4>12. 对比</h4> <p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/c98c1f5458644adf7a7a5b6561417209?showdoc=.jpg" alt="" /></p> <p><br></p> <h4>13. 参考</h4> <p><a href="https://www.zhihu.com/question/20122137">https://www.zhihu.com/question/20122137</a></p> <p><a href="https://blog.csdn.net/armlinuxww/article/details/92803381">https://blog.csdn.net/armlinuxww/article/details/92803381</a></p> <p><a href="https://blog.csdn.net/daaikuaichuan/article/details/83862311">https://blog.csdn.net/daaikuaichuan/article/details/83862311</a></p> <p><a href="https://www.cnblogs.com/lojunren/p/3856290.html">https://www.cnblogs.com/lojunren/p/3856290.html</a></p>

页面列表

ITEM_HTML