开机显示CPU还有内存和cpu的关系等消息

下面是网上看到的一些关于内存囷cpu的关系和CPU方面的一些很不错的文章. 整理如下:

转: CPU的等待有多久?

原文地址:)延迟大约45毫秒,与硬盘驱动器带来的延迟相当事实上,尽管硬盘比内存和cpu的关系慢了5个数量级它的速度与Internet是在同一数量级上的。目前一般家用网络的带宽还是要落后于硬盘连续读取速度的,泹"网络就是计算机"这句话可谓名符其实如果将来Internet比硬盘还快了,那会是个什么景象呢

我希望这些图片能对您有所帮助。当这些数字一起呈现在我面前时真的很迷人,也让我看到了计算机技术发展到了哪一步前文分开的两个图片只是为了叙述方便,我把包含南北桥的整张图片也贴出来供您参考。

转: CPU如何操作内存和cpu的关系

   [注:本人水平有限只好挑一些国外高手的精彩文章翻译一下。一来自己复习②来与大家分享。]

   在你试图理解一个复杂的系统时如果能揭去表面的抽象并专注于最低级别的概念,往往会有不小的收获在这个精神嘚指导下,让我们看看对于内存和cpu的关系和I/O端口操作来说最简单、最基础的概念即CPU与总线之间的接口。其中的细节是很多上层概念的基礎比如线程同步。当然了既然我是个程序员,就暂且忽略那些只有电子工程师才会去关注的东西吧下图是我们的老朋友,Core 2:

   Core 2 处理器囿775个管脚其中约半数仅仅用于供电而不参与数据传输。当你把这些管脚按照功能分类后就会发现这个处理器的物理接口惊人的简单。夲图展示了参与内存和cpu的关系和I/O端口操作的重要管脚:地址线数据线,请求线这些操作均发生在前端总线的事务上下文结构(the context of a transaction)中。前端總线事务的执行包含五个阶段:仲裁请求,侦听响应,数据操作在执行事务的过程中,前端总线上的各个部件扮演着不同的角色這些部件称之为agent。通常agent就是全部的处理器外加北桥。

本文只分析请求阶段在此阶段中,发出请求的agent往往是一个处理器它输出两个数據包。下图列出了第一个数据包中最为重要的位这些数据位通过处理器的地址线和请求线输出:

   地址线输出指定了事务发生的物理内存囷cpu的关系起始地址。我们有33条地址线他们指定了数据包的第35至第3位,第2至第0位为0因此,实际上这33条地址线构成了一个36位的、以8字节对齊的地址正好覆盖64GB的物理内存和cpu的关系。这种设定从奔腾Pro就开始了请求线指定了事务的类型。当事务类型为I/O请求时地址线指出的是I/O端口地址而不是内存和cpu的关系地址。当第一个数据包被发送以后同样由这组管脚,在下一个总线时钟周期发送第二个数据包:

   属性信号(attribute signal A[31:24])很有趣它反映了Intel处理器所支持的5种内存和cpu的关系缓冲功能。把这些信息发布到前端总线后发出请求的agent就可以让其他处理器知道如哬根据当前事务处理他们自己的cache,以及让内存和cpu的关系控制器(也就是北桥)知道该如何应对一块指定内存和cpu的关系区域的缓存类型由處理器通过查询页表(page table)来决定,页表由OS内核维护

   典型的情况是,内核把全部内存和cpu的关系都视为"回写"类型(write-back)从而获得最好的性能。在回写模式下内存和cpu的关系的最小访问单元为一个(cache line),在Core 2中是64字节当程序想读取内存和cpu的关系中的一个字节时,处理器会从L1/L2 cache读取包含此字节的整条缓存线的内容当程序做写入内存和cpu的关系操作时,处理器只是修改cache中的对应缓存线而不会更新主存中的信息。之后当真的需要更新主存时,处理器会把那个被修改了的缓存线整体放到总线上一次性写入内存和cpu的关系。所以大部分的请求事务其数據长度字段都是11(REQ[1:0]),对应64 字节下图展示了当cache中没有对应数据时,内存和cpu的关系读取访问的过程:

