Linux 5种网络模型

[参考]:《黑马程序员Redis》https://www.bilibili.com/video/BV1cr4y1671t/?p=166&share_source=copy_web&vd_source=9e65300ccca322aeb367bb1eb677b0fc

[参考]:《操作系统》

[参考]:《UNIX网络编程》

为了避免用户应用导致冲突甚至内核崩溃,用户应用与内核是分离的:
进程的寻址空间会划分为两部分:内核空间、用户空间
用户空间只能执行受限的命令(Ring3),而且不能直接调用系统资源,必须通过内核提供的接口来访问内核空间可以执行特权命令 (Ringo),调用一切系统资源 

 

必要的前置知识:

编译:由编译程序将用户源代码编译成若千个目标模块(编译就是把高级语言翻译为机器语言)

链接:由链接程序将编译后形成的一组目标模块,以及所需库函数链接在一起,形成一个完整的装入模块装入 (装载) : 由装入程序将装入模块装入内存运行

1、程序、内存、与寻址

程序代码通过(预处理、编译、汇编、链接)等步骤,形成可执行的机器指令后,这些指令会告诉CPU去内存的哪个地址读/写数据,然后与寄存器进行交互,进行一些计算操作,等等。

程序生成的指令中指明的地址,是逻辑地址(相对地址),而我们的数据真实所在的内存是物理地址

在C语言中,可执行文件最终是以装入模块的形式,进入内存。

1.1 程序的装入

装入方式有三种:

1、绝对装入

只适用于单道程序环境,程序驻留在内存的实际位置是已知的,程序中的逻辑地址与内存的实际地址完全相同。由程序员直接赋予,或程序中采用符号地址,在编译或汇编时转换为绝对地址。

2、可重定位装入(静态重定位)

多道程序环境下,多个目标模块的起始地址都是0,程序中的其他地址都是相对于起始地址的。装入时对目标程序的指令和数据地址进行修改的过程 称为重定位,因其地址变换通常是在进程装入时一次完成的,故称为【静态重定位】

3、动态运行时装入(动态重定位)

程序若在内存中会发生移动,则使用动态重定位,即重定位的过程并不是在装入时完成,而是推迟到了程序运行时进行,需要重定位寄存器支持。

1.2 逻辑地址与物理地址

编译后,每个目标模块都是从0号单元开始编址这称为该目标模块的相对地址(或逻辑地址),当链接程序将各个模块链接成一个完整的可执行的目标程序时,链接程序的顺序按照各个模块的相对地址构成统一的从0号地址单元开始编址的逻辑地址空间(或虚拟地址空间)。

对于32位系统,逻辑地址空间的范围是 0~2^32-1。进程在运行时,看到的和使用的都是逻辑地址。用户程序和程序员只需要知道逻辑地址,而内存管理的具体机制则是完全透明的(对用户不可视)。不同的进程可以有相同的逻辑,因为他们会被隐射到不同的主存位置。

寻址空间与计算机的位数有关系,因为每个地址单元的大小是1B,如果是32位系统,那么寻址空间大小是 2^32=4GB次方字节 。

2^10 =1024B =1K字节(1KByte),  2^20 =1MB,2^30 =1GB, 

2^32 =(2^30)*(2^2) =4GB;

因此地址编号长度要能表示出4GB/1B =2^32个内存单元

32位2进制 可以转换成8位16进制表达

0地址

0x0000 0000

高地址

0xFFFF FFFF

涉及内存地址增长、存储的问题还有一个大小端的情况:

大端存储:地址位存储数据位,地址位存储数据

小端存储:地址位存储数据位,地址位存储数据

物理地址就是内存中物理单元的集合,是地址转换的最终地址。

逻辑地址通过页表映射到物理内存,页表由操作系统维护并被处理器引用。

1.3 进程的内存映像

当一个程序调入内存运行时,就构成了进程的内存映像。

  • 代码段:即程序的二进制代码,代码段是只读的,可以被多个进程共享
  • 数据段:即程序运行时加工处理的对象,包括全局变量和静态变量
  • 进程控制块PCB:存放在系统区。操作系统通过PCB来控制和管理进程
  • 堆:用来存放动态分配的变量,通过调用malloc函数动态的向高地址分配空间。
  • 栈:用来实现函数调用,从用户控件的最大地址往低地址增长。

