深入理解计算机系统

深入理解计算机系统
#

参考:http://www.cnblogs.com/zy691357966/p/5548280.html

第6章 存储器层次结构

  存储器系统(memory system)是一个具有不同容量,成本和访问时间的存储设备的层次结构。
  CPU寄存器保存着最常用的数据。靠近CPU的小的、快速的高速缓存存储器(cache memory)作为一部分存储在相对慢速的主存储器(mainmemory,简称主存)中的数据和 指令的缓冲区域。主存暂时存放存储在容量较大的、慢速磁盘上的数据,而这些磁盘常常又作为存储在通过网络连接的其他机器的磁盘或磁带上的数据的缓冲区域。
  一般来说,如果你的程序需要的数据是存储在CPU寄存器中的,那么在指令的执行期间,在零个周期内就能访问到它们。如果存储在高速缓存中,需要1〜30个周期。如果存储在主存中,需要50〜200个周期。而如果存储在磁盘上,需要大约几千万个周期。

6.1 存储技术

6.1.1 随机访问存储器

随机访问存储器
  随机访问存储器(Random-Access Memory,RAM)分为两类:静态RAM (SRAM)和动态RAM(DRAM)。SRAM比DRAM更快,但也贵得多。SRAM用来作为高速缓存存储器,一般只有几兆。DRAM用来作为主存以及图形系统的帧缓冲区(显存),一般有几G。
  静态存储器SRAM将每个位存储在一个双稳态的存储器单元里。每个单元是用一个六晶体管电路来实现的。由于这种双稳态特性,只要有电,它就会永远保持他的值,即使有干扰。例如电子噪音,来扰乱电压,当消除干扰时,电路就会恢复稳定值。
  动态存储器DRAM将每个位存储为对一个电容的充电。这个电容非常小,通常只有30*10^-15法拉。 DRAM存储器可以造的十分密集。 每个单元由一个电容和一个访问晶体管组成。但是,DRAM存储器对干扰非常敏感。当电容电压被扰乱后,就永远不会恢复。很多原因会导致漏电,使得DRAM单元在10~100毫秒时间内失去电荷。幸运的是,计算机的时钟周期以纳秒衡量,这个保持时间也相当长。存储器系统必须周期性地读出,然后重写来刷新存储器的每一位。

**非易失性存储器 **
  如果断电,DRAM和SRAM会丢失它们的信息,从这个意义上说,它们是易失的(volatile)。另一方面,非易失性存储器(nonvolatilememory)即使是在关电后,也仍然保存着它们的信息。
  由于历史原因,虽然ROM有的类型既可以读也可以写,但是它们整体被称为只读存储器(Read-Only Memory,ROM)。PROM (Programmable ROM,可编程ROM)只能被编程一次。PROM的每个存储器单元有一种熔丝(fUse),它只能用高电流熔断一次。可擦写可编程ROM (EPROM)有一个透明的石英窗口,允许光到达存储单元。EPROM能够被擦除和重编程的次数的数量级可以达到 1000 次。电子可擦除 PROM (EEPROM)类似于 EPROM,但是它不需要一个物理上独立的编程设备,因此可以直接在印制电路卡上编程。EEPROM能够 被编程的次数的数量级可以达到10^5次。闪存(flash memory)是一类非易失性存储器,基于EEPROM,它已经成为了一种重要的存储技术。固态硬盘(Solid State Disk,SSD)也是基于闪存的磁盘驱动器。

访问主存
  数据流通过称为总线(bus)的共享电子电路在处理器和DRAM主存之间来来回回。每次CPU和主存之间的数据传送都是通过一系列步骤来完成的,这些步骤称为总线事务(bus transaction)。读事务(read transaction)从主存传送数据到 CPU。写事务(write transaction)从CPU传送数据到主存。IO桥是将系统总线的电子信号翻译成存储器总线的电子信号。总线是一组并行的导线,能携带地址,数据和控制信号。

6.1.2 磁盘存储

  磁盘是广为应用的保存大量数据的存储设备,存储数据的数量级可以达到1TB,1PB等等,而基于RAM的只能有几百到几千兆字节。不过,从DRAM中读比磁盘快10万倍,从SRAM读比磁盘快100万倍。
  磁盘是由盘片(platter)构成的。每个盘片有两面或者称为表面(surface),表面覆盖着磁性记录材料。盘片中央有一个可以旋转的主轴(spindle),它使得盘片以固定的旋转速率 (rotational rate)旋转,通常是 5400〜15000 转每分钟(Revolution Per Minute, RPM)。磁盘通常包含一个或多个这样的盘片,并封装在一个密封的容器内。
  一个典型的磁盘表面的结构如下图。每个表面是由一组称为磁道(track)的同心圆组成的。每个磁道被划分为一组扇区(sector)。每个扇区包含相等数量的数据位(通常是512字节),这些数据编码在扇区上的磁性材料中。扇区之间由一些间隙(gap)分隔开,这些间隙中不存储数据位。间隙用来存储标识扇区的格式化位。

  磁盘由一个或多个叠放在一起的盘片组成,他们被封装在一个密闭的包装里,如上图的(b)所示,整个装置称为磁盘驱动器,我们简称磁盘。有时又叫旋转磁盘(rotating disk),使之区别基于闪存的固态硬盘(SSD)。
  磁盘制造商通常用术语柱面(cylinder)描述多个盘片的构造 。柱面是所有盘片表面上到主轴中心的距离相等的磁道集合。例如,一个驱动器有三个盘片,六个面。那么柱面k是六个磁道k的集合。
  下面给出一个磁盘容量计算公式:

