从Hello world说起

毫无疑问,“hello world”对于程序员来说如雷贯耳。
但是,细想对于这样一个极其简单的程序又有很多问题模糊不清。例如用C语言编写:

#include <studio.h>

int main()
{
    printf("Hello World \n");
    return 0;
}
  1. 程序为什么要被编译器编译了之后才可以运行?
  2. 编译器在把C语言程序转换成可以执行的机器码的过程中做了什么,怎么做的?
  3. 最后编译出来的可执行文件里面是什么?除了机器码还有什么?它们怎么存放的,怎么组
    织的?
  4. “#include <stdio.h>”是什么意思?把stdio.h包含进来意味着什么?C语言库又是什么?它怎
    么实现的?
  5. 不同的编译器(Microsoft VC、GCC)和不同的硬件平台(x86、SPARC、MIPS、ARM),以及
    不同的操作系统(Windows、Linux、UNIX、Solaris),最终编译出来的结果一样吗?为什么?
  6. Hello World程序是怎么运行起来的?操作系统是怎么装载它的?它从哪儿开始执行,到哪
    儿结束?main函数之前发生了什么?main函数结束以后又发生了什么?
  7. 如果没有操作系统,Hello World可以运行吗?如果要在一台没有操作系统的机器上运行
    Hello World需要什么?应该怎么实现?
  8. printf是怎么实现的?它为什么可以有不定数量的参数?为什么它能够在终端上输出字符
    串?
  9. Hello World程序在运行时,它在内存中是什么样子的?

本文集里的文章将围绕以上问题展开。

计算机硬件架构

系列文章讲述的内容均基于使用最广泛的兼容x86指令集的32位(后面有很多例子可能是64位的运行结果,会作说明)的CPU的个人计算机。

站在软件开发者的角度,我们需要对于计算机硬件的三个最关键部件有了解:中央处理器CPU、内存和I/O控制芯片。

早期,CPU的核心频率不高,跟内存频率一样,它们都是直接连在同一个总线(Bus)上的。由于I/O设备诸如显示设备、键盘、软盘和磁盘等速度与CPU和内存相比还是慢很多,为了协调I/O设备与总线的速度,也为了能够让CPU能够与I/O设备通信,一般每个设备都有一个相应的I/O Controller。如下图所示:

u=2626718006,562897499&fm=26&gp=0.jpg

后来,由于CPU核心频率的提升,内存跟不上CPU的速度,于是产生了与内存频率一致的系统总线,而CPU采用倍频的方式与总线通信。

接着,随着图形化的操作系统普及,特别是SD游戏和多媒体的发展,使得图形芯片需要跟CPU和内存之间大量交换数据,为了协调,专门设计了高速的北桥(Northbridge,PCI Bridge)芯片,以便他们高效交换数据。

由于北桥运行的速度非常高,所有相对低速的设备如果全都直接连接在北桥上,北桥既须处理高速设备,又须处理低速设备,设计就会十分复杂。于是人们又设计了专门处理低速设备的南桥(Southbridge)芯片,磁盘、USB、键盘、鼠标等设备都连接在南桥上,由南桥将它们汇总后连接到北桥上。20世纪90年代的PC机在系统总线上采用的是PCI结构,而在低速设备上采用的ISA总线,,采用PCI/ISA及南北桥设计的硬件构架如下:

计算机的总线结构图1.jpg

位于中间是连接所有高速芯片的北桥,它就像人的心脏,连接并驱动身体的各个部位;它的左边是CPU,负责所有的控制和运算,就像人的大脑。北桥还连接着几个高速部件,包括左边的内存和下面的PCI总线。

PCI的速度最高为133 MHz,它还是不能满足人们的需求,于是人们又发明了AGP、PCI Express等诸多总线结构和相应控制芯片。虽然硬件结构看似越来越复杂,但实际上它还是没有脱离最初的CPU、内存,以及I/O的基本结构。我们从程序开发的角度看待硬件时可以简单地将它看成最初的硬件模型。

SMP与多核

人们总是希望计算机越快越好,在过去的50年里,CPU的频率从几十kHz提高到4GHz,整整提高了数十万倍。但自此便再没有质的提高,原因是CPU的制造工艺已经到达物理极限,除非CPU的制造工艺发生本质的突破。

在频率上已经没有提高的余地了,于是从另一方面来提高速度,就是增加CPU的数量。很早以前就出现了一个计算机有多个CPU,其中最常见的一种形式就是对称多处理器(SMP,Symmetrical Muti-Processing), 简单地讲就是每个CPU在系统中所处的地位和所发挥的功能都是一样的,是相互对称的。
理论上讲,增加CPU的数量就可以提高运算速度,并且理想情况下,速度的提高与CPU的数量成正比。但实际上并非如此,因为我们的程序并不是都能分解成若干个完全不相干的子问题。就比如一个女人可以花10个月生出一个孩子,但是10个女人并不能在一个月就生出一个孩子一样。
多处理器应用最多的场合也是这些商用的服务器和需要处理大量计算的环境。而在个人电脑中,使用多处理器则是比较奢侈的行为,毕竟多处理器的成本是很高的。

