zoukankan      html  css  js  c++  java
  • I/O模型详细解析

    内核空间和用户空间:
    由于操作系统都包括内核空间和用户空间(或者说内核态和用户态),内核空间主要存放的是内核代码和数据,是供系统进程使用的空间。而用户空间主要存放的是用户代码和数据,是供用户进程使用的空间。目前Linux系统简化了分段机制,使得虚拟地址与线性地址总是保持一致,因此,Linux系统的虚拟地址也是0-4G。Linux系统将这4G空间分为了两个部分:将最高的1G空间(从虚拟地址0xC0000000到0xFFFFFFFF)供内核使用,即为“内核空间”,而将较低的3G空间(从虚拟地址 0x00000000到0xBFFFFFFF)供用户进程使用,即为“用户空间”。同时由于每个用户进程都可以通过系统调用进入到内核空间,因此Linux的内核空间可以认为是被所有用户进程所共享的,因此对于一个具体用户进程来说,它可以访问的虚拟内存地址就是0-4G。另外Linux系统分为了四种特权级:0~3,主要是用来保护资源。0级特权最高,而3级则为最低,系统进程主要运行在0级,用户进程主要运行在3级。
    一般来说,IO操作都分为两个阶段,就拿套接口的输入操作来说,它的两个阶段主要是:
    1)等待网络数据到来,当分组到来时,将其拷贝到内核空间的临时缓冲区中
    2)将内核空间临时缓冲区中的数据拷贝到用户空间缓冲区中

    服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种:

    1)同步阻塞IO(Blocking IO):即传统的IO模型。

    2)同步非阻塞IO(Non-blocking IO):默认创建的socket都是阻塞的,非阻塞IO要求socket被设置为NONBLOCK。注意这里所说的NIO并非Java的NIO(New IO)库。

    3)IO多路复用(IO Multiplexing):即经典的Reactor设计模式,有时也称为异步阻塞IO,Java中的Selector和Linux中的epoll都是这种模型。

    4)异步IO(Asynchronous IO):即经典的Proactor设计模式,也称为异步非阻塞IO。

    同步和异步的概念描述的是用户线程与内核的交互方式:同步是指用户线程发起IO请求后需要等待或者轮询内核IO操作完成后才能继续执行;而异步是指用户线程发起IO请求后仍继续执行,当内核IO操作完成后会通知用户线程,或者调用用户线程注册的回调函数。

    阻塞和非阻塞的概念描述的是用户线程调用内核IO操作的方式:阻塞是指IO操作需要彻底完成后才返回到用户空间;而非阻塞是指IO操作被调用后立即返回给用户一个状态值,无需等到IO操作彻底完成。

    对于一个network IO (这里我们以read举例),它会涉及到两个系统对象,一个是调用这个IO的process (or thread),另一个就是系统内核(kernel)。当一个read操作发生时,它会经历两个阶段:
     1 等待数据准备 (Waiting for the data to be ready)
     2 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)
    记住这两点很重要,因为这些IO Model的区别就是在两个阶段上各有不同的情况。

    阻塞式I/O模型
    产生阻塞的原因
    linux进程调度算法-时间片调度算法
    每个进程占用CPU一个时间片后被挂起
    当前运行进程如果需要等待其他系统资源,且不是非阻塞方式运行,将进入等待状态

    设置socket为非阻塞方式
    函数fcntl
    int flags;
    flag=fcntl(sockfd,F_GETFL,0);
    fcntl(sockfd,F_SETFL,flag|O_NONBLOCK);
    函数ioctl
    int on=1;
    ioctl(sockfd,FIONBIO,&on);


    非阻塞式I/O模型对4种I/O操作返回的错误
    读操作
    接收缓冲区无数据时返回EWOULDBLOCK
    写操作
    发送缓冲区无空间时返回EWOULDBLOCK
    空间不够时部分拷贝,返回实际拷贝字节数
    建立连接
    启动3次握手,立刻返回错误EINPROGRESS
    服务器客户端在同一主机上connect立即返回成功
    接受连接
    没有新连接返回EWOULDBLOCK

    阻塞式I/O模型的超时控制
    调用alarm函数设置超时
    超时到达时产生SIGALARM信号中断I/O函数阻塞,对于4种产生阻塞的函数均有效
    多次调用alarm时,产生的SIGALARM信号无法区分是哪一次超时引发的,无法实现超时控制
    示例:alarmio.cpp

    当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。
    这个图和blocking IO的图其实并没有太大的不同,事实上,还更差一些。因为这里需要使用两个system call (select 和 recvfrom),而blocking IO只调用了一个system call (recvfrom)。但是,用select的优势在于它可以同时处理多个connection。(多说一句。所以,如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。)
    在IO multiplexing Model中,实际中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。

    注意1:select函数返回结果中如果有文件可读了,那么进程就可以通过调用accept()或recv()来让kernel将位于内核中准备到的数据copy到用户区。

    注意2: select的优势在于可以处理多个连接,不适用于单个连接

    阻塞式I/O模型的超时控制
    设置socket选项
    设置SO_RCVTIMEO和SO_SNDTIMEO选项
    设置了这两个选项之后,所有的读写操作可以保证在超时范围内返回
    只需设置一次选项,对以后的读写操作均有效
    不适用于accept和connect
    示例:timeoutio.cpp

  • 相关阅读:
    git init 与 git init --bare 区别
    python_集合_笔记
    git笔记
    screen命令
    python的and和or优先级
    计算机语言的发展史
    python3颜色输出
    mysql_windows解压包安装
    那些经常不开心的上班族
    mysql主从搭建
  • 原文地址:https://www.cnblogs.com/ningxin18/p/7588812.html
Copyright © 2011-2022 走看看