美文网首页计算机基础程序员代码改变世界
操作系统中的I/O,及高性能IO模型

操作系统中的I/O,及高性能IO模型

作者: ck2016 | 来源:发表于2017-07-17 15:31 被阅读711次

    I/O(Input/Output)输入输出,总体图

    一.操作系统与设备之间的IO

    简单来说(详细的请看《现代操作系统》),操作系统通过设备驱动程序访问IO设备。方式有:
    (1)轮询方式
    CPU主动在各种设备中轮询检查状态,有数据就IO。

    (2)中断方式
    设备有数据的时候,发出中断,由CPU决定要不要响应中断,然后中断,去处理设备的IO。CPU不用经常轮询设备状态。被动接收中断就行。

    (3)DMA直接存储器访问方式
    如果1个字节的数据中断一次,传1KB的数据得中断1024次,太浪费CPU时间,于是有了DMA方式,CPU只需要把开头和结束的地址告诉DMA,中间由DMA完成数据IO。CPU从字节干预解放到数据块的干预。

    (4)通道控制方式
    DMA方式只能控制一个设备的一块数据,多块数据还是要CPU干预多次。于是有了通道来控制IO,它比DMA更强大,能控制多块数据,多个设备的IO,更加解放了CPU参与IO过程。

    二.操作系统与用户进程间的IO

    (进程中的线程才是CPU基本的执行/调度单元,下面用线程举例,用socket举例)
    设备来的数据放在内核cache中,需要用户线程去内核cache中取数据,复制到自己进程的cache中。有5中读取数据方式:

    (1)阻塞
    用户线程调用某些系统函数去内核取数据,直到数据到达内核cache前,该线程处于阻塞状态,等待数据到达。

    (2)非阻塞
    用户线程去取数据,不管内核cache有没有数据,都直接返回,可能拿到数据,也可能拿不到,不会使线程进入阻塞态。

    (3)IO多路复用
    多路就是一个线程管理多路IO,线程还是被阻塞调用,其中一路或几路IO有数据了就返回。需要线程遍历全部IO,判断是哪个IO有数据。
    例如 socket 的 select() 函数,线程调用 select() 进入阻塞态,任何一个IO有数据了,线程就退出阻塞态,获得机会继续执行。

    (4)信号驱动IO
    给一个IO注册一个信号和信号触发的回调函数,一旦信号被触发,回调函数里读取数据。
    例如给 socket 注册一个“可读”的信号,当数据来了,可读的时候,信号被触发,执行回调函数从内核cache复制数据到用户空间。

    (5)异步IO
    异步IO中,操作系统完成了数据从内核到用户空间的拷贝后,以信号的方式通知用户线程可以下一步操作。省去了用户线程阻塞下来拷贝数据的过程。

    IO管理

    假设一台服务器需要被1万个客户端连接。方法有:

    (1)单路:
    最简单的一个线程管理一个客户端的socket IO,那么需要1万的线程,假设每个线程占内存3MB,需要300G内存,单台服务器没那么大的内存,并且操作系统最大线程数有限制,unix下一个进程好像是最多只能开 4096 个线程。

    (2)IO 多路复用:
    socket一旦多起来,单路IO 就扛不住了,需要一个线程管理多个 socket IO,下面都是在一个线程内的情况。

    (2.1)select
    一个线程管理多个socket IO,调用 select() 进入阻塞态,任何一个IO有数据则返回,由于不知道是哪个 socket 有数据,需要遍历所有 socket fd 去判断,当1万个 socket 大部分都是有IO的时候,效率较高,如果只是那么几百个有IO,此方法效率较低。

    (2.2)epoll 和 kqueue
    epoll 是 linux 下的,kqueue 是 unix 下的。
    由于 select 需要遍历全部的 socket fd,效率较低,于是有了 epoll, kqueue 方式,kqueue 管理多个IO,阻塞调用等待函数,当有一个或多个IO事件,kqueue 直接返回多个IO事件的 socket fd,不需要遍历全部 socket fd,效率较高。

    假设一个 socket 连接的对象是 3 kb,8G的内存可以管理 280w 个连接。


    • select,epoll,kqueue 原理

    已知的情况
    内核中注册 socket 的 IO 中断处理的回掉函数,有 IO 了会回调该函数。

    select
    select 管理多个 socket,select 收到一个来自网卡 IO 中断就返回,不知道这个中断对应是哪个 socket fd 的。需要用户线程遍历判断。

    epoll
    epoll 收到一个 IO 中断,会去查找这个中断对应哪个 socket fd。
    epoll 中建立一个红黑树(平衡二叉树的一种),红黑树查找很高效。
    用户注册感兴趣的 socket 事件,就是把这个 socket fd 插入到红黑树中,用中断号做key,可以理解为(中断号,socket fd)的二元组。
    用户移除事件就是,删除树上的某个节点。

    然后收到一个IO中断,epoll 把网卡数据拷贝到内核cache,根据中断号在红黑树中查找对应的 fd,把 fd 加入到就绪链表中,准备返回给用户线程。用户直接得到就绪的 fd。

    kqueue
    收到 socket IO 中断去哈希表中查找对应的 socket fd,再把它放到一个链表里,返回。
    用户注册一个感兴趣的事件,就是往哈希表中添加一个 fd。


    一些优化

    Memory Map (mmap)
    设备的数据到内核cache,再到用户空间,中间需要把内核数据复制到用户空间,比较耗时,那么直接用 mmap 把用户空间的地址映射到内核空间,省去了内核数据到用户空间的拷贝

    if((src = mmap(0, statbuf.st_size, PROT_READ, MAP_SHARED, fdin, 0)) == MAP_FAILED) err_sys("mmap error for input");
    if((dst = mmap(0, statbuf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fout, 0)) == MAP_FAILED) err_sys("mmap error for output");
    memcpy(dst, src, statbuf.st_size);
    

    参考文献:
    使用 kqueue 在 FreeBSD 上开发高性能应用服务器
    epoll 底层实现源码分析
    Kqueue: A generic and scalable event notification facility
    FreeBSD Kqueue的实现原理
    操作系统-IO系统
    操作系统-IO模型

    相关文章

      网友评论

      • Ryannnn:所以像ios这种主线程有runloop, 然后nsurlsession通过delegate回调到主线程的属于异步io么
        ck2016:@Ryannnn 印象中nsurlsession底层会开个线程去执行
      • 指尖流年:打扰一下,(2.1)select段落中,“当1万个 socket 大部分都是有IO的时候,效率较高,如果只是那么几百个有IO,此方法效率较低。”这一句何解?是不是写反了,select方式应该是挨个遍历所有的socket IO,不是应该数量越大效率越低吗?
        ck2016:@指尖流年 如果是 epoll 方式,1万个连接,有1万个请求,他要在红黑树中查找1万次,每次查找是 n * log n 的复杂度,select 数组遍历,数组查找下一个元素 O(1) 的时间就够了
        ck2016:@指尖流年 如果1万个连接,有1万个IO,那么他正好遍历1万次都是有用功
        ck2016:@指尖流年 问的好,你想如果有1万个连接,只有1个有IO请求,那么select方式要遍历1万次才能发现那1个请求,有9999次是无用功,是浪费的
      • atomsxw:通俗易懂,赞一个

      本文标题:操作系统中的I/O,及高性能IO模型

      本文链接:https://www.haomeiwen.com/subject/vxxuhxtx.html