于是处理器的厂商开始考虑将多个处理器“合并在一起打包出售”,这些“被打包”的处理器之间共享比较昂贵的缓存部件,只保留多个核心,并且以一个处理器的外包装进行出售,售价比单核心的处理器只贵了一点,这就是多核处理器(Multi-core Processor)的基本想法。多核处理器实际上就是SMP的简化版,当然它们在细节上还有一些差别,但是从程序员的角度来看,它们之间区别很小,逻辑上来看它们是完全相同的。只是多核和SMP在缓存共享等方面有细微的差别,使得程序在优化上可以有针对性地处理。简单地讲,除非想把CPU的每一滴油水都榨干,否则可以把多核和SMP看成同一个概念。

系统软件

将用于管理计算机本身的软件称为系统软件,以区别普通的应用程序。
系统软件分为两类,一类是平台性的,如操作系统、内核、驱动程序、运行库和数以千计的系统工具,二类是用于程序开发的,比如编译器、汇编器、连接器等开发工具和开发库。
计算机系统软件体系采用层的结构,有一句名言:

“计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决”
“Any problem in computer science can be solved by another layer of indirection.”

这句名言概括了计算机系统软件体系结构的设计要点,整个体系结构从上到下都是严格按照层次结构设计的。

u=3261613968,3684876964&fm=26&gp=0.jpg

每个层次之间都须要相互通信,既然须要通信就必须有一个通信的协议,我们一般将其称为接口(Interface),接口的下层是提供者,由它定义接口;接口的上层是使用者,它使用该接口来实现所需要的功能。
除了硬件和应用程序,其他都是所谓的中间层,每个中间层都是对它下面的那层的包装和扩展。
从整个层次结构上来看,开发工具与应用程序是属于同一个层次的,因为它们都使用一个接口,那就是操作系统应用程序编程接口(Application Programming Interface)。
运行库使用操作系统提供的系统调用接口(System call Interface),系统调用接口在实现中往往以软件中断(Software Interrupt)的方式提供。
硬件的生产厂商负责提供驱动程序如何操作硬件,如何与硬件进行通信。这种接口往往被叫做硬件规格(Hardware Specification)。

操作系统做什么

操作系统的一个功能是提供抽象的接口,另外一个主要功能是管理硬件资源。

计算机硬件的能力是有限的,比如一个CPU一秒钟能够执行的指令条数是1亿条或是1GB的内存能够最多同时存储1GB的数据。无论你是否使用它,资源总是那么多。
一个计算机中的资源主要分CPU、存储器(包括内存和磁盘)和I/O设备,我们分别从这三个方面来看看如何挖掘它们的潜力。

不要让CPU打盹

  • 早期,一个CPU只能运行一个程序,那么当程序读写磁盘时,CPU就空闲了下来。
  • 编写一个监控程序,当程序暂时无须使用CPU时,监控程序就把另外的正在等待CPU资源的程序启动,这种方法被称为多道程序(Multiprogramming)。缺点是程序之间不分轻重缓急,比如会出现点击鼠标10分钟以后系统才有反应。
  • 每个程序运行一段时间以后都主动让出CPU给其他程序,使得一段时间内每个程序都有机会运行一小段时间,这种程序协作模式叫做分时系统(Time-Sharing System)。缺点是一个程序进入了一个while(1)的死循环,那么整个系统都停止了。
  • 多任务(Multi-tasking)系统: 所有的应用程序都以进程(Process)的方式运行在比操作系统权限更低的级别,每个进程都有自己独立的地址空间,使得进程之间的地址空间相互隔离。CPU由操作系统统一进行分配,每个进程根据进程优先级的高低都有机会得到CPU,但是,如果运行时间超出了一定的时间,操作系统会暂停该进程,将CPU资源分配给其他等待运行的进程。这种CPU的分配方式即所谓的抢占式(Preemptive),操作系统可以强制剥夺CPU资源并且分配给它认为目前最需要的进程。如果操作系统分配给每个进程的时间都很短,即CPU在多个进程间快速地切换,从而造成了很多进程都在同时运行的假象。目前几乎所有现代的操作系统都是采用这种方式,比如我们熟悉的UNIX、Linux、Windows NT,以及Mac OS X等流行的操作系统。

设备驱动

