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

发表于:2017-9-04 09:58

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:ck2016    来源:51Testing软件测试网采编

分享:
  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);
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号