(补充一句:高级语言运行思路都是类似的,即使是Java这种运行逻辑依靠JVM的,其内部运行时数据区的设计思路都是源于操作系统管理的算法和逻辑)。

代码段和数据段在程序调入内存时就指定了大小。(其实我们在学C++时就说了这些东西其实是在编译期,运行之前就确定的,Java也有类似的方法区,其具体落地实现中,final 静态变量的值也是在编译期就已经确定值了)而堆和栈不同,当调用 malloc free 这样C标准库函数时,堆可以在运行时动态扩缩。用户栈也是随着程序中函数调用和返回,进行入栈弹栈操作。(java的堆和栈略有不同,可以参考我的JVM篇知识)。

1.4 内存的分配管理

详细内容可以看我的【操作系统】专栏

        1、连续分配管理
  1.         单一连续分配
  2.         固定分区分配
  3.         动态分区分配

2、离散分配管理

1、分页存储

2、分段存储

3、段页式存储

3、虚拟内存管理

2、用户发起IO的基本流程

3、Linux五种IO模型

在《UNIX网络编程》一书中,总结归纳了5种IO模型:

  • 阻塞I0 (Blocking IO)
  • 非阻塞I0 (Nonblocking IO)
  • IO多路复用 (IO Multiplexing)
  • 信号驱动IO (SignalDriven IO)
  • 异步IO(AsynchronousIO)

五种不同的IO模型其实关注点都是在这个区域

1、阻塞IO

过程:用户发起recvfrom调用,若当前无法获得数据,用户会一直阻塞等待,直到内核完成数据获取、拷贝,并返回ok给用户,通知用户处理,用户进程才被唤醒。

阻塞IO,用户进程在用户在【内核尝试获取数据】,和【内核从内核缓存拷贝数据到用户缓存】,这两个阶段都是阻塞状态。

2、非阻塞IO

用户进程发起recvfrom调用之后会立即返回,而不是阻塞进程。数据拷贝阶段,用户进程仍然是阻塞的。

第一阶段一直轮询,用户进程并没有去做其他的事,并没有提高效率,忙等反而会导致CPU空转,整个系统效率反而不高。

3、IO多路复用

无论是阻塞IO还是非阻塞IO,用户应用在一阶段都需要调用recvfrom来获取数据,差别在于无数据时的处理方案:

如果调用recvfrom时,恰好没有数据,阻塞IO会使进程阻塞,非阻塞IO使CPU空转,都不能充分发挥CPU的作用。

如果调用recvfrom时,恰好有数据,则用户进程可以直接进入第二阶段,读取并处理数据

比如服务端处理客户端Socket请求时,在单线程情况下,只能依次处理每一个socket,如果正在处理的sket恰好未就绪(数据不可读或不可写),线程就会被阻塞,所有其它客户端socket都必须等待,性能自然会很差。

如果用户进程去监听多个Socket,只要某个套接字数据就绪了,可以开始真正的读写了,我们再去调用recvfrom呢

原本是一个服务员窗口,几十个顾客排队点餐。这种情况下,某一顾客一纠结吃啥,后面的人都只能等待。

现在是大家都坐在自己位置上,某个顾客想好了点什么,就叫服务员。

具体实现:

文件描述符(File Descriptor):简称FD,是一个从0 开始递增的无符号整数,用来关联Linux中的一个文件。

在Linux中,一切皆文件,例如常规文件、视频、硬件设备等,当然也包括网络套接字 (Socket)

IO多路复用:是利用单个线程来同时监听多个FD,并在某个FD可读、可写时得到通知,从而避免无效的等待,充分利用CPU资源。

第二阶段调用recvfrom之所以要循环,是因为第一阶段可能有多个socketFD准备就绪,

在第二阶段里被循环依次被处理

select系统调用可以接收多个被监听的套接字FD