注意:1G字节究竟有多大?
  对于DRAM和SRAM容量相关的计量单位,通常\(K=2^{10},M=2^{20},G=2^{30},T=2^{40}\)。对于像磁盘和网络这样的I/O设备容量相关的计量单位,通常\(K=10^3,M=10^6,G=10^9,T=10^{12}\)。速率和吞吐量也常常使用这些前缀。  
  实际上,两者都可以工作的很好。例如:\((2^{20}-10^6)/10^6≈0.05,(2^{30}-10^9)/10^9≈0.07\)
格式化的磁盘容量
  通常磁盘在每个区中都会预留出一组柱面作为备用,如果区中一个或者多个柱面损坏,就可以使用这些备用的柱面,所以磁盘制造商所说的格式化容量比最大容量要小。

磁盘操作
  磁盘用读/写头(read/write head)来读写存储在磁性表面的位,而读写头连接到一个传动臂(actuator arm) —端,如图6-10a所示。通过沿着半径轴前后移动这个传动臂,驱动器可以将读/写头定位在盘面上的任何磁道上。这样的机械运动称为寻道(seek)。一旦读/写头定位到了期望的磁道上,那么当磁道上的每个位通过它的下面时,读/写头可以感知到这个位的值(读该位),也可以修改这个位的值(写该位)。有多个盘片的磁盘针对每个盘面都有一个独立的读/写头,如图6-10b所示。读/写头垂直排列,一致行动。在任刻,所有的读/写头都位于同一个柱面上。

  磁盘以扇区大小的块来读写数据。对扇区的访问时间(access time)主要有三个部分:寻道时间(seek time)、旋转时间(rotational time)和传送时间(transfer time)。

访问磁盘
  在磁盘控制器接收到CPU的读命令后,它将逻辑块号翻译成一个扇区地址,读该扇区的内容,然后将这些内容直接传送到主存,不需要CPU的干涉,这个过程称为直接存储器访问(Direct Memory Access, DMA),这种数据传送称为DMA传送。

6.2 局部性

  一个编写良好的计算机程序常常具有良好的局部性(locality)。也就是说,它们倾向于引用邻近于其他最近引用过的数据项的数据项,或者最近引用过的数据项本身。这种倾向性,被称为局部性原理(principle of locality)。
  局部性通常有两种不同的形式:时间局部性(temporal locality)和空间局部性(spatial locality)。在一个具有良好时间局部性的程序中,被引用过一次的存储器位置很可能在不远的将来再被多次引用。在一个具有良好空间局部性的程序中,如果一个存储器位置被引用了一次,那 么程序很可能在不远的将来引用附近的一个存储器位置。
  现代计算机系统各个层次都利用了局部性:

  • 硬件层:局部性原理允许计算机设计者引入高速缓存存储器。 保存最近被引用的指令和数据项,从而提高对主存的访问速度。
  • 操作系统级:利用主存缓存磁盘文件系统最近使用的磁盘块。
  • 应用程序设计:Web浏览器将最近被引用的文档放到本地磁盘上,利用的就是时间局部性。

局部性小结

  • 重复引用同一个变量的程序有良好的时间局部性。
  • 对于具有步长为t的引用模式的程序,步长越小,空间局部性越好。具有步长为1的引用模式的程序有很好的空间局部性。在存储器中以大步长跳来跳去的程序空间局部性会很差。
  • 对于取指令来说,循环有好的时间和空间局部性。循环体越小,循环迭代次数越多,局部性越好。

6.3 存储器层次结构

  典型的存储器层次结构如下图所示,从高层往底层走,存储设备变得更慢、更便宜和更大。

6.3.1 存储器层次结构中的缓存

  一般而言,高速缓存(cache,读作"cash")是一个小而快速的存储设备,它作为存储在更大,也更慢的设备中的数据对象的缓冲区域。使用高速缓存的过程称为缓存(caching,读作"cashing")。
  存储器层次结构的中心思想是:层次结构中较高一层作为较低一层的缓存。
  数据总是以块大小为传送单位(transfer unit)在第k层和第k+1层之间来回拷贝。在层次结构中任何一对相邻的层次之间块大小是固定的,但是其他的层次对之间可以有不同的大小。比如:L1和L0通常使用1个字(通常4字节或8字节,即32位或64位)的块,L2和L1(以及L3和L2、L4和L3之间)通常使用8~16个字的块,L5和L4之间通常使用几百或几千字节的块,实际上是为了补偿远离CPU的这些设备的较长的访问时间。