操作系统作为硬件层的上层,它是对硬件的管理和抽象。对于操作系统上面的运行库和应用程序来说,它们希望看到的是一个统一的硬件访问模式。作为应用程序的开发者,我们不希望在开发应用程序的时候直接读写硬件端口、处理硬件中断等这些繁琐的事情。比如我们希望在显示器上画一条直线,对于程序员来说,只要调用一个统一的LineTo()函数,具体的实现方式由操作系统来完成。
当成熟的操作系统出现以后,硬件逐渐被抽象成了一系列概念。在UNIX中,硬件设备的访问形式跟访问普通的文件形式一样。这些繁琐的硬件细节全都交给了操作系统,具体地讲是操作系统中的硬件驱动(Device Driver)程序来完成。驱动程序可以看作是操作系统的一部分,它往往跟操作系统内核一起运行在特权级,但它又与操作系统内核之间有一定的独立性,使得驱动程序有比较好的灵活性。

内存不够怎么办

在早期的计算机中,程序是直接运行在物理内存上的,也就是说,程序在运行时所访问的地址都是物理地址。当然,如果一个计算机同时只运行一个程序,那么只要程序要求的内存空间不要超过物理内存的大小,就不会有问题。但事实上为了更有效地利用硬件资源,我们必须同时运行多个程序,正如前面的多道程序、分时系统和多任务中一样,当我们能够同时运行多个程序时,CPU的利用率将会比较高。那么很明显的一个问题是,如何将计算机上有限的物理内存分配给多个程序使用。
存在的问题有:
地址空间不隔离: 所有程序都直接访问物理地址,程序所使用的内存空间不是相互隔离的。
内存使用效率低:由于没有有效的内存管理机制,通常需要一个程序执行时,监控程序就将整个程序装入内存中然后开始执行。
程序运行的地址不确定:因为程序每次需要装入运行时,我们都需要给它从内存中分配一块足够大的空闲区域,这个空闲区域的位置是不确定的。

关于隔离

虚拟地址空间是指虚拟的、人们想象出来的地址空间,其实它并不存在,每个进程都有自己独立的虚拟空间,而且每个进程只能访问自己的地址空间,这样就有效地做到了进程的隔离。

分段

最开始人们使用的是一种叫做分段(Segmentation)的方法,基本思路是把一段与程序所需要的内存空间大小的虚拟空间映射到某个地址空间。比如程序A需要10 MB内存,那么我们假设有一个地址从0x00000000到0x00A00000的10MB大小的一个假象的空间,也就是虚拟空间,然后我们从实际的物理内存中分配一个相同大小的物理地址,假设是物理地址0x00100000开始到0x00B00000结束的一块空间。然后我们把这两块相同大小的地址空间一一映射,即虚拟空间中的每个字节相对应于物理空间中的每个字节。这个映射过程由软件来设置,比如操作系统来设置这个映射函数,实际的地址转换由硬件完成。
分段的方法基本解决了上面提到的3个问题中的第一个和第三个。首先它做到了地址隔离,因为程序A和程序B被映射到了两块不同的物理空间区域,但是分段的这种方法还是没有解决我们的第二个问题,即内存使用效率的问题。分段对内存区域的映射还是按照程序为单位,如果内存不足,被换入换出到磁盘的都是整个程序,这样势必会造成大量的磁盘访问操作,从而严重影响速度,这种方法还是显得粗糙,粒度比较大。事实上,根据程序的局部性原理,当一个程序在运行时,在某个时间段内,它只是频繁地用到了一小部分数据,也就是说,程序的很多数据其实在一个时间段内都是不会被用到的。人们很自然地想到了更小粒度的内存分割和映射的方法,使得程序的局部性原理得到充分的利用,大大提高了内存的使用率。这种方法就是分页(Paging)。

分页

分页的基本方法是把地址空间人为地等分成固定大小的页,每一页的大小由硬件决定,或硬件支持多种大小的页,由操作系统选择决定页的大小。比如Intel Pentium系列处理器支持4KB或4MB的页大小,那么操作系统可以选择每页大小为4KB,也可以选择每页大小为4MB,但是在同一时刻只能选择一种大小,所以对整个系统来说,页就是固定大小的。目前几乎所有的PC上的操作系统都使用4KB大小的页。我们使用的PC机是32位的虚拟地址空间,也就是4GB,那么按4KB每页分的话,总共有1 048 576个页。物理空间也是同样的分法。

本章小结

在这一章中,我们对整个计算机的软硬件基本结构进行了回顾,包括CPU与外围部件的连接
方式、SMP与多核、软硬件层次体系结构、如何充分利用CPU及与系统软件十分相关的设备驱
动、操作系统、虚拟空间、物理空间、页映射的基础概念。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,864评论 6 494
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,175评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,401评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,170评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,276评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,364评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,401评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,179评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,604评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,902评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,070评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,751评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,380评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,077评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,312评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,924评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,957评论 2 351

推荐阅读更多精彩内容