操作系统闲谈01——IO多路复用

news/2024/4/27 20:01:09/文章来源:https://blog.csdn.net/qq_41945053/article/details/127442926

IO多路复用

同步异步IO问题

select,poll,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的。

异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间

select、poll、epoll

select、poll、epoll区别:

1、支持一个进程所能打开的最大连接数(存储fd的数据结构不同)

select:单个进程所能打开的最大连接数有FD_SETSIZE宏定义,其大小是32个整数的大小(在32位的机器上,大小就是3232,同理64位机器上FD_SETSIZE为3264),当然我们可以对进行修改,然后重新编译内核,但是性能可能会受到影响,这需要进一步的测试。

poll:poll本质上和select没有区别,但是它没有最大连接数的限制,原因是它是基于链表来存储的。

epoll:虽然连接数有上限,但是很大,1G内存的机器上可以打开10万左右的连接,2G内存的机器可以打开20万左右的连接。

2、FD剧增后带来的IO效率问题(遍历fd的时候产生的问题

select:因为每次调用时都会对连接进行线性遍历,所以随着FD的增加会造成遍历速度慢的“线性下降性能问题”。

poll:同上

epoll:因为epoll内核中实现是根据每个fd上的callback函数来实现的,只有活跃的socket才会主动调用callback,所以在活跃socket较少的情况下,使用epoll没有前面两者的线性下降的性能问题,但是所有socket都很活跃的情况下,可能会有性能问题。

3、 消息传递方式(从用户态拷贝到内核态的时间开销

select:内核需要将消息传递到用户空间,都需要内核拷贝动作(每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大

poll:同上

epoll:epoll通过内核和用户空间共享一块内存来实现的。

epoll详解

当某一进程调用epoll_create方法时,Linux内核会创建一个eventpoll结构体,这个结构体中有两个成员与epoll的使用方式密切相关。

eventpoll结构体如下所示:

struct eventpoll{..../*红黑树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件*/struct rb_root  rbr;/*双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件*/struct list_head rdlist;....
};

每一个epoll对象都有一个独立的eventpoll结构体,用于存放通过epoll_ctl方法向epoll对象中添加进来的事件。这些事件都会挂载在红黑树中,如此,重复添加的事件就可以通过红黑树而高效的识别出来(红黑树的插入时间效率是lgn,其中n为树的高度)。

而所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说,当相应的事件发生时会调用这个回调方法。这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到rdlist双链表中。

在epoll中,对于每一个事件,都会建立一个epitem结构体,如下所示:

struct epitem{struct rb_node  rbn;//红黑树节点struct list_head    rdllink;//双向链表节点struct epoll_filefd  ffd;  //事件句柄信息struct eventpoll *ep;    //指向其所属的eventpoll对象struct epoll_event event; //期待发生的事件类型
}

当调用epoll_wait检查是否有事件发生时,只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可。如果rdlist不为空,则把发生的事件复制到用户态,同时将事件数量返回给用户。

image-20221021111230959

从上面的讲解可知:通过红黑树和双链表数据结构,并结合回调机制,造就了epoll的高效。

OK,讲解完了Epoll的机理,我们便能很容易掌握epoll的用法了。一句话描述就是:三步曲。

第一步:epoll_create()系统调用。此调用返回一个句柄,之后所有的使用都依靠这个句柄来标识。

第二步:epoll_ctl()系统调用。通过此调用向epoll对象中添加、删除、修改感兴趣的事件,返回0标识成功,返回-1表示失败。

第三部:epoll_wait()系统调用。通过此调用收集收集在epoll监控中已经发生的事件。

// a simple echo server using epoll in linux  #include <sys/socket.h>  
#include <sys/epoll.h>  
#include <netinet/in.h>  
#include <arpa/inet.h>  
#include <fcntl.h>  
#include <unistd.h>  
#include <stdio.h>  
#include <errno.h>  
#include <iostream>  
using namespace std;  
#define MAX_EVENTS 500  
struct myevent_s  
{  int fd;  void (*call_back)(int fd, int events, void *arg);  int events;  void *arg;  int status; // 1: in epoll wait list, 0 not in  char buff[128]; // recv data buffer  int len, s_offset;  long last_active; // last active time  
};  
// set event  
void EventSet(myevent_s *ev, int fd, void (*call_back)(int, int, void*), void *arg)  
{  ev->fd = fd;  ev->call_back = call_back;  ev->events = 0;  ev->arg = arg;  ev->status = 0;bzero(ev->buff, sizeof(ev->buff));ev->s_offset = 0;  ev->len = 0;ev->last_active = time(NULL);  
}  
// add/mod an event to epoll  
void EventAdd(int epollFd, int events, myevent_s *ev)  
{  struct epoll_event epv = {0, {0}};  int op;  epv.data.ptr = ev;  epv.events = ev->events = events;  if(ev->status == 1){  op = EPOLL_CTL_MOD;  }  else{  op = EPOLL_CTL_ADD;  ev->status = 1;  }  if(epoll_ctl(epollFd, op, ev->fd, &epv) < 0)  printf("Event Add failed[fd=%d], evnets[%d]\n", ev->fd, events);  else  printf("Event Add OK[fd=%d], op=%d, evnets[%0X]\n", ev->fd, op, events);  
}  
// delete an event from epoll  
void EventDel(int epollFd, myevent_s *ev)  
{  struct epoll_event epv = {0, {0}};  if(ev->status != 1) return;  epv.data.ptr = ev;  ev->status = 0;epoll_ctl(epollFd, EPOLL_CTL_DEL, ev->fd, &epv);  
}  
int g_epollFd;  
myevent_s g_Events[MAX_EVENTS+1]; // g_Events[MAX_EVENTS] is used by listen fd  
void RecvData(int fd, int events, void *arg);  
void SendData(int fd, int events, void *arg);  
// accept new connections from clients  
void AcceptConn(int fd, int events, void *arg)  
{  struct sockaddr_in sin;  socklen_t len = sizeof(struct sockaddr_in);  int nfd, i;  // accept  if((nfd = accept(fd, (struct sockaddr*)&sin, &len)) == -1)  {  if(errno != EAGAIN && errno != EINTR)  {  }printf("%s: accept, %d", __func__, errno);  return;  }  do  {  for(i = 0; i < MAX_EVENTS; i++)  {  if(g_Events[i].status == 0)  {  break;  }  }  if(i == MAX_EVENTS)  {  printf("%s:max connection limit[%d].", __func__, MAX_EVENTS);  break;  }  // set nonblockingint iret = 0;if((iret = fcntl(nfd, F_SETFL, O_NONBLOCK)) < 0){printf("%s: fcntl nonblocking failed:%d", __func__, iret);break;}// add a read event for receive data  EventSet(&g_Events[i], nfd, RecvData, &g_Events[i]);  EventAdd(g_epollFd, EPOLLIN, &g_Events[i]);  }while(0);  printf("new conn[%s:%d][time:%d], pos[%d]\n", inet_ntoa(sin.sin_addr),ntohs(sin.sin_port), g_Events[i].last_active, i);  
}  
// receive data  
void RecvData(int fd, int events, void *arg)  
{  struct myevent_s *ev = (struct myevent_s*)arg;  int len;  // receive datalen = recv(fd, ev->buff+ev->len, sizeof(ev->buff)-1-ev->len, 0);    EventDel(g_epollFd, ev);if(len > 0){ev->len += len;ev->buff[len] = '\0';  printf("C[%d]:%s\n", fd, ev->buff);  // change to send event  EventSet(ev, fd, SendData, ev);  EventAdd(g_epollFd, EPOLLOUT, ev);  }  else if(len == 0)  {  close(ev->fd);  printf("[fd=%d] pos[%d], closed gracefully.\n", fd, ev-g_Events);  }  else  {  close(ev->fd);  printf("recv[fd=%d] error[%d]:%s\n", fd, errno, strerror(errno));  }  
}  
// send data  
void SendData(int fd, int events, void *arg)  
{  struct myevent_s *ev = (struct myevent_s*)arg;  int len;  // send data  len = send(fd, ev->buff + ev->s_offset, ev->len - ev->s_offset, 0);if(len > 0)  {printf("send[fd=%d], [%d<->%d]%s\n", fd, len, ev->len, ev->buff);ev->s_offset += len;if(ev->s_offset == ev->len){// change to receive eventEventDel(g_epollFd, ev);  EventSet(ev, fd, RecvData, ev);  EventAdd(g_epollFd, EPOLLIN, ev);  }}  else  {  close(ev->fd);  EventDel(g_epollFd, ev);  printf("send[fd=%d] error[%d]\n", fd, errno);  }  
}  
void InitListenSocket(int epollFd, short port)  
{  int listenFd = socket(AF_INET, SOCK_STREAM, 0);  fcntl(listenFd, F_SETFL, O_NONBLOCK); // set non-blocking  printf("server listen fd=%d\n", listenFd);  EventSet(&g_Events[MAX_EVENTS], listenFd, AcceptConn, &g_Events[MAX_EVENTS]);  // add listen socket  EventAdd(epollFd, EPOLLIN, &g_Events[MAX_EVENTS]);  // bind & listen  sockaddr_in sin;  bzero(&sin, sizeof(sin));  sin.sin_family = AF_INET;  sin.sin_addr.s_addr = INADDR_ANY;  sin.sin_port = htons(port);  bind(listenFd, (const sockaddr*)&sin, sizeof(sin));  listen(listenFd, 5);  
}  
int main(int argc, char **argv)  
{  unsigned short port = 12345; // default port  if(argc == 2){  port = atoi(argv[1]);  }  // create epoll  g_epollFd = epoll_create(MAX_EVENTS);  if(g_epollFd <= 0) printf("create epoll failed.%d\n", g_epollFd);  // create & bind listen socket, and add to epoll, set non-blocking  InitListenSocket(g_epollFd, port);  // event loop  struct epoll_event events[MAX_EVENTS];  printf("server running:port[%d]\n", port);  int checkPos = 0;  while(1){  // a simple timeout check here, every time 100, better to use a mini-heap, and add timer event  long now = time(NULL);  for(int i = 0; i < 100; i++, checkPos++) // doesn't check listen fd  {  if(checkPos == MAX_EVENTS) checkPos = 0; // recycle  if(g_Events[checkPos].status != 1) continue;  long duration = now - g_Events[checkPos].last_active;  if(duration >= 60) // 60s timeout  {  close(g_Events[checkPos].fd);  printf("[fd=%d] timeout[%d--%d].\n", g_Events[checkPos].fd, g_Events[checkPos].last_active, now);  EventDel(g_epollFd, &g_Events[checkPos]);  }  }  // wait for events to happen  int fds = epoll_wait(g_epollFd, events, MAX_EVENTS, 1000);  if(fds < 0){  printf("epoll_wait error, exit\n");  break;  }  for(int i = 0; i < fds; i++){  myevent_s *ev = (struct myevent_s*)events[i].data.ptr;  if((events[i].events&EPOLLIN)&&(ev->events&EPOLLIN)) // read event  {  ev->call_back(ev->fd, events[i].events, ev->arg);  }  if((events[i].events&EPOLLOUT)&&(ev->events&EPOLLOUT)) // write event  {  ev->call_back(ev->fd, events[i].events, ev->arg);  }  }  }  // free resource  return 0;  
}   

epoll边缘触发与水平触发

1、水平触发(EPOLLLT)
水平触发:只要缓冲区有数据,epoll_wait就会一直被触发,直到缓冲区为空;

水平触发(EPOLLLT)是epoll默认的工作模式,其优缺点如下:
优点:保证了数据的完整输出;
缺点:当数据较大时,需要不断从用户态和内核态切换,消耗了大量的系统资源,影响服务器性能;
应用场景:应用较少,一般用于连接请求较少及客户端发送的数据量较少的服务器,可一次性接收所有数据。此时,若使用边沿触发,会多调用一次accept/read等来判断数据是否为空。

2、边沿触发(EPOLLET)
边沿触发:只有所监听的事件状态改变或者有事件发生时,epoll_wait才会被触发;

epoll边沿触发时,假设一个客户端发送100字节的数据,而服务器设定read每次读取20字节,那么一次触发只能读取20个字节,然后内核调用epoll_wait直到下一次事件发生,才会继续从剩下的80字节读取20个字节,由此可见,这种模式其工作效率非常低且无法保证数据的完整性,因此边沿触发不会单独使用。

边沿触发通常与非阻塞IO一起使用,其工作模式为:epoll_wait触发一次,在while(1)循环内非阻塞IO读取数据,直到缓冲区数据为空(保证了数据的完整性),内核才会继续调用epoll_wait等待事件发生。

任何情况都应该优先选择“边沿触发(EPOLLET)+非阻塞IO”模式。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_404402.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

贴片电阻的读数方法

贴片电阻图 今天讲一下贴片电阻的阻值、精度与贴片电阻丝印之间细微的关系。 大家经常见到的贴片电阻上的丝印有纯数字、数字与R组合、数字与除R之外的字母组合的&#xff0c;但大家知不知道这样的标注与贴片电阻的i精度相关&#xff1f;同一个阻值因为精度不同&#xff0c;标…

【Git】Git基本配置和常用命令

&#x1f4ad;&#x1f4ad; ✨&#xff1a; git基本配置和命令   &#x1f49f;&#xff1a;东非不开森的主页   &#x1f49c;&#xff1a;学习的过程就是不断接触错误&#xff0c;不断提升自己&#xff0c;冲鸭&#x1f49c;&#x1f49c;   &#x1f338;: 如有错误或不…

从前端到全栈

你会学到什么&#xff1f; 掌握 Node.js 开发必备基础知识&#xff1b;理解 HTTP 协议核心原理与实践&#xff1b;基于 Node.js 实现自己的工程脚手架;从 0 打造在线绘图 Web 应用。 作者介绍 月影&#xff0c;字节跳动 ByteTech 负责人&#xff0c;资深 JavaScript 程序员&am…

GeoDetector --- 最优参数离散化

安装R包 (直接在RStudio安装GD包) install.packages("GD")加载数据 library(GD) #加载GD包 setwd("X:\\work\\GD") #设置工作路径 data1<-read.csv("data_raw.csv") #读取数据(未经离散化处理的原始数据) head(data1) #可以查看数…

(附源码)计算机毕业设计SSM基于的英语学习网站的设计与实现

&#xff08;附源码&#xff09;计算机毕业设计SSM基于的英语学习网站的设计与实现 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。…

ZK集群搭建和Hadoop单机版及Java API简单操作

一、背景 作为软件界最为复杂度的软件代表&#xff0c;当时操作系统、数据库、中间件。而学习中间件&#xff0c;分布式系统&#xff0c;必然绕不开zookeepr、Hadoop的学习。以下做个人搭建zookeeper和Hadoop的搭建的简单记录。相关的代码操作放在github上。 二、搭建过程 1、…

Markdown使用方法

Markdown是一种轻量级标记语言,排版语法简洁,让人们更多地关注内容本身而非排版。它使用易读易写的纯文本格式编写文档,可与HTML混编,可导出 HTML、PDF 以及本身的 .md 格式的文件。因简洁、高效、易读、易写,Markdown被大量使用,如Github、Wikipedia、简书等。 基本语法…

【ffmpeg】YUV实践

【ffmpeg】YUV实践前言生成YUV使用摄像头采集到YUV数据从现有的视频文件中提取YUV数据播放YUV播放Y分量提取各分量参考资料个人简介 &#x1f4e6;个人主页&#xff1a;一二三o-0-O的博客 &#x1f3c6;技术方向&#xff1a;C/C客户端资深工程师&#xff08;直播音视频剪辑&…

Spring 6.0 要来了,太强了

Spring Framework 6.0 发布了首个 RC 版本。 发布公告写道&#xff0c;Spring Framework 6.0 作为重大更新&#xff0c;目前 RC1 要求使用 Java 17 或更高版本&#xff0c;并且已迁移到 Jakarta EE 9&#xff08;在 jakarta 命名空间中取代了以前基于 javax 的 EE API&#xff…

HTML零基础入门(上)

一、什么是HTML html是一门标记性语言 全称是超文本标记语言(Hyper Text Markup Language)&#xff0c;它是用来描述网页的一种语言。 HTML 不是一种编程语言&#xff0c;而是一种标记语言 (markup language)。 html只是用来制作网页的一种语言&#xff0c;由各种各样的标签…

Visual Code配置C/C++

1. 前言 VS Code和以前的Visual studio 开发环境不一样,只是代码编辑器,如果需要运行代码C/C++,需要额外安装编译器。在Linux环境下,一般系统自带了gcc编译器,但是windows环境没有,所以要额外安装编译器,选择MinGW。总体参考资料: C/C++ for Visual Studio Code2. MinG…

ZnCdTe/ZnS三元/Cu2MoS4荧光量子点/MoS2QDs二硫化钼量子点的制备

瑞禧小编这里给大家准备了ZnCdTe/ZnS三元/Cu2MoS4荧光量子点/MoS2QDs二硫化钼量子点的制备步骤与方法&#xff0c;一起来学习吧&#xff01; MoS2QDs二硫化钼量子点的制备 以钼酸钠提供钼源,谷胱甘肽提供硫源,通过"自下而上"一步水热法制备MoS2 QDs,并以其荧光强度作…

Oracle——行转列与列转行

文章目录行转列创建表和增加测试数据方式一&#xff1a;先分组&#xff0c;再统计平铺方式二&#xff1a;使用Oracle11g自带函数PIVOT实现列转行创建表和增加测试数据方式一&#xff1a;union all 单个合并方式二&#xff1a;unpivot 函数实现总结资料参考行转列 把某一个字段…

Js中六种拖拽(拉)事件(drag 和 drop)

拖拽事件 今天同事问有没有实现过表格的拖拽功能&#xff0c;我当时想的是应该跟图片的拖拽代码逻辑是一样的主要是使用了浏览器中的以下几个事件 例如&#xff1a; 一、dragstart 二、dragend 三、dragover 四、dragenter 五、drop 等 文章目录拖拽事件前言(各个浏览器的兼容性…

基于matlab的神经网络设计,深度神经网络代码matlab

为什么谈论深度学习工具时&#xff0c;很少有人讨论matlab的神经网络工具包 首先深度学习不光是在学术界非常火热, 在工业界也有着大量的运用, 这就要求深度学习框架要方便在服务器上部署, 而这个恰恰是Matlab的软肋, 想象一下你前端用个Django做个页面接受用户输入的图像, 后…

【Linux驱动开发】并发控制机制:原子操作、自旋锁、信号量、互斥锁详解

并发控制机制 首先我们来了解一下 “操作系统的并发性” 这个概念&#xff1a; 操作系统的并发性(concurrence)&#xff1a;指的是两个或者两个以上事件在同一时间间隔内发生&#xff0c;即这个设备一会执行这个事件一会执行那个事件&#xff0c;多个事件共同使用一个设备。 操…

yolov5和yolov7之实例分割训练

还没来得及实验&#xff0c;我在这里就给出几个参考的链接 1、How to train your segmentation data with seg in U7? What is the data set format?Thanks&#xff01;&#xff01; Issue #732 WongKinYiu/yolov7 GitHub 2、JSON2YOLO/general_json2yolo.py at master …

南大通用GBase8s 常用SQL语句(287)

UPDATE 语句 使用 UPDATE 语句来更改表或视图中一个或多个现有的行的一个或多个列中的值。 语法 Target WHERE 选项 元素 描述 限制 语法 alias 您在此为本地表或远程表声明的临时的名称 如果 SET 是 alias 的标识符&#xff0c;则 AS 关键字必须在 alias 之前 …

计算机网络(六) | 应用层:HTTP协议详解

目录HTTP协议HTTP协议简介理解应用层协议HTTP协议的工作过程HTTP协议格式Fiddler的简介Fiddler的使用HTTP请求格式概述HTTP响应格式概述HTTP请求格式详解URL方法请求报头请求正文(body)HTTP响应格式详解状态码响应报头响应正文(body)构造HTTP请求form表单构造HTTP请求通过ajax构…

Unity9 路径权限、场景的加载、异步加载、场景跳转

Application类 using System.Collections; using System.Collections.Generic; using UnityEngine;public class ApplicationTest : MonoBehaviour {// Start is called before the first frame updatevoid Start(){//游戏数据文件夹路径 只读、加密压缩Debug.Log(Application…