在Intel计算机上有些物理内存和cpu的关系范围被而不是实际的RAM存储器地址,比如硬盘和网卡这使得驱动程序可以像读写内存和cpu的关系那样,方便的与设备通信内核会在页表中標记出这类内存和cpu的关系映射区域为不可缓存的(uncacheable)。对不可缓存的内存和cpu的关系区域的访问操作会被总线原封不动的按顺序执行其操莋与应用程序或驱动程序所发出的请求完全一致。因此这时程序可以精确控制读写单个字节、字、或其它长度的信息。这都是通过设置苐二个数据包中的字节使能掩码(byte

前面讨论的这些基本知识还包含很多关联的内容比如:

1、  如果应用程序想要尽可能高的运行速度,就應该把会被一起访问的数据尽量组织在同一条缓存线中一旦这条缓存线被载入,之后的读取操作就会不再需要额外的内存和cpu的关系访問了。

2、  对于回写式内存和cpu的关系访问作用于一条缓存线的任何内存和cpu的关系操作都一定是原子的(atomic)。这种能力是由处理器的L1 cache提供的所有数据被同时读写,中途不会被其他处理器或线程打断特别的,32位和64位的内存和cpu的关系操作只要不跨越缓存线的边界,就都是原孓操作

3、  前端总线是被所有的agent所共享的。这些agent在开启一个事务之前必须先进行总线使用权的仲裁。而且每一个agent都需要侦听总线上所囿的事务,以便维持cache的一致性因此,随着部署更多的、多核的处理器到Intel计算机总线竞争问题会变得越来越严重。为解决这个问题Core i7将處理器直接连接于内存和cpu的关系,并以点对点的方式通信取代之前的广播方式,从而减少总线竞争

本 文讲述的都是有关物理内存和cpu的關系请求的重要内容。当涉及到内存和cpu的关系锁定、多线程、缓存一致性的问题时总线这个角色又将浮出水面。当我第一次看到前端总線数据包的描 述时会有种恍然大悟的感觉,所以我希望您也能从本文中获益下一篇文章,我们将从底层爬回到上层去研究一个抽象概念:虚拟内存和cpu的关系。

[转]: 主板芯片组与内存和cpu的关系映射

   [注:本人水平有限只好挑一些国外高手的精彩文章翻译一下。一来自己复習二来与大家分享。]

   我打算写一组讲述计算机内幕的文章旨在揭示现代操作系统内核的工作原理。我希望这些文章能对电脑爱好者和程序员有所帮助特别是对这类话题感兴趣但没有相关知识的人们。讨论的焦点是LinuxWindows,和Intel处理器钻研系统内幕是我的一个爱好。我曾经編写过不少内核模式的代码只是最近一段时间不再写了。这第一篇文章讲述了现代Intel主板的布局CPU如何访问内存和cpu的关系,以及系统的内存和cpu的关系映射

   作为开始,让我们看看当今的Intel计算机是如何连接各个组件的吧下图展示了主板上的主要组件:

现代主板的示意图,北橋和南桥构成了芯片组

   当你看图时,请牢记一个至关重要的事实:CPU一点也不知道它连接了什么东西CPU仅仅通过一组与外界交互,它并不關心外界到底有什么可能是一个电脑主板,但也可能是烤面包机网络路由器,植入脑内的设备或CPU测试工作台。CPU主要通过3种方式与外堺交互:内存和cpu的关系地址空间I/O地址空间,还有中断

眼下,我们只关心主板和内存和cpu的关系安装在主板上的CPU与外界沟通的门户是前端总线(front-side bus),前端总线把CPU与北桥连接起来每当CPU需要读写内存和cpu的关系时,都会使用这条总线CPU通过一部分管脚来传输想要读写的物理内存和cpu的关系地址,同时另一些管脚用于发送将被写入或接收被读出的数据一个Intel Core 2 QX6600有33个针脚用于传输物理内存和cpu的关系地址(可以表示233个地址位置),64个针脚用于接收/发送数据(所以数据在64位通道中传输也就是8字节的数据块)。这使得CPU可以控制64GB的物理内存和cpu的关系(233个地址塖以8字节)尽管大多数的芯片组只能支持8GB的RAM。

现在到了最难理解的部分我们可能曾经认为内存和cpu的关系指的就是RAM,被各式各样的程序讀写着的确,大部分CPU发出的内存和cpu的关系请求都被北桥转送给了RAM管理器但并非全部如此。物理内存和cpu的关系地址还可能被用于主板上各种设备间的通信这种通信方式叫做I/O。这类设备包括显卡大多数的PCI卡(比如扫描仪或SCSI卡),以及BIOS中的flash存储器等

   当北桥接收到一个物悝内存和cpu的关系访问请求时,它需要决定把这个请求转发到哪里:是发给RAM抑或是显卡?具体发给谁是由内存和cpu的关系地址映射表来决定嘚映射表知道每一个物理内存和cpu的关系地址区域所对应的设备。绝大部分的地址被映射到了RAM其余地址由映射表来通知芯片组该由哪个設备来响应此地址的访问请求。这些被映射为设备的内存和cpu的关系地址形成了一个经典的空洞位于PC内存和cpu的关系的640KB到1MB之间。当内存和cpu的關系地址被保留用于显卡和PCI设备时就会形成更大的空洞。这就是为什么32位的操作系统4GB RAMLinux中,/proc/iomem这个文件简明的列举了这些空洞的地址范围下图展示了Intel PC低端4GB物理内存和cpu的关系地址形成的一个典型的内存和cpu的关系映射:

Intel系统中,低端4GB内存和cpu的关系地址空间的布局

实际的地址囷范围依赖于特定的主板和电脑中接入的设备,但是对于大多数Core 2系统情形都跟上图非常接近。所有棕色的区域都被设备地址映射走了記住,这些在主板总线上使用的都是物理地址在CPU内部(比如我们正在编写和运行的程序),使用的是逻辑地址必须先由CPU翻译成物理地址以后,才能发布到总线上去访问内存和cpu的关系

这个把逻辑地址翻译成物理地址的规则比较复杂,而且还依赖于当时CPU的运行模式(实模式32位保护模式,64位保护模式)不管采用哪种翻译机制,CPU的运行模式决定了有多少物理内存和cpu的关系可以被访问比如,当CPU工作于32位保護模式时它只可以寻址4GB物理地址空间(当然,也有个例外叫做但暂且忽略这个技术吧)。由于顶部的大约1GB物理地址被映射到了主板上嘚设备CPU实际能够使用的也就只有大约3GB的RAM(有时甚至更少,我曾用过一台安装了Vista的电脑它只有2.4GB可用)。如果CPU工作于那么它将只能寻址1MB嘚物理地址空间(这是早期的Intel处理器所支持的唯一模式)。如果CPU工作于64位保护模式则可以寻址64GB的地址空间(虽然很少有芯片组支持这么夶的RAM)。处于64位保护模式时CPU就有可能访问到RAM空间中被主板上的设备映射走了的区域了(即访问空洞下的RAM)。要达到这种效果就需要使鼡比系统中所装载的RAM地址区域更高的地址。这种技术叫做回收(reclaiming)而且还需要芯片组的配合。

这些关于内存和cpu的关系的知识将为下一篇文章莋好铺垫下次我们会探讨机器的启动过程:从上电开始,直到boot loader准备跳转执行操作系统内核为止如果你想更深入的学习这些东西,我强烮推荐Intel手册虽然我列出的都是第一手资料,但Intel手册写得很好很准确这是一些资料:

转: 计算机的引导过程

    [注:本人水平有限,只好挑一些国外高手的精彩文章翻译一下一来自己复习,二来与大家分享] 

   前一篇文章介绍了Intel计算机的,从而为本文设定了一个系统引导阶段的場景引导(Booting)是一个复杂的,充满技巧的涉及多个阶段,又十分有趣的过程下图列出了此过程的概要:

当 你按下计算机的电源键后(现在别按!),机器就开始运转了一旦主板上电,它就会初始化自身的固件(firmware)——芯片组和其他零零碎碎的东西 ——并尝试启动CPU如果此时出了什么问题(比如CPU坏了或根本没装),那么很可能出现的情况是电脑没有任何动静除了风扇在转。一些主板会在CPU 故障或缺失时发絀鸣音提示但以我的经验,此时大多数机器都会处于僵死状态一些USB或其他设备也可能导致机器启动时僵死。对于那些以前工作正常突然 出现这种症状的电脑,一个可能的解决办法是拔除所有不必要的设备你也可以一次只断开一个设备,从而发现哪个是罪魁祸首

如果一切正常,CPU就开始运行了在一个多处理器或多核处理器的系统中,会有一个CPU被动态的指派为引导处理器(bootstrap processor简写BSP)用于执行全部的BIOS和內核初始化代码。其余的处理器此时被称为应用处理器(application processor简写AP),一直保持停机状态直到内核明确激活他们为止虽然Intel CPU经历了很多年的發展,但他们一直保持着完全的向后兼容性所以现代的CPU可以表现得跟原先1978年的Intel 8086完全一样。其实当CPU上电后,它就是这么做的在这个基夲的上电过程中,处理器工作于功能是无效的。此时的系统环境就像古老的MS-DOS一样,只有1MB内存和cpu的关系可以寻址任何代码都可以读写任何地址的内存和cpu的关系,这里没有保护或特权级的概念

CPU上电后,大部分寄存器的都具有定义良好的初始值包括指令指针寄存器(EIP),它记录了下一条即将被CPU执行的指令所在的内存和cpu的关系地址尽管此时的Intel CPU还只能寻址1MB的内存和cpu的关系,但凭借一个奇特的技巧一个隐藏的基地址(其实就是个偏移量)会与EIP相加,其结果指向第一条将被执行的指令所处的地址0xFFFFFFF0(长16字节在4GB内存和cpu的关系空间的尾部,远高於1MB)这个特殊的地址叫做(reset

主板保证在复位向量处的指令是一个跳转,而且是跳转到BIOS执行入口点所在的这个跳转会顺带清除那个隐藏的、上电时的基地址。感谢芯片组提供的内存和cpu的关系映射功能此时的内存和cpu的关系地址存放着CPU初始化所需的真正内容。这些内容全部是從包含有BIOS的闪存映射过来的而此时的RAM模块还只有随机的垃圾数据。下面的图例列出了相关的内存和cpu的关系区域:

随后CPU开始执行BIOS的代码,初始化机器中的一些硬件之后BIOS开始执行(POST),检测计算机中的各种组件如果找不到一个可用的显卡,POST就会失败导致BIOS进入停机状态並发出鸣音提示(因为此时无法在屏幕上输出提示信息)。如果显卡正常那么电脑看起来就真的运转起来了:显示一个制造商定制的商標,开始内存和cpu的关系自检天使们大声的吹响号角。另有一些POST失败的情况比如缺少键盘,会导致停机屏幕上显示出错信息。其实POST即昰检测又是初始化还要枚举出所有PCI设备的资源——中断,内存和cpu的关系范围I/O端口。现代的BIOS会遵循(ACPI)协议创建一些用于描述设备的數据表,这些表格将来会被操作系统内核用到

POST完毕后,BIOS就准备引导操作系统了它必须存在于某个地方:硬盘,光驱软盘等。BIOS搜索引導设备的实际顺序是用户可定制的如果找不到合适的引导设备,BIOS会显示出错信息并停机比如"Non-System Disk or Disk Error"没有系统盘或驱动器故障。一个坏了的硬盤可能导致此症状幸运的是,在这篇文章中BIOS成功的找到了一个可以正常引导的驱动器。

现在BIOS会读取硬盘的第一个(0扇区),内含512个芓节这些数据叫做(Master Boot Record简称MBR)。一般说来它包含两个极其重要的部分:一个是位于MBR开头的操作系统相关的引导程序,另一个是紧跟其后嘚磁盘分区表BIOS 丝毫不关心这些事情:它只是简单的加载MBR的内容到内存和cpu的关系地址0x7C00处,并跳转到此处开始执行不管MBR里的代码是什么。

這段在MBR内的特殊代码可能是Windows 引导装载程序Linux 引导装载程序(比如LILO或GRUB),甚至可能是病毒与此不同,分区表则是标准化的:它是一个64字节嘚区块包含4个16字节的记录项,描述磁盘是如何被分割的(所以你可以在一个磁盘上安装多个操作系统或拥有多个独立的卷)传统上,Microsoft嘚MBR代码会查看分区表找到一个(唯一的)标记为活动(active)的分区,加载那个分区的引导扇区(boot sector)并执行其中的代码。引导扇区是一个汾区的第一个扇区而不是整个磁盘的第一个扇区。如果此时出了什么问题你可能会收到如下错误信息:"Invalid Partition Table"无效分区表或"Missing Operating System"操作系统缺失。這条信息不是来自BIOS的而是由从磁盘加载的MBR程序所给出的。因此这些信息依赖于MBR的内容

随着时间的推移,引导装载过程已经发展得越来樾复杂越来越灵活。Linux的引导装载程序Lilo和GRUB可以处理很多种类的操作系统文件系统,以及引导配置信息他们的MBR代码不再需要效仿上述"从活动分区来引导"的方法。但是从功能上讲这个过程大致如下:

1、  MBR本身包含有第一阶段的引导装载程序。GRUB称之为阶段一

2、  由于MBR很小,其Φ的代码仅仅用于从磁盘加载另一个含有额外的引导代码的扇区此扇区可能是某个分区的引导扇区,但也可能是一个被硬编码到MBR中的扇區位置

3、  MBR配合第2步所加载的代码去读取一个文件,其中包含了下一阶段所需的引导程序这在GRUB中是"阶段二"引导程序,在Windows Server中是C:/NTLDR如果第2步夨败了,在Windows中你会收到错误信息比如"NTLDR is missing"NTLDR缺失。阶段二的代码进一步读取一个引导配置文件(比如在GRUB中是grub.conf在Windows中是boot.ini)。之后要么给用户显示┅些引导选项要么直接去引导系统。

4、  此时引导装载程序需要启动操作系统核心。它必须拥有足够的关于文件系统的信息以便从引導分区中读取内核。在Linux中这意味着读取一个名字类似"vmlinuz-2.6.22-14-server"的含有内核镜像的文件,将之加载到内存和cpu的关系并跳转去执行内核引导代码在Windows Server 2003Φ,一部份内核启动代码是与内核镜像本身分离的事实上是嵌入到了NTLDR当中。在完成一些初始化工作以后NTDLR从"c:/Windows/System32/ntoskrnl.exe"文件加载内核镜像,就像GRUB所莋的那样跳转到内核的入口点去执行。

这里还有一个复杂的地方值得一提(这也是我说引导富于技巧性的原因)当前Linux内核的镜像就算被压缩了,在实模式下也没法塞进640KB的可用RAM里。我的vanilla Ubuntu内核压缩后有1.7MB然而,引导装载程序必须运行于实模式以便调用BIOS代码去读取磁盘,所以此时内核肯定是没法用的解决之道是使用一种倍受推崇的""。它并非一个真正的处理器运行模式(希望Intel的工程师允许我以此作乐)洏是一个特殊技巧。程序不断的在实模式和保护模式之间切换以便访问高于1MB的内存和cpu的关系同时还能使用BIOS。如果你阅读了GRUB的源代码你僦会发现这些切换到处都是(看看stage2/目录下的程序,对real_to_prot 和 prot_to_real函数的调用)在这个棘手的过程结束时,装载程序终于千方百计的把整个内核都塞到内存和cpu的关系里了但在这后,处理器仍保持在实模式运行

至此,我们来到了从"引导装载"跳转到"早期的内核初始化"的时刻就像第┅张图中所指示的那样。在系统做完热身运动后内核会展开并让系统开始运转。下一篇文章将带大家一步步深入Linux内核的初始化过程读鍺还可以参考Linux Cross reference的资源。我没办法对Windows也这么做但我会把要点指出来。

   [注:本人水平有限只好挑一些国外高手的精彩文章翻译一下。一来洎己复习二来与大家分享。]

   上一篇文章解释了计算机的正好讲到引导装载程序把系统内核镜像塞进内存和cpu的关系,准备跳转到内核入ロ点去执行的时刻作为引导启动系列文章的最后一篇,就让我们深入内核去看看操作系统是怎么启动的吧。由于我习惯以事实为依据討论问题所以文中会出现大量的链接引用Linux 内核2.6.25.6版的源代码(源自Linux Reference)。如果你熟悉C的 语法这些代码就会非常容易读懂;即使你忽略一些細节,仍能大致明白程序都干了些什么最主要的障碍在于对一些代码的理解需要相关的背景知识,比如机器的 底层特性或什么时候、为什么它会运行我希望能尽量给读者提供一些背景知识。为了保持简洁许多有趣的东西,比如中断和内存和cpu的关系文中只能点到为止叻。在本文 的最后列出了Windows的引导过程的要点

   当Intel x86的引导程序运行到此刻时,处理器处于实模式(可以寻址1MB的内存和cpu的关系)(针对现代嘚Linux系统)RAM的内容大致如下:

引导装载完成后的RAM内容

   引导装载程序通过BIOS的磁盘I/O服务,已经把内核镜像加载到内存和cpu的关系当中这个镜像只昰硬盘中内核文件(比如/boot/vmlinuz-2.6.22-14-server)的一份完全相同的拷贝。镜像分为两个部分:一个较小的部分包含实模式的内核代码,被加载到640KB内存和cpu的关系边界以下;另一部分是一大块内核运行在保护模式,被加载到低端1MB内存和cpu的关系地址以上

header)。这段内存和cpu的关系区域用于实现引导裝载程序与内核之间的Linux引导协议 此处的一些数据会被引导装载程序读取。这些数据包括一些令人愉快的信息比如包含内核版本号的可讀字符串,也包括一些关键信息比如实模式内核代码的大 小。引导装载程序还会向这个区域写入数据比如用户选中的引导菜单项对应嘚命令行参数所在的内存和cpu的关系地址。之后就到了跳转到内核入口点的时刻下图显示了内核 初始化代码的执行顺序,包括源代码的目錄、文件和行号:

与体系结构相关的Linux内核初始化过程

对于Intel体系结构内核启动前期会执行arch/x86/boot/header.S文件中的程序。它是用汇编语言书写的一般说來汇编代码在内核中很少出现,但常见于引导代码这个文件的开头实际上包含了引导扇区代码。早期的Linux不需要引导装载程序就可以工作这段代码是从那个时候留传下来的。现今如果这个引导扇区被执行,它仅仅给用户输出一个"bugger_off_msg"之后就会重启系统现代的引导装载程序會忽略这段遗留代码。在引导扇区代码之后我们会看到实模式内核头部(kernel header)最开始的15字节;这两部分合起来是512字节,正好是Intel硬件平台上┅个典型的磁盘扇区的大小

   在这512字节之后,偏移量0x200处我们会发现Linux内核的第一条指令,也就是实模式内核的入口点具体的说,它在header.S:110昰一个2字节的跳转指令,直接写成了机器码的形式0x3AEB你可以通过对内核镜像运行hexdump,并查看偏移量0x200处的内容来验证这一点——这仅仅是一个對神志清醒程度的检查以确保这一切并不是在做梦。引导装载程序运行完毕时就会跳转执行这个位置的指令进而跳转到header.S:229执行一个普通嘚用汇编写成的子程序,叫做start_of_setup这个短小的子程序初始化栈空间(stack),把实模式内核的bss段清零(这个区域包含静态变量所以用0来初始化咜们),之后跳转执行一段又老又好的C语言程序:arch/x86/boot/main.c:122

   main()会处理一些登记工作(比如检测内存和cpu的关系布局),设置显示模式等然后它会调鼡go_to_protected_mode()。然而在把CPU置于保护模式之前,还有一些工作必须完成有两个主要问题:中断和内存和cpu的关系。在实模式中处理器的总是从内存囷cpu的关系的0地址开始的,然而在保护模式中这个中断向量表的位置是保存在一个叫IDTR的CPU寄存器当中的。与此同时从逻辑内存和cpu的关系地址(在程序中使用)到线性内存和cpu的关系地址(一个从0连续编号到内存和cpu的关系顶端的数值)的翻译方法在实模式和保护模式中是不同的。保护模式需要一个叫做GDTR的寄存器来存放内存和cpu的关系的地址所以go_to_protected_mode()调用了setup_idt() 和 ,用于装载临时的中断描述符表和全局描述符表

现在我们鈳以转入保护模式啦,这是由另一段汇编子程序protected_mode_jump来完成的这个子程序通过设定CPU的CR0寄存器的PE位来使能保护模式。此时功能还处于关闭状態;分页是处理器的一个可选的功能,即使运行于保护模式也并非必要真正重要的是,我们不再受制于640K的内存和cpu的关系边界现在可以尋址高达4GB的RAM了。这个子程序进而调用压缩状态内核的32位内核入口点startup_32startup32会做一些简单的寄存器初始化工作,并调用一个C语言编写的函数decompress_kernel()用於实际的解压缩工作。

Linux…"(正在解压缩Linux)解压缩过程是原地进行的,一旦完成内核镜像的解压缩第一张图中所示的压缩内核镜像就会被覆盖掉。因此解压后的内核也是从1MB位置开始的之后,decompress_kernel()会显示"done"(完成)和令人振奋的"Booting the kernel"(正在引导内核)这里"Booting"的意思是跳转到整个故事嘚最后一个入口点,也是保护模式内核的入口点位于RAM的第二个1MB开始处(偏移量0x100000,此值是由芬兰Halti山巅之上的神灵授意给Linus的)在这个神圣嘚位置含有一个子程序调用,名叫…呃…但你会发现这一位是在另一个目录中的。

这位startup_32的第二个化身也是一个汇编子程序但它包含了32位模式的初始化过程:

1、  它清理了保护模式内核的bss段。(这回是真正的内核了它会一直运行,直到机器重启或关机)

2、  为内存和cpu的关系建立最终的全局描述符表。

3、  建立页表以便可以开启分页功能

下图显示了引导最后一步的代码执行流程:

与体系结构无关的Linux内核初始囮过程

   start_kernel()看起来更像典型的内核代码,几乎全用C语言编写而且与特定机器无关这个函数调用了一长串的函数,用来初始化各个内核子系统囷数据结构包括调度器(scheduler),内存和cpu的关系分区(memory zones)计时器(time thread)。cpu_idle()会在0号进程(process zero)中永远的运行下去一旦有什么事情可做,比如有叻一个活动就绪的进程(runnable process)0号进程就会激活CPU去执行这个任务,直到没有活动就绪的进程后才返回

   但是,还有一个小麻烦需要处理我們跟随引导过程一路走下来,这个漫长的线程以一个空闲循环(idle loop)作为结尾处理器上电执行第一条跳转指令以后,一路运行最终会到達此处。从复位向量(reset vector)->BIOS->MBR->引导装载程序->实模式内核->保护模式内核跳转跳转再跳转,经过所有这些杂七杂八的步骤最后来到引导处理器(boot processor)中的空闲循环cpu_idle()。看起来真的很酷然而,这并非故事的全部否则计算机就不会工作。

   在这个时候前面启动的那个内核线程已经准備就绪,可以取代0号进程和它的空闲线程了事实也是如此,就发生在kernel_init()开始运行的时刻(此函数之前被作为线程的入口点)kernel_init()的职责是初始化系统中其余的CPU,这些CPU从引导过程开始到现在还一直处于停机状态。之前我们看过的所有代码都是在一个单独的CPU上运行的它叫做引導处理器(boot processor)——启动以后,它们是处于实模式的必须通过一些初始化步骤才能进入保护模式。大部分的代码过程都是相同的你可以參考startup_32,但对于应用处理器还是有些细微的不同。最终kernel_init()会调用init_post(),后者会尝试启动一个用户模式(user-mode)的进程尝试的顺序为:/sbin/init,/etc/init/bin/init,/bin/sh如果都不行,内核就会报错幸运的是init经常就在这些地方的,于是1号进程(PID 1)就开始运行了它会根据对应的配置文件来决定启动哪些进程,这可能包括X11 Windows控制台登陆程序,网络后台程序等从而结束了引导进程,同时另一个Linux程序开始在某处运行至此,让我祝福您的电脑可鉯一直正常运行下去不出毛病。

   在同样的体系结构下Windows的启动过程与Linux有很多相似之处。它也面临同样的问题也必须完成类似的初始化過程。当引导过程开始后一个最大的不同是,Windows把全部的实模式内核代码以及一部分初始的保护模式代码都打包到了引导加载程序(C:/NTLDR)當中因此,Windows使用的二进制镜像文件就不一样了内核镜像中没有包含两个部分的代码。另外Linux把引导装载程序与内核完全分离,在某种程度上自动的形成不同的开源项目下图显示了Windows内核主要的启动过程:

   本文是引导启动系列话题的最后一篇。感谢每一位读者感谢你们嘚反馈。我很抱歉有些内容只能点到为止;我打算把它们留在其他文章中深入讨论,并尽量保持文章的长度适合blog的风格下次我打算定期的撰写关于"Software Illustrated"的文章,就像本系列一样最后,给大家一些参考资料:

?         《Linux内核》是本好书其中讨论了大量的Linux内核代码。这书也许有点過时有点枯燥但我还是将它推荐给那些想要与内核心意相通的人们。《Linux设备驱动程序》读起来会有趣得多讲的也不错,但是涉及的内嫆有些局限性最后,网友Patrick Moroney推荐Robert Love所写的《Linux内核开发》我曾听过一些对此书的正面评价,所以还是值得列出来的

转: 内存和cpu的关系地址转換与分段

   [注:本人水平有限,只好挑一些国外高手的精彩文章翻译一下一来自己复习,二来与大家分享]

   本文是Intel兼容计算机(x86)的内存囷cpu的关系与保护系列文章的第一篇,延续了系列文章的主题进一步分析操作系统内核的工作流程。与以前一样我将引用Linux内核的源代码,但对Windows只给出示例(抱歉我忽略了BSD,Mac等系统但大部分的讨论对它们一样适用)。文中如果有错误请不吝赐教。

在支持Intel的上CPU对内存囷cpu的关系的访问是通过连接着CPU和北桥芯片的前端总线来完成的。在前端总线上传输的内存和cpu的关系地址都是物理内存和cpu的关系地址编号從0开始一直到可用物理内存和cpu的关系的最高端。这些数字被北桥映射到实际的内存和cpu的关系条上物理地址是明确的、最终用在总线上的編号,不必转换不必分页,也没有特权级检查然而,在CPU内部程序所使用的是逻辑内存和cpu的关系地址,它必须被转换成物理地址后財能用于实际内存和cpu的关系访问。从概念上讲地址转换的过程如下图所示:

x86 CPU开启分页功能后的内存和cpu的关系地址转换过程

此图并未指出詳实的转换方式,它仅仅描述了在CPU的分页功能开启的情况下内存和cpu的关系地址的转换过程如果CPU关闭了分页功能,或运行于16位实模式那麼从分段单元(segmentation unit)输出的就是最终的物理地址了。当CPU要执行一条引用了内存和cpu的关系地址的指令时转换过程就开始了。第一步是把逻辑哋址转换成线性地址但是,为什么不跳过这一步而让软件直接使用线性地址(或物理地址呢?)其理由与:"人类为何要长有阑尾它嘚主要作用仅仅是被感染发炎而已"大致相同。这是进化过程中产生的奇特构造要真正理解x86分段功能的设计,我们就必须回溯到1978年

最初嘚8086处理器的寄存器是16位的,其指令集大多使用8位或16位的操作数这使得代码可以控制216个字节(或64KB)的内存和cpu的关系。然而Intel的工程师们想要讓CPU可以使用更多的内存和cpu的关系而又不用扩展寄存器和指令的位宽。于是他们引入了段寄存器(segment register)用来告诉CPU一条程序指令将操作哪一個64K的内存和cpu的关系区块。一个合理的解决方案是:你先加载段寄存器相当于说"这儿!我打算操作开始于X处的内存和cpu的关系区块";之后,洅用16位的内存和cpu的关系地址来表示相对于那个内存和cpu的关系区块(或段)的偏移量总共有4个段寄存器:一个用于栈(ss),一个用于程序玳码(cs)两个用于数据(ds,es)在那个年代,大部分程序的栈、代码、数据都可以塞进对应的段中每段64KB长,所以分段功能经常是透明嘚

   现今,分段功能依然存在一直被x86处理器所使用着。每一条会访问内存和cpu的关系的指令都隐式的使用了段寄存器比如,一条跳转指囹会用到代码段寄存器(cs)一条压栈指令(stack push instruction)会使用到堆栈段寄存器(ss)。在大部分情况下你可以使用指令明确的改写段寄存器的值段寄存器存储了一个16位的段选择符(segment selector);它们可以经由机器指令(比如MOV)被直接加载。唯一的例外是代码段寄存器(cs)它只能被影响程序执行顺序的指令所改变,比如CALL或JMP指令虽然分段功能一直是开启的,但其在实模式与保护模式下的运作方式并不相同的

   在实模式下,仳如在段选择符是一个16位的数值,指示出一个段的开始处的物理内存和cpu的关系地址这个数值必须被以某种方式放大,否则它也会受限於64K当中分段就没有意义了。比如CPU可能会把这个段选择符当作物理内存和cpu的关系地址的高16位(只需将之左移16位,也就是乘以216)这个简單的规则使得:可以按64K的段为单位,一块块的将4GB的内存和cpu的关系都寻址到遗憾的是,Intel做了一个很诡异的设计让段选择符仅仅乘以24(或16),一举将寻址范围限制在了1MB还引入了过度复杂的转换过程。下述图例显示了一条跳转指令cs的值是0x1000:

实模式的段地址以16个字节为步长,从0开始编号一直到0xFFFF0(即1MB)你可以将一个从0到0xFFFF的16位偏移量(逻辑地址)加在段地址上。在这个下对于同一个内存和cpu的关系地址,会有哆个段地址/偏移量的组合与之对应而且物理地址可以超过1MB的边界,只要你的段地址足够高(参见臭名昭著的A20线)同样的,在实模式的C語言代码中一个(far pointer)既包含了段选择符又包含了逻辑地址,用于寻址1MB的内存和cpu的关系范围真够"远"的啊。随着程序变得越来越大超出叻64K的段,分段功能以及它古怪的处理方式使得x86平台的软件开发变得非常复杂。这种设定可能听起来有些诡异但它却把当时的程序员推進了令人崩溃的深渊。

在32位保护模式下段选择符不再是一个单纯的数值,取而代之的是一个索引编号用于引用段描述符表中的表项。這个表为一个简单的数组元素长度为8字节,每个元素描述一个段看起来如下:

有三种类型的段:代码,数据系统。为了简洁明了呮有描述符的共有特征被绘制出来。基地址(base address)是一个32位的线性地址指向段的开始;段界限(limit)指出这个段有多大。将基地址加到逻辑哋址上就形成了线性地址DPL是描述符的特权级(privilege level),其值从0(最高特权内核模式)到3(最低特权,用户模式)用于控制对段的访问。

這些段描述符被保存在两个表中:全局描述符表(GDT)和局部描述符表(LDT)电脑中的每一个CPU(或一个处理核心)都含有一个叫做gdtr的寄存器,用于保存GDT的首个字节所在的线性内存和cpu的关系地址为了选出一个段,你必须向段寄存器加载符合以下格式的段选择符:

对GDTTI位为0;对LDT,TI位为1;index指出想要表中哪一个段描述符(译注:原文是段选择符应该是笔误)。对于RPL请求特权级(Requested Privilege Level),以后我们还会详细讨论现在,需要好好想想了当CPU运行于32位模式时,不管怎样寄存器和指令都可以寻址整个线性地址空间,所以根本就不需要再去使用基地址或其怹什么鬼东西那为什么不干脆将基地址设成0,好让逻辑地址与线性地址一致呢Intel的文档将之称为"扁平模型"(flat model),而且在现代的x86系统内核Φ就是这么做的(特别指出它们使用的是基本扁平模型)。基本扁平模型(basic flat model)等价于在转换地址时关闭了分段功能如此一来多么美好啊。就让我们来看看32位保护模式下执行一个跳转指令的例子其中的数值来自一个实际的Linux用户模式应用程序:

段描述符的内容一旦被访问,就会被cache(缓存)所以在随后的访问中,就不再需要去实际读取GDT了否则会有损性能。每个段寄存器都有一个隐藏部分用于缓存段选择苻所对应的那个段描述符如果你想了解更多细节,包括关于LDT的更多信息请参阅《Intel System Programming Guide》3A卷的第三章。2A和2B卷讲述了每一个x86指令同时也指明叻x86寻址时所使用的各种类型的操作数:16位,16位加段描述符(可被用于实现远指针)32位,等等

在Linux上,只有3个段描述符在引导启动过程被使用他们使用GDT_ENTRY宏来定义并存储在boot_gdt数组中。其中两个段是扁平的可对整个32位空间寻址:一个是代码段,加载到cs中一个是数据段,加载箌其他段寄存器中第三个段是系统段,称为任务状态段(Task State Segment)在完成引导启动以后,每一个CPU都拥有一份属于自己的GDT其中大部分内容是楿同的,只有少数表项依赖于正在运行的进程你可以从segment.h看到Linux GDT的布局以及其。这里有4个主要的GDT表项:2个是扁平的用于内核模式的代码和數据,另两个用于用户模式在看这个Linux GDT时,请留意那些用于确保数据与CPU缓存线对齐的填充字节——目的是克服最后要说说,那个经典的Unix錯误信息"Segmentation fault"(分段错误)并不是由x86风格的段所引起的而是由于分页单元检测到了非法的内存和cpu的关系地址。唉呀下次再讨论这个话题吧。

Intel巧妙的绕过了他们原先设计的那个拼拼凑凑的分段方法而是提供了一种富于弹性的方式来让我们选择是使用段还是使用扁平模型。由於很容易将逻辑地址与线性地址合二为一于是这成为了标准,比如现在在64位模式中就强制使用扁平的线性地址空间了但是即使是在扁岼模型中,段对于x86的保护机制也十分重要保护机制用于抵御用户模式进程对系统内核的非法内存和cpu的关系访问,或各个进程之间的非法內存和cpu的关系访问否则系统将会进入一个狗咬狗的世界!在下一篇文章中,我们将窥视保护级别以及如何用段来实现这些保护功能

转: CPU嘚运行环, 特权级与保护

   [注:本人水平有限,只好挑一些国外高手的精彩文章翻译一下一来自己复习,二来与大家分享]

   可能你凭借直觉僦知道应用程序的功能受到了Intel x86计算机的某种限制,有些特定的任务只有操作系统的代码才可以完成但是你知道这到底是怎么一回事吗?茬这篇文章里我们会接触到x86的特权级(privilege level),看看操作系统和CPU是怎么一起合谋来限制用户模式的应用程序的特权级总共有4个,编号从0(朂高特权)到3(最低特权)有3种主要的资源受到保护:内存和cpu的关系,I/O端口以及执行特殊机器指令的能力在任一时刻,x86 CPU都是在一个特萣的特权级下运行的从而决定了代码可以做什么,不可以做什么这些特权级经常被描述为保护环(protection ring),最内的环对应于最高特权即使是最新的x86内核也只用到其中的2个特权级:0和3。

   在诸多机器指令中只有大约15条指令被CPU限制只能在ring 0执行(其余那么多指令的操作数都受到┅定的限制)。这些指令如果被用户模式的程序所使用就会颠覆保护机制或引起混乱,所以它们被保留给内核使用如果企图在ring 0以外运荇这些指令,就会导致一个一般保护错(general-protection exception)就像一个程序使用了非法的内存和cpu的关系地址一样。类似的对内存和cpu的关系和I/O端口的访问吔受特权级的限制。但是在我们分析保护机制之前,先让我们看看CPU是怎么记录当前特权级的吧这与前篇文章中提到的(segment selector)有关。如下所示:

数据段和代码段的段选择符

数据段选择符的整个内容可由程序直接加载到各个段寄存器当中比如ss(堆栈段寄存器)和ds(数据段寄存器)。这些内容里包含了请求特权级(Requested Privilege Level简称RPL)字段,其含义过会儿再说然而,代码段寄存器(cs)就比较特别了首先,它的内容不能由装载指令(如MOV)直接设置而只能被那些会改变程序执行顺序的指令(如CALL)间接的设置。而且不像那个可以被代码设置的RPL字段,cs拥囿一个由CPU自己维护的当前特权级字段(Current Privilege Level简称CPL),这点对我们来说非常重要这个代码段寄存器中的2位宽的CPL字段的值总是等于CPU的当前特权級。Intel的文档并未明确指出此事实而且有时在线文档也对此含糊其辞,但这的确是个硬性规定在任何时候,不管CPU内部正在发生什么只偠看一眼cs中的CPL,你就可以知道此刻的特权级了

记住,CPU特权级并不会对操作系统的用户造成什么影响不管你是根用户,管理员访客还昰一般用户。所有的用户代码都在ring 3上执行所有的内核代码都在ring 0上执行,跟是以哪个OS用户的身份执行无关有时一些内核任务可以被放到鼡户模式中执行,比如Windows Vista上的用户模式驱动程序但是它们只是替内核执行任务的特殊进程而已,而且往往可以被直接删除而不会引起严重後果

由于限制了对内存和cpu的关系和I/O端口的访问,用户模式代码在不调用系统内核的情况下几乎不能与外部世界交互。它不能打开文件发送网络数据包,向屏幕打印信息或分配内存和cpu的关系用户模式进程的执行被严格限制在一个由ring 0之神所设定的沙盘之中。这就是为什麼从设计上就决定了:一个进程所泄漏的内存和cpu的关系会在进程结束后被统统回收之前打开的文件也会被自动关闭。所有的控制着内存囷cpu的关系或 打开的文件等的数据结构全都不能被用户代码直接使用;一旦进程结束了这个沙盘就会被内核拆毁。这就是为什么我们的服務器只要硬件和内核不出毛病就可以 连续正常运行600天,甚至一直运行下去这也解释了为什么Windows 95/98那么容易死机:这并非因为微软差劲,而昰因为系统中的一些重要数据结构出于兼容的目的被设计成可以由用户直接访问了。这在当时可能是一个很好的折中当然代价也很大。

CPU会在两个关键点上保护内存和cpu的关系:当一个段选择符被加载时以及,当通过线形地址访问一个内存和cpu的关系页时因此,保护也反映在的过程之中既包括分段又包括分页。当一个数据段选择符被加载时就会发生下述的检测过程:

因为越高的数值代表越低的特权,仩图中的MAX()用于挑出CPL和RPL中特权最低的一个并与描述符特权级(descriptor privilege level,简称DPL)比较如果DPL的值大于等于它,那么这个访问就获得许可了RPL背后的設计思想是:允许内核代码加载特权较低的段。比如你可以使用RPL=3的段描述符来确保给定的操作所使用的段可以在用户模式中访问。但堆棧段寄存器是个例外它要求CPL,RPL和DPL这3个值必须完全一致才可以被加载。

事实上段保护功能几乎没什么用,因为现代的内核使用扁平的哋址空间在那里,用户模式的段可以访问整个线形地址空间真正有用的内存和cpu的关系保护发生在分页单元中,即从线形地址转化为物悝地址的时候一个内存和cpu的关系页就是由一个页表项(page table entry)所描述的字节块。页表项包含两个与保护有关的字段:一个超级用户标志(supervisor flag)一个读写标志(read/write flag)。超级用户标志是内核所使用的重要的x86内存和cpu的关系保护机制当它开启时,内存和cpu的关系页就不能被ring 3访问了尽管讀写标志对于实施特权控制并不像前者那么重要,但它依然十分有用当一个进程被加载后,那些存储了二进制镜像(即代码)的内存和cpu嘚关系页就被标记为只读了从而可以捕获一些指针错误,比如程序企图通过此指针来写这些内存和cpu的关系页这个标志还被用于在调用fork創建Unix子进程时,实现写时拷贝功能(copy on write)

   最后,我们需要一种方式来让CPU切换它的特权级如果ring 3的程序可以随意的将控制转移到(即跳转到)内核的任意位置,那么一个错误的跳转就会轻易的把操作系统毁掉了但控制的转移是必须的。这项工作是通过门描述符(gate descriptor)调用门提供了一个可以用于通常的CALL和JMP指令的内核入口点,但是由于调用门用得不多我就忽略不提了。任务门也不怎么热门(在Linux上它们只在处悝内核或硬件问题引起的双重故障时才被用到)。

   剩下两个有趣的:中断门和陷阱门它们用来处理硬件中断(如键盘,计时器磁盘)囷异常(如缺页异常,0除数异常)我将不再区分中断和异常,在文中统一用"中断"一词表示这些门描述符被存储在中断描述符表(Interrupt Descriptor Table,简稱IDT)当中每一个中断都被赋予一个从0到255的编号,叫做中断向量处理器把中断向量作为IDT表项的索引,用来指出当中断发生时使用哪一个門描述符来处理中断中断门和陷阱门几乎是一样的。下图给出了它们的格式以及当中断发生时实施特权检查的过程。我在其中填入了┅些Linux内核的典型数值以便让事情更加清晰具体。

伴随特权检查的中断描述符

   门中的DPL和段选择符一起控制着访问同时,段选择符结合偏迻量(Offset)指出了中断处理代码的入口点内核一般在门描述符中填入内核代码段的段选择符。一个中断永远不会将控制从高特权环转向低特权环特权级必须要么保持不变(当内核自己被中断的时候),或被提升(当用户模式的代码被中断的时候)无论哪一种情况,作为結果的CPL必须等于目的代码段的DPL如果CPL发生了改变,一个堆栈切换操作就会发生如果中断是被程序中的指令所触发的(比如INT n),还会增加┅个额外的检查:门的DPL必须具有与CPL相同或更低的特权这就防止了用户代码随意触发中断。如果这些检查失败正如你所猜测的,会产生┅个一般保护错(general-protection exception)所有的Linux中断处理器都以ring 0特权退出。

3"system gate"是Intel的陷阱门,也可以从用户模式访问除此之外,术语名词都与本文对得上号然而,硬件中断门并不是在这里设置的而是由适当的驱动程序来完成。

   有三个门可以被用户模式访问:中断向量3和4分别用于调试和检查数值运算溢出剩下的是一个系统门,被设置为SYSCALL_VECTOR对于x86体系结构,它等于0x80它曾被作为一种机制,用于将进程的控制转移到内核进行┅个系统调用(system call),然后再跳转回来在那个时代,我需要去申请"INT 0x80"这个没用的牌照 J从奔腾Pro开始,引入了sysenter指令从此可以用这种更快捷的方式来启动系统调用了。它依赖于CPU上的特殊目的寄存器这些寄存器存储着代码段、入口点及内核系统调用处理器所需的其他零散信息。茬sysenter执行后CPU不再进行特权检查,而是直接进入CPL

   最后当需要跳转回ring 3时,内核发出一个iretsysexit指令分别用于从中断和系统调用中返回,从而离開ring 0并恢复CPL=3的用户代码的执行噢!Vim提示我已经接近1,900字了,所以I/O端口的保护只能下次再谈了这样我们就结束了x86的运行环与保护之旅。感谢您的耐心阅读

转: Cache: 一个隐藏并保存数据的场所

   [注:本人水平有限,只好挑一些国外高手的精彩文章翻译一下一来自己复习,二来与大家汾享]

   本文简要的展示了现代Intel处理器的CPU cache是如何组织的。有关cache的讨论往往缺乏具体的实例使得一些简单的概念变得扑朔迷离。也许是我可愛的小脑瓜有点迟钝吧但不管怎样,至少下面讲述了故事的前一半即Core 2的 L1 cache是如何被访问的:

   在cache中的数据是以缓存线(line)为单位组织的,┅条缓存线对应于内存和cpu的关系中一个连续的字节块这个cache使用了64字节的缓存线。这些线被保存在cache bank中也叫(way)。每一路都有一个专门嘚目录(directory)用来保存一些登记信息你可以把每一路连同它的目录想象成电子表格中的一列,而表的一行构成了cache的一(set)列中的每一個单元(cell)都含有一条缓存线,由与之对应的目录单元跟踪管理图中的cache有64 组、每组8路,因此有512个含有缓存线的单元合计32KB的存储空间。

   茬cache眼中物理内存和cpu的关系被分割成了许多4KB大小的物理内存和cpu的关系页(page)。每一页都含有4KB / 64 bytes == 64条缓存线在一个4KB的页中,第0到63字节是第一条緩存线第64到127字节是第二条缓存线,以此类推每一页都重复着这种划分,所以第0页第3条缓存线与第1页第3条缓存线是不同的

   在全相联缓存(fully associative cache)中,内存和cpu的关系中的任意一条缓存线都可以被存储到任意的缓存单元中这种存储方式十分灵活,但也使得要访问它们时检索緩存单元的工作变得复杂、昂贵。由于L1和L2 cache工作在很强的约束之下包括功耗,芯片物理空间存取速度等,所以在多数情况下使用全相聯缓存并不是一个很好的折中。

cache)意思是,内存和cpu的关系中一条给定的缓存线只能被保存在一个特定的组(或行)中所以,任意物理內存和cpu的关系页的第0条缓存线(页内第0到63字节)必须存储到第0组第1条缓存线存储到第1组,以此类推每一组有8个单元可用于存储它所关聯的缓存线(译注:就是那些需要存储到这一组的缓存线),从而形成一个8路关联的组(8-way associative set)当访问一个内存和cpu的关系地址时,地址的第6箌11位(译注:组索引)指出了在4KB内存和cpu的关系页中缓存线的编号从而决定了即将使用的缓存组。举例来说物理地址0x的组索引是000010,所以此地址的内容一定是在第2组中缓存的

   但是还有一个问题,就是要找出一组中哪个单元包含了想要的信息如果有的话。这就到了缓存目錄登场的时刻每一个缓存线都被其对应的目录单元做了标记(tag);这个标记就是一个简单的内存和cpu的关系页编号,指出缓存线来自于哪┅页由于处理器可以寻址64GB的物理RAM,所以总共有64GB / 4KB == 224个内存和cpu的关系页需要24位来保存标记。前例中的物理地址0x对应的页号为524,289下面是故事的後一半:

   由于我们只需要去查看某一组中的8路,所以查找匹配标记是非常迅速的;事实上从电学角度讲,所有的标记是同时进行比对的我用箭头来表示这一点。如果此时正好有一条具有匹配标签的有效缓存线我们就获得一次缓存命中(cache hit)。否则这个请求就会被转发嘚L2 cache,如果还没匹配上就再转发给主系统内存和cpu的关系通过应用各种调节尺寸和容量的技术,Intel给CPU配置了较大的L2 cache但其基本的设计都是相同嘚。比如你可以将原先的缓存增加8路而获得一个64KB的缓存;再将组数增加到4096,每路可以存储256KB经过这两次修改,就得到了一个4MB的L2 cache在此情況下,需要18位来保存标记12位保存组索引;缓存所使用的物理内存和cpu的关系页的大小与其一路的大小相等。(译注:有4096组就需要lg(4096)==12位的组索引,缓存线依然是64字节所以一路有4096*64B==256KB字节;在L2 cache眼中,内存和cpu的关系被分割为许多256KB的块所以需要lg(64GB/256KB)==18位来保存标记。)

   如果有一组已经被放滿了那么在另一条缓存线被存储进来之前,已有的某一条则必须被腾空(evict)为了避免这种情况,对运算速度要求较高的程序就要尝试仔细组织它的数据使得内存和cpu的关系访问均匀的分布在已有的缓存线上。举例来说假设程序中有一个数组,元素的大小是512字节其中┅些对象在内存和cpu的关系中相距4KB。这些对象的各个字段都落在同一缓存线上并竞争同一缓存组。如果程序频繁的访问一个给定的字段(仳如通过vtable调用虚函数),那么这个组看起来就好像一直是被填满的缓存开始变得毫无意义,因为缓存线一直在重复着腾空与重新载入嘚步骤在我们的例子中,由于组数的限制L1 cache仅能保存8个这类对象的虚函数表。这就是组相联策略的折中所付出的代价:即使在整体缓存嘚使用率并不高的情况下由于组冲突,我们还是会遇到缓存缺失的情况然而,鉴于计算机中各个存储层次的不管怎么说,大部分的應用程序并不必为此而担心

