计算机原理(一)

大学毕业作也快两年了,回顾下,其实大学很多课程都是非常基础,对IT工作者而言其实是非常有作用的,特别是当要学得更加深入的时候,再去回顾下基础的东西是非常有必要的,所以就记录下回顾下计算机原理 的心得体会!

计算机之父

计算机发展史也不必刻意去多写了,我们都说冯诺依曼是计算机之父,其实图灵大大才是教父!

计算机组成

我们的计算机核心就是 :存储程式,然后执行!

下图就是一个基本结构流程:输入,存储,执行,输出。

对我们写代码的来说,比较重要的是存储与执行,因为这涉及到程序的性能如IO,计算速度等,我们最关心的就是CPU与内存了!程序数据是存储在硬盘里面,而如果要执行,一般来说我们需要把程序启动,把执行指令加载到内存中(数据交互更快)缓存着,然后CPU执行内存中的指令运算得出结果输出!

指令集

我们都知道,计算机是只能识别0跟1的(不通电OR通电),但是如果只是使用 0跟1进行编程,OMG,我想我们都要晕了,所以就出现了指令,我们装机买CPU的时候,都会听到指令集这么个说法,指令集跟牛逼啥的,其实就对应了我们这里说的0和1的一些组合,每个指令集可以然CPU执行一个(或者一组【牛逼的指令集】)操作,越牛逼的指令集在相同硬件条件下,能够让计算机速度更快!

汇编语言

二进制指令集虽然可以让CPU识别并执行,但是作为人类,还是太难懂了,所以就出现汇编语言,他用符号来映射具体的指令集,可读性进一步提升!那么把汇编语言转化为指令集,就需要一个辅助工具,汇编器

高级语言

计算机技术要普及,汇编语言显然还是太晦涩难懂,并且跟具体的硬件还有太多耦合,不同CPU可能只能识别不同的指令集,这样就需要写不同的汇编程序去实现,为了生产力的发展,于是不断出现各种高级语言,如C,C++,JAVA,C#…,从前面我们知道,高级语言也是不能被计算机识别的,所以我们就要编译器将高级语言转换成汇编程序,不同CPU硬件,我们就用不同编译器进行转换成不同的汇编程序,而不需要写不同的程序,编译器帮我们把硬件的差异屏蔽!

高级语言之间的差异

C,C++这种语言,其实是可以跨平台的,但是所写的程序代码,在不同平台(硬件)就需要不同编译器从新编译过才能执行。那么是不是有语言是可以做到写一次代码,编译一次代码就可以到处运行了呢!答案是肯定的,根据前面的一些讲解,你应该猜到了,这中间肯定还要加一层东西吧?答案是肯定的,JVM 虚拟机就是这么个中间的东西,JAVA语言写的代码,是先编译成中间代码的,然后由JVM负责解释执行。因为是在运行的时候解释执行的,所以性能上会有所下降,当然这提高了生产力,所以性能下降算个P啊(当然这只是针对大多数普通程序来说)!当然还有更加牛逼的脚本语言,都不需要编译成中间代码,直接依靠解释器就执行起来了,这又叫脚本语言,JS,大蟒蛇…说实在,也不慢啊!

本来想说计算机原理来着,顺便扯了些语言的东西,我们还是回来看CPU的是如何工作的吧:

CPU

有前面的图我们看到,CPU主要是由运算器与控制器组成的,控制器负责指令的,运算器接收指令进行运算处理,如果就是看上去那么简单,那么就太小看CPU了,实际CPU的组成运算比这个复杂多了,我们来看看Intel奔腾处理器的详细组成:

好晕啊,我来来具体分析下组要部件运算器与控制器的组成:

 控制器由程序计数器、指令寄存器、指令译码器、时序产生器和操作控制器组成。它是计算机指挥系统,完成计算机的指挥工作。尽管不同计算机的控制器结构上有很大的区别,当就其基本功能而言,具有如下功能:

    • 取指令 从内存中取出当前指令,并生成下一条指令在内存中的地址。 
    • 分析指令 指令取出后,控制器还必须具有两种分析的功能。一是对指令进行译码或测试,并产生相应的操作控制信号,以便启动规定的动作。比如一次内存读/写操作,一个算术逻辑运算操作,或一个输入/输出操作。二是分析参与这次操作的各操作数所在的地址,即操作数的有效地址。 
    • 执行指令 控制器还必须具备执行指令的功能,指挥并控制CPU、内存和输入/输出设备之间数据流动的方向,完成指令的各种功能。 
    • 发出各种微操作命令 在指令执行过程中,要求控制器按照操作性质要求,发出各种相应的微操作命令,使相应的部件完成各种功能。 
    • 改变指令的执行顺序 在编程过程中,分支结构、循环结构等非顺序结构的引用可以大大提供编程的工作效率。控制器的这种功能可以根据指令执行后的结果,确定下一步是继续按原程序的顺序执行,还是改变原来的执行顺序,而转去执行其它的指令。 
    • 控制程序和数据的输入与结果输出 这实际也是一个人机对话的设计,通过编写程序,在适当的时候输入数据和输出程序的结果。 
    • 对异常情况和某些请求的处理 当计算机正在执行程序的过程中,发生了一些异常的情况,例如除法出错、溢出中断、键盘中断等。

