我对计算机系统的理解

计算机系统的组成

一个计算机系统是由软件与硬件组成的,就硬件来说,当我们一般去电脑城配电脑的时候,一般会购买这些基本零部件:主板,CPU,内存,磁盘,机箱,键盘鼠标,显示器。当然还有一些额外的部件,例如独立显卡或者网卡,音箱等。如果除去非必要的部件来看,其实一个计算机系统主要由下面这些重要的部件组成:
CPU,存储器(内存),磁盘,IO设备(键鼠,显示器),以及连接这些器件总线,只不过我们的成品电脑是用一块电路板将这些部件连接在了一起。当然随着电脑系统越来越强大,可能除了上述部件之外,还多了很多其他的部件,例如个人PC的主板上可能还有南桥,北桥等等一系列的额外的芯片。但是我们这里讨论的是组成一个计算机系统最基本的部件,其他的部分,为了简化说明,暂时省略。

CPU:是一个计算机最核心的部分,计算机的所有运算,程序的所有指令的执行是通过CPU来进行的,CPU是计算机的大脑。

存储器:CPU所执行的指令,计算的中间结果,产生的数据都需要通过某种方式进行保存,方便CPU读取和存入,这就是存储器的功能。

磁盘:提供永久保存数据的办法。

IO:也即是连接在计算机上的输入输出设备,一般最常见的就是鼠标,键盘和显示器了,当然还有其他的一些输入输出设备,例如打印机,USB外接设备等。IO设备主要提供给人们与计算机进行交互的时候使用。

总线:所有这些部件之间通过总线进行连接,有些设备共享一组总线,有的设备之间有专用的总线。

计算机的软件组成:计算机除了硬件系统之外,在硬件系统之上运行着一系列的软件,最重要的当然是操作系统软件,另外还有各种驱动程序用于与特定的设备进行通信。除此之外还有各种运行在操作系统之上的用户应用程序。

程序是如何在计算机上运行的

程序是什么:
在计算机系统上运行着各种各样的程序,包括操作系统本身也是程序,那么程序到底是什么呢,程序一般以文件的形式存在于磁盘上,这些文件是具有一定结构的二进制文件,这些文件被以一种特定含义的格式进行组织,例如典型的二进制的可执行文件(不管是windows还是linux系统)以一定的结构描述,在这些描述中,该文件被分成了多个段,每个段中的数据内容具有不同的含义,例如数据段,代码段,符号表等等。其中代码段中的二进制数据,我们认为就是CPU直接执行的指令。

CPU的指令是什么:
一条CPU的指令是一串不定长的二进制的位串,而当CPU读取这一个位串并执行的时候,一般是运行一个特定的功能,例如加法,减法,从内存读取一个字节到寄存器等等这样的功能,而一个CPU能够执行的所有二进制位串(也就是指令)的集合则是CPU的指令集。不同的硬件厂商生产的CPU他们能够识别执行的二进制位串序列集(CPU指令)可能是不一样的。而同样的功能,例如加法功能,不同的CPU需要执行的二进制位串可能是不一样的。例如我们常见的PC机器的x86架构的CPU基本上是兼容的,目前市面上主流的PC端的CPU由Intel和AMD生产,这两个厂家生产的CPU的指令基本上是兼容的,但是也可能各自有一些特定的指令是对方没有的。另外的例如Arm或者mips的CPU的指令集则与x86体系是不同的,而前者多用于嵌入式系统。

CPU的硬件本身可以理解为是一系列的电路,该电路实现一定的功能,该电路中又分成了很多的功能单元,例如在现代CPU的设计中,一条计算机指令在执行的时候,会经过:取指、译码、执行、访存,写回,更新PC等阶段,而现代CPU被设计成以流水线的方式执行指令。如前所述,一条指令是若干个二进制bit位的位串,在取指阶段CPU根据程序计数器(PC)中的地址取出当前需要执行的指令。接着对该指令译码,所谓译码则是解析该指令,一般一条指令被分成了多个部分,每个部分代表不同的含义,一条IA32的指令是由1到15个字节组成,操作数较少的指令所需要的字节数少,那些不太常用的或者操作数较多的指令所需要的字节数较多。在执行阶段由CPU的算术/逻辑单元执行指令。访存阶段将数据写入存储器或者从存储器中读出数据。写回阶段将结果写到寄存器中。更新PC阶段则将PC计数器设置成下一条指令的地址。

