FPGA开发——使用verilog实现异步FIFO

news2024/12/25 0:25:14

一、FIFO 介绍

1、FIFO的分类

FIFO First In First Out)先进先出存储器。根据接入的时钟信号,可以分为同步 FIFO 和异步 FIFO
FIFO 底层是基于双口 RAM ,同步 FIFO 的读写时钟一致,异步 FIFO 读时钟和写时钟不同。
作用:
同步 FIFO:主要用于数据缓冲,类似于乒乓缓存思想,可以让后级不必等待前级过多时间。
异步 FIFO a. 跨时钟域传输数据 b. 不同位宽的数据接口

2、FIFO 的常见参数

FIFO 的宽度:即 FIFO 一次读写操作的数据位;
FIFO 的深度:指的是 FIFO 可以存储多少个 N 位的数据(如果宽度为 N)。
满标志: FIFO 已满或将要满时由 FIFO 的状态电路送出的一个信号,以阻止FIFO 的写操作继续向 FIFO 中写数据而造成溢出( overflow)。
空标志: FIFO 已空或将要空时由 FIFO 的状态电路送出的一个信号,以阻止FIFO 的读操作继续从 FIFO 中读出数据而造成无效数据的读出( underflow)。
读时钟:读操作所遵循的时钟,在每个时钟沿来临时读数据。
写时钟:写操作所遵循的时钟,在每个时钟沿来临时写数据
读使能:写使能信号有效时写入数据。
写使能:都市能信号有效时读出数据。

二、同步 FIFO 实现

在同步FIFO的实现中我们完全不用考虑不同的时钟和跨时钟的问题,因为在同步FIFO中读写都使用同一个时钟资源。

三、异步 FIFO 实现

        异步 FIFO 实质上是基于中间的双口 RAM,外加一些读写控制电路组成的,主要是实现不同时钟域之间的数据交互。异步 FIFO 读/写操作在两个不同的时钟域,这个过程会设计到跨时钟域处理,所以需要考虑跨时钟域会产生亚稳态的问题。另外,异步 FIFO 也需要通过空满标志去衡量存储器的使用情况,空满标志的产生同样也需要考虑读 /写时钟域,其产生的条件和方式也是需要重点考虑的。以下为具有同步指针的异步 FIFO 示意图。

 

1、/写指针

写指针:总是指向下一次要写的数据地址,写完后自动加 1 ;系统复位后( FIFO为空),写指针指向 0 地址。
读指针:总是指向下一次要读的数据地址,读完后自动加 1 ;系统复位后(FIFO为空),写指针指向 0 地址。
异步 FIFO 中的指针因设计需要,位宽比地址多 1 位。此处 FIFO 的地址对应 FIFO 的存储单元。如深度为 128 的 FIFO,理论上指针位需要[6:0],但为了判断空满都 需要将指针拓展到[7:0]。

2、空满判断

外部电路对异步 FIFO 进行读写操作时,需要根据异步 FIFO 输出的空满信号来判断是否能继续对异步 FIFO 进行读或者写的操作。
空标志:读指针追上写指针,即指针相等。
满标志:写指针追上读指针,即写指针与读指针再次相等,读 /写指针最高位不同即说明再次追上。
异步 FIFO 的读写时钟不同,判断时需要将写指针同步到读时钟域或将读指针同 步到写时钟域进行判断。
跨时钟域传输数据,则有可能会出现亚稳态。亚稳态无法完全避免,但可以通过引入同步机制(打两拍)和格雷码来降低亚稳态出现的概率。

3、亚稳态(打两拍)

上图位亚稳态为打两拍降低亚稳态的示意图。

A 时钟域下的 Din 传递到 B 时钟域,而当 B 时钟上升沿来临时,恰好 Din 数据发生跳变,则 Ds 极有可能出现亚稳态,若使用 Ds,则会导致亚稳态逐级传播下去。固可以再用以及触发器寄存,此时 Dout 的电平就稳定。(有一定可能仍是亚稳态,但概率极低,不做考虑。)单比特信号直接打两拍降低亚稳态的效果较好,但是多比特传输,若多位发生变化时,变化的位都有可能产生亚稳态,所以多比特不直接用打拍的方式进行同步。异步 FIFO 的地址指针每次变化都是加 1,将指针转化为格雷码后(相邻两位格雷码只有 1 位二进制发生变化),可直接进行打两拍来降低亚稳态。

