Bo2SS

Bo2SS

2 阻塞与非阻塞IO

  • 图片

课程内容#

理解两者的真正含义及区别,能回答以上问题

两者的字面意思#

上班

  • 阻塞:上班时堵车了,等或者使用其他方法,总之要去上班
  • 非阻塞:上班堵车了,直接不去了

老妈让买酱油

  • 阻塞:没有酱油了,问妈要不换别的,或者换别的买
  • 非阻塞:没有酱油了,不买了

让小明写报告

  • 阻塞:小明写的时候,等他写完
  • 非阻塞:不用干等小明写,之后再告知结果 [自己问小明 - 同步 / 小明主动告诉我 - 异步]

烧开水

  • 阻塞:烧开水的过程中,你不能干其他事情,只能站那等水开
  • 非阻塞:烧开水的过程中,你可以干其他事情,比如去客厅看看电视
  • 参考同步异步、阻塞非阻塞极简解释——CSDN

引入#

  • 回顾 open 函数中的 O_NONBLOCK
    • image-20210120101248947
    • 一切皆文件,所以一切都可以阻塞 / 非阻塞
  • 分析写文件的过程
    • 打开→write→关闭
    • 其中,write 属于系统调用,具体过程如下
      • 数据→内核→磁盘:内核拷贝数据,放在缓冲区 [块缓冲],清刷缓冲时,调度 IO 设备,找到 inode 和 block,将数据写入磁盘
      • NON_BLOCK 设置的是用户态 [数据→内核 或者 内核→数据] 的过程
        • 大部分的阻塞是在从内核拿数据出来
      • 内核→磁盘的过程实际是阻塞的
        • 为了方便上层应用快速返回写的状态,内核将数据写到磁盘的过程,普通用户不需要感知
        • 内核拷贝完数据,程序返回,普通用户就认为写成功了,但数据很有可能还在缓冲区 [缓冲 IO]

fcntl#

操作文件描述符 [可以将文件变成非阻塞的]

  • man fcntl
  • 原型
    • 图片
    • fd:文件描述符。最常见的文件描述符:0、1、2
    • cmd:操作方式
    • ... /* arg */
      • 可变参数
      • arg,指示该参数是前面参数 [cmd] 的参数,其含义取决于 cmd
  • 描述
    • 图片
    • cmd 在圆括号里指示是否有可变参数,最多一个
    • 可变参数类型一般是 int,使用用宏定义,本质是位掩码,通过位运算改变状态
    • 其中有一类 cmd:与【文件状态标志】相关
      • 图片
      • 即可获得或设置文件状态 [如 O_NONBLOCK]
  • 返回值
    • 图片
    • 观察返回值,考虑程序中的判断
    • 出错都是返回 - 1,设置操作成功都是返回 0

select#

同步 I/O 多路复用 [接口]

  • man select
  • 原型
    • 图片
    • nfds:文件描述符的数量
    • fd_set:文件描述符的集合
      • 可读、可写、异常
      • 底层是用数组是实现的
    • timeout:时间间隔
    • 四个宏用来操作集合,见后
  • 描述
    • 图片
    • 允许程序去监控多个文件描述符,等待一个或多个文件描述符的 I/O 操作 "ready" 的情况
      • ready:就绪,可以对文件进行相应的 IO 操作,如没有阻塞的读,或足够小的写
    • 可监控的文件描述符数量小于 FD_SETSIZE [一般为 1024]
    • 图片
    • 退出的时候,每个文件描述符集合会被修改,只留下状态发生变化的文件描述符,起指示作用
      • 所以,如果循环使用 select,每次调用 select 前需要重新初始化每个集合
    • 集合可以是 NULL,说明该类事件中没有文件被监控
    • 用来操作集合
      • FD_ZERO:清空一个集合
      • FD_SET:向一个集合添加一个文件描述符
      • FD_CLR:从一个集合移除一个文件描述符
      • FD_ISSET:判断一个文件描述符的所属集合,可用于 select 返回后,因为集合已被修改
    • nfds 的值是三个集合中数字最大的文件描述符加一
      • 文件描述符的索引是从 0 开始的
    • 图片
    • struct timeval timeout
      • 指定 select 阻塞着等待每个文件描述符就绪的时间间隔
        • 这里阻塞的含义就是单纯的阻塞,不是阻塞 I/O 的阻塞
        • [个人理解] 同步 I/O 的多路复用的同步就体现在这
      • 三种停止阻塞的情况
        • 一个文件描述符就绪
        • 信号中断 [kill]
        • 超时
      • 时间间隔不是精确的,很难做到真正的精确
        • 系统时钟粒度、内核调度延迟
      • timeval 结构体中有两个成员:秒、微秒
        • 如果两者都为 0,就会立即返回,可用于轮询 [polling]
        • 如果为 NULL,就会无期限等待
      • 图片
      • timeout 更新功能只在 Linux 上生效
      • 为了兼容性,尽量别用,尽量使用比较公共的功能
  • 返回值
    • 图片
    • 返回此时所有集合中文件描述符 [ready] 数量
      • 根据数量,再通过宏定义 FD_ISSET 询问所有文件描述符的所在集合,来感知状态变化
      • 0:时间过完了,也没有感兴趣的事件发生
      • -1:error,并设置 errno;此时集合不会改变,timeout 变成未定义的了