程序的编译与链接过程:
一个程序的生成流程可以大致描述为:程序文本文件、编译成汇编文件(.s)、编译成二进制文件(.obj)、链接成最终的可执行文件。CPU只能识别并执行他的指令集中的位串,所以我们必须将程序员可以识别的文本文件的程序代码转换成CPU能够执行的一系列的指令,这个转换过程就是编译过程。而链接是一个非常复杂的过程,其中包括静态链接和动态链接,链接的过程是将所有模块中使用的符号替换成实际使用的内存地址的过程(而其实现代操作系统中程序访问的内存地址并不是实际的物理地址,而是虚拟地址)。静态链接是在编译阶段由链接器完成链接过程直接生成可执行程序的过程。动态链接是在程序执行阶段动态将程序中的符号替换成为实际的内存地址的过程,例如我们的C程序里面一般调用了C的标准库函数,而C的标准库函数是在其动态链接库libc.so中的,在我们的程序执行的时候,会将libc.so所在的内存地址映射到程序的虚拟地址空间,并修改程序中所有访问libc.so中的符号为正确的访问地址的过程。这个过程就是动态链接。

程序的执行过程:
一个程序要想被执行,首先要将其文件内容加载到内存中,然后CPU从程序的代码段的入口点的第一条指令开始执行,后面的所有执行过程都是按照程序中的指令进行的,一直到程序结束为止。这一整个过程实际上并没有这几句话描述的这么简单,首先程序被从磁盘读取到内存这一过程,可能涉及到磁盘的IO中断,DMA的拷贝,虚拟内存等,当程序被加载进内存之后,如果程序使用了动态连接库,例如最简单的C程序也可能链接了C的标准库,所以还需要加载动态库(一般是通过内存映射的方式),将动态库加载进内存,并将其映射到程序的虚拟地址空间中。最后经过一些初始化之后(初始化寄存器,库的初始化等),开始从程序的入口处的指令执行。

操作系统是什么

什么是操作系统呢,一般情况下我们提到操作系统就认为高深莫测,深不见底。其实操作系统的作用可以从这么几个方面来理解,首先操作系统的一个非常重要的功能即是提供用户接口,也就是说当人们要使用计算机的时候,必须通过一种方式来控制计算机,那么操作系统提供了这样的方式来使人们可以控制计算机,例如命令行的或者是UI的方式,这种接口我们可以理解为使用计算机的入口。另外一种接口用于扩展计算机的功能,例如程序员编写程序的时候就会使用操作系统提供的系统接口,这种接口是更底层的概念。

其实我们说的用户接口不一定是操作系统的一部分,例如典型的linux类型的操作系统,其桌面环境是可以不用的,或者是可以随意替换的,其shell命令行环境也可以自己替换,在linux中有多种可以使用的shell,而唯一不能改变的是linux内核,这么说来linux的桌面环境以及shell命令行环境好像并不属于操作系统,但是却是计算机中不可缺少的。而在windows操作系统中,特别是windows xp 和windows NT 中其GUI界面是夹在内核中的,这样一来其又是操作系统的一部分了。所以其实从功能上来说现代操作系统的软件界限其实是越来越模糊了。

操作系统除了提供用户接口之外,还是计算机系统的管理者,负责管理各种软硬件资源,制定计算机世界的规则,例如操作系统管理CPU,调度各种进程,线程来使CPU执行指令。管理内存,提供进程访问内存的规则,还有磁盘,外设等设备的管理。如果没有这些管理,我们的程序可以乱执行,谁先执行,谁后执行? 执行多久?内存区域可以乱写,我可以破坏你的内存,你可以破坏我的内存。就像在道路上没有警察去约束,那交通肯定是一片混乱。