一个内存和cpu的关系访问经常由一个线性(或虚拟)地址发起,所以L1 cache需要依赖分页单元(paging unit)来求出物理内存和cpu嘚关系页的地址以便用于缓存标记。与此相反组索引来自于线性地址的低位,所以不需要转换就可以使用了(在我们的例子中为第6到11位)因此L1 cache是物理标记但虚拟索引的(physically tagged but virtually indexed),从而帮助CPU进行并行的查找操作因为L1 cache的一路绝不会比MMU的一页还大,所以可以保证一个给定的物悝地址位置总是关联到同一组即使组索引是虚拟的。在另一方面L2 cache必须是物理标记和物理索引的因为它的一路比MMU的一页要大。但是当┅个请求到达L2 cache时,物理地址已经被L1 cache准备(resolved)完毕了所以L2 cache会工作得很好。

最后目录单元还存储了对应缓存线的状态(state)。在L1代码缓存中嘚一条缓存线要么是无效的(invalid)要么是共享的(shared意思是有效的,真的J)在L1数据缓存和L2缓存中,一条缓存线可以为4个MESI状态之一:被修改嘚(modified)独占的(exclusive),共享的(shared)无效的(invalid)。Intel缓存是包容式的(inclusive):L1缓存的内容会被复制到L2缓存中在下一篇讨论线程(threading),锁定(locking)等内容的文章中这些缓存线状态将发挥作用。下一次我们将看看前端总线以及内存和cpu的关系访问到底是怎么工作的。这将成为一个內存和cpu的关系研讨周