4、格雷码与二进制相互转换

二进制转格雷码

格雷码转二进制

5、代码实现思路

(1 )输入输出端口定义
(2)位宽和深度参数化
(3)如何定义一个内存块?
方法一:
定义一个一维数组。将内存定义为一个 reg 类型的一维数组,这个数组中
的任何一个单元都可以通过一个下标去访问。
如: reg [ 7 : 0 ] data [ 255 : 0 ];
其中 [7:0] 表示一维数组中的每个元素的位宽大小,而在变量后面的
[255:0] ,表示的却不是位宽大小,它表示的是所创建的数组的深度,也就
是一维数组中的元素大小,也可以称作为数组的容量大小。
初始化可用 for 循环清零
方法二:
调用一个双端口的 RAM IP
4 wrusedw rdusedw 怎么求取?写指针领先于读指针的,即写指针减去
读指针即可,异步 FIFO 此处要考虑不同时钟域。

四、代码实现

1、设计文件的编写

新建一个async_fifo.v文件,如下:

//---------<模块及端口声名>------------------------------------------------------
module async_fifo #(parameter FIFO_WIDTH = 8  ,//FIFO输入数据位宽
                              FIFO_DEPTH = 128 //FIFO深度
)(
    //Write clock domain
    wrclk   ,//写时钟
    wrrst_n ,//写侧复位,异步复位,低有效
    wren    ,//写使能
    wrdata  ,//写数据输入
    wrempty ,//写侧空标志
    wrfull  ,//写侧满标志
    wrusedw ,//写时钟域下可读数据量
    //Read clock domain
    rdclk   ,//读时钟
    rdrst_n ,//读侧复位,异步复位,低有效
    rden    ,//读使能
    rddata  ,//读数据输出
    rdempty ,//读侧空标志
    rdfull  ,//读侧满标志
    rdusedw  //读时钟域下可读数据量
);			

//参数声明
    localparam  ADDR_W = log2b(FIFO_DEPTH),//指针位宽
                DATA_W = FIFO_WIDTH;//FIFO深度

//function声明
/************* 用取对数的方法计算地址指针的位宽 ************************/
    function integer log2b(input integer data);
        begin 
            for(log2b=0;data>0;log2b=log2b+1)begin
                data = data>>1;
            end
                
            log2b = log2b - 1;
        end  
    endfunction

//---------<内部信号定义>-----------------------------------------------------
    //端口声明
    //Write clock domain
    input                               wrclk   ;//写时钟
    input                               wrrst_n ;//写侧复位,异步复位,低有效
    input                               wren    ;//写使能
    input           [DATA_W-1:0]        wrdata  ;//写数据输入
    output                              wrempty ;//写侧空标志
    output                              wrfull  ;//写侧满标志
    output          [ADDR_W-1:0]        wrusedw ;//写时钟域下可读数据量
    //Read clock domain
    input                               rdclk   ;//读时钟
    input                               rdrst_n ;//读侧复位,异步复位,低有效
    input                               rden    ;//读使能
    output          [DATA_W-1:0]        rddata  ;//读数据输出
    output                              rdempty ;//读侧空标志
    output                              rdfull  ;//读侧满标志
    output          [ADDR_W-1:0]        rdusedw ;//读时钟域下可读数据量
    
    reg         [DATA_W-1:0]    fifo_mem[FIFO_DEPTH - 1:0]    ;//FIFO存储阵列
    // reg         [ADDR_W-1:0]    fifo_mem[0:(1'b1<<ADDR_W)-1]    ;//两种写法皆可

    wire        [ADDR_W-1:0]    wr_addr     ;//写地址
    wire        [ADDR_W-1:0]    rd_addr     ;//读地址

    reg         [ADDR_W:0]      wr_ptr      ;//二进制写指针
    reg         [ADDR_W:0]      rd_ptr      ;//二进制读指针

    wire        [ADDR_W:0]      wr_ptr_gray ;//格雷码写指针
    reg         [ADDR_W:0]      wr_ptr_gray1;//打2拍,写指针同步寄存器
    reg         [ADDR_W:0]      wr_ptr_gray2;

    wire        [ADDR_W:0]      rd_ptr_gray ;//格雷码读指针
    reg         [ADDR_W:0]      rd_ptr_gray1;//打2拍,读指针同步寄存器
    reg         [ADDR_W:0]      rd_ptr_gray2;

    reg         [ADDR_W:0]      wr_gray2_bin;//将同步至读时钟域的格雷码写指针转换为二进制
    reg         [ADDR_W:0]      rd_gray2_bin;//将同步至写时钟域的格雷码读指针转换为二进制

    reg         [DATA_W-1:0]    rd_data_r   ;//读出数据输出寄存器

    reg         [ADDR_W-1:0]    wr_usedw_r  ;//写时钟域下可读数据量寄存器
    reg         [ADDR_W-1:0]    rd_usedw_r  ;//读时钟域下可读数据量寄存器

    integer i;