运算器的组成和功能: 运算器由算术逻辑单元(ALU)、累加寄存器、数据缓冲寄存器和状态条件寄存器组成,它是数据加工处理部件,完成计算机的各种算术和逻辑运算。相对控制器而言,运算器接受控制器的命令而进行动作,即运算器所进行的全部操作都是由控制器发出的控制信号来指挥的,所以它是执行部件。运算器有两个主要功能:

  • 执行所有的算术运算,如加、减、乘、除等基本运算及附加运算;
  • 执行所有的逻辑运算,并进行逻辑测试,如与、或、非、零值测试或两个值的比较等。

总结起来就是如下的一个流程:

光看CPU貌似我们已经了解了基本原理,其实最需要了解的其实是各个模块之间的交互,因为外部之间的IO是最容易产生瓶颈与疑惑的,所以接下来我们得学习下 CPU与内存及其他设备的交互等!

计算机原理-X86 CPU和内存管理(六)

这里主要简单介绍下32位处理器的内存访问,内存管理以及应用程序的内存布局。而32位CPU主要就是从intel 的80386架构CUP开始的,这也是我们最开始熟悉的CPU。而CPU最直接的作用就是 取指令、指令译码、执行指令、访存取数和结果写回。说白了就是对指令进行操作。而前面的可执行程序在CPU看来就是编译好的指令集合及数据集合。

其总图可以抽象为如下:

image

我们分别看看CPU及内存的一些操作细节:

一.32CPU
CPU架构

从80386开始,地址线变为了32位,和CPU寄存器以及运算单元位数一样,最大寻址范围增加到4G。另外80386处理器都可以支持虚拟存储器,支持多任务。 而之后的CPU,主要的改进就在于:

  1. CPU内部集成DMA,计数器,定时器等;
  2. 制造工艺的提示,更多的晶体管,更快的速度
  3. 加入更多的指令集,如MMX,SSE,SSE2等
  4. 集成L1,L2,L3高速缓存,减少外部总线的访问
  5. 超线程,多核心提高CPU效率
CPU内存访问方式

80386引入多任务后,内存管理方式有如下方式:

  1. 地址空间:这个是对物理内存的一个抽象,就好像进程是对CPU的一个抽象。一个进程可用于寻址的一套地址的集合,每个进程都有自己的地址空间,相互独立,这就解决了安全问题。
  2. 交换:把程序全部加载到内存,当进程处于空闲时,把他移除内存,存放到硬盘上,然后载入其他程序。这样使得每个进程可以使用更多的内存。
  3. 虚拟内存:利用到计算机系统的局部性和存储分层,我们可以只加载一部分需要使用的代码和数据到内存,当访问的内容不在内存时,和已经使用完的部分进行交换,这样就能在小内存的机器上运行大的程序了。对于程序来说这是透明的,看起来自己好像使用了全部内存。而多个应用完全可以使用相同的虚拟地址。
  4.  

    由于上面内存管理方式,产生了如下的内存访问方式:

虚拟寻址

CPU在内部增加了一个MMU(Memory Management Unit)单元来管理内存地址的转换。除了可以转换地址,还能提供内存访问控制。如操作系统固定使用一段内存地址,当进程请求访问这一段地址时,MMU可以拒绝访问以保证系统的稳定性。而MMU的翻译过程则需要操作系统的支持。所以可见硬件和软件在计算机发展过程中是密不可分的。

CPU寄存器

通用寄存器 ,指令指针寄存器。。。

二.虚拟内存

他就是现在保护模式下的内存获得方式,它的基本思想是:

  • 每个进程都有自己的地址空间;
  • 每个地址空间被分为多个块,每个块称为页,每个页有连续的地址空间;
  • 这些页被映射到物理内存,但不是所有也都在内存中程序才能运行;
  • 当使用的页不在物理内存中时,由操作系统负责载入相应的页;//运行时映射

