《操作系统真象还原》阅读笔记——第0章

0. 软件是如何访问硬件的

IO接口

因为操作系统不可能及时更新各种硬件的驱动方法,便出现了各种各种硬件适配设备,这就是IO接口。接口就是标准,硬件按照这个标准工作就实现了通用。
硬件在输入输出大体上分为串行和并行,响应的接口也就是串行接口和并行接口。串行硬件用串行接口与CPU相互通信或数据传输。并行设备的访问类似,只是通过并行接口进行的。

访问外部硬件的两种方式

  1. 将某个外设的内存映射到一定范围的地址空间中,CPU 通过地址总线访问该区域时会落到外设的内存中,这种映射让 CPU 访问外设的内存就如同访问主板的物理内存一样。有的设备是这样做的,比如显卡,显卡是显示器的适配器,CPU 不直接和显示器交互,它只和显卡通信。显卡上有片内存叫显存,它被映射到主机物理内存上的低端1MB的0xB8000~0xBFFFF。CPU 访问这片内存就是访问显卡,往这片内存写字节便是往屏幕上打印内容。
  2. 外设是通过 IO 接口与 CPU 通信的,CPU 访问外设,就是访问 IO 接口,由 IO 接口将信息传递给另一端的外设,也就是说,CPU 从来不知道有这些设备的存在,它只知道自己操作的是 IO 接口。如何访问IO接口呢?IO 接口上有一些端口。具体地说,处理器是通过端口(Prot)来和外设打交道的。本质上,端口就是就是一些寄存器,类似于处理器内部的寄存器。不同之处仅仅在于,这些叫端口的寄存器位于 IO 接口电路中。

1. 应用程序是什么,和操作系统如何配合在一起

应用程序和操作系统都是软件。
操作系统是人想出来的,为了让自己管理计算机方便而创造出来的一套管理办法。
应用程序要用某种语言编写,而语言又是编译器来提供的。其实根本就没有什么语言,有的只是编译器。是编译器决定怎么解释某种关键字及某种语法。语言只是编译器和大家的约定,只有这样写代码,编译器便将其翻译成某种机器指令,翻译成什么样取决于编译器的行为,和语言无关,比如说 C 语言的 printf 函数,它的功能不是说一定要把字符打印到屏幕上,这要看编译器对这种关键字的处理。
应用程序加上操作系统提供的功能才算是完整的程序。由于有了操作系统的支持,一些现成的东西已经摆在那了,但这些是属于操作系统的,不是应用程序的,所以咱们平时写的应用程序只是半成品,需要调用操作系统提供好的函数才能完整地做成一件事,而这个函数便是系统调用。

2.为什么称“陷入”内核

应用程序处于特权级 3,操作系统内核处于特权级 0。当应用程序欲访问系统资源时(无论是硬件,还是内核数据结构),它需要进行系统调用。这样 CPU 便进入了内核态,也称管态。

用户态与内核态

用户态与内核态是对 CPU 来讲的,是指 CPU 运行在用户态(特权 3 级)还是内核态(特权 0 级)。
用户进程陷入内核态是指:由于内部或外部中断发生,当前进程被暂时终止执行,其上下文被内核的中断程序保存起来后,开始执行一段内核的代码。是内核的代码,不是用户程序在内核的代码,用户代码怎么可能在内核中存在,所以“用户态和内核态”是对 CPU 来说的。
当应用程序陷入内核后,以后发生的事,应用程序完全不知道,它的上下文环境已经被保存到自己的 0 特权级栈中了,那时在 CPU 上运行的程序已经是内核程序了。所以要清楚,内核代码并不是成了应用程序的内核化生,操作系统是独立的部分,用户进程永远不会因为进入内核态而变身为操作系统了。

3.内存访问的分段,和代码中的分段

内存访问中的段

程序分段首先是为了重定位。在8086时,寄存器是16位的,也就是一个段最多可以访问到64KB,当时内存再小也有1MB,改变段基地址,由一个段变为另一个段,所以说,程序分段又是为了将大内存分成可以访问的小段,通过这样的变通的方法便能够访问到所有的内存。但是当时16位的寄存器并无法访问20位(1M是2的20次方)地址空间。CPU 的设计者在地址处理单元动了手脚,该地址部件接到“段基地址 + 段内偏移”的地址后,自动将 段基地址左移4位 + 段内偏移,使其成为20位物理地址。

