自学内容网 自学内容网

Linux——五种IO模型

目录

一、I/O的理解

二、五种IO模型

1.阻塞式IO

2.非阻塞式IO

3.信号驱动IO

4.多路复用IO

5.异步IO 


一、I/O的理解

I/O的本质就是输入输出,C语言的stdio,C++的iostream,添加了这两个库,我们才能够进行printf、scanf、cin、cout。

在I/O中,我们大部分时间是在等,比如你调用了scanf(),当前进程就在等待你的输入,你输入完毕后,把输入数据拷贝到内存中,才会继续执行。因此,I/O = 等 + 拷贝。拷贝的效率是硬件决定的,但是等待的时间,是可以由程序来优化的。单位时间内,减少了等的比重,这也就提高了I/O效率。

在网络中,我们经常需要去recv和send,recv是在等待对方发送消息,我来接受,如果此时对方还没有发送消息,我们就会在recv这里阻塞;send则是我发送消息,对方接受消息,当对方的接受缓冲区满了,也会造成阻塞,直到对方接收缓冲区有空间了,才可以发送过去。

如果现在我可以去干自己的事情,等我收到的对方通知(他的条件满足),我们再去调用recv或者send函数,这样就大大提高了效率

二、五种IO模型

为了理解五种IO模型,我们来看个钓鱼的例子,钓鱼 = 等 + 钓 ,这里可以把钓看做是拷贝,钓都是一样的,但是等的过程不相同。

  • 张三:永远看着鱼漂,谁叫他都没有                                                ——阻塞式IO
  • 李四:一会看看鱼漂,一会看看书、刷书视频                                  ——非阻塞轮询式IO
  • 王五:铃铛绑在的鱼竿顶部(铃铛响了,就开始抬杆)                    ——信号驱动式IO
  • 赵六:100个鱼竿一起钓,哪个鱼漂动了就去哪里                            ——多路复用IO
  • 田七:发起了钓鱼,让助手小王去钓鱼,自己去干其他事情             ——异步IO

其中,阻塞、非阻塞、信号驱动、多路复用都是同步IO,他们都参与了IO的过程,要么等了、要么拷贝了、或者都做了。

而田七只是发起了钓鱼,然后就当甩手掌柜,交给小王去等去拷贝,自己并没有参与,他是异步IO。

在这个故事中,河流是操作系统,鱼是数据,鱼漂是数据就绪条件,鱼竿是文件描述符。

1.阻塞式IO

阻塞IO: 在内核将数据准备好之前,系统调用会一直等待。 所有的套接字,默认都是阻塞方式

#include <iostream>
#include <unistd.h>
#include <fcntl.h>

int main()
{
    while (true)
    {
        char buff[1024];
        ssize_t s = read(0, buff, sizeof(buff - 1));
        if(s>0)
        {
            buff[s] = '\0';
            std::cout<<"echo# "<<buff<<std::endl;
        }
        else if (s==0)
        {
            std::cout<<"end stdin"<<std::endl;
            break;
        }
        else
        {
            //TODO
        }
    }
}

2.非阻塞式IO

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

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

#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <cstdlib>

//使用F_GETFL将当前的文件描述符的属性取出来(这是一个位图)。
//然后再使用F_SETFL将文件描述符设置回去, 设置回去的同时, 加上一个O_NONBLOCK参数
void SetNoBlock(int fd) 
{             
    // 获取fd文件描述符的状态标志
    int f1 = fcntl(fd, F_GETFL);
    if (f1 < 0)
    {
        std::cerr << "fcntl error" << std::endl;
        exit(0);
    }
    // 设置fd文件描述符的状态标志   f1为老标志的内容,O_NONBLOCK为非阻塞
    fcntl(fd, F_SETFL, f1 | O_NONBLOCK);
}

int main()
{
    SetNonBlock(0);
    while (true)
    {
        char buff[1024];
        ssize_t s = read(0, buff, sizeof(buff) - 1);
        if (s > 0)
        {
            buff[s] = '\0';
            std::cout << "echo# " << buff << std::endl;
        }
        else if (s == 0)
        {
            std::cout << "end stdin" << std::endl;
            break;
        }
        else
        {
            // 非阻塞等待,数据没有准备好,返回值仍然是 -1  但不认为是出错
            // 因此我们需要查看错误码,看具体原因
            if (errno == EWOULDBLOCK || errno == EAGAIN)
            {
                std::cout << "OS的底层数据还没有就绪,errno: " << errno << std::endl;
            }
            else if(errno == EINTR)
            {
                std::cout << "IO 被信号中断,请再次尝试"<< std::endl;
            }
            else
            {
                //这里才是read出错
                break;
            }
        }
        sleep(1);
    }
}

3.信号驱动IO

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

4.多路复用IO

IO多路复用:虽然从流程图上看起来和阻塞IO类似。实际上最核心在于IO多路复用能够同时等待多个文件描述符的就绪状态

操作系统给我们提供了select系统调用,我们可以将很多文件描述符交给select管理。多个文件描述符就绪的概率是要比前面IO模式单个文件描述符就绪的概率高得多,当有文件描述符准备就绪,select不进行拷贝,而是通知应用程序,让应用程序去进行相应的IO处理,此时,是不会出现IO阻塞的情况,因为是收到的通知才进行的IO。大大提高了效率。

select的使用 

5.异步IO 

异步IO:由内核在数据拷贝完成时,通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据)。

异步IO,只是发起的IO请求,由操作系统去完成IO等+拷贝的操作,最后吃现成的就行了。 


原文地址:https://blog.csdn.net/kkbca/article/details/140326362

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!