注意区分这里的字和字节

  • 字:计算机进行数据处理时,一次存取、加工和传送的数据长度称为字(word)。计算机的字长决定了其CPU一次操作处理实际位数的多少,由此可见计算机的字长越大,其性能越优越。
  • 字节:字节来自英文Byte,习惯上用大写的“B”表示。 字节是计算机中数据处理的基本单位。规定一个字节由八个二进制位构成,即1个字节等于8个比特(1Byte=8bit)。

缓存命中
  当程序需要第k+1层的某个数据对象d时,它首先在当前存储在第k层的一个块中查找d,如果在,则称为缓存命中;否则,就称为缓存不命中。

存储器层次结构概念小结
  基于缓存的结构行之有效,因为较慢的存储设备比较快的存储设备更便宜;还因为程序往往展示局部性:

  • 利用时间局部性:由于时间局部性,同一数据对象可能被多次使用,一旦一个数据对象在第一次不命中时被拷贝到缓存中,我们就会期望后面对该目标有一系列的访问命中。由于缓存比低一层的存储设备更快,对后面的数据命中比全都不命中要快很多。
  • 利用空间局部性:块通常包含多个数据对象。由于空间局部性,我们希望对该块中其他对象访问命中节约的时间补偿不命中时的拷贝时间。

第8章 异常控制流

  程序运行的顺序是根据程序计数器(PC)的值而定的,PC从一个地址到另一个地址的过渡称为控制转移(control transfer),这样的控制转移序列叫做处理器的控制流(control flow)。一般如果PC是按照顺序地址进行过渡的,控制流是一个平滑的序列,但是有些情况下PC是来回跳转的,比如:

  • 数据从磁盘或者网络适配器到达
  • 指令除以了零
  • 用户按下 ctrl+c
  • 系统的计时器到时间

异常控制流(Exceptional Control Flow 简称ECF):
现代操作系统通过使控制流发生突变来对系统状态做出反应。ECF发生在计算机系统的各个层次,硬件层的ECF:异常、操作系统层的ECF:进程和信号、应用层的ECF:非本地跳转。首先需要注意的是,虽然名称里包含异常(实际上也用到了异常),但是跟代码中 try catch 所涉及的异常是不一样的。

内核:
内核是操作系统的内部核心程序,它向外部提供了对计算机设备的核心管理调用。我们将操作系统的代码分成2部分。内核所在的地址空间称作内核空间。而在内核以外的统称为外部管理程序,它们大部分是对外围设备的管理和界面操作。外部管理程序与用户进程所占据的地址空间称为外部空间。
  通常,一个程序会跨越两个空间。当执行到内核空间的一段代码时,我们称程序处于内核态,而当程序执行到外部空间代码时,我们称程序处于用户态。
  程序只有通过系统调用(system call)的方式访问内核结构。Linux不支持用户态线程。在用户态中,Linux认为线程就是共享上下文(Context)的进程。Linux通过LWP(轻量级进程,light weight process)的机制来实现用户态线程的概念。

8.1 异常(硬件层)

  异常:是异常控制流的一种形式,一部分由硬件实现,一部分由操作系统实现。其实就是控制流中的突变,用来响应处理器状态中的某些变化。注意和语言中的应用级的异常概念区分。下图展示了异常的基本思想:

  处理器中,状态被编码为不同的位和信号,状态变化被称为事件,事件不一定和当前指令的执行有关(比如相关的:除以零、数学运算溢出;不相关的:I/O 请求完成、用户按下了 ctrl+c或者一个系统定时器产生信号 )。处理器检测到有事件发生时,会通过异常表(Exception Table)来确定跳转的位置,跳转到一个专门设计处理这类事件的操作系统子程序(异常处理程序)。
  异常处理程序完成处理后,根据异常事件的类型会(执行一种):

  • 将控制返回给当前指令(事件发生时正在执行的)。
  • 将控制返回给下一条指令(没有异常将会执行的)。
  • 终止被中断的程序。

8.1.1 异常处理

  系统为每个异常类型都分配了一个唯一的非负整数的异常号,每个异常处理号都对应了一个异常处理程序。在系统启动时,操作系统分配和初始化一张称为异常表的跳转表。

  当运行本机上的一个应用程序的时候,如果处理器检测到了一个事件,并且确定了异常号4,处理器就会触发异常,通过异常表中的地址4跳转到相应的异常处理程序中执行。
注意:
1.异常表的起始地址放在一个叫做异常表基址寄存器的特殊CPU寄存器里。
2.异常处理模式不同于普通的过程调用:

  • 过程调用:跳转到处理程序前,处理器将返回地址压入栈中。对于异常,返回地址是当前指令,或下一跳指令。
  • 处理器会把额外的处理器状态压入栈中,以便处理程序返回时调用。
  • 如果控制从一个用户程序到内核,那么所有这些项目会被压入内核栈中,而不是用户栈。
  • 由于异常处理程序运行在内核模式下,意味着拥有对所有资源的访问权限。

