Linux高级IO(二)

简介: Linux高级IO

三、五种IO模型

3.1 阻塞式IO

阻塞IO即在内核将数据准备好前,系统调用会一直等待


37f1afbb2f8f411780125fcd19e5b3d7.png


阻塞IO是最常见的IO模型,套接字默认都是阻塞方式


如当调用recvfrom函数从某个套接字上读取数据时,可能底层数据还没准备好,此时就需等待数据就绪,当数据就绪后再将数据从内核拷贝到用户空间,最后recvfrom函数才会返回

在recvfrom函数等待数据就绪期间,在用户看来该进程或线程就阻塞了,本质就是操作系统将该进程或线程的状态设置为了某种非R状态,然后将其放入等待队列中,当数据就绪后操作系统再将其从等待队列中唤醒,然后该进程或线程再将数据从内核拷贝到用户空间

以阻塞方式进行IO操作的进程或线程,在"等"和"拷贝"期间都不会返回,在用户看来就是阻塞了,因此被称为阻塞IO


3.2 非阻塞IO

非阻塞IO:若内核还未将数据准备好,系统调用仍然会直接返回,并且返回EWOULDBLOCK错误码


4d7e2bf7899440a59af34d32a72c7ac1.png


非阻塞IO需要程序员以循环的方式反复尝试读写文件描述符,即轮询,这对CPU是较大的浪费,一般只有特定场景下使用


如当调用recvfrom函数以非阻塞方式从某个套接字上读取数据时,若底层数据还没有准备好,那么recvfrom函数会立马错误返回,而不会让该进程或线程进行阻塞等待

因为没有读取的数据,因此该进程或线程后续还需要继续调用recvfrom函数,检测底层数据是否就绪,若没有就绪则继续错误返回,直到某次检测到底层数据就绪后,再将数据从内核拷贝到用户空间然后进行成功返回

每次调用recvfrom函数读取数据时,就算底层数据没有就绪,recvfrom函数也会立马返回,在用户看来该进程或线程就没有被阻塞,因此被称为非阻塞IO

阻塞IO和非阻塞IO的区别在于,阻塞IO当数据没有就绪时,后续检测数据是否就绪的工作是由操作系统发起的,而非阻塞IO当数据没有就绪时,后续检测数据是否就绪的工作是由用户发起的


3.3 信号驱动IO

信号驱动IO:当内核将数据准备好后,使用SIGIO信号通知应用程序进行IO操作


2c36edb0648c45c08bb6a6b9f9d965d4.png


当底层数据就绪的时候会向当前进程或线程递交SIGIO信号,因此可以通过signal或sigaction函数将SIGIO的信号处理程序自定义为需要进行的IO操作,当底层数据就绪时就会自动执行对应的IO操作


比如需要调用recvfrom函数从某个套接字上读取数据,那么就可以将该操作定义为SIGIO的信号处理程序

当底层数据就绪时,操作系统就会递交SIGIO信号,此时就会自动执行定义的信号处理程序,进程将数据从内核拷贝到用户空间

信号的产生是异步的,但信号驱动IO是同步IO的一种


信号的产生异步的,因为信号在任何时刻都可能产生

但信号驱动IO是同步IO的一种,因为当底层数据就绪时,当前进程或线程需要停下正在做的事情,转而进行数据的拷贝操作,当前进程或线程仍然需要参与IO过程

判断一个IO过程是同步的还是异步的,本质就是看当前进程或线程是否需要参与IO过程,若参与即为同步IO,否则为异步IO


3.4 IO多路转接

IO多路转接也被称为IO多路复用,能够同时等待多个文件描述符的就绪状态


9be427f4ce234932aff452e2ca89e286.png


IO多路转接的思想:


因为IO过程分为"等"和"拷贝"两个步骤,因此使用的recvfrom等接口的底层实际上都做了两件事,第一件事是数据不就绪时需要等,第二件事是数据就绪后需要进行拷贝