(在回复中Dave提到了直接映射缓存(direct-mapped cache)。它们基本上是一种特殊的组相联缓存只是只有一路而已。在各种折中方案Φ它与全相联缓存正好相反:访问非常快捷,但因组冲突而导致的缓存缺失也非常多)

1.         内存和cpu的关系层次结构的意义在于利用引用的涳间局部性和时间局部性原理,将经常被访问的数据放到快速的存储器中而将不经常访问的数据留在较慢的存储器中。

2.         一般情况下除叻寄存器和L1缓存可以操作指定字长的数据,下层的内存和cpu的关系子系统就不会再使用这么小的单位了而是直接移动数据块,比如以缓存線为单位访问数据

a)         假如它是直接映射缓存,由于它往往使用地址的低位直接映射缓存线编号所以所有的32K倍数的地址(32K,64K96K等)都会映射到同一条线上(即第0线)。假如程序的内存和cpu的关系组织不当交替的去访问布置在这些地址的数据,则会导致冲突从外表看来就好潒缓存只有1条线了,尽管其他缓存线一直是空闲着的

b)        如果是全相联缓存,那么每条缓存线都是独立的可以对应于内存和cpu的关系中的任意缓存线。只有当所有的512条缓存线都被占满后才会出现冲突

c)        组相联是前两者的折中,每一路中的缓存线采用直接映射方式而在路与路の间,缓存控制器使用全相联映射算法决定选择一组中的哪一条线。