8.1.2 异常的分类

  异常分为一下四类:中断(interrupt),陷阱(trap),故障(fault)和终止(abort)。

异步异常:由处理器外部的I/O设备中的事件产生的。同步异常:执行一条指令的直接产物。

中断:是异步发生的,由处理器外部的I/O设备中的事件产生的。
  比如我在键盘打字,我的电脑上也在播放音乐,当在听歌的同时输入字符到屏幕上的时候,处理器就会注意到I/O设备键盘上的中断引脚电压变高了,当前指令执行完毕以后就会从系统总线中读取异常号,然后调用中断处理程序,输入完字符以后,中断处理继续执行听歌程序的下一条指令。由于这个过程相当的快,就好像什么都没有发生一样。

陷阱:实现系统调用,在用户程序和内核之间提供一个像函数调用一样的接口,叫做系统调用。比如读文件(read)、创建进程(fork) 、新的程序(execve) 、终止当前进程(exit),为了这些内核服务的受控访问,处理器提供了一条特殊的syscall n的指令。系统调用是运行在内核模式下,允许执行指令并访问定义在内核中的栈。而普通调用是在用户模式下,限制了可以执行的指令类型,并且只能访问与调用函数相同的栈。

故障:故障是由错误引起,可能被故障处理程序修正。如果能被修正,返回引起故障的指令,并重新执行。 否则返回abort,终止引起故障的应用程序。

终止:终止是不可恢复的致命错误造成的结果,通常是一些硬件错误,比如DRAM和SRAM被损坏。 终止处理程序从不将控制返回给应用程序。

8.1.3 Linux系统中的异常

  有高达256种不同的异常,0-31的号码是Intel架构师定义的;32-255号是操作系统定义的中断和陷阱。

8.2 进程

  进程的经典定义:一个执行中的程序的实例。
  系统中每个程序都是运行在某个进程的上下文中的。上下文是由程序正确运行所需的状态组成,包括存放在存储器中的代码和数据、栈、通用目的寄存器的内容、程序计数器、环境变量和打开文件描述符的集合。在shell中运行程序时,shell会创建一个新的进程,然后在新进程的上下文中运行这个可执行目标文件。应用程序也能创建新进程。
  进程给应用程序提供了两个关键抽象:

  • 独立的逻辑控制流,提供程序独占处理器的假象。
  • 私有的地址空间,提供程序独占存储器系统的假象。

8.2.1 逻辑控制流

  程序执行的一系列PC(程序计数器)值唯一地对应于包含在程序的可执行目标文件中的指令或包含在运行时动态链接的共享库中的指令,这个PC值的序列称为逻辑控制流。

  进程轮流使用处理器,每个进程执行它的流的一部分,然后被抢占,其他进程开始执行。程序运行在进程的上下文中,像是在独占地使用处理器。

8.2.2 并发流

  异常处理程序、进程、信号处理程序、线程和Java进程都是逻辑流的例子。一个逻辑流的执行在时间上与另一个流重叠,称为并发流,这两个进程称为并发运行。比如上图中:A进程开始执行以后并且未完成之前,跳转到B进程执行,这两个就是并发。进程执行控制流的一部分的时间段称为时间片,进程和其他进程轮换运行称为多任务,也称时间分片。

8.2.3 私有地址空间

  进程为每一个程序提供它自己的私有地址空间,和这个空间中某地址相关联的存储器字节不能被其他进程读写。和私有地址空间关联的存储器内容一般不同,但空间有相同的结构。比如下图是x86Linux进程的地址空间的组织结构,这个私有的地址空间最上部是内核保留的,包含内核在代表进程执行指令时使用的代码、数据和栈。最下部是预留给用户程序的,包括通常的文本、数据、堆和栈。代码段始终是从0x08048000处开始(32位系统),0x00040000处开始(64位系统)。

8.2.4 用户模式和内核模式

  处理器提供一种机制,限制一个应用程序可以执行的指令以及它可以访问的地址空间范围,这就是用户模式和内核模式。处理器通过控制寄存器中的一个模式位来提供这个功能。
  设置了模式位后,进程就运行在内核模式中(有时也叫超级用户模式)。内核模式下的进程可以执行指令集的任何指令,访问系统所有存储器的位置。
  没有设置模式位时,进程运行在用户模式。用户模式不允许程序执行特权指令。比如停止处理器,改变模式位,发起一个I/O操作。 不允许用户模式的进程直接引用地址空间的内核区代码和数据。 任何尝试都会导致保护故障。 用户通过系统调用间接访问内核代码和数据。
  进程从用户模式转变为内核模式的方法:通过中断,故障,陷阱这样的异常。 在异常处理程序中会进入内核模式。退出后,又返回用户模式。