代码中的分段

CPU是自动化程度极高的芯片,只要给出CPU第一个指定地址,CPU在它执行本指令的时候,他会自动获取下一条的地址。因为程序中的指令都是挨着的,彼此之间无空隙。但是为了对齐,编译器在程序塞了很多0,使程序出现了数据间空隙,指令间不存在空隙,下一条指令的地址是按照前面指令的尺寸大小安排下来的,这就是Intel处理器的程序计数器cs:eip能够自动获得下一条指令的原理,即当前eip中的地址加上当前指令机器码的大小便是内存中下一条指令的起始地址。即使指令间有空隙或其他非指令的数据,这也仅仅是在物理上将其断开了,依然可以通过jmp指令将非指令跳过以保持原指令在逻辑上的连续。为了让程序内指令连接不断的执行,要把指令全部安排在一起,形成一片连续的指令区域,这就是代码段。指令由操作码和操作数组成,而指令的操作数的数据,则连续地并排在一起存储在形成段落,叫数据段。

将代码和数据分开的好处:

  • 可以为它们赋予不用的属性。
  • 为了提高提高CPU内部缓存的命中率。
  • 节省空间。

段保护属性:

  • 编译器负责挑选出数据具备的属性,从而根据属性将程序片段分类。编译器并没有让段具备某种属性,对于代码段,编译器所做的只是将代码段归类到一起而已。
  • 操作系统通过设置GDT全局描述符表来构建段描述符,在段描述符中指定段的位置,大小以及属性(包括s字段和type字段)。
  • CPU中的段寄存器提前被操作系统赋予相应的选择子,从而确定了指向段。在执行指令时,会根据该段的属性来判断指令的行为,若没有返回则返回异常。
    总之,编译器、操作系统、CPU三个配合在一起才能对程序保护,检测出指令的违规行为。

总结一下,程序中的段只是逻辑上的划分,用于不同数据的归类,但是可以用CPU中的段寄存器直接指向它们,然后用内存分段机制去访问它们。内存分段机制指的是处理器为了访问内存而采用的机制,程序分段是软件中人为逻辑划分内存区域。

4.物理地址、逻辑地址、有效地址、线性地址、虚拟地址的区别

  • 物理地址:物理内存真正的地址,相当于内存中每个存储单元的门牌号,具有唯一性。不管什么模式下,不管是虚拟地址还是线性地址,cpu最终都要以物理地址去访问内存,只有物理地址才是内存访问的终点站。
  • 在实模式下,“段基地址+段内偏移”经过段部件的处理,直接输出的就是物理地址。
  • 在保护模式下,“段基地址+段内偏移”称为线性地址,它是一个段选择子的东西,本质是一个下标,通过这个下标索引能在GDT中找到相应的段描述符,该段描述符记录了段基地址、大小等信息。若是未开启了地址分页功能,该线性地址就是物理地址,若是开启了分页功能,此线性地址还有一个名字:虚拟地址,虚拟地址要经过页部件转换称具体的物理地址。
  • 逻辑地址:无论在实模式下还是保护模式下,段内偏移地址又称为有效地址,也称为逻辑地址。
    在这里插入图片描述

5.为什么Linux系统下的应用程序不能在Windows系统下运行

格式不同,Linux下的可执行文件是elf,也就是“Executable and Linking Format”。而Windows下的可执行文件是PE格式(portable executable,可移植的可执行文件)。
API不同,Linux中的API称为系统调用,是通过int 0x80这个软中断实现的。而Windows中的API是放在动态链接库中的。也就是说Linux中的可执行程序获取系统资源的方法和Windows不一样,所以显然是不能在Windows中运行的。
除了以上原因外,还有编译器、标准库有关。

6.局部变量和函数参数为什么要放在栈中

局部变量,顾名思义其作用域属于局部,并不像全局变量那样具有全局性。全局变量,意味着可以随时访问,所以将其放在数据段。而局部变量只是自己在用,为了不浪费空间,故将其放在自己的栈中。
堆是程序运行中动态内存分配的内存空间,是操作系统为每个用户进程规划的,属于软件范畴。
栈是处理器运行必备的内存空间,是硬件必需的,但又是软件(操作系统)提供的。堆栈是栈,和堆没关系。

