操作系统_04_IO相关(个人总结)

article/2025/10/5 16:50:06

    声明: 1. 本文为我的个人复习总结, 并那种从零基础开始普及知识 内容详细全面, 言辞官方的文章
              2. 由于是个人总结, 所以用最精简的话语来写文章
              3. 若有错误不当之处, 请指出

软硬链接

软链接(快捷方式):

删除原文件时, 软链接则失效

在这里插入图片描述

硬链接:

删除硬文件时, 硬链接不会失效

​ 硬链接是多个目录项中的「索引节点」指向同一个 inode,但是 inode 是不可能跨越文件系统的,每个文件系统都有各自的 inode 数据结构和列表,所以硬链接不可跨文件系统
​ 由于多个目录项都是指向一个 inode, 那么只有删除原文件以及所有硬链接时, 系统才会彻底删除该文件

在这里插入图片描述

IO类型

缓冲IO

先访问标准库的缓存, 再访问文件

非缓冲IO

不访问标准库的缓存, 直接去访问文件

直接IO

直接访问磁盘, 不会发生内核态用户态间的数据拷贝

非直接IO

不直接访问磁盘, 会发生内核态用户态间的数据拷贝

读操作时, 数据从内核态拷贝到用户态

写操作时, 数据从用户态拷贝到内核态

五种IO模型

  1. BIO(同步阻塞)

    一个线程只能连接一个Socket连接, 处理一个Socket的请求

    accept时阻塞等待有客户端连接, read时阻塞等待有数据输入

  2. NIO(同步非阻塞)

    一个线程可以连接多个Socket连接, 处理多个Socket的请求; 更好的利用了线程, 避免因一个文件阻塞而导致整个线程啥也不干了

    accept时得不到连接 和 read时得不到数据就算了, 直接返回个默认值负数, 不会在那一直阻塞着等待数据输入

  3. IO多路复用(同步(半同步), IO多路复用器)

    别名: 事件驱动IO

    一个线程可以连接多个Socket连接, 处理多个Socket的请求; 更好的利用了线程, 避免因一个文件阻塞而导致整个线程啥也不干了

    是对NIO的优化, 是基于Linux内核的select->poll->epoll去实现的

  4. AIO(异步IO, 异步回调通知)

    基于操作系统去谈:

    IO阻塞分为两个阶段

    • 数据准备阶段
    • 数据从内核态拷贝到用户态的阶段

    BIO和AIO在这两阶段, 都是同步的

    IO多路复用在第一阶段是异步的, 内核通知应用程序去内核态拷贝数据, 然后第二阶段是同步的

    AIO 在这两阶段, 都是异步的, 准备好数据 & 拷贝内核态数据到用户态, 都是内核完成的, 内核完成之后再去通知应用程序

  5. 信号驱动IO

基础概念:

A线程调用B线程

同步:

​ A线程需要等得到B线程的处理结果后才能继续执行下一步任务, B线程处理完成后不会主动通知A线程, A线程需要自己等待着

异步:

  1. A线程需要等待B线程的处理结果, 但期间A线程可以继续做别的事, 因为B线程处理完后会主动回调通知A线程
  2. 或是 A线程不依赖于B线程, 各忙各的, 无需等待

阻塞:

​ A线程即调用者 一直在等待而且不做别的事情

非阻塞:

​ A线程即调用者 等待时可以先做别的事情, 过一会再来继续询问查看

总结:

同步&异步 的讨论对象是B线程即被调用者, 关注的是消息通知机制

阻塞&非阻塞 的讨论对象是A线程即调用者, 关注的是等待得到返回值时的行为, 能否干其他事

Socket连接又被称为Socket描述符/文件描述符(FileDescriptor, fd)

FileDescriptor是指向 打开文件 或 新建文件 的指针

为了安全考虑, 内核态的权限较大, 用户态权限小; 二者间使用系统调用进行切换状态开销较大

BIO:

一个线程只能处理一个Socket连接, 太浪费线程

accept( )阻塞, read( )阻塞