虽然recvfrom等接口也有"等"的能力,但这些接口一次只能"等"一个文件描述符上的数据或空间就绪,IO效率太低

因此系统提供了三组接口,即select、poll和epoll,这些接口的核心工作就是"等",可将所有"等"的工作都交给这些多路转接接口

因为这些多路转接接口是一次"等"多个文件描述符的,因此能将"等"的时间重叠,数据就绪后再调用对应的recvfrom等函数进行数据的拷贝,此时这些函数就能够直接进行拷贝,而不需要"等"了

IO多路转接就像是帮人排队的黄牛,因为多路转接接口实际并没有进行数据拷贝。排队黄牛可以一次帮多个人排队,此时就将多个人排队的时间进行了重叠


3.5 异步IO

异步IO:由内核在数据拷贝完成时,通知应用程序


b044f81d96de429cb90ac7a2bef3fd82.png


进行异步IO需调用一些异步IO接口,异步IO接口调用后会立马返回,因为异步IO不需要发起者进行"等"和"拷贝"的操作,都由操作系统来完成,只需发起IO

当IO完成后操作系统会通知应用程序,因此进行异步IO的进程或线程并不参与IO的所有细节

四、重要概念

4.1 异步同行 && 同步通信

同步和异步关注的是消息通信机制


所谓同步,在发出一个调用时,在没有得到结果之前,该调用就不返回,但是一旦调用返回,就得到返回值了;即由调用者主动等待这个调用的结果

异步则是相反,调用在发出之后,这个调用就直接返回了,所有没有返回结果;即当一个异步过程调用发出后,调用者不会立刻得到结果;而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用

为什么非阻塞IO在没有得到结果之前就返回了?


IO是分为"等"和"拷贝"两步的,当调用recvfrom进行非阻塞IO时,若数据没有就绪,那么调用会直接返回,此时这个调用返回时并没有完成一个完整的IO过程,即便调用返回了也是属于错误的返回

因此该进程或线程后续还需继续调用recvfrom,轮询检测数据是否就绪,当数据就绪后再把数据从内核拷贝到用户空间,这才是一次完整的IO过程

因此,在进行非阻塞IO时,在没有得到结果之前,虽然这个调用会返回,但后续还需要继续进行轮询检测,因此可以理解成调用还没有返回,而只有当某次轮询检测到数据就绪,并且完成数据拷贝后才认为该调用返回了

同步通信、同步与互斥


在多进程和多线程中有同步与互斥的概念,但是这里的同步通信和进程或线程之间的同步是不相干的概念


进程/线程同步指的是,在保证数据安全的前提下,让进程/线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,谈论的是进程/线程间的一种工作关系

同步IO指的是进程/线程与操作系统之间的关系,谈论的是进程/线程是否需要主动参与IO过程

因此当看到"同步"这个词的时候,需先明确这个"同步"是同步通信的同步,还是同步与互斥的同步


4.2 阻塞 && 非阻塞

阻塞和非阻塞关注的是程序在等待调用结果(消息、返回值)时的状态


阻塞调用是指调用结果返回之前,当前线程会被挂起,调用线程只有在得到结果之后才会返回

非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程

4.3 其他高级IO

非阻塞IO,记录锁,系统V流机制,I/O多路转接(I/O多路复用),readv和writev函数以及存储映射IO(mmap),统称为高级IO


五、阻塞IO

系统中大部分的接口都是阻塞式接口,如使用read函数从标准输入中读取数据

#include <iostream>
#include <unistd.h>
#include <fcntl.h>
using namespace std;
int main()
{
  char buffer[1024];
  while (true){
    ssize_t size = read(0, buffer, sizeof(buffer)-1);
    if (size < 0){
      cerr << "read error" << endl;
      break;
    }
    else {
            buffer[size] = '\0';
        cout << "echo# " << buffer << endl;
        }
  }
  return 0;
}