//****************************************************************
//--wr_ptr、rd_ptr
//****************************************************************
    //写指针
    always @(posedge wrclk or negedge wrrst_n)begin 
        if(!wrrst_n)begin
            wr_ptr <= 'd0;
        end 
        else if(wren && ~wrfull)begin 
            wr_ptr <= wr_ptr + 1'b1;
        end 
    end

    //读指针
    always @(posedge rdclk or negedge rdrst_n)begin 
        if(!rdrst_n)begin
            rd_ptr <= 'd0;
        end 
        else if(rden && ~rdempty)begin 
            rd_ptr <= rd_ptr + 1'b1;
        end 
    end

//****************************************************************
//--wr_addr、rd_addr
//****************************************************************
    assign wr_addr = wr_ptr[ADDR_W-1:0];//数据写入地址
    assign rd_addr = rd_ptr[ADDR_W-1:0];//数据读出地址

//****************************************************************
//--写入数据、读出数据
//****************************************************************
    //写入数据
    always @(posedge wrclk or negedge wrrst_n)begin 
        if(!wrrst_n)begin
            for (i=0;i<(1'b1<<ADDR_W);i=i+1) begin  //利用for循环循环清零fifo_ram
                fifo_mem[i] <= 'd0;
            end
        end 
        else if(wren && ~wrfull)begin              //只要写使能有效就一直写入数据,数据数据量超过fifo深度,则会重新覆盖
            fifo_mem[wr_addr] <= wrdata;
        end  
    end

    //读出数据
    always @(posedge rdclk or negedge rdrst_n)begin 
        if(!rdrst_n)begin
            rd_data_r <= 'd0;
        end 
        else if(rden & !rdempty)begin 
            rd_data_r <= fifo_mem[rd_addr];
        end 
    end

//****************************************************************
//--二进制转格雷码
//****************************************************************
    assign wr_ptr_gray = wr_ptr^(wr_ptr>>1);//写指针格雷码
    assign rd_ptr_gray = rd_ptr^(rd_ptr>>1);//读指针格雷码

//****************************************************************
//--格雷码同步
//****************************************************************
    //将写指针格雷码同步到读时钟域 
    always @(posedge rdclk or negedge rdrst_n)begin 
        if(!rdrst_n)begin
            wr_ptr_gray1 <= 'd0;
            wr_ptr_gray2 <= 'd0;
        end 
        else begin 
            wr_ptr_gray1 <= wr_ptr_gray;
            wr_ptr_gray2 <= wr_ptr_gray1;
        end 
    end

    //将读指针格雷码同步到写时钟域
    always @(posedge wrclk or negedge wrrst_n)begin 
        if(!wrrst_n)begin
            rd_ptr_gray1 <= 'd0;
            rd_ptr_gray2 <= 'd0;
        end 
        else begin 
            rd_ptr_gray1 <= rd_ptr_gray;
            rd_ptr_gray2 <= rd_ptr_gray1;
        end 
    end

//****************************************************************
//--格雷码转二进制
//****************************************************************
    /*
    格雷码转二进制:格雷码的最高位作为二进制的最高位,二进制次高位产生过程是
    使用二进制的高位和格雷码次高位相异或得到
    */

    //将同步至读时钟域的写指针格雷码转换为二进制
    always @(*)begin 
        wr_gray2_bin[ADDR_W] = wr_ptr_gray2[ADDR_W];
        for (i=ADDR_W-1;i>=0;i=i-1) begin
            wr_gray2_bin[i] = wr_gray2_bin[i+1]^wr_ptr_gray2[i];
        end
    end

    //将同步至写时钟域的格雷码读指针转换为二进制
    always @(*)begin 
        rd_gray2_bin[ADDR_W] = rd_ptr_gray2[ADDR_W];
        for (i=ADDR_W-1;i>=0;i=i-1) begin
            rd_gray2_bin[i] = rd_gray2_bin[i+1]^rd_ptr_gray2[i];
        end
    end

//****************************************************************
//--输出
//****************************************************************
    //空标志
    assign wrempty = wr_ptr == rd_gray2_bin;
    assign rdempty = rd_ptr == wr_gray2_bin;

    //满标志
    assign wrfull = (wr_ptr != rd_gray2_bin) && (wr_ptr[ADDR_W-1:0] == rd_gray2_bin[ADDR_W-1:0]);
    assign rdfull = (rd_ptr != wr_gray2_bin) && (rd_ptr[ADDR_W-1:0] == wr_gray2_bin[ADDR_W-1:0]);

    //读出数据
    assign rddata = rd_data_r;

    //可读数据量
    always @(posedge wrclk or negedge wrrst_n)begin 
        if(!wrrst_n)begin
            wr_usedw_r <= 'd0;
        end 
        else begin 
            wr_usedw_r <= wr_ptr - rd_gray2_bin;
        end 
    end

    always @(posedge rdclk or negedge rdrst_n)begin 
        if(!rdrst_n)begin
            rd_usedw_r <= 'd0;
        end 
        else begin 
            rd_usedw_r <= wr_gray2_bin - rd_ptr;
        end 
    end

    assign wrusedw = wr_usedw_r;
    assign rdusedw = rd_usedw_r;

endmodule

2、测试文件的编写

新建一个tb_async_fifo.v文件,如下:

`timescale 1ns/1ns
    
module tb_async_fifo();

//激励信号定义 
    reg				tb_clk  	;
    reg				tb_rst_n	;
    reg             wren        ;
    reg     [7:0]   wrdata      ;
    reg             rden        ;

//输出信号定义
    wire            wrempty     ;
    wire            wrfull      ;
    wire    [6:0]   wrusedw     ;
    wire    [7:0]   rddata      ;
    wire            rdempty     ;
    wire            rdfull      ;
    wire    [6:0]   rdusedw     ;

//时钟周期参数定义	
    parameter		CLOCK_CYCLE = 20;   

//模块例化
    async_fifo #(.FIFO_WIDTH(8),.FIFO_DEPTH(128)) async_fifo_inst(
    //Write clock domain
    /*input                               */.wrclk   (tb_clk    ),//写时钟
    /*input                               */.wrrst_n (tb_rst_n  ),//写侧复位,异步复位,低有效
    /*input                               */.wren    (wren      ),//写使能
    /*input           [FIFO_WIDTH-1:0]    */.wrdata  (wrdata    ),//写数据输入
    /*output                              */.wrempty (wrempty   ),//写侧空标志
    /*output                              */.wrfull  (wrfull    ),//写侧满标志
    /*output          [FIFO_DEPTH-1:0]    */.wrusedw (wrusedw   ),//写时钟域下可读数据量
    
    /*input                               */.rdclk   (tb_clk    ),//读时钟
    /*input                               */.rdrst_n (tb_rst_n  ),//读侧复位,异步复位,低有效
    /*input                               */.rden    (rden      ),//读使能
    /*output          [FIFO_WIDTH-1:0]    */.rddata  (rddata    ),//读数据输出
    /*output                              */.rdempty (rdempty   ),//读侧空标志
    /*output                              */.rdfull  (rdfull    ),//读侧满标志
    /*output          [FIFO_DEPTH-1:0]    */.rdusedw (rdusedw   ) //读时钟域下可读数据量
);		

//产生时钟
    initial 		tb_clk = 1'b0;
    always #(CLOCK_CYCLE/2) tb_clk = ~tb_clk;

    integer k;
//产生激励
    initial  begin 
        tb_rst_n = 1'b1;
        wren   = 'd0;
        wrdata = 'd0;
        rden   = 'd0;
        #(CLOCK_CYCLE*2);
        tb_rst_n = 1'b0;
        #(CLOCK_CYCLE*20);
        tb_rst_n = 1'b1;
        #1;

        //模拟写操作
        for (k=0;k<127;k=k+1) begin
            wren = 1'b1;
            wrdata = {$random}%256;
            #CLOCK_CYCLE;
        end
        wren = 1'b0;

        #(CLOCK_CYCLE*50);

        //模拟读操作
        for (k=0;k<127;k=k+1) begin
            rden = 1'b1;
            #CLOCK_CYCLE;
        end
        rden = 1'b0;

        #(CLOCK_CYCLE*50);

        //模拟复位,清空fifo
        // tb_rst_n = 1'b0;
        // #(CLOCK_CYCLE*150);

        #(CLOCK_CYCLE*50);
        $stop;

    end

endmodule 
                                                 

3、波形图仿真

 通过前后两张波形图我们可以看到是先写入128个数据之后再进行数据的输出,其中的剩余量,空满信号啥的也是正常变换,说明我们设计的异步FIFO成功。

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

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

相关文章

一看就会的Mysql 集群技术

目录 一、Mysql介绍 1.1什么是MySQL 1.2MySQL的优势 1.3MySQL的常用语句 二、MySQL源码安装 三、实验练习 3.1MySQL部署 实验环境 实验步骤 1.创建用户&#xff0c;数据目录&#xff0c;更改权限 2.修改文件 3.初始化&#xff0c;会生成一个密码&#xff0c;将其保…

没有人会窃取你的想法,关键在于执行

没有人会窃取你的想法&#xff0c;关键在于执行 引言 当我第一次读到 Pieter Levels 的创业故事时&#xff0c;我感到无比激动和鼓舞。那种看到未来无限可能的感觉&#xff0c;让我充满了希望与奋斗的力量。他的经历不仅让我明白了创意的价值&#xff0c;更重要的是让我深刻感…

内网穿透的几种方法

内网穿透的几种方法 随着网络技术和应用的不断发展&#xff0c;越来越多的企业和个人需要实现内外网之间的数据交互和服务访问。然而&#xff0c;由于防火墙、NAT&#xff08;网络地址转换&#xff09;等安全措施的存在&#xff0c;直接从外部访问内部网络中的服务器或设备变得…

Linux基础软件-dns(一)

作者介绍&#xff1a;简历上没有一个精通的运维工程师。希望大家多多关注作者&#xff0c;下面的思维导图也是预计更新的内容和当前进度(不定时更新)。 Linux进阶部分又分了很多小的部分,我们刚讲完了Linux日常运维。讲的那些东西都算是系统自带的&#xff0c;但是Linux作为一个…

Nvidia驱动莫名其妙不好使了?nvidia-smi报错?如何解决?已解决!!

文章目录 一、报错提示二、解决方案2.1 原因1的解决办法2.2 原因2的解决方案 一、报错提示 Ubuntu20.04出现Failed to initialize NVML: Driver/library version mismatch问题NVIDIA-SMI has failed because it couldn‘t communicate with the NVIDIA driver. 二、解决方案 …

深入探究linux文件IO

一、原子操作和竞争条件 所有系统调用都是以原子操作方式执行的。之所以这么说&#xff0c;是指内核保证了某系统调用中的所有步骤会作为独立操作而一次性加以执行&#xff0c;其间不会为其他进程或线程所中断。 以独占方式创建一个文件 结合 O_CREAT 和 O_EXCL 标志来一次性…

AI学习记录 - 怎么理解 torch 的 torch.nn.BatchNorm2d

画图不易&#xff0c;有用就点个赞 这里创建了一个随机张量&#xff0c;形状为 (4, 3, 4, 4)&#xff0c;分别对应 形状为 (batch_size, num_channels, height, width) batch_size&#xff1a;批次 num_channels&#xff1a; 通道&#xff08;什么是通道看上一章节&#xff0…

串口和RS485通信

一、 定义串口收发数据结构体 /*COM Received Data Structure*/ typedef struct {uint8_t ubr_EndFlag; //Received data end flag uint8_t ubr_buffer[300]; //Received data bufferuint8_t ubr_bufferTemp[300]; //Received data bu…

K-medoids算法原理及Python实践

一、原理 K-medoids算法是一种聚类算法&#xff0c;它的原理与K-Means算法相似&#xff0c;但关键区别在于它使用数据集中的实际点&#xff08;称为medoids&#xff09;作为簇的中心点&#xff0c;而不是像K-Means那样使用簇内所有点的平均值。以下是K-medoids算法的主要原理&…

如何在算家云搭建模型Stable-Fast-3D(3D模型生成)

一、模型介绍 Stable-Fast-3D 具有 UV 展开和照明解缠的稳定快速 3D 网格重建&#xff0c;它是一种从单个图像进行快速前馈 3D 网格重建的最先进的开源模型。 二、模型搭建流程 基础环境最低要求说明&#xff1a; 环境名称版本信息1Ubuntu22.04.4 LTSCudaV12.1.105Python3.…

【项目日记】高并发内存池 ---项目介绍及组件定长池的实现

余生还长&#xff0c;你别慌&#xff0c;也别回头&#xff0c;别念旧. --- 余华 --- 1 高并发内存池简介 高并发内存池项目是实现一个高并发的内存池&#xff0c;他的原型是google的一个开源项目tcmalloc&#xff0c;tcmalloc全称Thread-Caching Malloc&#xff0c;即线程缓存…

快速排序与其例题

一、快速排序 1、简单介绍&#xff1a;快速排序&#xff08;Quick Sort&#xff09;是一种高效的排序算法&#xff0c;由计算机科学家Tony Hoare在1960年提出。它是基于分治法的排序算法&#xff0c;其基本思想和步骤如下&#xff1a; 基本概念 快速排序的核心思想是将待排序…

一种商业模式既解决引流又解决复购 你想了解一下嘛?

欢迎各位&#xff0c;我是你们的电商策略顾问&#xff0c;吴军。今天&#xff0c;我将向大家介绍一种新颖的商业模式——循环购物模式。这种模式有何独特之处&#xff1f;商家真的在进行慷慨的赠金活动吗&#xff1f;消费者在购物的同时还能获得额外收益&#xff1f;甚至可以将…

Python控制流:条件语句(if, elif, else)①

文章目录 前言1. 基本条件语句1.1 if 语句1.2 else 语句1.3 elif 语句1.4 嵌套条件语句 2. 条件表达式3. 多条件判断4. 比较运算符和逻辑运算符5. 常见错误和最佳实践5.1 常见错误5.2 最佳实践 6. 综合详细的例子&#xff1a;学生成绩管理系统6.1 类和方法Student 类 6.2 主函数…

LD/T698.45 协议解析(新)

通信架构 客户机和服务器在开始通信前&#xff0c;通信信道必须先完成预连接。预连接建立后&#xff0c;默认具有一个最低权限的应用连接&#xff0c;客户机和服务器之间可直接进行数据交换。当客户机需要得到更高权限的服务器服务时&#xff0c;客户机必须发起建立更高权限的…

浦发银行不良堆积,新任领导的无奈

撰稿|芋圆 浦发银行在2023年进行了一波董监高人员大变动&#xff0c;董事长和行长两位掌舵人双双离职&#xff0c;在其任内&#xff0c;浦发银行自2020年起的营收、利润状况和资产质量就一直难有起色。 目前&#xff0c;距新任领导班子上任已差不多过去一年之久。在这一年里&a…

Redis(面试题【速记】)

Redis简介 Redis 是一个开源(BSD 许可)内存数据结构存储用作数据库、缓存、消息代理和流引擎。Redis 提供数据结构&#xff0c;例如 字符串、散列、列表、集合、带范围查询的排序集合、位图、超日志、地理空间索引和流。Redis 内置了复制、Lua 脚本、LRU 驱逐、事务和不同级别的…

【Linux —— 线程同步 - 条件变量】

Linux —— 线程同步 - 条件变量 条件变量的概念互斥量与条件变量的关系条件变量的操作代码示例 条件变量的概念 条件变量是一种用于线程间同步的机制&#xff0c;主要用于协调线程之间的执行顺序&#xff0c;允许线程在某个条件不满足时进入等待状态&#xff0c;直到其他线程通…

【Linux I/O】万字长文带思维导图,一文彻底掌握Linux I/O:深入解析操作系统数据交互的艺术

Linux I/O Linux I/O&#xff08;输入/输出&#xff09;是操作系统与外部设备进行数据交互的过程。在Linux系统中&#xff0c;I/O操作的管理和优化对于系统性能有着至关重要的影响。本文将详细介绍Linux中的各种I/O模型&#xff0c;包括它们的工作原理、优缺点以及适用场景&am…

ImportError: DLL load failed while importing _ssl: 找不到指定的模块。

windonw cmd下的输出&#xff1a; (python3.9) PS D:\git\ImageAnalysisService\core\medical_bills> python Python 3.9.19 (main, May 6 2024, 20:12:36) [MSC v.1916 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or …