什么是驱动程序呢,驱动程序严格的来说并不是一个单独的程序,也不是一个独立运行的进程,实际上我们可以理解驱动程序是一个程序片段,或者是一个符合一定约定提供约定接口的程序库,操作系统调用该库中提供的接口,去操作特定的硬件。该库实现了对特定硬件的操作,并提供一个对外的接口供操作系统调用。例如对于磁盘来说,生产磁盘的硬件厂商一定会提供从磁盘读取或者写入磁盘数据的办法,该办法(可能是一系列的底层C接口)可能很复杂,需要很多个步骤,寻址,寻道等。而且不同的磁盘厂商提供的底层接口并不一定相同,那么磁盘驱动程序封装了这些底层的操作磁盘的功能,并对外提供统一的操作接口(该接口规范是操作系统规定的,这样操作系统才能统一调用)。那么符合规定接口的该软件库我们可以认为就是驱动,有的驱动程序是随操作系统启动的时候进行加载的,而有的可以直接即时安装进行动态加载,这有赖于动态链接库的运行时加载功能。

进程与线程

什么是进程呢,我们的程序实际上是一个二进制文件存放在磁盘中,当将程序载入到内存中,并从第一条指令开始执行,一直到最后一条指令结束该程序文件中的指令不再被CPU执行,那么该程序的这一次的运行过程,我们可以理解为是一个进程。也就是说一个进程是一个二进制的程序中的指令从第一条指令被CPU执行,一直到最后一条指令被CPU执行的这整个过程。进程是一个磁盘中的程序运行的实例。

一个程序文件是一个具有一定结构的二进制文件,一般情况下不同的操作系统的二进制文件的结构是不同的,但是其大体概念是相似的,例如linux和windows程序文件都包含有代码段,数据段,符号表等等区域。程序文件中除了代码段中的二进制数据是CPU真正要执行的指令以外,其他的段要么是在CPU执行过程中需要使用到的内存信息(例如变量),要么是编译器调试的时候需要用到的辅助信息(例如符号表)。

在操作系统中有一些信息是与一个运行的程序,也就是进程相关联的,一般我们称之为PCB(进程控制块),该信息中描述了进程在运行过程与该进程关联的信息,例如进程的ID,程序计数器,堆栈等等。

通常每一个进程有一个地址空间和一个控制线程(主线程),当然一个进程中可以有多个控制线程,这些线程就像分离的进程一样,只不过他们共享同一个进程的地址空间以及其他资源。在内存中也有一个信息结构来记录与每个线程关联的信息,例如程序计数器,堆栈信息等。

进程是资源分配的最小单位,线程是CPU调度的最小单位。

单线程与多线程程序、并发

一个程序到底是单线程结构好还是多线程好呢?从CPU的角度上来看,如果是单核CPU在任何时刻,只能运行一个进程,也就是说只能调度一个线程(如果是单线程的程序则运行进程的时候实际上调度的是该进程的主线程),单核CPU的多任务并发实际上是伪并发,任何时刻只有一个进程处于运行中,只不过CPU的运行很快,多个进程可以轮番得到CPU,看起来好像是同时运行的一样。线程是CPU调度的最小单位,任何时刻只有一个线程获得CPU运行,所以理论上多个线程运行的时候多了线程上下文切换的开销,应该比单线程的进程效率低。但是由于复杂程序的业务逻辑,单线程程序中如果有阻塞的逻辑,则反而因为阻塞,该线程被操作系统切换出去,不能得到CPU,而降低了程序得到CPU的时间,另外单线程程序的设计往往比较复杂,由程序设计带来的复杂性可能会抵消多线程上下文切换的损失。所以一般情况下当程序中有比较耗时的操作时,单线程程序是不合适的,应该用多线程比较好。如果是CPU密集型的程序,加上良好的程序设计,则使用单线程的结构效率比较高(单线程的程序一般使用状态机来驱动程序的运行)。而即使多线程的程序有自己的优点,也并不是线程越多越好,当线程非常多的时候线程上下文切换的开销就比较可观了。现代计算机一般都拥有多核CPU,从这点上来讲多线程的程序更能取得比较高的CPU利用率。

存储管理

存储器是计算机系统中的重要组成部分,一个程序要执行,必须先从磁盘拷贝到内存上,然后从内存中读取计算机指令进行执行。而计算机存储的使用历史也是伴随着计算机以及操作系统的发展变化的。

最开始的操作系统是单任务的操作系统,某一时刻只有一个进程在执行,也没有线程的概念。所以程序中使用的地址都是实际的物理地址,而该物理地址是编译器在编译阶段分配的。程序在运行之前被载入内存中然后被执行。