8.2.5 上下文切换

  操作系统内核使用一种称为上下文切换的较高层次的异常控制流来实现多任务。上下文切换机制建立在之前讨论的较低层次异常机制上的。内核为每个进程维护一个上下文。上下文就是内核重新启动一个被抢占的进程所需的状态。它由一些对象的值组成,这些对象包括包含程序计数器、用户栈、状态寄存器等
  在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占的进程。这种决定叫做调度(shedule),是由内核中称为调度器(scheduler)的代码处理的。当内核选择一个新的进程运行时,我们就说内核调度了这个进程。
当调度进程时,使用一种称为上下文切换的机制来将控制转移到新的进程。上下文切换包括:

  • 保存当前进程的上下文
  • 恢复某个先前被抢占的进程的上下文
  • 将控制传递给这个新恢复的进程

什么时候会发生上下文切换
  内核代表用户执行系统调用时,如果系统调用因为某个事件阻塞,那么内核可以让当前进程休眠,切换另一个进程。 或者可以用sleep系统调用,显式请求让调用进程休眠。 即使系统调用没有阻塞,内核也可以决定执行上下文切换。
  中断也可能引发上下文切换。所有系统都有某种产生周期性定时器中断的机制,典型为1ms或10ms。 每次定时器中断,内核就能判断当前进程运行了足够长的时间,切换新的进程。


  内核中有一个专门的调度程序,当从进程A切换到进程B的时候,内核调度器为每个进程保存一个上下文状态(运行环境保存):包含程序计数器、用户栈、状态寄存器等,然后切换到另外一个进程处开始执行。如上图所示,当进程A调用read函数的时候,内核代表进程A开始执行系统调用读取磁盘上的文件,这需要耗费相对很长的时间,处理器这时候不会闲着什么都不做,而是开始一种上下文切换机制,切换到进程B开始执行。当B在用户模式下执行了一段时间,磁盘读取完文件以后发送一个中断信号,将执行进程B到进程A的上下文切换,将控制权返回给进程A,系统调用read指令后面的那条指令,继续执行进程A。

高速缓存污染和异常控制流
  一般而言,硬件高速缓存存储器不能和诸如中断和上下文切换这样的异常控制流很好地交互,如果当前进程被一个中断暂时中断,那么对于中断处理程序来说高速缓存器是冷的(即程序所需要的数据都不在高速缓存中)。如果处理程序从主存访问足够多的表项,被中断的进程继续的时候,高速缓存对于它来说也是冷的,必须再次热身,我们称中断处理程序污染了高速缓存。使用 上下文切换也会发生类似的现象。

进程切换

  左边是单进程的模型,内存中保存着进程所需的各种信息,因为该进程独占 CPU,所以并不需要保存寄存器值。而在右边的单核多进程模型中,虚线部分可以认为是当前正在执行的进程,因为我们可能会切换到其他进程,所以内存中需要另一块区域来保存当前的寄存器值,以便下次执行的时候进行恢复(也就是所谓的上下文切换)。整个过程中,CPU 交替执行不同的进程,虚拟内存系统会负责管理地址空间,而没有执行的进程的寄存器值会被保存在内存中。切换到另一个进程的时候,会载入已保存的对应于将要执行的进程的寄存器值,但是由于需要重新为高速缓存热身,会存在切换开销。

而现代处理器一般有多个核心,所以可以真正同时执行多个进程。这些进程会共享主存以及一部分缓存,具体的调度是由内核控制的,示意图如下:

8.4 进程控制

  Unix操作进程的系统调用。

  1. getpid()返回调用进程的PID,getppid()返回它的父进程的PID。
  2. 父进程通过调用fork函数创建一个新的运行子进程。新创建的子进程几乎但不完全与父进程相同。子进程得到与父进程相同但独立的地址空间,包括文本,数据和bss段,堆以及用户栈。子进程还获得与父进程任何打开文件描述符相同的拷贝。 意味着当父进程调用fork时,子进程可以读写父进程中打开的任何文件。 父进程和新创建的子进程之间最大的区别在于有不同的PID 。fork()函数会一次调用,返回两次,一次在父进程,一次在子进程,返回值用来明确是在父进程还是在子进程中执行。
  3. 当一个进程由于某种原因终止时,内核并不是立即把它从系统中清除。相反,进程被保持在一种已终止的状态,直到被它的父进程回收(reap)。当父进程回收已终止的子进程时,内核将子进程的退出状态传递给父进程,然后抛弃已终止的进程。一个终止了但还未被回收的进程叫做僵死进程。如果父进程没有回收子进程而终止了,那么内核安排init进程来回收它们。init进程的的PID位1,是在系统初始化时由内核创建的长时间运行的程序,如shell,服务器,总是应该回收他们的僵死子进程。
  4. 让进程休眠:sleep函数(设定时间恢复)和pause函数(直到收到信号恢复)
  5. 加载并运行程序:execve函数(调用一次,从不返回)