d)        如果是2路组相联缓存那么这512条缓存线就被分为了2路,每路256条线┅路16KB。此时所有为16K整数倍的地址(16K32K,48K等)都会映射到第0线但由于2路是关联的,所以可以同时有2个这种地址的内容被缓存不会发生冲突。当然了如果要访问第三个这种地址,还是要先腾空已有的一条才行所以极端情况下,从外表看来就好像缓存只有2条线了尽管其怹缓存线一直是空闲着的。

e)         如果是8路组相联缓存(与文中示例相同)那么这512条缓存线就被分为了8路,每路64条线一路4KB。所以如果数组中え素地址是4K对齐的并且程序交替的访问这些元素,就会出现组冲突从外表看来就好像缓存只有8条线了,尽管其他缓存线一直是空闲着嘚

转: 剖析程序的内存和cpu的关系布局

   [注:本人水平有限,只好挑一些国外高手的精彩文章翻译一下一来自己复习,二来与大家分享]

       内存和cpu的关系管理模块是操作系统的心脏;它对应用程序和系统管理非常重要。今后的几篇文章中我将着眼于实际的内存和cpu的关系问题,泹也不避讳其中的技术内幕由于不少概念是通用的,所以文中大部分例子取自32位x86平台的Linux和Windows系统本系列第一篇文章讲述应用程序的内存囷cpu的关系布局。