函数参数放在栈区的原因:

  • 也是局部性导致的,只有这个函数会用到这个参数。
  • 函数在程序执行过程中是动态调用的,编译器无法预测何时调用以及被调用的次数。

7.为什么说汇编比C语言快

高级语言如C语言为了通用性等,需要兼顾的东西比较多,往往加入了一些额外的代码,因此编译出来的汇编代码比较多,很多部分都是一些周边功能,并不是直接起作用的,不如汇编语言直接写功能相关的部分效果来得更直接,C语言被编译成机器指令后,生成的机器指令当然包括这些额外的部分,相当于多执行了一些“看似没有”的指令,因此会比直接用汇编指令慢。

8.编译型程序与解释型程序的区别

解释型语言,也称为脚步语言,如JavaScript、Python、perl、PHP、Shell脚本等。它们本身是文本文件,是某个应用程序的输入,这个应用程序是脚本解释器。这些脚本中的代码在脚本解释器看来与字符串无异。脚本中的代码从来没真正在CPU去执行,CPU的cs:ip寄存器从来没有指向过它们,CPU眼里只看得到解释器,解释器就是一个进程,本质上是脚本解释器在分析这个脚本,动态根据关键字和语法来做相应的行为。
因此脚本中若出现了错误,先前正确的部分也会被正常执行,这和编译型程序有很大区别。
编译型程序,运行时本身就是一个进程。它是由操作系统直接调用。由操作系统加载到内存后,上CPU执行。

9.大段字节序与小端字节序

小段字节序与大端字节序是两种相反的排列顺序。

  • 小端字节序是数值的低字节放在内存的低地址处,数值的高字节放在内存的高地址。
  • 大端字节序是数值的低字节放在内存的高地址处,数值的高字节放在内存的低地址。

优势:

  • 小端:因为地位在低字节,强制转换数据型时不需要调整字节。
  • 大端:有符号数,其字节最高位不仅表示数值本身,还起到了符号的作用。符号位固定为第一字节,也就是最高位占据最低位置,符号直接可以取出来,容易判断正负。

常见的CPU字节序:

  • 小端字节序:x86、DEC
  • 大端字节序:IBM、Sun、PowerPC

ARM体系的CPU则大小端字节序通吃,具体用哪些字节序由硬件选择。
字节序不仅在CPU访问内存中的概念,而且在文件存储和网络传输中。bmp格式的图片就属于小端字节序,而jpeg格式的图片则为大端字节序,采用什么序列完全是开发者设计产品时的需要。
网络字节序就是大端字节序,所以在x86架构上的程序在发送网络数据时,需要转换字节顺序。

10.BIOS中断、DOS中断、Linux中断的区别

中断

在计算机系统中,无论是在实模式,还是在保护模式,在任何情况下就会有来自外部或内部的事件发生。如果事件来自于CPU内部就称为异常,即Exception。例如,CPU在计算算法时,发现分母为0,就抛出了除0异常。如果事件来自于外部,也就是该事件由外部设备法出并通知了CPU,这个设备就称为中断。

BIOS中断

BIOS和DOS都是存在于实模式下的程序,由它们创建的中断调用都是建立在中断向量表(Interrupt Vector Table,IVT),它们都是通过软中断指令int中断号来调用的。
BIOS中断调用的主要功能是提供了硬件访问的方法,该方法使对硬件的操作变得简单易行。操作系统通过int/out指令来读写外设的端口,BIOS中断程序处理是用来操作硬件的,故该处理程序中一定到处都是in/out指令。

BIOS添加中断处理例程原因:

  • 给自己用,因为BIOS也是一段程序,为了避免重复性的代码,把重复性执行的某段代码写出中断函数,直接调用。
  • 给后来的程序使用,如加载器或boot loader。它们在调用硬件资源时就不需要自己重写代码了。