进程总是处于下面三种状态:

  • 运行:进程要么在CPU中执行,要么等待执行,最终被内核调度。
  • 停止:进程的执行被挂起(suspend),且不会被调度。收到SIGSTOP,SIGTSTP,SIDTTIN或者SIGTTOU信号,进程就会停止。 直到收到一个SIGCONT信号,在这个时刻,进程再次开始运行。
  • 终止。进程永远停止。停止的三种原因:收到一个信号,信号默认行为是终止进程;从主程序返回;调用exit函数

程序和进程
  程序是一堆代码和数据的集合,可以作为目标模块存在于磁盘,或作为段存在于地址空间中。进程是执行中程序的一个具体实例。程序总是运行在某个进程的上下文中。fork函数:在新的子进程中运行相同的程序,除了pid几乎都相同。execve函数:在当前进程的是上下文中加载并运行一个新的程序,他会覆盖当前进程的地址空间,但并没有创建一个新进程,新的程序有相同的pid和文件描述符。

8.5 信号

  信号是一种更高层次的软件形式的异常,它允许进程中断其他进程。一个信号就是一个消息,我们列出Linux系统上30个不同种类的信号:

  正在运行的前台子进程,当键入ctrl-c,发送序号2(SIGINT);当一个进程发送信号9(SIGKILL)就会强制终止另外一个进程;当子进程终止时,就会发送信号17(SIGCHILD)给父进程。

8.6 非本地跳转(应用层)

  C提供了一种用户级的异常控制流,称为非本地跳转。它将控制直接从一个函数转移到另一个正在执行的函数。非本地跳转可以用来从一个深层嵌套的函数调用中立即返回,如检测到错误;或者使一个信号处理程序转移到一个特殊的代码位置,而不是返回到信号中断的指令的位置。

第9章 虚拟存储器

  为了更加有效地管理存储器且少出错,现代系统提供了对主存的抽象概念,叫做虚拟存储器(VM)。
  虚拟存储器是硬件异常,硬件地址翻译,主存,磁盘文件和内核软件的完美交互。为每个进程提供一个大的,一致的和 私有的地址空间。提供了3个重要能力:

  • 将主存看成磁盘地址空间的高速缓存,只保留了活动区域,并根据需要在磁盘和主存间来回传送数据,高效使用主存。
  • 为每个进程提供一致的地址空间,简化存储器管理。
  • 保护了每个进程的地址空间不被其他进程破坏。

9.1 物理与虚拟寻址

  物理地址(Physical Address,PA):计算机系统的主存被组织为M个连续的字节大小的单元组成的数组,每个字节的地址叫物理地址。
  CPU访问存储器的最自然的方式使用物理地址,这种方式称为物理寻址。早期的PC,数字信号处理器,嵌入式微控制器以及Cray超级计算机使用物理寻址。
  现代处理器使用的是虚拟寻址(virtual addressing)的寻址形式。

  CPU通过生成一个虚拟地址(Virtual address,VA)来访问主存。将虚拟地址转换为物理地址叫做地址翻译(address translation)。地址翻译也需要CPU硬件和操作系统之间的紧密结合。CPU芯片上有叫做存储器管理单元(Memory Management Unit,MMU)的专用硬件,利用存储在主存中的查询表来动态翻译虚拟地址,该查询表由操作系统管理。

9.2 地址空间

  地址空间(address space)是一个非负整数地址的有序集合。
  在一个带虚拟存储器的系统中,CPU从一个有N=2^n个地址的地址空间中生成虚拟地址,这个地址空间称为虚拟地址空间(virtual address space)。一个地址空间大小是由表示最大地址所需要的位数来描述的,如N=2n个地址的虚拟地址空间叫做n位地址空间,现在操作系统支持32位或64位。一个系统还有物理地址空间,它与系统中物理存储器的M=2m(假设为2的幂)个字节相对应。
  地址空间的概念很重要,因为它区分了数据对象(字节)和 它们的属性(地址)。每个字节(数据对象)一般有多个 独立的地址(属性),每个地址都选自不同的地址空间。比如一般来说,字节有一个在虚拟地址空间的虚拟地址,还有一个在物理地址空间的物理地址,两个地址都能访问到这个字节。

9.3 虚拟存储器作为缓存的工具

  虚拟存储器(VM)被组织为一个存放在磁盘上的N个连续字节大小的单元组成的数组,每个字节都有一个唯一的虚拟地址,这个虚拟地址作为到数组的索引。磁盘上数组的内容被缓存到主存中。同存储器层次结构其他缓存一样,磁盘上的数据被分割称块,这些块作为磁盘和主存之间的传输单元,这个块的大小就是虚拟页(Virtual Page,VP)的大小。每个虚拟页大小为P=2^p字节。物理类似的,物理存储器被分割为物理页,大小也为P字节,被称为页帧(page frame)
  任何时候,虚拟页的集合都被分为3个不相交的子集:

  • 未分配的:VM系统还未分配(或者创建)的页,未分配的块没有任何数据与之相关联,不占用磁盘空间
  • 缓存的:当前缓存在物理存储器的已分配页。
  • 未缓存的:没有缓存在物理存储器中的已分配页。

