zoukankan      html  css  js  c++  java
  • i/o多路复用笔记

    1、用户空间和内核空间

    操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也可以访问底层硬件设备。为了保护用户进程不能直接操作内核,保证内核的安全,操作系统将虚拟空间划分为两部分,一部分是内核空间,一部分是用户空间。

    2、进程切换

    内核挂起当前正在cpu上运行的进程,并恢复以前挂起的某个进程的执行。

    • 保存处理机上下文,包括程序计数器和其它寄存器。
    • 更新PCB信息
    • 把进程的PCB移入到相应的队列,如就绪或阻塞队列
    • 选择另一个进程执行,并更新PCB
    • 更新内存管理的数据结构
    • 恢复上下文

    3、进程阻塞

    正在执行的进程,由于期待某些事件的发生,比如IO,自动执行阻塞原语,进入阻塞状态。这是一种主动行为,只有运行中的进程才可以切换到阻塞状态。在阻塞状态的进程是不占用CPU资源的。

    4、I/O模式

    标准I/O,数据会先被拷贝到操作系统的内核缓冲区,然后才会从操作系统的内核缓冲区拷贝到应用程序的地址空间。

    linux有五种网络模式:

    • 阻塞I/O
    • 非阻塞I/O
    • I/O多路复用
    • 信号驱动I/O(signal driven i/o,不常用)
    • 异步I/O

    1、阻塞I/O

    blocking i/o

    用户进程调用recvfrom系统调用,进程进入阻塞状态,等待数据准备好,数据会从内核拷贝到用户内存,然后内核返回结果,用户进程解除block状态,重新运行。

    2、非阻塞I/O

    nonblocking i/o

    用户进程调用read操作,如果数据还没准备好,内核立刻返回error,用户进程不会阻塞。用户进程可以通过反复调用read操作轮询来获得数据。

    3、I/O多路复用

    I/O multiplexing,也就是select、poll、epoll,也可以被称为event driven I/O。好处是单个进程可以处理多个网络I/O,原理就是select、poll、epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达时,就通知用户进程。

    i/o multiplexing

    用户进程调用了select,整个进程会被block,同时,kernel会监听所有负责的socket,当有一个socket的数据准备好了,select会返回,用户进程再调用read操作,将数据从内核拷贝到用户进程。如果处理的并发连接数不是很高,使用select、poll、epoll的服务端性能不一定比使用multi-threading + blocking IO的服务端性能好。I/O多路复用时,每一个socket,一般设置为非阻塞模式。

    5、I/O多路复用

    • select

    int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)
    

    select函数监视的文件描述符分3类,分别是readfds、writefds、exceptfds。调用select函数会阻塞,直到有描述符就绪或者超时,函数返回。当select函数返回后,可以通过遍历fdset,找到就绪的描述符。
    select可以良好的跨平台。缺点是单个进程能够监视的文件描述符的数量存在最大限制。在Linux上一般为1024,可以通过修改宏定义甚至重新编译内核的方式提升这一限制。

    • poll

    int poll(struct pollfd *fds, unsigned int nfds, int timeout)
    struct pollfd {
      int fd;
      short events; // requested events to watch
      short revents; //returned events witnessed
    }
    

    pollfd结构包含了要监视的event和发生的event,不再使用select『参数-值』传递的方式。pollfd没有最大数量限制,但仍需要轮询返回的pollfd来获取就绪的文件描述符。随着监视的文件描述符数量的增长,效率也会线性下降。

    • epoll

      和select、poll相比,epoll更加灵活,没有描述符的限制。epoll使用一个文件描述符管理多个描述符,将用户进程的文件描述符事件存放在内核的一个事件表中,这样在用户空间和内核空间只需copy一次。
    int epoll_create(int size)
    

    创建一个epoll的句柄,size用来告诉内核监听的数目。size并不是限制了epoll所能监听的最大描述符个数,只是对内核初始分配内部数据结构的一个建议。

    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
    

    函数对指定描述符fd执行op操作。op取值由三个宏表示:添加EPOLL_CTL_ADD,删除EPOLL_CTL_DEL,修改EPOLL_CTL_MOD。epoll_event告诉内核需要监听什么事。

    typedef union epoll_data {
      void *ptr;
      int fd;
      _uint32_t u32;
      _uint64_t u64;
    } epoll_data_t;
    struct epoll_event {
      _uint32_t events; 
      epoll_data_t data;
    };
    
    //epoll events
    EPOLLIN:表示对应的文件描述符可读(包括对端SOCKET正常关闭)
    EPOLLOUT: 表示对应的文件描述符可写
    EPOLLPRI: 表示对应的文件描述符有紧急数据可读(带外数据)
    EPOLLERR: 表示对应的文件描述法发生错误
    EPOLLHUP: 表示对应的文件描述法被挂起
    EPOLLET: 设置为边缘触发
    EPOLLONESHOT: 只监听一次事件,当监听完这次事件后,如果还需要监听这个socket,需要再次把这个socket加入epoll
    
    int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
    

    参数events表示内核得到的事件的集合。maxevents告知内核这个events有多大,取值不能大于创建epoll_create()时的size。参数timeout时超时时间(ms,0表示立刻返回,-1表示不确定,也有说法是永久堵塞)。该函数返回需要处理的事件数目,如返回0表示已超时。

    6、epoll工作模式

    两种:LT(level trigger), ET(edge trigger)。缺省是LT。

    • LT: 当epoll_wait检测到描述符事件发生时,将此事件通知应用程序。应用程序可以不立即处理该事件,下次调用epoll_wait时,会再次响应应用程序并通知。
    • ET: 当epoll_wait检测到描述符事件发生时,将此事件通知应用程序。应用程序必须立即处理该事件,下次调用epoll_wait时,不会再次通知此事件。

    LT是缺省工作模式,支持block和non-block socket。ET是高速工作模式,只支持non-block。

    7、总结

    在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描。epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。epoll监视的描述符的数量不受限制,它所支持的FD上限是系统最大可打开文件数目。

  • 相关阅读:
    Linux .o a .so .la .lo的区别
    linux源码Makefile详解
    Kconfig详解
    如何将驱动程序静态编译进内核
    getpeername
    Socket programming in C on Linux | tutorial
    C Socket Programming for Linux with a Server and Client Example Code
    UDP protocol
    TCP protocol
    How to learn linux device driver
  • 原文地址:https://www.cnblogs.com/xianzhedeyu/p/6369379.html
Copyright © 2011-2022 走看看