ServerSocket serverSocket = new ServerSocket(8081);
while (true) {//阻塞1 ,等待客户端连接Socket socket = serverSocket.accept();new Thread(() -> {//阻塞2 ,等待客户端发送数据while ((length = inputStream.read(bytes)) != -1) {System.out.println("-----444 成功读取" + new String(bytes, 0, length));}).start( );}}

如果要是单线程, 那么被Client1阻塞着时, Client2的请求就得不到处理

如果是多线程, 太浪费线程资源

NIO:

一个线程可以连接多个Socket连接, 处理多个Socket的请求; 更好的利用了线程, 避免因一个文件阻塞而导致整个线程啥也不干了

accept( )方法是非阻塞的, 如果没有客户端连接就返回error默认值数据
read( )方法是非阻塞的, 如果读取不到数据就返回error默认值数据

while (true) {SocketChannel socketChannel = serverSocket.accept();if (socketChannel != null) {socketChannel.configureBlocking(false);// 添加socket连接到List中socketList.add(socketChannel);}for (SocketChannel element : socketList) {int read = element.read(byteBuffer);// 读不到数据就返回负数了, 这里做判断是否读到实际数据了if (read > 0) {System.out.println("-----读取实际数据: " + read);}}
}

如果要是单线程, 那么就不会被Client1阻塞着, Client2的请求便可以得到处理

依然存在的问题:

  1. 在客户端少的时候十分好用,但是如果客户端很多, 比如有1万个客户端进行连接,那么每次循环就要遍历1万个socket; 如果一万个socket中只有10个socket有数据,也会遍历一万个socket,就会做很多无用功
  2. 遍历过程是在用户态进行的, 调用内核的read( )方法涉及了用户态和内核态的切换, 每遍历一个就要切换一次开销很大

IO多路复用:

别名: 事件驱动IO

"多路"是指多个Socket连接, “复用” 指的是复用同一个线程

一个线程可以连接多个Socket连接, 处理多个Socket的请求; 更好的利用了线程, 避免因一个文件阻塞而导致整个线程啥也不干了

基于Linux内核实现, 操作系统会在Socket连接有数据时 通知应用程序,进行业务处理

select:

​ 其实就是把NIO中用户态要遍历的fd数组(List集合)拷贝到了内核态, 外加select能感知到有事件发生(只是不清楚具体是哪一个socket有数据), 让内核态来遍历(处于内核态时调用内核的read( )判断是否有数据, 就不用切换用户态和内核态了)

存在的问题:

  1. 一个进程最多只能处理1024个客户端
  2. 每一次调用select( ), fd数组都会被从用户态拷贝到内核态, 仍具有很大开销
  3. select只是感知到了有事件发生, 并没有通知用户态具体是哪一个socket有数据, 仍需要O(n)的遍历

数据结构: 固定大小的BitMap, 因此有1024个客户端的数量上限

poll:

​ 基于select进行改进, 一个进程最多可以处理的客户端数量没上限

存在的问题:

  1. 每一次调用select( ), fd数组都会被从用户态拷贝到内核态, 仍具有很大开销
  2. select只是感知到了有事件发生, 并没有通知用户态具体是哪一个socket有数据, 仍需要O(n)的遍历

数据结构: 动态数组(可扩容)

epoll:

​ 基于poll进行改进, 解决了剩下的2个问题

如何解决的:

  1. 只需要把fd数组从用户态拷贝到内核态一次, 而不用每次调用epoll都进行拷贝fd数组, 从而就不用频繁切换用户态和内核态了
  2. 事件通知机制中, 每次socket中有数据会主动通知内核, 并加入到就绪链表中, 便不需要遍历所有的socket, 而是只遍历就绪列表, 复杂度为O(1)

数据结构: 红黑树

实际应用:

windows上有select, 但没有epoll

Nginx和Redis都使用了Linux内核函数epoll来实现IO多路复用

Redis的IO多路复用:

用epoll函数来实现IO多路复用, 将连接信息和事件放到队列中, 事件分派器将队列中的事件分发给事件处理器(这一步是单线程的, 所以Redis核心是单线程模型)

Redis基于 多Reactor多线程 模式开发了网络(文件)事件处理器:

在这里插入图片描述

组成架构:

  1. 多个Socket套接字连接
  2. IO多路复用器(epoll)
  3. 事件分派器(Dispatcher) 分发过程是单线程的
  4. 事件处理器Handler

Reactor

Reactor模式组成架构:

  1. Reactor: 负责 监听是否有事件发生 和 分发事件
  2. Handler: 处理事件

Reactor的实现方案:

  1. 单Reactor-单线程

适用于处理占用时间不长的任务
优点:
1. 避免了切换上下文的开销
2. 实现起来简单
缺点:
1. 只有⼀个 Reactor 对象来承担所有事件的监听和响应, 并发量低
2. Handler在处理某一任务时被卡住或太久,会导致其他任务一直等待着,无法充分利用多核cpu

  1. 单Reactor-多线程

有两个线程池,Readers 和 Handlers:
Readers 去读请求,然后将其封装成 Call 对象放到消息队列中,
然后再由 Handlers 从消息队列中取出 RPC 请求进行处理
缺点:
只有⼀个 Reactor 对象来承担所有事件的监听和响应, 并发量低

  1. 多Reactor-多线程

    1. mainReactor 负责监听 Socket 连接事件
    2. readReactor (多个) 负责监听 IO 读事件 (处理 Reader 线程的请求)
    3. respondReactor 负责监听 IO 写事件 (处理 Handler 线程的写请求), 给客户端返回响应信息

Reactor 是「来了事件操作系统通知应用进程去处理」

Proactor (异步) 是「来了事件操作系统来处理,处理完再通知应用进程」

零拷贝

DMA:

内核缓冲区用户缓冲区之间的拷贝要CPU

内核缓冲区Socket缓冲区之间的拷贝要CPU

磁盘/网卡内核缓冲区/Socket缓冲区要DMA, 不必占用CPU

IO过程:

  1. 用户进程调用 read( ), 向操作系统发出 I/O 请求,请求读取数据到用户进程缓冲区中,进程进入阻塞状态

  2. 操作系统收到请求后,进⼀步将 I/O 请求发送 DMA,然后让 CPU 执行其他任务

  3. DMA 进⼀步将 I/O 请求发送给磁盘;

  4. 磁盘收到 DMA 的 I/O 请求,把数据从磁盘读取到磁盘控制器的缓冲区中,当磁盘控制器的缓冲区被读满后,向 DMA 发起中断信号,告知自己缓冲区已满

  5. DMA 收到磁盘的信号, 将磁盘控制器缓冲区中的数据拷贝到内核缓冲区中

  6. 当 DMA 读取了足够多的数据,就会发送中断信号给 CPU

  7. CPU 收到 DMA 的信号,知道数据已经准备好 于是将数据从内核拷贝到用户空间,系统调用返回;

CPU 在这个过程中也是必不可少的,因为传输什么数据 & 从哪里传输到哪里,都需要 CPU 来告诉 DMA 控制器。

PageCache:

功能:

  1. 缓存最近被访问的数据(时间局部性)

  2. 预读功能(空间局部性)

PageCache 不适合于大文件:

  1. PageCache 由于长时间被大文件占据,其他「热点」的小文件可能就无法充分使用到 PageCache
  2. DMA 将大文件拷贝到 PageCache 也很耗时, 如果要是没有被经常读取到 将很亏

解决方案: 直接I/O

零拷贝:

一次系统调用会发生2次用户态和内核态的切换

在文件传输的应用场景中,在用户空间我们并不会对数据「再加工」,所以数据实际上可以不必搬运到用户空间

1. 传统IO:

在这里插入图片描述

2. mmap代替read

在这里插入图片描述

3. sendfile代替 read+write [初期]

在这里插入图片描述

4. sendfile代替 read+write [后期]

在这里插入图片描述


http://chatgpt.dhexx.cn/article/7t6dXUpN.shtml

相关文章

gcc 命令

gcc 命令 GCC 编译器 gcc 命令格式 gcc 选项 文件名字主要选项 -c&#xff1a; 只编译不链接为可执行文件&#xff0c;编译器将输入的.c 文件编译为.o 的目标文件。-o&#xff1a; <输出文件名>用来指定编译结束以后的输出文件名&#xff0c;如果不使用这个选项的话 G…

Ubuntu Linux gcc的常用命令

目录 一、gcc简介 二、简单的gcc编译 1 预处理 2 编译为汇编语言代码 3 汇编 4 连接 三、多个程序文件的编译 四、检查错误 五、连接库文件 1 编译成可执行文件 2 链接 3 强制链接时使用静态链接库 六、总结 一、gcc简介 Linux系统下的Gcc&#xff08;GNU C Compi…

Linux GCC 编译详解

文章目录 一、GCC 编译器简介二、GCC 工作流编程语言的发展GCC 工作流程gcc 和 g 的区别 三、使用 GCC 编译GCC 编译格式GCC 编译流程多个源文件编译 一、GCC 编译器简介 首先&#xff0c;什么是编译器呢&#xff1f; 我们可以使用编辑器&#xff08;如 linux 下的 vi、window…

gcc编译命令的常用选项——强烈推荐大家使用 -Wall 选项

C程序编编译的过程分为如下四个阶段 1.预处理&#xff1a;头文件展开&#xff08;#include&#xff09;、宏替换&#xff08;#define&#xff09;、条件编译&#xff08;#ifdef&#xff09;(.i)使用预处理器&#xff08;预处理阶段处理的都是以#开头的代码&#xff09; 2.编译…

Linux(gcc编译原理、过程以及常用调试命令)

PS&#xff1a;红色字体表示重点&#xff0c;绿色字体表示重要标题&#xff0c;块引用中表示Linux终端中的命令。 1.gcc / g的安装 命令&#xff1a;sudo apt install gcc /gcc 2.gcc编译连接 //main.c文件 &#xff08;1&#xff09;预编译 ①删除所有的“#define”&#…

Linux中gcc编译步骤

我们这里以C语言为例&#xff0c;看看C语言程序在Linux中编译执行的详细步骤 1.创建一个.c文件 2.写入一些简单的代码 我们使用gcc -E filename.c -o filename.i 命令对程序先进行预处理&#xff1a;处理头文件、宏定义&#xff0c;接下来我们修改一下头文件&#xff0c;使用错…

Linux GCC常用命令

目录 一、示例一 1.简单编译 1.1预处理 1.2编译为汇编代码 1.3汇编 1.4连接 2.多个程序文件的编译 3检错 4库文件连接 二、示例二 1.准备hello.c 2.预处理 3.编译 4.汇编 5.链接 6.分析ELF文件 6.1ELF文件的段 6.2反汇编ELF 一、示例一 1.简单编译 创建文件mkd…

GCC编译过程及基本命令总结

一、GCC简介 GCC即GNU Compiler Collection&#xff0c;原本只是针对C语言的编译工具&#xff0c;现在已经变成了一个工具集&#xff0c;包含了C、C、JAVA等语言的集合体。 管理和维护&#xff1a;由GNU项目负责。 二、GCC对C、C的编译流程 (1) 预处理&#xff08;Preproce…

Linux 下的 gcc 编译常用命令

gcc&#xff1a;Linux下的一款编译器。 gcc工作流程&#xff1a; 完整编译流程&#xff1a; 创建一个c语言源文件&#xff1a; gcc -E hello.c -o hello.i&#xff08;头文件展开&#xff0c;进行源文件中的宏替换&#xff0c;注释过滤&#xff09; gcc -S hello.i -o hello.…

GCC命令编译

1.GCC编译过程&#xff08;原理如下&#xff0c;使用命令请见2&#xff09; 从 hello.c 到 hello(或 a.out)文件&#xff0c; 必须历经 hello.i、 hello.s、 hello.o&#xff0c;最后才得到 hello(或a.out)文件&#xff0c;分别对应着预处理、编译、汇编和链接 4 个步骤&#…

gcc 命令详解及最佳实践

介绍 GCC&#xff08;英文全拼&#xff1a;GNU Compiler Collection&#xff09;是 GNU 工具链的主要组成部分&#xff0c;是一套以 GPL 和 LGPL 许可证发布的程序语言编译器自由软件&#xff0c;由 Richard Stallman 于 1985 年开始开发。 GCC 原名为 GNU C语言编译器&#…

使用GCC编译程序常用命令

一、编译器驱动程序 首先梳理一下源文件到可执行文件的整个过程&#xff0c;下面是两个源文件的组成main.c和sum.c // main.c int sum(int *a, int n); int array[2] { 1, 2 }; int main() {int val sum(array, 2);return val; }// sum.c int sum(int *a, int n) {int i, s…

matlab 反向二值化,MATLAB:图像二值化、互补图(反运算)(im2bw,imcomplement函数)...

图像二值化、反运算过程涉及到im2bw&#xff0c;imcomplement函数&#xff0c;反运算可以这么理解&#xff1a;原本黑的区域变为白的区域&#xff0c;白的区域变为黑的区域。 实现过程如下&#xff1a; close all;%关闭当前所有图形窗口&#xff0c;清空工作空间变量&#xff0…

图像处理Matlab阈值的设置imadjust(),graythresh(),im2bw()函数使用

1、imadjust()函数调节图像的对比度(若图像较暗&#xff0c;可用imadjust函数命令来调节图像的对比度) I1imadjust(I,stretchlim(I),[0;1]); % stretchlim(I2)自适应找到一个分割阈值向量来改变一幅图像的对比度 figure,imshow(I1); 2、matlab中DIP工具箱函数im2bw使用阈值&…

MATLAB--对于im2bw函数的优化

由于我在研究的是uint8图像&#xff0c;所以对于优化im2bw函数的形式为&#xff1a;im2bw(uint8[], T)&#xff0c;不过其它形式也可以由类似思想导出。 测试代码&#xff1a;遍历8个图像得出时间 for n1:8cchar(str(n));fimread(c);testtime3%要测试的代码 end 算法一 tic fR…

MATLAB中将图像转换为二值图像im2bw

在MATLAB中将图像转换为二值图像&#xff0c;主要运用im2bw函数&#xff0c;涉及到一个灰度门槛的数值。 对于灰度图像 bwim2bw(I,level); level空着的话&#xff0c;默认是0.5。level一般使用graythresh函数来计算&#xff0c;至于graythresh函数中运用到的Otsus method&am…

matlab函数im2bw_图像分割之阈值分割(matlab)(转载)

转载自&#xff1a;https://blog.csdn.net/weixin_39824223/article/details/112249214 matlab函数im2bw_图像分割之阈值分割&#xff08;matlab&#xff09; weixin_39824223 2021-01-02 06:21:09 373 收藏 2 文章标签&#xff1a; matlab函数im2bw 图像分割是一种重要的…

MATLAB中im2bw函数-将图像转换为二值图像

matlab中DIP工具箱函数im2bw使用阈值&#xff08;threshold&#xff09;变换法把灰度图像&#xff08;grayscale image&#xff09;转换成二值图像。所谓二值图像&#xff0c; 一般意义上是指只有纯黑&#xff08;0&#xff09;、纯白&#xff08;255&#xff09;两种颜色的图像…

4、im2bw 和 imbinarize 的区别与图像分割的综合应用

1. im2bw 和 imbinarize 的区别 将图片转换为二值图有两个函数&#xff0c;分别为&#xff1a; bw imbinarize(g); 与 bw im2bw(g);在 matlab2018 中建议用 imbinarize 来将图片转换为二值图&#xff0c;其参数必须为灰度图。 在 matlab2016 中&#xff0c;只有 im2bw 函…

理解Kalman滤波的使用

Kalman滤波简介 Kalman滤波是一种线性滤波与预测方法&#xff0c;原文为&#xff1a;A New Approach to Linear Filtering and Prediction Problems。文章推导很复杂&#xff0c;看了一半就看不下去了&#xff0c;既然不能透彻理解其原理&#xff0c;但总可以通过实验来理解其具…