而recvfrom只能监听一个FD

** 所以前面阻塞IO 和非阻塞IO其实都是逮着一只羊薅(只服务一个FD),而IO多路复用虽然第一阶段也是阻塞的,但是select本质上是一种批处理的思想,同时监听多个FD,只要有一个FD就绪,就先处理它。

监听FD的方式、通知的方式有多种,常见的方式:

  • select
  • poll
  • epoll

差异

  • select和poll只会通知用户进程有FD就绪,但不确定具体是哪个FD,需要用户进程逐个遍历FD来确认
  • epoll则会在通知用户进程FD就绪的同时,把已就绪的FD写入用户空间

1、Select模型

Select是Linux中最早的IO多路复用实现方案

fd_set 使用了位示图来表示fd状态。32个long型元素 即 32*32 =1024bit=1kB

select模式存在的问题:

  • 需要将整个fd_set从用户空间拷贝到内核空间,select结束还要再次拷贝回用户空间
  • select无法得知具体是哪个fd就绪,需要遍历整个fd_set
  • fd_set监听的fd数量不能超过1024

2、poll模式

fds 是一个结构体指针,同时它也是能构成一个自定义大小的结构体数组,fds+1即指向下一个数组元素。

该结构体内部由 fd,当前要监听的事件类型,和该事件真实发生的类型状态组成。

用户态调用poll函数,传入若干需要被监听fd结构体(每一个结构体实例就代表一个fd)构成结构体数组。

当系统进入内核态后,如果这些fd就绪,内核会修改这些fd对应的结构体实例的revent,最终把整个结构体数组返回给用户态。

IO流程

  1. 创建pollfd数组,向其中添加关注的fd信息,数组大小自定义调用poll函数,将pollfd数组拷贝到内核空间,转链表存储,无上限
  2. 内核遍历fd,判断是否就绪
  3. 数据就绪或超时后,拷贝pollfd数组到用户空间,返回就绪fd数量n
  4. 用户进程判断n是否大于0
  5. 大于0则遍历pollfd数组,找到就绪的fd

poll和Select 

1、poll没用监听fd数量限制,Select限制为1024,因为二者记录fd的数据结构不同。

2、Select和poll 都需要把 记录fd的数组 从用户态传入内核态。内核处理完后,同样从内核态拷贝回用户态

3、二者从内核态返回的fd数组,都没有直接指明具体是哪个fd就绪,需要用户逐一遍历,找到就绪fd

这就产生一个问题:数组容量变大了,但是任然需要遍历,样本空间变大的情况下,效率反而下降了。

3、epoll

红黑树  的特点 :有序、按序插入删除时间复杂度相对链表要更低。(O(lg2N)) 

epfd是 eventpoll实例的唯一标识,用户端调用几次epoll_crate, 内核就会创建几个eventpoll,并返回对应的epfd 标识。

select模式存在的三个问题

  • 能监听的FD最大不超过1024
  • 每次select都需要把所有要监听的FD都拷贝到内核空间
  • 每次都要遍历所有FD来判断就绪状态

poll模式的问题:


poll利用链表解决了select中监听FD上限的问题,但依然要遍历所有FD,如果监听较多,性能会下降

epoll模式中如何解决这些问题的?

  • 基于epoll实例中的红黑树保存要监听的FD,理论上无上限,而且增删改查效率都非常高,性能不会随监听的FD数量增多而下降
  • 每个FD只需要执行一次epoll ctl添加到红黑树,以后每次epol_wait无需传递任何参数,无需重复拷贝FD到内核空间
  • 内核会将就绪的FD直接拷贝到用户空间的指定位置,用户进程无需遍历所有FD就能知道就绪的FD是谁

lO多路复用-事件通知机制


当FD有数据可读时,我们调用epoll_wait就可以得到通知。但是事件通知的模式有两种:

LevelTriggered:简称LT。当FD有数据可读时,会重复通知多次,直至数据处理完成。是Epoll的默认模式

EdgeTriggered:简称ET。当FD有数据可读时,只会被通知一次,不管数据是否处理完成

举例:

1、假设一个客户端socket对应的FD已经注册到了epoll实例中

2、客户端socket发送了2kb的数据
3、服务端调用epoll_wait,得到通知说FD就绪服务端从

4、FD读取了1kb数据
5、回到步骤3(再次调用epoll wait,形成循环)

结论:
ET模式避免了LT模式可能出现的惊群现象

ET模式最好结合非阻塞IO读取FD数据,相比LT会复杂一些

4、信号驱动IO

信号驱动10是与内核建立SIGI0的信号关联并设置回调,当内核有FD就绪时,会发出SIGI0信号通知用户,期间用户应用可以执行其它业务,无需阻塞等待。

 

当有大量IO操作时,信号较多,SIGIO处理函数不能及时处理可能导致信号队列溢出

而且内核空间与用户空间的频繁信号交互性能也较低 

5、异步IO

异步IO的整个过程都是非阻塞的,用户进程调用完异步API后就可以去做其它事情,内核等待数据就绪并拷贝到用户空间后才会递交信号,通知用户进程。

异步IO要做好限流,防止异步IO请求请求过多,造成系统负荷过高。

IO操作是同步还是异步,关键看数据在内核空间与用户空间的拷贝过程(数据读写的10操作),也就是阶段是同步还是异步:

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

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

相关文章

基于SSM的奶茶店管理系统

末尾获取源码 开发语言:Java Java开发工具:JDK1.8 后端框架:SSM 前端:采用JSP技术开发 数据库:MySQL5.7和Navicat管理工具结合 服务器:Tomcat8.5 开发软件:IDEA / Eclipse 是否Maven项目&#x…

WebSocket实战之三遇上PAC

一、前言 前两天销售数据实时刷新功能开发测试完成,开开心心部署到生产环境,然后直接懵逼傻眼了,竟然连接不上WebSocket服务端,浏览器端请求头报 Provisional headers are shown 信息,然后采用一系列操作排查问题。 …

89、Redis 的 value 所支持的数据类型(String、List、Set、Zset、Hash)---->Zset 相关命令

本次讲解要点: ** Set相关命令:是指value中的数据类型** 启动redis服务器: 打开小黑窗: C:\Users\JH>e: E:>cd E:\install\Redis6.0\Redis-x64-6.0.14\bin E:\install\Redis6.0\Redis-x64-6.0.14\bin>redis-server.exe …

创建型设计模式 原型模式 建造者模式 创建者模式对比

创建型设计模式 单例 工厂模式 看这一篇就够了_软工菜鸡的博客-CSDN博客 4.3 原型模式 4.3.1 概述 用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。 4.3.2 结构 原型模式包含如下角色: 抽象原型类:规定了…

excel中将一个sheet表根据条件分成多个sheet表

有如下excel表,要求:按月份将每月的情况放在一个sheet中。 目测有6个月,就应该有6个sheet,每个sheet中体现本月的情况。 一、首先增加一个辅助列,月份,使用month函数即可。 填充此列所有。然后复制【月份】…

力扣练习——链表在线OJ

目录 提示: 一、移除链表元素 题目: 解答: 二、反转链表 题目: 解答: 三、找到链表的中间结点 题目: 解答: 四、合并两个有序链表(经典) 题目: 解…

Redis与分布式-分布式锁

接上文 Redis与分布式-集群搭建 1.分布式锁 为了解决上述问题,可以利用分布式锁来实现。 重新复制一份redis,配置文件都是刚下载时候的不用更改,然后启动redis服务和redis客户。 redis存在这样的命令:和set命令差不多&#xff0…

十四天学会C++之第二天(函数和库)

1. 函数的定义和调用 在C中,函数是组织和结构化代码的关键工具之一。它们允许您将一段代码封装成一个可重复使用的模块,这有助于提高代码的可读性和维护性。 为什么使用函数? 函数在编程中的作用不可小觑。它们有以下几个重要用途&#xf…

ASUS华硕飞行堡垒5笔记本FX504GM_FX80GM原装出厂Windows10系统