space)在32位模式下它总是一个4GB的内存和cpu的关系地址块。这些虚拟地址通过页表(page table)映射到物理内存和cpu的关系页表由操作系統维护并被处理器引用。每一个进程拥有一套属于它自己的页表但是还有一个隐情。只要虚拟地址被使能那么它就会作用于这台机器仩运行的所有软件,包括内核本身因此一部分虚拟地址必须保留给内核使用:

    这并不意味着内核使用了那么多的物理内存和cpu的关系,仅表示它可支配这么大的地址空间可根据内核需要,将其映射到物理内存和cpu的关系内核空间在页表中拥有较高的(ring 2或以下),因此只要鼡户态的程序试图访问这些页就会导致一个页错误(page fault)。在Linux中内核空间是持续存在的,并且在所有进程中都映射到同样的物理内存和cpu嘚关系内核代码和数据总是可寻址的,随时准备处理中断和系统调用与此相反,用户模式地址空间的映射随进程切换的发生而不断变囮:

    蓝色区域表示映射到物理内存和cpu的关系的虚拟地址而白色区域表示未映射的部分。在上面的例子中Firefox使用了相当多的虚拟地址空间,因为它是传说中的吃内存和cpu的关系大户地址空间中的各个条带对应于不同的内存和cpu的关系段(memory segment),如:堆、栈之类的记住,这些段呮是简单的内存和cpu的关系地址范围与Intel处理器的段没有关系。不管怎样下面是一个Linux进程的标准的内存和cpu的关系段布局:

    当计算机开心、咹全、可爱、正常的运转时,几乎每一个进程的各个段的起始虚拟地址都与上图完全一致这也给远程发掘程序安全漏洞打开了方便之门。一个发掘过程往往需要引用绝对内存和cpu的关系地址:栈地址库函数地址等。远程攻击者必须依赖地址空间布局的一致性摸索着选择這些地址。如果让他们猜个正着有人就会被整了。因此地址空间的随机排布方式逐渐流行起来。Linux通过对、、的起始地址加上随机的偏迻量来打乱布局不幸的是,32位地址空间相当紧凑给随机化所留下的空当不大,

    进程地址空间中最顶部的段是栈,大多数编程语言将の用于存储局部变量和函数参数调用一个方法或函数会将一个新的栈桢(stack frame)压入栈中。栈桢在函数返回时被清理也许是因为数据严格嘚遵从LIFO的顺序,这个简单的设计意味着不必使用复杂的数据结构来追踪栈的内容只需要一个简单的指针指向栈的顶端即可。因此压栈(pushing)和退栈(popping)过程非常迅速、准确另外,持续的重用栈空间有助于使活跃的栈内存和cpu的关系保持在CPU缓存中从而加速访问。进程中的每┅个线程都有属于自己的栈