BIOS如何设置中断处理例程:

  • BIOS也要调用别人的函数例程。首先硬件厂商为了让自己生产的产品以用,事先写好调用接口,直接给接口函数传入参数,硬件就能返回一个输出。
  • 每个外设,包括显卡、键盘、各种控制器等,都有自己的内存,不过这种内存都是只读存储器ROM。硬件自己的功能调用例程及初始化代码就存放在这ROM中。根据规范,第一个内存单元的内容是 0x55,第二个存储单元是0xAA,第三个存储单元以512字节为单位的代码长度。从第四个存储单元起就是实际的代码了。

访问外设的两种方式:

  • 内存映射:通过地址总线将外设自己的内存映射到某个内存区域。
  • 端口操作:外设都有自己的控制器,控制器上有寄存器,寄存器又称端口。通过in/out指令来读写端口来访问硬件的内存。

从内存的物理地址0xA0000 ~ 0xFFFFF这部分内存中,一部分是专门用来做映射的,如果硬件存在,硬件自己的ROM会被映射到这片内存中的某处。
BIOS在运行期间会扫描0xC0000 ~ 0xE0000 直接的内存,若发现某个区域前两个字节是0x55和0xAA时,意味着该区域对应的ROM中有代码存在,再对该区域做累加和检查,若结果等于第三个字节,说明代码无误,就从第四个字节进入。这时开始执行硬件自带的例程以初始化硬件自身,最后,BIOS填写中断向量表中相关项,使它们指向硬件自带的例程。
中断向量表中的第0H ~ 1FH项是BIOS中断。
中断向量表是CPU原生支持的,不用谁负责创建。软件能实现什么功能,取决于硬件提供了哪些支持。软件中只要执行int中断向量号,CPU便会把向量号当做下标,去中断向量表中定位中断处理程序并执行。

DOS中断

DOS是运行在实模式下的,故其建立的中断调用也建立在中断向量表中,只不过其中断向量和BIOS的不能冲突。
0x20 ~ 0x27是DOS中断。因为DOS在实模式下运行,故其可以调用BIOS中断。
DOS中断只占用0x21这个中断号。
DOS中断是通过先往ah寄存器中写好子功能号,再执行int 0x21,这时在中断向量表第0x21个表项,即物理地址0x21 * 4的中断处理程序开始根据寄存器ah中值来调用相应的子功能。

Linux中断

Linux内核是再进入保护模式才建立中断例程的,不过在保护模式下,中断向量标已经不存在了,取而代之的是中断描述符表(Interrupt Descriptor Table,IDT)。
Linux的系统调用和DOS中断调用类似,不过Linux是通过int 0x80指令进入一个中断程序后,再根据eax寄存器的值来调用不同的子功能函数的。

11. 指令集、体系结构、微架构

CISC复杂指令集和 RISC精简指令集,并不是具体的指令集,而是两种不同的指令体系,相当于指令集的门派,是指令集的设计思想。
指令集是具体的一套指令编码,微架构是指令集的物理实现方式。
x86指令集本属于CISC体系,但由于效率低下,最终在其内部实现上采取了RISC内核,即一条CISC指令在译码时,分解成多条RISC指令,这样其执行效率便与RISC媲美。
目前市面上常见的指令集有五种,除x86是CISC指令体系外,ARM、MIRS、Power、C6000都是RISC指令体系的指令集。
CPU与指令集是对应的,一种CPU只能识别一种指令集,所以很多CPU都以其支持的指令集来称呼。

12.库函数是用户进程与内核的桥梁

操作系统提供了一套系统调用接口,用户进程直接调用这些接口。接口就是某个功能模块的入口,通过接口提供该模块 一个输入,它就返回一个输出。
在Linux下C编程,写的程序通常是用户级程序,为了输出文本,一般会在文件开始include<stdio.h>。而这个库文件stdio.h,包含了库函数,调用系统调用的代码是库函数的功劳。
尽管系统调用封装在库函数中,但用户程序可以直接调用“系统调用”,不过用库函数会比较高效。

13.MBR、EBR、DBR和OBR

因为BIOS是位于逐步上的小程序,空间有限,代码量少,功能受限,必须采取控制权接力的方式,最终把处理器的使用权交给操作系统。

MBR

BIOS只完成一些简单的检测或初始化工作,然后把处理器使用权转交给MBR,MBR必须在固定的位置等待,因此MBR位于硬盘最开始的扇区,即0盘0道1扇区,这个扇区称为MBR引导扇区,这是约定好的,因此它会将0盘0道1扇区中的MBR引导程序加载到物理地址0x7c00,然后跳过去执行,这样BIOS就把处理器的使用权交给了MBR。一般情况扇区大小是512字节,但并不固定。