程序运行后,若不进行输入操作,该进程就会阻塞,根本原因就是因为此时底层数据不就绪,因此read函数需进行阻塞等待


66b07601e8da483f9820dcb1392ffdc7.png


一旦进行输入操作,此时read函数就会检测到底层数据就绪,然后将数据读取到从内核拷贝到程序员传入的buffer数组中,并且将读取到的数据输出到显示器上面,最后就看到了输入的字符串



a4b0ea1b95df4d6fb9aad914c84e3308.png


六、非阻塞IO

打开文件时默认都是以阻塞的方式打开的,若要以非阻塞的方式打开某个文件,需在使用open函数打开文件时携带O_NONBLOCK或O_NDELAY选项,此时就能够以非阻塞的方式打开文件


fdefb1ddb5e543f88e72f731007f49c8.png


fcntl函数


int fcntl(int fd, int cmd, ... /* arg */);

参数说明:


fd:已打开的文件描述符

cmd:需要进行的操作

…:可变参数,传入的cmd值不同,后面追加的参数也不同

fcntl函数常用的5种功能与其对应的cmd取值如下


复制一个现有的描述符(cmd=F_DUPFD)

获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD)

获得/设置文件状态标记(cmd=F_GETFL或F_SETFL)

获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN)

获得/设置记录锁(cmd=F_GETLK, F_SETLK或F_SETLKW)

返回值说明:

若函数调用成功,则返回值取决于具体进行的操作

若函数调用失败,则返回-1,同时错误码被设置

实现SetNonBlock函数


可定义一个函数,该函数用于将指定的文件描述符设置为非阻塞状态


先调用fcntl函数获取该文件描述符对应的文件状态标记(位图结构),此时调用fcntl函数时传入的cmd值为F_GETFL

在获取到的文件状态标记上添加非阻塞标记O_NONBLOCK,再次调用fcntl函数对文件状态标记进行设置,此时调用fcntl函数时传入的cmd值为F_SETFL

#include <iostream>
#include <fcntl.h>
#include <unistd.h>
#include <cstring>
using namespace std;
bool SetNonBloack(int fd) 
{
    int fl = fcntl(fd, F_GETFL);//获取该fd对应的文件读写标志位
    if(fl < 0) return false;
    fcntl(fd, F_SETFL, fl | O_NONBLOCK);//设置非阻塞
    return true;
}
int main()
{
    SetNonBloack(0);
    char buffer[1024];
    while(true) 
    {
        sleep(1);
        errno = 0;
        ssize_t size = read(0,buffer,sizeof(buffer) - 1);
        if(size > 0) {
            buffer[size - 1] = 0;
            cout << "echo# " << buffer << " errno[success]:" << errno << " errString:" << strerror(errno) <<endl;
        }
        else {
            if(errno == EWOULDBLOCK || errno == EAGAIN) {
                cout << "当前0号fd数据没有就绪,请待会再进行尝试" << endl;
                continue;
            } 
            else if(errno == EINTR) {
                cout << "当前IO可能被信号中断,请再次尝试" << endl;
                continue;
            }
            else {//差错处理
                cout << "read error" << " errno:" << errno << " errString:" << strerror(errno) <<endl;
            }
        }
    }
    return 0;
}


当read函数以非阻塞方式读取标准输入时,若底层数据不就绪,那么read函数就会立即返回,并且是以出错的形式返回的,此时的错误码会被设置为 EAGAIN 或 EWOULDBLOCK

因此在以非阻塞方式读取数据时,若调用read函数时得到的返回值是-1,此时还需通过错误码进一步进行判断,若错误码的值是EAGAIN或EWOULDBLOCK,说明本次调用read函数出错是因为底层数据还没有就绪,因此后续还应该继续调用read函数进行轮询检测数据是否就绪,当数据继续时再进行数据的读取