[PS]

  • select → poll → epoll,越来越高级
    • man poll
    • man epoll
    • 均与 select 做的任务相似

代码演示#

实现使文件非阻塞和阻塞的接口#

  • common.h
    • 图片
    • 两个接口
  • head.h
    • 图片
    • 头文件的顺序是有讲究的,一般把自己本项目的头文件放后面
    • 图片
    • 参考#include 的路径及顺序——Google C++ 编程风格
  • common.c
    • 图片
    • 设置 flag 时不能改变原有的 flag
    • 👉先 get,再通过位或 / 位与来添加 / 删除 flag

非阻塞的 0 号文件#

  • 图片
  • 常用文件 0、1、2 不用手动打开
    • 创建进程时自动打开这三个文件
    • 它们是继承过来的
  • 编译命令:gcc 1.test.c ../common/common.c  -I ../common/
    • 编译记得添加 common.c 文件
  • 0 号文件设置成非阻塞后,关键变化在于 0 号文件的读写变成了非阻塞的
    • ① 没有 sleep
      • 图片
      • scanf 就是读 0 号文件
      • 但没有数据,直接走,不会阻塞着等 0 号文件有数据
    • ② 有 sleep
      • 图片
      • 在 sleep 的 5 秒里,在终端输入数据 [+ 回车,行缓冲]
        • 进程暂停,不占用 CPU
        • 但标准输入流仍然打开着,用户在终端输入的数据会由内核传给 0 号文件
          • 文件是内核维护的,sleep 的过程内核可以给文件写数据
      • 在 sleep 结束后,scanf 读走 0 号文件的数据
    • 【明确】非阻塞是指某个对象,比如 0 号文件;而不是某个函数、某个操作
  • [PS]
    • 阻塞:可能浪费时间
    • 非阻塞:可能浪费资源
      • 一般用于大型的程序、高并发服务器

对 0 号文件的 select#

  • 拷贝自 man 手册 ——man select 里的 EXAMPLE
  • 图片
  • select 可以感知到 I/O 的到来
  • 运行效果
    • image-20210120101405659
    • 终端输入 ls,程序结束后还会执行 ls
    • 因为缓冲区的内容没有被取走 [如 scanf],最终被 zsh 接走了
  • select 在这监控的时间间隔里发生了阻塞
    • 在所有的用户层面的程序里面,所谓的阻塞就是睡觉 [sleep ()]
    • 阻塞终止条件:ready、signal 中断、超时
  • [延伸应用]
    • 给阻塞的地方设置一个定时,超时则使用默认值
      • 避免异常情况阻塞过久 [SSH 主机不可达...]
      • 更人性化

附加知识点#

  • I/O 感知的方式可以有很多种,其中内核完全知道 IO 的到来

思考点#

Tips#

  • 课程预告:多进程 [fork...]
  • 可以考虑 cp 的实现
    • 考察文件的读写操作
    • 必须阻塞

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.