随着用户不满足于只运行一个程序,多个程序开始被同时执行,那么此时程序中的地址就不可能使用实际的物理地址了,因为要保证两个程序同时执行互相不产生干扰的话,他们就不应该同时访问相同的物理地址,而如果此时程序中使用的地址还是实际的物理地址并且需要保证程序访问的地址空间不会重叠,在编译阶段编译器几乎不可能实现。编译器可能的实现方式是使用相对地址,而程序只需要在运行阶段知道一个基地址,程序中的其他地址则通过基地址加上偏移地址(也就是相对地址由编译阶段填到程序中)的方式来实现。当然也会有一个寄存器去记录程序访问内存的最大边界,以防止程序访问越界。这种方式解决了多个程序同时运行的内存分配问题,使用统一的方式来分配内存,也降低了编译器编译的复杂度。

如果内存空间无限大,使用基地址加上偏移地址的方式即可解决内存的分配问题。而实际上虽然即使现在内存空间越来越大,但是随着程序越来越大,用户要求同时运行的程序越来越多,要想在程序运行之前将所有程序载入到内存,以现在的内存空间恐怕是办不到的,特别是有的程序动则以G为单位。为了适应这样的需求,出现了内存交换的技术,其实现过程是这样的,例如有3个程序需要运行,先载入程序A运行,再载入程序B运行,此时再载入C运行,然而剩余的内存空间不够载入C程序,此时可以选择将B程序运行的所有内存映像交换到磁盘空间保存,这样B程序占用的内存空间被释放,然后载入C程序运行,如此反复。这种方式就是内存交换技术。但是即使采用内存交换技术,也只能解决一部分问题,另外即使现代磁盘的传输速度越来越快,但是频繁的内存交换,仍然会大大影响程序的性能,特别是当运行的程序很大的时候,例如将1G的程序交换到磁盘上,即使SATA磁盘传输的峰值是100MB/s也仍然需要10秒才能完成交换操作,而将该程序载入到内存中重新运行也需要同样的时间。

现代操作系统采取另外一种方式来做这样的操作,即将程序分割成很多的小块,每一小块的大小或是4K,或是8K,16K,32K等,每次载入程序的时候以块为单位载入。这些分块操作全部由计算机完成,不需要程序员干预,这种方式被称为虚拟内存。每个程序有自己的地址空间,这个空间被分割成多个块,每个块称为一个页或者页面,每一页有连续的地址范围。这些页被映射到物理内存,而并不要求所有的页都在内存中才能运行程序,当程序引用到的地址在实际的物理内存中有对应的映射的时候,由硬件来翻译该地址为实际的物理地址,如果没有对应的映射,则会引发一个缺页中断,由操作系统负责将缺失的部分载入到物理内存并重新执行失败的指令。

程序使用的地址空间叫虚拟地址空间,虚拟地址空间中的地址都是虚拟地址,其地址范围可以从0到最大的物理内存地址,例如物理内存是4G,那么程序的虚拟地址空间是0-4G,每个程序都是这样。那么如果同时运行多个程序会怎样呢,这样两个程序都可以访问到最大的4G内存,如果他们访问的是同一个地址会怎么样?在实际运行过程中,虚拟地址会被一个叫做MMU(内存管理单元)的设施翻译成为实际的物理地址,也就是说每一个程序使用的虚拟地址都会被映射到实际的物理地址,而两个程序中相同虚拟地址会被映射到不同的物理地址,这样就不存在访问冲突的问题了。程序的虚拟地址空间是相同的,这给编译器的编写带来了极大的方便,生成的程序代码使用统一的地址空间模型,地址的翻译工作交给了MMU与操作系统。

以上实际上是现代计算机采用的内存运行模型,实际上这是最基本的原理,还有一些其他的细节,例如程序的虚拟地址空间被分成多个页,每个页会映射到实际的物理地址页,而物理地址页我们称为页框,页与页框的大小一般是相同的,而页与页框的映射关系表则称为页表。关于页到页框的解析也是一个复杂过程,在页表项中的各种二进制位用于解析页到页框的映射。