系统自带所有驱动、出厂主题壁纸、系统属性华硕专属LOGO标志、Office办公软件、MyASUS华硕电脑管家等预装程序 下载链接:https://pan.baidu.com/s/1C8vPvqiwqoUY3PxC915LXg?pwdv079

基于被囊群优化的BP神经网络(分类应用) - 附代码

基于被囊群优化的BP神经网络(分类应用) - 附代码 文章目录 基于被囊群优化的BP神经网络(分类应用) - 附代码1.鸢尾花iris数据介绍2.数据集整理3.被囊群优化BP神经网络3.1 BP神经网络参数设置3.2 被囊群算法应用 4.测试结果&#x…

小白自己​制作一个苹果.ios安卓.apk文件app应用手机下载的代码合并文件一码双端的落地页面详细教程

小白自己制作一个苹果.ios安卓.apk文件app应用手机下载的代码落地页面详细教程 图片取自这里哈 我们在这篇文章中教你如何制作一个手机下载引导落地页。这个落地页将可以自动识别访问者使用的是安卓还是苹果设备,并引导下载相应的应用程序。让我们按照以下步骤一…

Selenium 浏览器坐标转桌面坐标

背景: 做图表自动化项目需要做拖拽操作,但是selenium提供的拖拽API无效,因此借用pyautogui实现拖拽,但是pyautogui的拖拽是基于Windows桌面坐标实现的,另外浏览器中的坐标与windows桌面坐标并不是一比一对应的关系&am…

【计算机组成原理】考研真题攻克与重点知识点剖析 - 第 1 篇:计算机系统概述

前言 本文基础知识部分来自于b站:分享笔记的好人儿的思维导图,感谢大佬的开源精神,习题来自老师划的重点以及考研真题。此前我尝试了完全使用Python或是结合大语言模型对考研真题进行数据清洗与可视化分析,本人技术有限&#xff…

基于SpringBoot+MyBatis实现的个人博客系统(一)

这篇主要讲解一下如何基于SpringBoot和MyBatis技术实现一个简易的博客系统(前端页面主要是利用CSS,HTML进行布局书写),前端的静态页面代码可以直接复制粘贴,后端的接口以及前端发送的Ajax请求需要自己书写. 博客系统需要完成的接口: 注册登录博客列表页展示博客详情页展示发布博…

如何在 Google Earth 中创建轨迹、路线并制作动画

如何创建航迹 https://kurviger.de/en Google 地球飞行教程(天桥动画) 选择合适的点 (可调整视图快照)点击录制,依次点击图标即可

WebSocket实战之六心跳重连机制

一、前言 WebSocket应用部署到生产环境,我们除了会碰到因为经过代理服务器无法连接的问题(注:该问题可以通过搭建WSS来解决,具体配置请看 WebSocket实战之四WSS配置 ),另外一个问题就是外网环境不稳定经常…

基于SSM的餐厅点菜管理系统的设计与实现

末尾获取源码 开发语言:Java Java开发工具:JDK1.8 后端框架:SSM 前端:采用Vue技术开发 数据库:MySQL5.7和Navicat管理工具结合 服务器:Tomcat8.5 开发软件:IDEA / Eclipse 是否Maven项目&#x…

使用Visual Studio调试排查Windows系统程序audiodg.exe频繁弹出报错

VC常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585C软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...&a…

lv7 嵌入式开发-网络编程开发 03 TCP/IP与五层体系结构

目录 1 TCP/IP协议族体系结构 1.1 OSI与TCP/IP 1.2 TCP/IP 的体系结构 1.3 TCP/IP 体系结构的另一种表示方法 1.4 沙漏计时器形状的 TCP/IP 协议族 2 五层协议的体系结构 2.1 各层的主要功能 2.2 互联网中客户-服务器工作方式 2.3 同时为多个客户进程提供服务 3 练…

vertx的学习总结三

一、event bus是什么 各个verticle的通信 二、point-to-point, request-reply, publish/subscribe 通过 the event bus 例题一:点对点 package eventBus;import io.vertx.core.AbstractVerticle; import io.vertx.core.Vertx;public class EventBusExample exte…