9.3.1 DRAM缓存的组织结构

  我们常用SRAM缓存来表示位于CPU和主存之间的L1,L2,和L3高速缓存,用DRAM缓存表示虚拟存储器系统的缓存,它在主存中缓存虚拟页,有两个特点。SRAM大约比DRAM快10倍,而DRAM大约比磁盘快10 0000倍,从磁盘的一个扇区读取第一个字节的时间开销要比从该扇区中读连续的字节慢大约100000倍(多次:寻道时间+旋转时间)。因此DRAM缓存不命中处罚十分严重。
  因为大的不命中处罚和访问第一字节的开销,虚拟页往往很大,典型地是4KB~2MB。DRAM缓存是全相联,也就是: 任何虚拟页都能放在任何物理页中。因为对磁盘的访问时间很长,DRAM缓存总是使用写回,而不用直写。

9.3.2 页表

  判断命中和替换由多种软硬件联合提供,比如操作系统软件,MMU中的地址翻译硬件和页表(page table)。页表是存放在物理存储器的数据结构,将虚拟页映射到物理页。地址翻译硬件将虚拟地址转换为物理地址都会读取页表。操作系统负责维护页表的内容,以及在磁盘与DRAM之间来回传送页。
  如下图为一个页表的基本组织结构。
  

  页表就是一个页表条目(Page Table Entry,PTE)的数组,虚拟地址空间 中每个页在页表的固定偏移量处都有一个PTE,每个PTE由一个有效位和n位地址字段,有效位表明了该虚拟页当前是否被缓存在DRAM中。如果有效位存在,那么地址字段指向DRAM中相应的屋里也得起始地址,这个物理页中缓存了该虚拟页。如果没有设置有效位,那么这个空地址表示这个虚拟页还未被分配。否则,这个地址就指向该虚拟页在磁盘上的起始位置。

9.3.3 页命中

  当CPU读包含在VP 2中的虚拟存储器的一个字时,因为设置了有效位,那么地址翻译硬件就知道VP 2被缓存到了存储器中,所以他使用PTE中的物理存储器地址,称为页命中

9.3.4 缺页

  在虚拟存储器的习惯说法中,DRAM缓存不命中称为缺页(page fault)。处理过程如下:读取PTE有效位,发现未被缓存,触发缺页异常,缺页异常调用内核中的缺页异常处理程序,该程序会选择一个牺牲页。如果牺牲页发生了改变,将其拷贝回磁盘(因为是写回),需要读取的页代替了牺牲页的位置。结果:牺牲页不被缓存,需要读取的页被缓存。中断结束,重新执行最开始的指令。在DRAM中读取成功。磁盘和DRAM之间传送页的活动叫做交换(swapping)或者页面调度(paging)。有不命中发生时,才换入页面,这种策略叫做按需页面调度(demand paging)。现代系统基本都是用这个。

9.3.5 分配页面

  比如某个页面所指向地址为NULL,将这个地址指向磁盘某处,那么这就叫分配页面。此时虚拟页从未分配状态变为未缓存。

9.3.6 又是局部性拯救了我们

  虚拟存储器工作的相当好,主要归功于老朋友--局部性(locality)。尽管从头到尾的活动页面数量大于物理存储器总的大小。但是在局部性原则保证了在任意时刻,程序往往在一个较小的活动页面集合工作,这个集合叫做工作集(working set)或者叫常驻集(resident set)。初始载入开销一般比较大,但如果程序有良好的时间局部性,虚拟存储器都工作的相当好。如果程序实在很烂,或者物理空间很小,工作集大于物理存储器大小,这时,页面将不断换进换出,性能十分低,这种状态叫颠簸(thrashing)。可以利用Unix的getrusage函数检测缺页数量。

9.4 虚拟存储器作为存储器的管理工具

  实际上,操作系统为每个进程提供一个独立的页表,因而也就是一个独立的虚拟地址空间。

  注意,多个虚拟页面可以映射到同一个物理共享页面上。VM(虚拟存储器)简化了链接和加载,代码和数据共享,以及应用程序的存储器分配。

  • 简化链接:独立的空间地址允许每个进程的存储器映像使用相同的基本格式,而不管代码和数据实际存放在物理存储器的何处。比如:文本节总是从0x08048000(32位)处或0x400000(64位)处开始。然后是数据,bss节,栈。一致性极大简化了链接器的设计和实现。
  • 简化加载:加载器实际上可以不从磁盘到存储器拷贝任何数据,而只是构建页表,然后当一个指令引用一个存储器位置时,再由存储器系统按需自动调入数据页。
  • 简化共享:独立地址空间为操作系统提供了一个管理用户进程和操作系统自身之间共享的一致性机制。一般每个进程都有自己私有的代码、数据、堆和栈空间,是不和其他进程共享的。此时操作系统为不同的进程分别创建页表,将相应的虚拟页映射到不同的物理页。但是,在一些情况下,还是需要进程共享代码和数据。比如:调用相同的操作系统内核代码,C标准库的printf。因此操作系统需要将不同进程的适当的虚拟页映射到相同的物理页面,从而多个进程共享这部分代码的一个拷贝,而不是每个进程都要加载单独的内核和C标准库的拷贝
  • 简化存储器分配:即虚拟页连续(虚拟页还是单独的),物理页可以不连续,使得分配更加容易。