在MBR引导扇区中的内容:

  • 446字节的引导程序;
  • 64字节的分区表
  • 2字节结束标记 0x55 和 0xAA。

MBR引导扇区中除了引导程序外,还4个分区表项,占4 * 16字节,里面是分区信息,这4个分区就是“次引导扇区”,也就是操作系统加载器,这一步要做的就是将控制权交给操纵系统加载器,由该加载器完成操纵系统的自举,最终使控制权交付给操作系统内核。为了让MBR知道哪个分区安装了操作系统,该分区会被标记为“活动分区”(0x80),活动分区标记位于分区表项的第一个字节,其值是0x80或0。为了方便MBR找到内核加载器,内核加载器的入口地址是固定位置的,位于各分区的最开始的扇区,这也是约定好的。这个起始扇区中存放的是操作系统引导程序——内核加载器,该扇区称为操作系统引导扇区,其中的引导程序(内核加载器)称为操作系统引导记录OBR(OS Boot Record),所以此扇区也称OBR引导扇区。在OBR扇区的前三个字节存放跳转指令,这也是约定。小结,MBR找到活动分区后,主动跳到活动分区OBR引导扇区的起始处,该起始处的挑转指令马上将处理器带入操作系统引导程序,从此完成交接工作。

OBR和DBR

OBR是从DBR遗留下来的,所以从DBR开始了解。DBR是DOS Boot Record,也就是DOS操作系统的引导记录,DBR中的内容大概是:

  • 跳转指令,使MBR跳转到引导代码;
  • 厂商信息、DOS版本信息;
  • BIOS参数块BPB,即BIOS Parameter Block。
  • 操作系统引导程序
  • 结束标记 0x55 和 0xAA。

在DOS时代只有4个分区,不存在扩展分区,这4个分区都相当于主分区,所以各主分区最开始的扇区称为DBR引导扇区。后来有了扩展分区之后,无论主分区还是逻辑分区,为了兼容,分区最开始的扇区都作为DOS引导扇区。其他操作系统也继承了这个习俗,都将个分区最开始的扇区作为自己的引导扇区,里面存放着自己操作系统的引导扇区,由于操作系统类型太多,且DOS退出历史舞台,所以DBR也称OBR。

EBR

EBR是扩展分区中为了兼容MBR才提出的概念,主要是兼容MBR中的分区表。分区是用分区表来描述的。扩展分区中是一个个的逻辑分区,因此扩展分区也有分区表。为扩展分区存储分区表的扇区称为EBR,即Expand Boot Record。兼容的内容是分区表,因此它与MBR结构相同,只是位置不同,EBR位于各子扩展分区中最开始的扇区(主要,各主分区和各逻辑分区最开始的扇区都是操作系统引导扇区),理论上MBR只有1个,EBR有无数个。

总结

OBR其实就是DBR,指的都是操作系统引导程序,位于各分区(主分区和逻辑分区)最开始的扇区。OBR的数量与分区数有关,等于主分区数加逻辑分区数之和。一个子扩展分区中只包含一个逻辑分区。
MBR和EBR是分区工具创建和维护的,不属于操作系统管理的范围,不要随意修改其地址数据,OBR是个各分区的(主分区和逻辑分区)最开始的扇区,因此属于操作系统管理。
DBR、OBR、MBR、EBR都包含引导程序,因此称它们为引导扇区,只要该扇区中存在可执行的程序,该扇区就是引导扇区。若该扇区位于整个硬盘的起始扇区,并以0x55 和 0xaa结束,就认为该扇区是MBR引导扇区。若该扇区位于各个分区最开始的扇区,并且以0x55 和 0xaa结束,MBR就认为该扇区是OBR引导扇区。
DBR、OBR、MBR、EBR结构中都有引导代码和结束标记0x55 和 0xaa。最大的区别是分区表只存在MBR和EBR中。
在这里插入图片描述

发布了1 篇原创文章 · 获赞 0 · 访问量 188

猜你喜欢

转载自blog.csdn.net/qq_42457363/article/details/104074813