调用read函数在读取到数据前可能被其他信号中断,此时read函数也会以出错的形式返回,此时的错误码会被设置为EINTR,应重新执行read函数进行数据的读取

因此在以非阻塞的方式读取数据时,若调用read函数读取到的返回值为-1,此时并不应该直接认为read函数在底层读取数据时出错,而应该继续判断错误码,若错误码的值为EAGAIN、EWOULDBLOCK 或 EINTR则应该继续调用read函数再次进行读取


目录
相关文章
|
2天前
|
机器学习/深度学习 缓存 监控
linux查看CPU、内存、网络、磁盘IO命令
`Linux`系统中,使用`top`命令查看CPU状态,要查看CPU详细信息,可利用`cat /proc/cpuinfo`相关命令。`free`命令用于查看内存使用情况。网络相关命令包括`ifconfig`(查看网卡状态)、`ifdown/ifup`(禁用/启用网卡)、`netstat`(列出网络连接,如`-tuln`组合)以及`nslookup`、`ping`、`telnet`、`traceroute`等。磁盘IO方面,`iostat`(如`-k -p ALL`)显示磁盘IO统计,`iotop`(如`-o -d 1`)则用于查看磁盘IO瓶颈。
|
2天前
|
Linux
Linux操作系统调优相关工具(三)查看IO运行状态相关工具 查看哪个磁盘或分区最繁忙?
Linux操作系统调优相关工具(三)查看IO运行状态相关工具 查看哪个磁盘或分区最繁忙?
31 0
|
2天前
|
数据采集 异构计算
LabVIEW编程LabVIEW开发高级数据采集技术 操作数字IO 例程与相关资料
LabVIEW编程LabVIEW开发高级数据采集技术 操作数字IO 例程与相关资料
42 22
|
2天前
|
Unix Linux Shell
【探索Linux】P.12(文件描述符 | 重定向 | 基础IO)
【探索Linux】P.12(文件描述符 | 重定向 | 基础IO)
13 0
|
18小时前
|
存储 Linux C语言
|
2天前
|
监控 Linux 数据处理
|
2天前
|
存储 安全 Linux
从基础到高级:Linux用户与用户组权限设置详解
从基础到高级:Linux用户与用户组权限设置详解
|
2天前
|
Unix Linux 开发工具
【探索Linux】P.11(基础IO,文件操作)
【探索Linux】P.11(基础IO,文件操作)
12 0
|
2天前
|
运维 监控 Linux
【专栏】Linux中的ping命令不仅用于基础网络连通性检查,Linux 中这些高级 ping 命令可以提高工作效率!
【4月更文挑战第28天】Linux中的ping命令不仅用于基础网络连通性检查,还有许多高级功能。了解如`-c`(设置数据包数量)、`-i`(设置间隔时间)和`-w`(设置超时时间)等选项能提升效率。进阶技巧包括自定义数据包大小(`-s`)、详细统计信息(`-v`)、持续ping(`-t`)、指定源地址(`-S`)和多目标ping。这些在网络性能测试、故障排查和监控中极其有用。注意权限、参数选择,并结合其他工具以准确解读结果。提升网络管理技能,善用ping命令的全部潜力。
|
2天前
|
安全 网络协议 Linux
【专栏】Linux系统中ping命令的使用,包括其基本语法、输出信息、常用参数及高级用法
【4月更文挑战第28天】本文详细介绍了Linux系统中ping命令的使用,包括其基本语法、输出信息、常用参数及高级用法。通过ping,用户可测试网络连通性、诊断故障及评估性能。此外,文章还讨论了ping在不同协议、模拟网络环境及与其他命令结合使用时的场景。注意防火墙和网络环境可能影响ping结果,理解错误信息有助于网络问题排查。熟练掌握ping命令,能助你成为Linux网络专家。不断学习和实践,提升网络技能,为构建稳定网络环境贡献力量。