9.5 虚拟存储器作为存储器保护的工具

  任何现代计算机系统必须为操作系统提供手段来控制对存储器系统的访问,比如:

  • 不应该允许用户进程修改它的只读文本段。
  • 不允许它读或修改任何内核的代码和数据结构
  • 不允许读写其他进程的私有存储器。
  • 不允许修改共享的虚拟页,除非所有共享者显示允许这么做(通过调用明确的进程间通信)

实现方式:在PTE上添加一些额外的许可位来控制对一个虚拟页面内容的访问

  • SUP:是否只有在内核模式(超级用户)下才能访问
  • READ:读权限。
  • WRITE:写权限。

9.6 地址翻译

内容较多,这里只列出多级页表的基本概念。

多级页表
  如果我们有一个32位地址空间,4KB大小的页面(p=212)和一个4B的PTE,即使应用所引用的只是虚拟地址空间中很小的一部分,也总是需要一个4MB的页表驻留在存储器中(计算方式,最多可能要232/4KB=1MB 个页面,每个页面需要4B的PTE 所以需要4MB大小的页表)。用来压缩页表的常用方法是:使用层次结构的页表。

  如果一级页表PTE为空,那么相应的二级页表就根本不会存在,这是一种巨大的潜在节约,因为对于一个典型的程序,4GB的虚拟空间大部分都将是未分配的。同时,只有一级页表才需要总是在主存中,虚拟存储器系统可以在需要时创建,页面调入、调出二级页面,减少主存压力。

第12章 并发编程

  如果逻辑控制流在时间上是重叠,那么它们就是并发的(concurrent)。这种常见的现象称为并发(concurrency)。现代操作系统提供三种基本的构造并发程序的方法:

进程

  每个逻辑控制流都是一个进程,由内核来调度和维护。因为进程有独立的虚拟地址空间,和其他进程通信,控制流必须使用某种显式的进程间通信(interprocess communication,IPC)进制。

I/O多路复用(不要求)

  应用程序在一个进程的上下文中显式地调度它们自己的逻辑流。逻辑流被模型化为状态机,数据到达文件描述符后,主程序显式地从一个状态转换到另一个状态。因为程序是一个单独的进程,所以所有的流都共享同一个地址空间。

线程

  线程是运行在一个单一进程上下文中的逻辑流,由内核调度。像进程一样由内核进行调度,而像I/O多路复用流一样共享同一个虚拟地址空间。

12.1 基于进程的的并发编程

  一个构造并发服务器的自然方法就是,在父进程中接收客户端连接请求,然后创建一个新的子进程来为每个新客户端提供服务。
  对于在父,子进程间共享状态信息,进程有一个非常清晰的模型:共享文件表,但是不共享用户地址空间。
进程拥有独立的私有地址空间即是优点,也是缺点。
优点:一个进程不可能不小心覆盖另一个进程的虚拟存储空间,消除许多令人迷惑的错误。
缺点:1.独立的地址空间使得进程间共享信息也很困难,必须使用显式的IPC(进程间通信)机制。2.往往还比较慢,进程控制和IPC的开销都很大。

12.3 基于线程的并发编程

  线程(thread) 就是运行在进程上下文中的逻辑流。线程由内核调度。每个线程都有它自己的线程上下文(thread context),包括一个唯一的整数线程ID(Thread ID,TID)、栈、程序计数器、通用目的寄存器和条件码。所有运行在该进程里的线程共享该进程的整个虚拟地址空间,因此这个共享虚拟地址空间的整个内容,包括它的代码,数据,堆,共享库和打开的文件。
  每个进程开始生命周期时都是单一线程,这个线程称为主线程(main thread)。在某时刻,主线程创建一个对等线程(peer thread)。当主线程执行一个慢速系统调用,例如read或sleep或者被系统的间隔计时器中断。控制就会通过上下文切换传递到对等线程。对等线程执行一段时间,将控制传递回主线程。在某些方面,线程执行是不等同于进程的。比如:
线程的上下文切换开销比进程的小得多,也比进程的上下文切换快得多,线程不是按照严格的父子层次来组织。和一个进程相关的线程组成一个线程池(pool),独立于其他线程创建的线程。主线程和其他线程的区别仅仅是:他总是进程中第一个运行的线程。线程池概念的主要影响是一个线程可以杀死它的任何对等线程,或等待任意对等线程终止。每个对等线程都能读写相同的共享数据。

posted @ 2017-07-16 19:42  何必等明天  阅读(1159)  评论(0编辑  收藏  举报