C语言的输入输出模型

来源:互联网 发布:尤克里里调音软件 安卓 编辑:程序博客网 时间:2024/06/05 21:14

参考:http://blog.csdn.net/shrdlu/article/details/48929865

其实对于计算机器的理解,难点是为什么会有这个模型的建立,也就是模型建立的实际意义,另外还有一点,这个模型的形象化表示,如果能理解这个模型的形象化表示,就可以更深刻的理解模型。
网络或者书籍往往不能这样去解释模型的意义以及模型的形象化表示,所以学习计算机的初期,你或许会有点进步,但是学到最后往往会很迷茫,这时候有个导师帮助你显得尤为重要。

个人以前并没有深入去研究输入输出模型,但是,网络编程却使用了这个模型,导致在做数据传输的时候各种迷茫,只能退到C语言的层面,了解C语言的基本输入输出模型,因为soket描述符其实就是一个文件描述符,但是这个描述符有其特殊的意义,但是,这个描述符的输入输出模型和一般性的文件描述符都是类似的。

先给出模型图,稍后再解释这个模型图:
这里写图片描述

这篇文章会很长,因为需要解释的东西很多,希望大家有耐心的读下去,我保证所有的内容都是些基本的概念(但是我不保证概念的正确性,毕竟这是个人见解,又没有做事实论证,我的目的是你能够理解输入输出模型形象化的表示,至于理解的过程因人而异),有些概念如果有疑问,请留言,我会直接测试一下。如果可以的话,我会对概念进行测试。使用的操作系统是centos6/centos7(内核是linux, 不使用windows的原因是我个人正在研究《UNIX网络编程》)。

首先,大部分的输入输出模型就是上图所展示的,部分输入输出模型是上图的简化,其余的我暂时没有概念。

我们先来描述这个模型图,然后解释每个组成部分的意义(这些都是个人的见解,不一定对,但是绝对有参考价值)。

“设备”其实就是文件描述符所指定的东西,这个“设备”对应LINUX操作系统就是文件(一切皆文件),对应实际的物理设备就是硬盘、网卡、键盘、显示器等等。我们为了理解上的方便,暂时定义这个“设备”就是“文件”。

“输出缓冲区”与“输入缓冲区”这部分对应操作系统来说一般就是“内存”这个物理设备,但是,我们不关心他的具体形式,我们关心的是,“代码”部分可以直接对这部区域进行读或写,那么问题来了,“代码”部分能不能直接对“设备”读或写呢?答案是肯定的。既然可以直接读写“设备”,为什么还要有“输入输出缓冲区”呢?这个我后文进行阐述,这也是整个模型理解起来最困难的地方。

“代码”部分就是我们C语言编辑的部分,这部分对于程序员来说是可见的部分。

“A”过程是把“设备”的内容写入到“输入缓冲区”,对应的操作系统API是“read”(低级I/O)。

“B”过程是把“输入缓冲区”的内容写入“代码”,对应的标准库API是“getc”等(高级I/O)

“C”过程是把“代码”的内容写入到“输出缓冲区 ”,对应的标准库API是“putc”等(高级I/O)

“D”过程是把“输出缓冲区”写入到“设备”, 对应的操作系统API是“write”(低级I/O)

对于实际的实现,“输入输出缓冲区”部分还有一些特性,我们忽略这个特性,我们简单的模拟一下,代码是如何实现读的操作的,我们以标准输入stdin做为例子。
我们在终端部分输入一个内容,当我们按下回车的时候,操作系统唤醒代码的读取代码(比如我们正在等待getc),假设我们的输入时“abcd回车”,然后这个内容就首先会写入到输入缓冲区(A过程),假设输入缓冲区足够的大,那么“abcd回车”就会全部写入到“输入缓冲区”,接着就会写入到代码(B过程),这时候我们的代码紧紧消费了一个字符“a”,输入缓冲区还有“bcd回车”等,如果我们再次调用getc,我们的紧紧会执行B过程,这时候的getc也不会阻塞,因为输入缓冲区是有数据的而且是足够的,如果我们持续消费,导致输入缓冲区为空,我们再次使用getc函数,这时候getc函数就会阻塞,等待唤醒,整个过程就会重复。(个人没有代码实践,可以的话最好使用代码实验一下)

stdout拥有类似的过程,但是这还是有些不一样,C过程的部分就是putc所做的工作,D过程是write控制的,这部分一般会有两个问题,D过程是否可能会阻塞?答案是肯定的,D过程可能会阻塞的,比如网卡设备的写,如果使用TCP协议,TCP协议是有流量控制的,这时候D过程就会阻塞。D过程什么时候执行?一般有两种方式,一种是自动的,比如设置一个条件,当“输出缓冲区”满的时候,执行D过程,还有一种方式是手动的,比如我们可以执行fflush()方法,或者执行fclose()方法,这些方法就会执行D过程。

如果能够理解上面所说的流程方式,我在说下,我对“输入输出缓冲区”的理解,这部分有几个好处:

  1. 我们可以屏蔽掉低级I/O的实现,低级I/O的实现依赖操作系统本身内核的实现,所以如果能够屏蔽这部分的差异,我们可以很容易写出可移植的程序。
  2. 我们可以使用这部分的内容实现“行”读取的行为,对于计算机而言是没有“行”这个概念,有了这部分,我们可以定义“行”的概念,然后解析缓冲区的内容,返回一个“行”。

这里有个设计性的问题,如果我们想屏蔽设备的差异性,而设备之间又有某些相同的特性(比如都是关系数据库),那么我们就可以利用上面的模型,建立一个“缓冲区”层,这样代码部分只针对“缓冲区”层进行设计。另外,我们还会发现,当我们使用getc的时候,我们还是需要一个文件描述符号,而文件描述符是针对设备的,也就是,不论我们怎么设计代码,上层的代码必须告诉底层我需要从什么设备读或者写操作,我们可以使用类似“文件描述符”的概念去执行这个通知的操作,但是,你还是必须告诉底层,你到底想干什么才行。“缓冲区”层只是屏蔽具体的读或写的具体方式,但是没有屏蔽掉设备本身的差异性。

这部分的内容对于《UNIX网络编程》很有参考意义。因为对于网卡的读或者写,我们是没有“缓冲区”层的,标准库并没有支持这部分内容,所以我们只能依赖A和D过程。另外,网络编程的I/O输入设备往往不是单一的,可能是标准输入设备和网卡设备同时操作(交互性的程序),这时候,我们往往不能使用标准库的函数,因为“缓冲区”层对于不是内核的任务,如果有这一层可能会增加代码的复杂度。简单的说,如果你想写一个“读行”这个行为,其实是很困难的,因为你不能把使用缓冲层,就只能从设备依次读取单个字符,而这个过程是低效的,从设备读我们往往使用块的方式。

0 0
原创粉丝点击