虚拟地址(线性地址),虚拟地址就是同上CPU的MMU将虚拟地址映射为物理地址,然后送到总线,进行内存访问。这里最关键的就是虚拟地址的映射。

内存分页

对于虚拟内存来说,是对物理内存的抽象,整个虚拟内存空间被划分成了多个大小固定的页(page),每个页连续的虚拟地址,组合成了一个完整的虚拟地址空间。同样,操作系统也会把物理内存划分为多个大小固定的块,我们称为页框(page frame),它只是操作系统为了方便管理在逻辑上的划分的一个数据结构,并不存放实际内存数据,但是我们可以简单的认为它就是内存。这样一个虚拟内存的page就可以和一个物理内存的page frame对应起来,产生映射关系。

关于一个虚拟页的大小,现在的操作系统中一般是512B-64K(准确的说是地址范围大小,而非容纳数据的大小)。但是内存页的大小会对系统性能产生影响,内存页设得太小,内存页会很多,管理内存页的数组会比较大,耗内存。内存页设大了,因为一个进程拥有的内存是内存页大小的整数倍,会导致碎片,即申请了很多内存,真正用到的只有一点。目前Windows和Linux上默认的内存页面大小都是4K。

从上图我们也可以看出,虚拟内存的页和物理内存的页框并不一定是一一对应的,虚拟内存的大小和系统的寻址能力相关,也就是地址线的位数,而物理内存的页框数取决于实际的内存大小。所以可能只有一部分页有对应的页框,而当访问的数据不在物理内存中时就会出现缺页,这个时候操作系统会负责调入新的页,也就是建立新的映射。这样就不需要一次把程序全部加载到内存。

存储分页

加载应用程序到内存时,因为和虚拟地址有关,我们需要把应用程序文件和虚拟内存关联起来,在Linux中称为存储器映射,同时提供了一个mmap的系统调用来实现次功能。文件被分成页面大小的片,每一片包含一个虚拟页面的内容,因为页面调度程序是按需求调度,所以在这些虚拟页面并没有实际的进入内存,而是在CPU发出访问请求时,会创建一个虚拟页并加载到内存。我们在启动一个进程时,加载器为我们完成了文件映射的功能,所以此时我们的执行文件又可以称为映像文件。实际上加载器并不真正负责从磁盘加载数据到内存,因为和虚拟内存建立了映射关系,所以这个操作是虚拟内存自动进行的。 正是有了存储器映射的存在,使得我们可以很方便的将程序和数据加载到内存中。

MMU映射方式

如何将虚拟页映射到物理页中,MMU通过页表映射,页表是实时在内存中的,而虚拟页是运行时通过页表映射到实际的物理内存页上的。

页表分级

为了解决页表占用内存过多的问题,引入了分级页表

页表缓冲区

虚拟地址转换时,一次内存访问查找页表的过程。我们知道内存速度比CPU慢很多,每次从内存取数据都要访问2次内存,会使得系统性能下降。为了解决这个问题,在MMU中包含了一个关于PTE的缓冲区TLB(Translation Lookaside Buffer ),TLB是一个寄存器,所以它运行的速度和CPU速度相同。每次在去页表中查找之前,可以先在TLB中进行查找,如果命中则直接拿到物理页地址,如果不命中则再次访问内存。

进程调度和虚拟内存

我们知道在系统中,每个进程都有自己独立的虚拟空间,于是每个进程都有一张属于自己的内页表。 而我们翻译地址时,从cr3中取出页表目录的首地址。对于不同的进程,他们都使用同一个寄存器。于是在CPU调度进程的时候,虚拟的地址空间也需要切换。于是对于普通用户程序需要做下面几件事情:

  1. 保存当前进程的页表目录地址,也就是保存cr3中存放的地址到进程上下文中
  2. 清空TLB中缓存的数据
  3. 调度新的进程到CPU,并设置cr3寄存器的值为页表目录的首地址

但是内存中除了用户程序之外还存在操作系统自身占用的内存。我们可以简单的把操作系统看成一个超大的进程,他和其他普通进程一样需要使用虚拟内存,需要使用到页表。当然作为内核程序,它必须是有一些特权的,下一篇我们将会介绍虚拟内存的布局。而对于内核而言不是存在进程调度的。因为所有的内核进程都是共享内核的虚拟地址空间,而我们一般都称之为内核线程,而非进程。 当然对于Linux而言,没有线程的概念,线程和进程唯一不同就是是否共享虚拟地址空间。一般来说内核代码是常驻在内存的,那么内核会不会缺页呢?