fault),并被Linux的expand_stack()处理它会调用acct_stack_growth()来检查是否还有合适的地方用于栈的增长。如果栈的大小低于RLIMIT_STACK(通常是8MB)那么┅般情况下栈会被加长,程序继续愉快的运行感觉不到发生了什么事情。这是一种将栈扩展至所需大小的常规机制然而,如果达到了朂大的栈空间大小就会栈溢出(stack overflow),程序收到一个段错误(Segmentation Fault)当映射了的栈区域扩展到所需的大小后,它就不会再收缩回去即使栈鈈那么满了。这就好比联邦预算它总是在增长的。

       动态栈增长是唯一一种访问未映射内存和cpu的关系区域(图中白色区域)而被允许的情形其它任何对未映射内存和cpu的关系区域的访问都会触发页故障,从而导致段错误一些被映射的区域是只读的,因此企图写这些区域也會导致段错误

       在栈的下方,是我们的内存和cpu的关系映射段此处,内核将文件的内容直接映射到内存和cpu的关系任何应用程序都可以通過Linux的mmap()系统调用()或Windows的CreateFileMapping() / 请求这种映射。内存和cpu的关系映射是一种方便高效的文件I/O方式所以它被用于加载动态库。创建一个不对应于任何攵件的匿名内存和cpu的关系映射也是可能的此方法用于存放程序的数据。在Linux中如果你通过malloc()请求一大块内存和cpu的关系,C运行库将会创建这樣一个匿名映射而不是使用堆内存和cpu的关系'大块'意味着比MMAP_THRESHOLD还大,缺省是128KB可以通过mallopt()调整。

       说到堆它是接下来的一块地址空间。与栈一樣堆用于运行时内存和cpu的关系分配;但不同点是,堆用于存储那些生存期与函数调用无关的数据大部分语言都提供了堆管理功能。因此满足内存和cpu的关系请求就成了语言运行时库及内核共同的任务。在C语言中堆分配的接口是malloc()系列函数,而在具有垃圾收集功能的语言(如C#)中此接口是new关键字。

       如果堆中有足够的空间来满足内存和cpu的关系请求它就可以被语言运行时库处理而不需要内核参与。否则堆会被扩大,通过brk()系统调用()来分配请求所需的内存和cpu的关系块堆管理是很的,需要精细的算法应付我们程序中杂乱的分配模式,優化速度和内存和cpu的关系使用效率处理一个堆请求所需的时间会大幅度的变动。实时系统通过来解决这个问题堆也可能会变得零零碎誶,如下图所示:

    最后我们来看看最底部的内存和cpu的关系段:BSS,数据段代码段。在C语言中BSS和数据段保存的都是静态(全局)变量的內容。区别在于BSS保存的是未被初始化的静态变量内容它们的值不是直接在程序的源代码中设定的。BSS内存和cpu的关系区域是匿名的:它不映射到任何文件如果你写static int

    另一方面,数据段保存在源代码中已经初始化了的静态变量内容这个内存和cpu的关系区域不是匿名的。它映射了┅部分的程序二进制镜像也就是源代码中指定了初始值的静态变量。所以如果你写static int cntWorkerBees = 10,则cntWorkerBees的内容就保存在数据段中了而且初始值为10。盡管数据段映射了一个文件但它是一个私有内存和cpu的关系映射,这意味着更改此处的内存和cpu的关系不会影响到被映射的文件也必须如此,否则给全局变量赋值将会改动你硬盘上的二进制镜像这是不可想象的。

    下图中数据段的例子更加复杂因为它用了一个指针。在此凊况下指针gonzo(4字节内存和cpu的关系地址)本身的值保 存在数据段中。而它所指向的实际字符串则不在这里这个字符串保存在代码段中,玳码段是只读的保存了你全部的代码外加零零碎碎的东西,比如字符串字面 值代码段将你的二进制文件也映射到了内存和cpu的关系中,泹对此区域的写操作都会使你的程序收到段错误这有助于防范指针错误,虽然不像在C语言编程时就注意防范来得那么有效下图展示了這些段以及我们例子中的变量:

    你可以通过阅读文件/proc/pid_of_process/maps来检验一个Linux进程中的内存和cpu的关系区域。记住一个段可能包含许多区域比如,每个內存和cpu的关系映射文件在mmap段中都有属于自己的区域动态库拥有类似BSS和数据段的额外区域。下一篇文章讲说明这些"区域"(area)的真正含义囿时人们提到"数据段",指的就是全部的数据段 +

    你可以通过nm和objdump命令来察看二进制镜像打印其中的符号,它们的地址段等信息。最后需要指出的是前文描述的虚拟地址布局在Linux中是一种"灵活布局"(flexible layout),而且以此作为默认方式已经有些年头了它假设我们有值RLIMIT_STACK。当情况不是这樣时Linux退回使用"经典布局"(classic layout),如下图所示:

       对虚拟地址空间的布局就讲这些吧下一篇文章将讨论内核是如何跟踪这些内存和cpu的关系区域的。我们会分析内存和cpu的关系映射看看文件的读写操作是如何与之关联的,以及内存和cpu的关系使用概况的含义

转: 内核是如何管理内存和cpu的关系的

   [注:本人水平有限,只好挑一些国外高手的精彩文章翻译一下一来自己复习,二来与大家分享]

descriptor),即mm_struct一个程序的内存囷cpu的关系的执行期摘要。它存储了上图所示的内存和cpu的关系段的起止位置进程所使用的物理内存和cpu的关系页的(rss表示Resident Set Size),虚拟内存和cpu的關系空间的以及其他信息。我们还可以在内存和cpu的关系描述符中找到用于管理程序内存和cpu的关系的两个重要结构:虚拟内存和cpu的关系区域集合(the set of

    每一个虚拟内存和cpu的关系区域(简称VMA)是一个连续的虚拟地址范围;这些区域不会交叠一个vm_area_struct的实例完备的描述了一个内存和cpu的關系区域,包括它的起止地址决定访问权限和行为的,还有vm_file字段用于指出被映射的文件(如果有的话)。一个VMA如果没有映射到文件則是匿名的(anonymous)。除memory mapping 段以外上图中的每一个内存和cpu的关系段(如:堆,栈)都对应于一个单独的VMA这并不是强制要求,但在x86机器上经常洳此VMA并不关心它在哪一个段。

       一个程序的VMA同时以两种形式存储在它的内存和cpu的关系描述符中:一个是按起始虚拟地址排列的链表保存茬mmap字段;另一个是,根节点保存在mm_rb字段红黑树使得内核可以快速的查找出给定虚拟地址所属的内存和cpu的关系区域。当你读取文件/proc/pid_of_process/maps时内核只须简单的遍历指定进程的VMA链表,并来即可

4GB虚拟地址空间被分割为许多(page)。x86处理器在32位模式下所支持的页面大小为4KB2MB和4MB。Linux和Windows都使鼡4KB大小的页面来映射用户部分的虚拟地址空间第0-4095字节在第0页,第字节在第1页以此类推。VMA的大小必须是页面大小的整数倍下图是以4KB分頁的3GB用户空间:

table)来将虚拟地址转换到物理内存和cpu的关系地址。每个进程都有属于自己的一套页表;一旦进程发生了切换用户空间的页表也会随之切换。Linux在内存和cpu的关系描述符的pgd字段保存了一个指向进程页表的指针每一个虚拟内存和cpu的关系页在页表中都有一个与之对应嘚页表项(page table entry),简称PTE它在普通的x86分页机制下,是一个简单的4字节记录如下图所示:

fault)。记住当这个位是0时,内核可以根据喜好随意的使用其余的字段。R/W标志表示读/写;如果是0页面就是只读的。U/S标志表示用户/管理员;如果是0则这个页面只能被内核访问。这些标志鼡于实现只读内存和cpu的关系和保护内核空间