当发生缺页中断的时候,由操作系统负责载入缺失的页面到物理内存中,当然如果内存够的话是没有任何问题的,如果在内存不够的情况下,则必须要做出选择将内存中的其他页交换到磁盘中,这样腾出空间给即将加载的页,当操作系统成功加载需要的页之后,会重新修改页表中的映射关系,并执行引发缺页中断的指令,继续原来程序的执行过程。那么操作系统如何选择将哪些页面交换到磁盘以腾出内存空间呢,这种选择的策略则是页面置换算法的内容。现代操作系统有多种页面置换算法,并不会单纯的采用某一种算法,而是综合使用。例如最优页面置换,最近未使用页面置换,先进先出页面置换,时钟页面置换,最近最少使用页面置换等等。

因为有了虚拟内存的存在,可以将同一个物理内存映射到两个不同程序的虚拟地址空间中,这样两个程序可以同时读写该块物理内存区域,这就是共享内存。同样可以将虚拟地址空间映射到一个文件,这样程序可以像操作内存一样方便的读写一个文件,这是内存映射文件。

文件系统

什么是文件系统呢,在说明这个概念之前,我们先说说文件是怎么存储的,我们以存储介质硬盘为例,硬盘的物理结构是由很多盘片组成的,每一个盘片有两面,每一面由一圈一圈的同心圆组成的磁道,而将多个盘片叠在一起之后,所有叠在一起的盘片上的同一个位置的同心圆组成一个圆柱体,这个由所有盘片上相同的磁道组成圆柱称为柱面。而每一个盘片上的每一个磁道被分割成一些等分的圆弧,这些圆弧叫扇区。在物理上通过电磁的原理从磁盘读写数据,我们可以认为可以将0,1这两种不同的状态记录到磁盘的磁道上,而这些连续的0,1的状态就是计算机中的二进制位流。所以可以认为在磁道上存储的就是表示不同状态的二进制位流数据。那么整个磁盘是由多个盘片组成的,每个盘片上有很多磁道,如何组织这些数据呢?就必须要对所有的盘片,柱面,扇区,磁道进行标识,并且方便查找与读写。也就是说需要用一个结构或者一组规则来组织他们,这就是文件系统。

文件系统是操作系统用于明确存储设备或分区上的文件的方法和数据结构,也就是存储设备上组织文件的方法,从系统角度看,文件系统是对文件存储设备的空间进行组织和分配,负责文件存储并对存入的文件进行保护和检索的系统。具体的说,它负责为用户建立文件,存入、读出、修改、转储文件,控制文件的存取,当用户不再使用时撤销文件等。

如果没有文件系统这样的组织规则,那么我们看磁盘则是一连串的无意义的二进制位流数据,我们分不清楚哪里是开始,哪里是结束,这些二进制位流信息的含义。就好像一个doc文档,我们使用二进制编辑软件打开之后看到的就是一连串毫无意义的二进制位流一样,然而使用doc文档的结构规则来解析这些位流信息的含义,我们可以解析出该doc文档中的文字,图片,以及格式等信息。而文件系统也起到类似的作用,文件系统本身也占用磁盘空间,是存放在磁盘上的。多数磁盘划分为一个或多个分区,每个分区中都可以有一个独立的文件系统。磁盘的0号扇区称为主引导记录(MBR),用来引导计算机。在MBR的结尾是分区表。该表给出了每个分区的起始和结束地址。表中的一个分区被标记为活动分区,在计算机被引导时,BIOS读入并执行MBR。MBR做的第一件事是确定活动分区,读入它的第一个块,称为引导块,并执行之。引导块中的程序将装载该分区中的操作系统。

每一个磁盘分区由引导块、超级块、空闲空间管理、i节点、根目录、其他文件和目录的顺序组成。超级块包含文件系统的所有关键参数,在计算机启动的时候或者该文件系统首次使用时,把超级块读入内存。超级块中的典型信息包括:确定文件系统类型用的魔数、文件系统中数据块的数量以及其他重要的管理信息。

在读文件前必须先打开文件,打开文件时,操作系统利用用户给出的路径找到相应的目录项,目录项中提供了查找文件磁盘块所需要的信息。而文件的属性信息可以存放在目录项中或者存放在文件的i节点的信息中。而这些目录项以及i节点存放在物理磁盘的哪个柱面,哪个磁道,哪个扇区的信息是可以在i节点信息以及从根目录根据路径层层查找的目录项信息中得到的。

