程序员应该这样理解IO
引言
很多程序员会谈及IO,仿佛是种时尚或者给自己贴金的方式,因为提上它一般会涉及应用程序的性能相关话题,离高深的底层知识更接近了。但多数人的理解其实是模糊的,他们的脑海里会浮动着抽象的场景:IO就是输入输出啊,电脑在磁盘上读写就是IO,仅此而已,或者,更高明一点,他们隐隐约约的意识到IO是个耗性能的家伙。
原谅这些肤浅的程序员吧,他们在无数个夜晚挑灯夜战,加班加点,做项目赶进度,又基本没有个人生活,还随时可能猝死,哪里还有什么时间回炉深造,更何况作者也曾经是他们中的一员。
什么是IO
IO是输入input输出output的首字母缩写形式,直观意思是计算机输入输出,它描述的是计算机的数据流动的过程,因此IO第一大特征是有数据的流动;另外,对于一次IO,它究竟是输入还是输出,是针对不同的主体而言的,不同的主体有不同的描述。例如,甲乙两人交谈,甲将大脑中的想法通过声带震动,继而通过声波传入乙的耳朵,乙通过耳膜的震动再由神经将信号解析到大脑,这个数据流动的过程对甲而言是输出,对乙而言是输入。
因此,理解IO一定要弄清楚所要研究的本体。
下面,我们从三个层面来理解IO。
首先,从直观层面去理解IO。
此时,IO是计算机和外设之间的数据流动过程,本体是一个有使用意义的可运行的电脑,它是计算机运行的完全必要部分。姑且认为这个完全必要部分是台式电脑的主机,里面有CPU、内存、主板、电源等设备,因为有了这些,一台有使用意义的电脑即可运行。有了主机,并不能方便的为人所服务,因此得有外设。外设是电脑的外围设备,如显示器、键盘、鼠标等,它们是完成人机交互的辅助工具。外设包含两种重要设备(但不限于此):输入设备和输出设备。像鼠标键盘属于输入设备,将人的指令转成“鼠键行为”这种数据传给主机;显示器是输出设备,主机通过运算,把“返回信息”这种数据传给显示器。
其次,从计算机架构的角度去理解IO。
从计算机架构上来讲,任何涉及到计算机核心(CPU和内存)与其他设备间的数据转移的过程就是IO。本体就是计算机核心(CPU和内存)。例如从硬盘上读取数据到内存,是一次输入,将内存中的数据写入到硬盘就产生了输出。在计算机的世界里,这就是IO的本质。
最后,从编程的角度去理解IO。
此时,IO的主体是其应用程序的运行态,即进程,特别强调的是我们的应用程序其实并不存在实质的IO过程,真正的IO过程是操作系统的事情,这里把应用程序的IO操作分为两种动作:IO调用和IO执行。IO调用是由进程发起,IO执行是操作系统的工作。因此,更准确些来说,此时所说的IO是应用程序对操作系统IO功能的一次触发,即IO调用。
IO调用的目的是将进程的内部数据迁移到外部即输出,或将外部数据迁移到进程内部即输入。这里,外部数据指非进程空间数据,在编程时,通常讨论的场景是来自外部存储设备的数据,如硬盘、CD-ROM、以及需要socket通信传输的网络数据。
以一个进程的输入类型的IO调用为例,它将完成或引起如下工作内容:
- 进程向操作系统请求外部数据
- 操作系统将外部数据加载到内核缓冲区
- 操作系统将数据从内核缓冲区拷贝到进程缓冲区
- 进程读取数据继续后面的工作
从上面的描述来看,我们更容易理解一个IO操作,应用程序和操作系统都干了些什么,也帮助我们更容器理解阻塞和非阻塞,异步和同步的相关IO编程概念。
阻塞和非阻塞IO
阻塞和非阻塞强调的是进程对于操作系统IO是否处于就绪状态的处理方式。
上面已经说过,应用程序的IO实际是分为两个步骤,IO调用和IO执行。IO调用是由进程发起,IO执行是操作系统的工作。操作系统的IO情况决定了进程IO调用是否能够得到立即响应。如进程发起了读取数据的IO调用,操作系统需要将外部数据拷贝到进程缓冲区,在有数据拷贝到进程缓冲区前,进程缓冲区处于不可读状态,我们称之为操作系统IO未就绪。
进程的IO调用是否能得到立即执行是需要操作系统IO处于就绪状态的,对于读取数据的操作,如果操作系统IO处于未就绪状态,当前进程或线程如果一直等待直到其就绪,该种IO方式为阻塞IO。如果进程或线程并不一直等待其就绪,而是可以做其他事情,这种方式为非阻塞IO。所以对于非阻塞IO,我们编程时需要经常去轮询就绪状态。
异步和同步IO
我们经常会谈及同步IO和异步IO。同步和异步描述的是针对当前执行线程、或进程而言,发起IO调用后,当前线程或进程是否挂起等待操作系统的IO执行完成。
我们说一个IO执行是同步执行的,意思是程序发起IO调用,当前线程或进程需要等待操作系统完成IO工作并告知进程已经完成,线程或进程才能继续往下执行其他既定指令。
如果说一个IO执行是异步的,意思是该动作是由当前线程或进程请求发起,且当前线程或进程不必等待操作系统IO的执行完毕,可直接继续往下执行其他既定指令。操作系统完成IO后,当前线程或进程会得到操作系统的通知。
以一个读取数据的IO操作而言,在操作系统将外部数据写入进程缓冲区这个期间,进程或线程挂起等待操作系统IO执行完成的话,这种IO执行策略就为同步,如果进程或线程并不挂起而是继续工作,这种IO执行策略便为异步。
总结
综上,从编程的角度讲述了程序员应该如何去理解IO编程的一些概念。其关键点是要将应用程序的IO操作分为两个步骤来理解:IO调用和IO执行。IO调用才是应用程序干的事情,而IO执行是操作系统的工作。在IO调用时,对待操作系统IO就绪状态的不同方式,决定了其是阻塞或非阻塞模式;在IO执行时,线程或进程是否挂起等待IO执行决定了其是否为同步或异步IO。