1 review old to know new
程序员文章站
2022-03-27 21:05:05
文章目录1.1从 Hello World说起1.2万变不离其宗1.4操作系统做什么1.4.1不要让CPU打盹1.5内存不够怎么办1.1从 Hello World说起对于C语言编写的 Hello World程序: 程序为什么要被编译器编译了之后才可以运行?编译器在把C程序转换成可以执行的机器码的过程中做了什么,怎么做的?最后编译出来的可执行文件里面是什么?除了机器码还有什么?它们怎么存放的,怎么组织的?include< 是什么意思?把 stdio....
文章目录
1.1从 Hello World说起
- 程序为什么要被编译器编译后才可运行?
- 编译器在把C程序转换成可以执行的机器码的过程中做了什么,
- 怎么做的?
- 编译出来的可执行文件里面是什么?
- 除机器码还有什么?
- 它们怎么存放的,怎么组织?
- include< <stdio.h>是什么意思?
- 把 stdio.h包含进来意味什么?
- C语言库又是什么?
- 它怎么实现的?
- 不同( Microsoft VC、GCC)和
- 不同硬件(x86、 SPARC、MIPS、ARM)
- 不同( Windows、 Linux、UNIX、 Solaris),
- 编译出来的结果一样吗?
- 为什么?
- 程序怎么运行起来?
- 操作系统是怎么装载它?
- 它从哪开始执行到哪结東?
- main之前发生什么?
- main结束后又发生什么?
- 如果没操作系统, Hello World可运行吗?
- 没有操作系统的机器上运行 Hello World需要什么?
- 应该怎么实现?
- printf怎么实现?
- 为什么可有不定数量的参数?
- 为什么它能够在终端上输出字符串?
- Hello World程序在运行时,
- 它在内存中是什么样子?
- 从最基本的编译、
- 静态链接
- 操作系统如何装载程序、动态链接
- 运行库和标准库的实现,
- 甚至一些操作系统的机制
- 计算机系统回顾过程将分两部分
- 硬件部分和软件部分
- 1章巩固和总结计算机软硬件体系里面几个重要概念
1.2万变不离其宗
- 本书将计算机的范围限定在最流行的PC
- 采用兼容x86指令集的32位CPU的
- 哪种平台并不关键
- 各种平台的软硬件差别很多,
- 本质上它们的基本概念和工作原理都一样
- 能掌握一种平台上的技术,其他的平台大同小异
- 能理解x86平台下的系统软件背后的机理,
- 有一天你需要在MIPS指令集的嵌入式平台上开发
- 或要为64位的 Windows或 Linux开发应用程序时
- 很快就能找到相通
- 撒开计算机硬件中复杂的设备、芯片及外围接口
- 软件开发者的角度看
- 只须抓住硬件的几个关键部件
- 对系统程序开发
- 硬件设备三个部件最关键,CPU、内存和IO控制芯片,
- 对普通应用程序开发者来说,似乎除了要关心CPU,其他的硬件细节不用关心,
- 对高级平台的开发者来说(如Java、.NET或脚本语言开发者),
- CPU都不需关心,这些平台为它们提供了一个通用的抽象的计算机
- 他们只要关心这个抽象的计算机就可
- 早期计算机没有复杂的图形功能,CPU频率不高,
- 跟内存的频率一样,
- 它们都直接连接在同一个总线上
- IO设备诸如显示设备、键盘、软盘和磁盘等速度与CPU和内存相比慢很多,
- 当时也没有复杂图形设备,
- 显示设备大多是只能输出字符的终端
- 为协调IO设备与总线之间的速度,也为能够让CPU能够和I/O设备通信,每个设备都会有一个相应的I/O控制器。
- 早期的计算机硬件结构
- 由于CPU频率提升,内存跟不上CPU的速度,
- 产生了与内存频率一致的系统总线,
- CPU采用倍频的方式与系统总线通信。
- 图形化的操作系统普及,3D游戏和多媒体发展,
- 使图形芯片需要跟CPU和内存之间大量交换数据,
- 慢速的IO总线无法满足图形设备
- 为协调CPU、内存和高速的图形设备
- 设计了高速北桥芯片,以便它们间能高速交换数据
- 北桥运行速度高,低速的设备如果全直接连在北桥,
- 北桥须处理高速设备,又须处理低速设备
- 又设计专门处理低速设备的南桥芯片
- 磁盘、USB、键盘、鼠标等设备都连在南桥
- 由南桥将它们汇总后连接到北桥
- 90年代的PC机在系统总线上采用的是PCI结构
- 低速设备上采用的ISA总线,采用 PCII/ISA及南北桥设计的硬件构架如图1-2
- 中间是连接所有高速芯片的北桥( PCI Bridge)
- 它的左边是CPU
- 北桥还连接着几个高速部件,包括左边的内存和下面的PCI总线
- PCI的速度最高133MH
- 又发明了AGP
- PCI Express等总线结构和相应控制芯片。
- 硬件结构看似越来越复杂,但还是没有脱离最初的CPU、内存,以及IO的基本结构。
- 程序开发的角度看待硬件时可以简单地将它看成最初的硬件模型。
SMP与多核
- 过去的50年,CPU的频率从儿十KHz到现在的4GHz,
- 提高了数十万倍,每18个月频率就会翻倍。
- 2004年来,似乎己经失效,CPU的频率自从那时开始再也没有发生质的提高。
- 制造CPU的工艺方面已经达到了物理极限,除非CPU制造工艺有本质的突破,否则CPU的频率将会一直被目前4GHz的“天花板”限制
- 在频率上短期内已经没有提高的余地了,
- 很早以前就有了多CPU的计算机,
- 最常见的一种形式就是对称多处理器(SMP
- 每个CPU在系统中所处的地位和所发挥的功能都一样,是相互对称的。
- 理论上讲,增加CPU的数量就可以提高运算速度,理想情况下,速度的提高与CPU的数量成正比。
- 实际上并非如此,因为我们的程序并不是都能分解成若干个完全不相干的子问题。
- 一个女人可以花10个月生出一个孩子
- 但10个女人并不能在一个月就生出一个孩子
- 大型的数据库、网络服务器要同时处理大量的请求,
- 请求之间相互独立的,
- 多处理器就可以最人效能地发挥威力
- PC用多处理器奢侈,多处理器的成本很高
- 考虑将多个处理器“合并在一起打包出售”,
- 这些“被打包”的处理器之间共享昂贵的缓存部件
- 只保留多个核心,且以一个处理器的外包装出售
- 售价比单核心的处理器只贵一点
- 这就是多核处理器的基本想法
- 多核处理器是SMP简化版,
- 当然在细节上还有差别,
- 但从程序员的角度看
- 它们之间区判很小,逻辑上来看是完全相同
- 多核和SMP在缓存共享等方面有细微差别,
- 使得程序在优化上可有针对性地处理。
- 除非想把CPU的每一滴油水都榨干,否则可以把多核和SMP看成同一个概念
1.3站得高,望得远
- 管理计算机的软件称系统软件
- 以区别普通的应用程序
- 系统软件可分两块
- 平台性的,操作系统内核、驱动程序、运行库和数以千计的系统工具
- 用于程序开发的,编译器,汇编器、链接器等开发工具和开发库
- 本书将着重介绍系统软件的一部分
- 主要是链接器和库(包括运行库和开发库)
- 计算机系统软件体系结构采用层结构
Any problem in computer science can be solved by another layer of indirection.
- 这句话概括了计算机系统软件体系结构的设计要点,整个体系结构从上到下都是按照严格的层次结构设计的
- 不仅是计算机系统软件整个体系是这样的,体系里面的每个组件
- 操作系统本身,很多应用程序、软件系统甚至很多硬件结构都是按照这种层次的结构组织和设计的。
- 系统软件体系结构中,各种软件的位置如图1-3
- 每层次间都须通信,通信就必须有一个通信的协议,
- 称接口,
- 接口的下面那层是接口的提供者,由它定义接口
- 接口的上面那层是接口的使用者,它使用该接口来实现所需要的功能
- 层次体系中,接口是被精心设计过的,尽量不变
- 层次之间只要遵循这接口,任何一个层都可被修改或被替換
- 除硬件和应用程序,其他都中间层,
- 每个中间层都是对它下面的那层的包装和扩展
- 中间层使应用程序和硬件间保持独立,
- 硬件和操作系统日新月异发展,
- 最初为80386芯片和DOS系统设计的软件在最新的多核处理器和 Windows Vista下还能运行,归功于硬件和操作系统本身保持了向后兼容性
- 另一方面不得不归功于这种层次结构
- 虚拟机技术更在硬件和操作系统之间增加了一层虚拟层,
- 使一个计算机上可同时运行多个操作系统,
- 也是层次结构带来的好处,
- 在尽可能少改变甚至不改变其他层的情况下,
- 新增加一个层就可以提供前所未有的功能。
- 位于最上层的是应用程序,比如我们平时用到的网络浏览器
- 开发工具与应用程序是属于同一个层次,
- 因为它们都用一个接口
- 操作系统应用程序编程接口
- 应用程序接口的提供者是运行库,什么样的运行库提供什么样的
API - Linux下的Glibc库提供 POSIX的API;
- Windows的运行库提供 Windows API,
- 32位 Windows提供的API又被称为Win32。
- 运行庠使用操作系统提供的系统调用接口
- 系统调用接口在实现中往往以软件中断方式提供
- Linux用0x80号中断作为系统调用接口
- Windows使用0x2E号中断作为系统调用接口(从 Windows XP Sp2开始,Windows用一种新的系统调用方式)。
- 操作系统内核层对于硬件层来说
- 是硬件接口的使用者
- 硬件是接口的定义者,
- 硬件的接口定义决定了操作系统内核,
- 驱动程序如何操作硬件,如何与硬件进行通信。
- 这种接口往往被叫做硬件规格
- 硬件生产厂商负责提供硬件规格,
- 操作系统和驱动程序开发者
- 阅读硬件规格文档所规定的各种硬件编程接口标准来编写操作系统和驱动程序
1.4操作系统做什么
- 操作系统提供抽象的接口
- 另外一个功能
- 管理硬件资源
- 计算机硬件的能力有限
- 充分挖掘硬件能力,使计算机运行得更有效
- 更短的时间内处理更多任务,才是目标
- 早期百万美元的古董计算机来说更如此
- 挖空心思让计算机硬件发挥所有潜能
- 一个计算机中的资源
- CPU、存储器(包括内存和磁盘)和I/O设备
- 三方面来看看如何挖掘澘力
1.4.1不要让CPU打盹
- 计算机发展早期,CPU资源贵,
- 一个CPU只能运行一个程序,
- 当程序读写磁盘(当时可能磁带),CPU就空闲
- 写一个监控程序,某个程序暂时无须用CPU,
- 监控程序就把另外的在等待CPU资源的程序启动,使CPU利用起来
- 称多道程序
- 看似原始,但当时大大提高CPU利用率
- 问题是程序之间的调度策略粗糙
- 对多道程序来说,程序之间不分轻重缓急,
- 有些程序急需使用CPU来完成一些任务(比如用户交互的任务),
- 可能很长时间后オ有机会分配CPU
- 有些响应时间要求高的程序来说致命
- 经过改进,程序运行模式变成协作模式
- 每个程序运行一段时间以后都主动让出CPU给其他程序,使得一段时间内每个程序都有机会运行一小段
- 这对于一些交互式的任务尤重要
- 点击一下鼠标或按下一个键盘按键后
- 程序所要处理的任务可能并不多
- 但它需要尽快地被处理,使用户能立即看到效果
- 这种程序协作模式叫做分时系统,这时监控程序己比多道程序要复杂多,完整的操作系统维形逐渐形成
- Windows的早期版本(Windows95和 Windows NT之前),
- Mac OS X之前的 Mac OS版本都用这种分时系统的方式来调度程序的
- Windows3.1中,程序调Yield、 Getmessage或 Peekmessage这几个系统调用时, Windows3.1 会判断是否有其他程序正在等待CPU,如果有,则可能暂停执行当前的程序,把CPU让出来给其他程序。
- 如果一个程序在进行一个很耗时的计算,一直霸占CPU,那OS没法,其他程序都只有等着,整个系统看过去好像死机了
- 一个程序进入了一个 while(1),那么整个系统都停止
这里问题大的
这特么叫协作模式
- 系统中任何一个死循环都导致系统死机
- 当然当时的PC硬件处理能力本身就很弱
- 应用也大多是低端应用,
- 这种分时方式勉强也能应付一下当时的交互式环境
- 此前在高端领域,非PC的大中小型机领域,
- 已经在研究一种更为先进的操作系统模式了
- 多任务系统,操作系统接管所有的硬件资源,
- 且本身运行在一个受硬件保护的级别
- 所有的应用程序都以进程的方式运行在比操作系统权限更低的级別,
- 每个进程有自己独立地址空间,使进程之间的地址空间相互隔
离
- 每个进程有自己独立地址空间,使进程之间的地址空间相互隔
- CPU由操作系统统一分配,每个进程根据进程优先级都有机会得到CPU.
- 但如果运行时间超出了一定时间,OS暂停该进程,将CPU资源分配给其他等待运行的进程
- 即抢占式,操作系统可强制剥夺CPU资源并且分配给它认为目前最需要的进程
- 如果OS分配给每个进程的时间都很短,
- CPU在多个进程间快速切换,
- 造成很多进程都在同时运行的假象
- 所有现代OS都采用这方式
- UNIX、 llinux、 Windows NT,
- Mac Os X
1.4.2设备驱动
- OS作为使件层的上层,它是对硬件的管理和抽象。
- 对操作系统上面的运行库和应用程序,它们希望看到的是一个统一的硬件访问模式。
- 应用程序的开发者不希望在开发应用程序时直接读写硬件端口、
- 处理硬件中断
- 硬件千差万別,操作方式和访问方式都有区別。
- 希望在显示器上画一条直线,
- 最好的方式是不管计算机用什么显卡、什么显示器,多少大小多少分辨率
- 只调统一的函数,具体的实现方式由操作系统
- 如果程序员要关心具体硬件
- A型号的显卡,往IO端0x1001写一个命令0x1111,
- 从端口0x1002中读取一个4字节的显存地址
- 然后用DDA(一种画直线的图形算法)逐个地在显存上画点…
- B型号的显卡,另外一种方式
- 操作系统成熟前,的确存在这样,
- 应用程序的程序员需要直接跟硬件打交道
1.5内存不够怎么办
- 进程目标是希望每个进程从逻辑上看都独占计算机资源
- 操作系统的多任务功能使CPU能在多个进程间很好共享
- 从进程的角度看好像是它独占了CPU而不用考虑与其他进程分享CPU的事情
- 操作系统的I/O抽象模型很好实现IO设备的共享和抽象
- 剩下的就是主存
- 内存的分配问题
- 早期程序是直接运行在物理内存上
- 程序在运行时所访问的地址都是物理地址
- 如果一个计算机同时只运行一个程序,那么只要程序要求的内
存空间不要超过物理内存的大小,就不会有问题。 - 为更有效地利用硬件,须同时运行多个程序,
- 正如前面的多道程序、分时系统和多任务中一样,
- 当我们能够同时运行多个程序时,CPU利用率将比较高
- 如何将计算机上有限的物理内存分配给多个程序使用
- 128MB内存
- A运行需10
- B需100
- C要20
- 同时运行A和B,前10MB分配给程序A,10MB~110MB分配给B
- 就能实现A和B两程序同时运行
- 但是这种内存分配策略问题多
- 地址空间不隔离
- 所有程序都直接访问物理地址,
- 程序所用的内存空间不相互隔离
- 恶意的程序易改写其他程序的内存数据
- 臭虫程序可能不小心修改了其他程序的数据,使其他程序崩溃
- 用户希望他在用计算机时,其中一个任务失败了,不会影响其他任务
- 内存使用效率低
- 没有有效的内存管理机制,需要一个程序执行时,监控程序就将整个程序装入内存中执行
- 如果忽然需要运行程序C,这时内存空间已不够,
- 可以用的一个办法是将其他程序的数据暂时写到磁盘,
- 需要用到的时候再读回来。
- 程序所需要的空间是连续的,
- 将程序A换出到磁盘所释放的内存空间是不够的,
- 只能将B换出到磁盘,
- 然后将C读入到内存开始运行。
- 整个过程中有大量的数据换入换出,效率十分低下
- 程序运行地址不确定
- 程序每次需要装入运行时,都需给它从内存中分配块足够大的空闲区域,这个空闲区域的位置不确定
- 程序在编写时,它访问数据和指令跳转时的目标地址很多都是固定的,这涉及程序的重定位问题,
- 第2部分和第3部分还会详细探讨重定位的问题。
- 增加中间层,
- 用一种间接的地址访问方法。
- 把程序给出的地址看作是一种虚拟地址,
- 通过某些映射的方法,将这个虚拟地址转换成实际的物理地址。
- 只要我们能够妥善地控制这个虛拟地址到物理地址的映射过程,
- 就可保证任意一个程序所能够访问的物理内存区域跟另外个程序不重叠,达到地址空间隔离
1.5.1关于隔离
- 用户程序在运行时不希望介入到这些复杂的存储器管理过程
- 普通的程序需要的是一个简单的执行环境
- 有一个单“的地址空间、
- 有已的CPU
- 整个程序占有整个计算机而不用关心其他的程序
- (程序间通信的部分除外,这是程序主动要求跟其他程序通信和联系)
- 物理地址空间对于每台计算机来说只有唯一,
- 把物理空间想象成物理内存,
- Intel的 Pentium4的处理器,那么它是32位的机器,
- 即计算机地址线有32条(实际上36条,暂时认为只32条),物理空间就4GB
- 装512MB的内存,物理地址的真正有效部分只0x000 0000 --0x0001 FFFF,其他部分无效的(还有一些外部IO设备映射到物理空间的,也是有效的,暂时无视)
- 虚拟地址空间是并不存在,
- 每个进程都有自独立的虚拟空间,
- 每个进程只能访问自己的地址空间,这样就有效做到进程的隔离。
1.5.2分段( Segmentation)
- 最开始用的是一种叫做分段,
- 把一段与程序所需要的内存空间大小的虚拟空间映射到某个地址空间
- A需10,
- 有一个地址从0x 000000到0x00A 0000的10MB大小的一个假象的空间,也就是虚拟空间
- 然后从实际的物理内存中分配一个相同大小的物理地址,
- 假设物理地址0x0010000开始到0x00B0000结束的一块空间
- 把这两块相同大小的地址空间一一映射,
- 即虚拟空间中的每个字节相对应于物理空间中的每个字节。
- 这个映射过程由软件设置,
- OS来设置这个映射函数,实际的地址转换由硬件完成。
- A中访问地址0x0001000时,CPU会将这个地址转换成实际的物理地址0x00101000
- 程序A和程序B在运行时,
- 它们的虚拟空间和物理空间映射关系可能如图1-5
1.6众人抬柴火焰高
1.6.1线程基础
- CPU频率增长开始出现停滞,开始向多核发展
- 多线程,实现软件并发执行的重要方法
- 这节回顾线程
- 线程的概念、线程调度、线程安全、用户线程与内核线程之间映射关系
- 理解线程对更加深入地理解裝载、
- 动态链接和运行库,
- 运行库与多线程相关部分的内容有帮助
什么是线程
- 线程
- 轻量级进程,
- 是程序执行流的最小单元
- 线程由线程ID、当前指令指针(PC)、寄存器集合和堆栈组成
- 一个进程由一个到多个线程组成
- 各个线程之间共享程序的内存空间(包括代码段、数据段、堆等)
- 及一些进程级的资源(如打开文件和信号)。
- 线程与进程的关系如图1-8
- 大多数软件应用,线程的数量都不止一个。
- 多个线程互不千扰地并发执行,并共享进程的全局变量和堆的数据。
- 多个线程与单线程的进程相比,又有哪些优势?
- 用多线程的原因有如下几点
- 某操作陷入长时间等待,等待的线程会进入睡眠,无法继续执行。
- 多线程执行可有效利用等待时间
- 等待网络响应,这可能要花费数秒甚至数十秒
- 某操作(计算)消耗大量时间,如果只有一个线程,程序和用户之间的交互会中断
- 多线程让一个线程负责交互,
- 另一个线程负责计算程序
- 逻辑本身就要求并发操作,一个多端下载软件(如Bittorrent)
- 多CPU或多核计算机,
- 本身具备同时执行多个线程的能力,
- 单线程程序无法全面地发挥计算机的全部计算能力。
- 相对于多进程应用,多线程在数据共享方面效率要高很多
1.6.2线程安全
- 多线程程序处于一个多变环境当中,
- 可访问的全局变量和堆数椐随时都可能被其他线程改变。
- 多线程程序在并发时数据的一致性变得重要。
竞争与原子操作
- 多线程同时访问一个共享数据,可能造成很恶劣的后果。
- 设有两个线程分别要执行如表1-3
- 许多体系结构上
- 读取i到某个寄存器X。
- X++。
- 将X的内容存储回i。
- 线程1和线程2并发执行,
- 两个线程的执行序列如下(寄存器X的内容在不同的线程中是不一样的),如表1-4
- 逻辑来看,
- 两个线程都执行完毕之后,i的值应该为1,
- 但i得到的值是0。
- 实际上这两个线程如果同时执行的话,i的结果有可能是0或1或
2。 - 两个程序同时读写同一个共享数据会导致意想不到
- 自增(++)在多线程环境下会出错
- 这个操作被编译为汇编代码之后不止一条指令,
- 执行时可能执行了一半就被调度系统打断,去执行别代码
- 把单指令的操作称为原子的,
- 单条指令的执行是不会被打断的。
- 很多体系结构都提供一些常用操作的原子指令,
- i386就有条inc可直接增加一个内存单元值,
- 避免出现上例中的错误
- Windows里,有一套API专门一些原子操作(见表1-5),
- 为 Interlocked API。
- 用这些函数时, Windows将保证是原子操作的,因此可以不用担心出现问题
- 原子操作指令非常方便,仅适用于比较简単特定的场合。
- 在复杂的场合下
- 保证一个复杂的数据结构更改的原子性,原子操作指令就力不从心了。
- 要更加通用的手段:锁
本文地址:https://blog.csdn.net/zhoutianzi12/article/details/107435689
上一篇: 【python】文件操作(2)