光盘的存储方式与磁盘类似,只不过光盘使用的介质并不是磁介质,而是其他类型的介质,所以光盘没有磁道的概念,光盘实际上是通过一个螺旋状的类似于磁道的轨迹来存储数据的,在这条轨迹上记录了0,1两种不同状态的位流数据。由于光盘的存储特性,光盘上的文件系统与磁盘使用的文件系统是有不同的,具体可以查阅光盘相关的格式与文件系统文档,但是其思路与磁盘文件系统是类似的。

输入输出(IO)

操作系统管理者各种各样的连接到计算机上的输入输出设备,IO设备可以分为两类,块设备和字符设备,块设备把信息存储在固定大小的块中,每个块有自己的地址。通常块的大小在512字节和32768字节之间。所有传输以一个或多个完整的块为单位。块设备的基本特征是每个块都能独立于其他的块进行读写。硬盘,CD-ROM,USB盘是常见的块设备。另一种设备是字符设备,字符设备以字符为单位发送或接受一个字符流,而不考虑任何块结构。字符设备是不可寻址的,也没有寻道操作。打印机,网络接口,鼠标,以及大多数与磁盘不同的设备都可以看作是字符设备。

IO设备一般由机械部件和电子部件两部分组成,电子部件称作设备控制器或者适配器。在个人计算机上它经常以主板上的芯片的形式出现,或者以插入(PCI)扩展槽中的印刷电路板的形式出现。机械部件则是设备本身。

控制器与设备之间的接口是一个很低层次的接口。以磁盘设备为例,从设备中出来的是一个串行的位流,控制器的任务是把串行的位流转换为字节块,并进行必要的错误校正工作,字节块通常首先在控制器内部的一个缓冲区按位进行组装,然后进行校验证明字节块没有错误之后,将它复制到主存中。

CPU如何从设备中读取数据(例如磁盘)或写数据到设备中呢? CPU从磁盘读写数据是直接与磁盘的设备控制器(也就是磁盘适配器)进行数据交互的,每个设备控制器中有几个寄存器用来与CPU进行通信,通过写入这些寄存器,操作系统可以命令设备发送数据,接收数据,开启或者关闭,或者执行其他操作。通过读取这些寄存器的值,操作系统可以知道设备当前的状态。除了设备控制器中的寄存器之外,许多设备还有一个操作系统可以读写的数据缓冲区,可以供程序或者操作系统读写数据。那么操作系统要操作这些控制寄存器,就需要有一个地址来标识这些控制寄存器,有两种方式对控制寄存器进行地址编号,一种是为每个控制寄存器分配一个IO端口号,所有IO端口形成IO端口空间,该端口空间独立于内存的地址空间,是完全不同的。IO端口空间受到保护使普通的用户程序不能访问,只有操作系统可以访问。CPU可以通过这些IO端口号来读写控制寄存器的内容,从而达到与设备控制器通信的目的。另一种是将IO端口号映射到内存的地址空间,每个控制寄存器被分配一个内存地址,这样的系统称为内存映射IO。通常分配给控制寄存器的地址位于内存地址的顶端,并且该地址不会再分配给应用程序。在这种模式下,IO端口的地址与内存地址是一个地址空间,并不是独立的。这种模式的好处是,不需要使用特定的指令去操作控制寄存器,只需要普通的访问内存的指令即可以对控制寄存器进行读写操作,就像程序中普通读写内存一样。

上面讲述了CPU如何与设备进行通信的原理,那么读写文件的具体过程是怎么样的呢? 一般情况是这样的,在程序中我们一般通过系统提供的read,write函数对文件进行读写,当然在读写之前一般会有一个open操作(传递一个文件路径,以及打开的方式),读写完成之后一般会有一个close操作关闭文件。当使用open操作打开一个文件的时候,最终会调用到文件系统的相关接口,并且返回的文件描述符与文件的inode节点信息在操作系统的内核中肯定有对应的关联关系,此后在read或者write的时候传递进去的文件描述符,最后应该被文件系统关联到文件相关的底层结构例如inode信息,通过inode信息就可以操作该文件在磁盘上的块了(文件系统应该有查找文件的第几个字节在磁盘的哪个块上的能力)。在write或者read的时候,CPU先通知磁盘设备控制器,设备控制器从磁盘寻道,寻址,一位一位的读取某一个块(或者是以扇区为单位),直到将整个块读入到控制器的内部缓冲区中,计算校验和,保证没有错误。然后控制器产生一个中断,等到操作系统响应的时候,CPU从设备控制器的缓冲中一个字节一个字节的读取数据,并拷贝到内存中(read的时候会指定一个buffer,也就是数据要读到的内存地址)。这种模式是IO操作模式中的一种。下面说说IO实现的三种方法。