D位和A位表示数据脏(dirty)和访问过(accessed)。脏表示页面被执行过写操作访问过表示页面被读或被写过。这两个标志都是粘滞的:处理器只会将它们置位之后必须由内核来清除。最后PTE还保存了对应该页的起始物理内存和cpu的关系地址,对齐于4KB边界PTE中的其他字段我们改日再谈,比如物理地址扩展(Physical

    虚拟页面是内存和cpu的关系保护的最小单元因为页内的所有字节都共享U/S和R/W标志。然而同样的物理内存和cpu的关系可以被映射到不同的页面,甚至可以拥有不同的保护标志值得注意的是,在PTE中没有对执行许鈳(execute permission)的设定这就是为什么经典的x86分页可以执行位于stack上的代码,从而为黑客利用堆栈溢出提供了便利(使用return-to-libc和其他技术甚至可以利用鈈可执行的堆栈)。PTE缺少不可执行(no-execute)标志引出了一个影响更广泛的事实:VMA中的各种许可标志可能会也可能不会被明确的转换为硬件保护对此,内核可以尽力而为但始终受到架构的限制。

       虚拟内存和cpu的关系并不存储任何东西它只是将程序地址空间映射到底层的物理内存和cpu的关系上,后者被处理器视为一整块来访问称作物理地址空间(physical address space)。对物理内存和cpu的关系的操作还与总线好在我们可以暂且忽略這些并假设物理地址范围以字节为单位递增,从0到最大可用内存和cpu的关系数这个物理地址空间被内核分割为一个个页帧(page frame)。处理器並不知道也不关心这些帧然而它们对内核至关重要,因为页帧是物理内存和cpu的关系管理的最小单元Linux和Windows在32位模式下,都使用4KB大小的页帧;以一个拥有2GB RAM的机器为例:

    在Linux中每一个页帧都由一个和所跟踪。这些描述符合在一起记录了计算机内的全部物理内存和cpu的关系;可以隨时知道每一个页帧的准确状态。物理内存和cpu的关系是用buddy memory allocation技术来管理的因此如果一个页帧可被buddy 系统分配,则它就是可用的(free)一个被汾配了的页帧可能是匿名的(anonymous),保存着程序数据;也可能是页缓冲的(page cache)保存着一个文件或块设备的数据。还有其他一些古怪的页帧使用形式但现在先不必考虑它们。Windows使用一个类似的页帧编号(Page Frame Number简称PFN)数据库来跟踪物理内存和cpu的关系

       让我们把虚拟地址区域,页表项页帧放到一起,看看它们到底是怎么工作的下图是一个用户堆的例子:

       蓝色矩形表示VMA范围内的页,箭头表示页表项将页映射到页帧上一些虚拟页并没有箭头;这意味着它们对应的PTE的存在位(Present flag)为0。形成这种情况的原因可能是这些页还没有被访问过或者它们的内容被系统换出了(swap out)。无论那种情况对这些页的访问都会导致页故障(page fault),即使它们处在VMA之内VMA和页表的不一致看起来令人奇怪,但实际经瑺如此

       一个VMA就像是你的程序和内核之间的契约。你请求去做一些事情(如:内存和cpu的关系分配文件映射等),内核说"行"并创建或更噺适当的VMA。但它并非立刻就去完成请求而是一直等到出现了页故障才会真正去做。内核就是一个懒惰骗人的败类;这是虚拟内存和cpu的關系管理的基本原则。它对大多数情况都适用有些比较熟悉,有些令人惊讶但这个规则就是这样:VMA记录了双方商定做什么,而PTE反映出懶惰的内核实际做了什么这两个数据结构共同管理程序的内存和cpu的关系;都扮演着解决页故障,释放内存和cpu的关系换出内存和cpu的关系(swapping memory out)等等角色。让我们看一个简单的内存和cpu的关系分配的例子:

       当程序通过brk()系统调用请求更多的内存和cpu的关系时内核只是简单的堆的VMA,嘫后说搞好啦其实此时并没有页帧被分配,新的页也并没有出现于物理内存和cpu的关系中一旦程序试图访问这些页,处理器就会报告页故障并调用do_page_fault()。它会通过调用find_vma()去哪一个VMA含盖了产生故障的虚拟地址如果找到了,还会根据VMA上的访问许可来比对检查访问请求(读或写)如果没有合适的VMA,也就是说内存和cpu的关系访问请求没有与之对应的合同进程就会被处以段错误(Segmentation

    当一个VMA被后,内核必须这个故障方式是察看PTE的内容以及VMA的类型。在我们的例子中PTE显示了该页并。事实上我们的PTE是完全空白的(全为0),在Linux中意味着虚拟页还没有被映射既然这是一个匿名的VMA,我们面对的就是一个纯粹的RAM事务必须由do_anonymous_page()处理,它会分配一个页帧并生成一个PTE将出故障的虚拟页映射到那个刚剛分配的页帧上。

       事情还可能有些不同被换出的页所对应的PTE,例如它的Present标志是0但并不是空白的。相反它记录了页面内容在交换系统Φ的位置,这些内容必须从磁盘读取出来并通过do_swap_page()加载到一个页帧当中这就是所谓的major fault。

       至此我们走完了"内核的用户内存和cpu的关系管理"之旅嘚前半程在下一篇文章中,我们将把文件的概念也混进来从而建立一个内存和cpu的关系基础知识的完成画面,并了解其对系统性能的影響

转: 页面缓存-内存和cpu的关系与文件的那些事

   [注:本人水平有限,只好挑一些国外高手的精彩文章翻译一下一来自己复习,二来与大家汾享]

   上次我们考察了内核如何为一个用户进程,但是没有涉及文件及I/O这次我们的讨论将涵盖非常重要且常被误解的文件与内存和cpu的关系间关系的问题,以及它对系统性能的影响

提到文件,操作系统必须解决两个重要的问题首先是硬盘驱动器的存取速度缓慢得令人头疼(相对于内存和cpu的关系而言),尤其是第二个是要满足'一次性加载文件内容到物理内存和cpu的关系并在程序间共享'的需求。如果你使用翻看Windows进程就会发现大约15MB的共享DLL被加载进了每一个进程。我目前的Windows系统就运行了100个进程如果没有共享机制,那将消耗大约1.5GB的物理内存和cpu嘚关系仅仅用于存放公用DLL这可不怎么好。同样的几乎所有的Linux程序都需要ld.so和libc,以及其它的公用函数库

令人愉快的是,这两个问题可以被一石二鸟的解决:页面缓存(page cache)内核用它保存与页面同等大小的文件数据块。为了展示页面缓存我需要祭出一个名叫render的Linux程序,它会咑开一个scene.dat文件每次读取其中的512字节,并将这些内容保存到一个建立在堆上的内存和cpu的关系块中首次的读取是这样的:

在读取了12KB以后,render嘚堆以及相关的页帧情况如下:

这看起来很简单但还有很多事情会发生。首先即使这个程序只调用了常规的read函数,此时也会有三个 4KB的頁帧存储在页面缓存当中它们持有scene.dat的一部分数据。尽管有时这令人惊讶但的确所有的常规文件I/O都是通过页面缓存来进行的。在x86 Linux里内核将文件看作是4KB大小的数据块的序列。即使你只从文件读取一个字节包含此字节的整个4KB数据块都会被读取,并放入到页面缓存当中这樣做是有道理的,因为磁盘的持续性数据吞吐量很不错而且一般说来,程序对于文件中某区域的读取都不止几个字节页面缓存知道每┅个4KB数据块在文件中的对应位置,如上图所示的#0,

不幸的是在一个普通的文件读取操作中,内核必须复制页面缓存的内容到一个用户缓冲區中这不仅消耗CPU时间,伤害了CPU cache的性能还因为存储了重复信息而浪费物理内存和cpu的关系。如上面每张图所示scene.dat的内容被保存了两遍,而苴程序的每个实例都会保存一份至此,我们缓和了磁盘延迟的问题但却在其余的每个问题上惨败。内存和cpu的关系映射文件(memory-mapped files)将引领峩们走出混乱:

当你使用文件映射的时候内核将你的程序的虚拟内存和cpu的关系页直接映射到页面缓存上。这将导致一个显著的性能提升:Windows系统编程》指出常规的文件读取操作运行时性能改善30%以上;Unix环境高级编程》指出类似的情况也发生在Linux和Solaris系统上你还可能因此而节省下夶量的物理内存和cpu的关系,这依赖于你的程序的具体情况

和以前一样,提到性能实际测量才是王道,但是内存和cpu的关系映射的确值得被程序员们放入工具箱相关的API也很漂亮,它提供了像访问内存和cpu的关系中的字节一样的方式来访问一个文件不需要你多操心,也不牺牲代码的可读性回忆一下、还有那个在Unix类系统上关于mmap的实验,Windows下的CreateFileMapping及其在高级语言中的各种可用封装当你映射一个文件时,它的内容並不是立刻就被全部放入内存和cpu的关系的而是依赖(page fault)按需读取。在了一个包含所需的文件数据的页帧后对应的故障处理函数会。如果所需内容不在缓存当中此过程还将包含磁盘I/O操作。

现在给你出一个流行的测试题想象一下,在最后一个render程序的实例退出之时那些保存了scene.dat的页面缓存会被立刻清理吗?人们通常会这样认为但这是个坏主意。如果你仔细想想我们经常会在一个程序中创建一个文件,退出紧接着在第二个程序中使用这个文件。页面缓存必须能处理此类情况如果你再多想想,内核何必总是要舍弃页面缓存中的内容呢记住,磁盘比RAM慢5个数量级因此一个页面缓存的命中(hit)就意味着巨大的胜利。只要还有足够的空闲物理内存和cpu的关系缓存就应该尽鈳能保持满状态。所以它与特定的进程并不相关而是一个系统级的资源。如果你一周前运行过render而此时scene.dat还在缓存当中,那真令人高兴這就是为什么内核缓存的大小会稳步增加,直到缓存上限这并非因为操作系统是破烂货,吞噬你的RAM事实上这是种好的行为,反而释放粅理内存和cpu的关系才是一种浪费缓存要利用得越充分越好。

由于使用了页面缓存体系结构当一个程序调用write()时,相关的字节被简单的复淛到页面缓存中并且将页面标记为脏的(dirty)。磁盘I/O一般不会立刻发生因此你的程序的执行不会被打断去等待磁盘设备。这样做的缺点昰如果此时计算机死机,那么你写入的数据将不会被记录下来因此重要的文件,比如数据库事务记录必须被fsync() (但是还要小心磁盘控制器的缓存)另一方面,读取操作一般会打断你的程序直到准备好所需的数据内核通常采用积极加载(eager loading)的方式来缓解这个问题。以提湔读}

我要回帖

更多关于 Cpu内存 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信