第一种方法是程序控制IO,程序需要轮询IO设备的状态,以查看IO设备是否准备好,例如打印机,程序需要轮询打印机中的寄存器,以查看打印机是否准备好接受打印的字符,如果准备好,程序将需要打印的字符拷贝到打印机的缓冲区中,如果打印机此时开始打印一个字符,并同时设置打印机的状态为非就绪,在打印机打印该字符的过程中,程序必须一直查询并等待打印机完成该字符的打印直到打印机完成该字符的打印过程之后,其寄存器中表示打印机状态的值被改为就绪状态,程序拷贝第二个需要打印的字符到打印机的缓冲区中。如此循环,直到所有打印完成为止。这种模式浪费了大量的CPU时间,因为打印机打印的过程是一个耗时的过程,而在CPU等待打印机打印字符的过程,将大量的CPU周期浪费在轮询打印机的状态上,这就出现了第二种方式。

第二种方式是中断驱动的IO,当打印机开始打印第一个字符的时候,CPU去做其他事情(与当前打印相关的进程被调度到阻塞状态,其他的进程或者线程获得CPU),打印机完成第一个字符的打印之后,发出一个中断,中断处理程序运行,之前被挂起的进程重新开始运行,拷贝第二个需要打印的字符给打印机,如此反复。在这种模型下,由于IO设备的操作很慢(例如打印机的打印操作),在IO设备执行操作的时候CPU不必等在那里,而是可以做其他事情,等IO操作完成之后,以中断的方式通知,这样大大的节约了CPU的时钟周期。这种方式的缺点是每一次IO设备的读写都会产生中断,中断发生在每个字符上。而且中断是需要花费时间的,所以这一方法将浪费一定量的CPU时间。

第三种方式是使用DMA(直接存储器存取),让DMA控制器来操作IO而不是CPU。本质上DMA是程序控制IO,只不过是由DMA控制器而不是主CPU做全部工作。其实我们可以想象成DMA是一个特定功能的CPU,该特定功能也即是对IO设备的操作。将原来需要CPU进行IO操作的部分,放到DMA控制器上实现。而对于IO设备来说,例如磁盘控制器并不知道或者并不关心,磁盘的读写请求是来自CPU还是DMA控制器。当程序需要将文件中的某些字节拷贝到内存中的一块区域时,首先CPU告诉DMA控制器需要将什么数据传送到什么地方,而该进程(或者当前请求IO的线程)被挂起阻塞,接着DMA控制器向磁盘控制器发出命令,磁盘控制器寻道,寻址,将数据拷贝到磁盘控制器的缓冲区中。接着DMA控制器在总线上发出一个读请求给磁盘控制器,开始DMA传送。传输完成之后DMA控制器将中断CPU,以让CPU知道传输已经完成。接着中断服务程序开始运行,之前被挂起的进程(线程)继续开始运行,而此时文件的内容已经读到了指定的内存地址了。这期间没有占用CPU的时钟周期。

总结与其他

计算机系统是一个非常复杂与庞大的系统,以上的内容仅仅是一个入门的指引,限于篇幅很多细枝末节的内容都没有提到,如果有任何错误欢迎大家指出。另外计算机网络本身是一个非常大的课题所以我希望用单独的一篇文章进行说明。另外留下一个问题,也是我有疑惑的地方,如果大家有答案,可以在留言区域讨论。

问题:在单核CPU的架构下,多线程之间的锁其实相对比较容易实现,因为任何时刻其实只有一个进程的一个线程在占用CPU,所谓的并发实际上是伪并发,真正的多核CPU的并发叫并行。单核CPU情况下,由于CPU轮转的非常快,让我们看起来是同时运行了多个程序,那么问题是,如果在多个CPU的情况下,原来写的多线程程序的互斥与同步还有效果吗?因为很可能一个进程中的两个线程同时在两个不同CPU上执行,他们访问同一个变量,这个加锁还有效果吗?

posted @ 2016-03-06 22:44  薰衣草的旋律  阅读(3356)  评论(1编辑  收藏  举报