第0章 一些你可能正感到迷惑的问题
软件如何访问硬件
● cpu通过 接口访问硬件。总体上大致分为串行和并行。
访问外部硬件有两个方式:
- 将某个外设的内存映射到一定范围的内存空间中,cpu通过地址总线访问该内存区域会落到相应外设的的内存中。举例:显卡,汇编时候,显存被映射到主机物理内存上的低端1M 的0xB8000~0xBFFFF地址上。
- 外设通过IO接口与CPU进行通信。CPU访问外设,就是访问IO接口。
访问IO接口本市上就是访问那些寄存器,这些寄存器就是人么常说的端口。
应用程序是什么,和操作系统是如何配合到一起的?
他们两都是软件,CPU知道去cs:ip寄存器中指向的内存取指令并执行。
总结,操作系统是人想出来的,为了让自己管理计算机方便而创造出来的一套管理方法。
- 其实没有语言的存在,有的只是编译器。语言只是编译器和大家的约定。
- 编译器提供了一套库函数,库函数中又有封装的系统调用。
- 应用程序是通过系统调用来和操作系统配合完成某项功能的。
- 应用程序加上操作系统才算是完整的程序。
用户态和内核态是对CPU来讲的:是指CPU运行在用户态(特权3级)还是内核态(特权0级)。
注意,用户进程永远不会因为进入内核态而变身为操作系统。
编译器下载的时候对应操作系统版本号,因为已经把宿主系统的系统调用号写死了。不同系统不一样。linux系统,直接嵌入汇编代码“int 0x80”便可以直接执行系统调用。
为什么称为“ 陷入 ” 内核
● 当用户程序访问系统资源时,需要进行系统调用,CPU进入内核态,也称为管态。
内存访问为什么要分段?
首先,内存是随机读写设备,就是访问其内部任何一处,不需要从头开始找,只要直接给出其地址便可。
分段是内存访问机制,是给CPU用的访问内存的方式,只有CPU才关注段。
分段是从8086开始的,16位寄存器,那时没有虚拟地址一说,只有物理地址,访问任何存储单元都可以直接给出物理地址。
在直接以绝对物理地址访问内存的CPU上运行程序,该程序中指令的地址也必须得是绝对物理地址。
总结,要想在一个硬件上运行,就要遵循该硬件的规则,操作系统和编译器无一例外。
但是这样也就产生了一个重要问题——若加载程序运行,不论是内核程序,还是用户程序,程序中的地址若都是绝对物理地址,那该程序必须放在内存中固定的地方,于是,两个编译出来地址相同的用户程序无法同时运行,只能运行一个。
为了解决以上的问题,所以,采用分段的方式,让CPU采用“段基址+段内偏移地址”的方式来访问任意内存,这样的好处是程序可以重定位了,尽管程序指令中给的是绝对物理地址,但是可以同时运行多个程序了。
重定位:简单的说,就是将程序中的指令的地址改写成另外一个地址,但该地址处的内容还是原地址处的内容。
要想访问某个物理地址,只要凑出合适的段基地址和段内偏移地址,其和为该物理地址就行了。
关键点:只要程序分了段,把整个段平移到任何位置后,段内的地址相对于段基址是不变的,无论段基址是多少,只要给出段内偏移地址,CPU就能访问到正确的指令。于是加载用户程序时,只要将整个段的内容复制到新的位置,再将段基址寄存器中的地址改成该地址,程序便可准确无误地运行,因为程序中用的是段内偏移地址,相对于新的段基址,该偏移地址处的内存内容还是一样的。
说白了,就是段基址,变了,但是段偏移地址没变,也就是相对位置没变,因为段的内容是直接拷贝的,所以段的内容也没变,所以可以找到原来的内容的拷贝。
一个段最多访问64KB。改变段基址,有一个段变为另一个段,就像一个段在内存中滑动,采用这种在内存中来回挪位置的方式可以访问到任意内存位置。
所以,程序分段又是为了将大内存分成可以访问的小段,通过这样变通的方法就可以访问到所有的内存。
代码中为什么分为代码段、数据段?这和内存访问机制中的段是一回事吗
● 首先指令是连续紧凑的,只要给出 CPU 第一个指令的起始地址, CPU 在它执行本指令的同时,它会自动获取下一条的地址,
然后重复上述过程,继续执行,继续取址 。下一条指令的地址是按照前面指令的尺寸大小排下来的。其次是通过机器码能够判断当前指令长度,当前指令地址 + 当前指令长度 = 下一条指令地址。
指令的连续是指执行逻辑上的连续性。
● 只有数据间有空隙,指令间不存在有空隙。
● 指令是由操作码和操作数组成的,操作数就是指程序中的数据。 把数据连续地并排在一起存储形成的段落,就称为数据段。
- 编译器负责挑选出数据具备的属性,从而根据属性将程序片段分类,比如,划分出制度属性的代码段和可写属性的数据段。
- 操作系统通过设置GDT全局描述符表来构建段描述符,在段描述符中指定段的位置、大小以及属性。
- 这里,操作系统认为代码应该是只读的,所以给用来指向代码段的那个段描述符设置了只读的属性,这才是真正给段添加属性的地方。
- CPU中的段寄存器提前被操作系统赋予相应的选择子,从而确定了指向的段,在执行指令时,会根据该段的属性来判断指令的行为,若有返回则发出异常。
物理地址、逻辑地址、有效地址、线性地址、虚拟地址的区别?
● 物理地址就是物理内存真正的地址,相当于内存中每个存储单元的门牌号,具有唯一性。不管在什么模式下,不管什么虚拟地址、线性地址, CPU 最终都要以物理地址去访问内存。
在实模式下,“段基址+段内偏移地址”经过段部件的处理,直接输出的就是物理地址,CPU可以直接通过此地址访问内存。
在保护模式下,“段基址 + 段内偏移地址”称为线性地址,不过,此时的段基址已经不是真正的地址了,而是选择子。若没有开启地址分页功能,此线性地址就被当作物理地址来用,可直接访问内存。 若开启了分页功能,此线性地址又多了一个名字,就是 虚拟地址 (虚拟地址、线性地址在分页机制下都是一回事) 。
无论在实模式或是保护模式下,段内偏移地址又称为有效地址,也称为逻辑地址,这是程序员可见的地址 。
线性地址或称为虚拟地址,都不是真实的内存地址。他们都用来描述程序或任务的地址空间。由于分页功能是需要在保护模式下开启的,32位系统保护模式下的寻址空间式4GB,所以虚拟地址或线性地址就是0~4GB的范围。
什么是段重叠
● 不管在实模式下还是保护模式下都会存在段重叠。
什么是平坦模型
● 平坦模型是相对于多段模型来说的,所以说平坦模型指的就是一个段 。
平坦模型不需要额外打开 A20 地址线。 多段模型需要额外打开 A20 地址线。
Cs Ds这类sreg段寄存器位宽是多少
● 32 位 CPU 有两种不同的工作模式:实模式和保护模式。
每种模式下,段寄存器中值的意义是不同的,但不管其为何值,在段寄存器中所表达的都是指向的段 在哪里。
- 在实模式下, cs 、 DS 、 ES 、 SS 中的值为段基址,是具体的物理地址,内存单元的逻辑地址仍为 “段基值:段内偏移量”的形式。
- 在保护模式下,装入段寄存器的不再是段地址,而是“段选择子” C Selector), 当然,选择子也是数值,其依然为 16 位宽度。
sreg 都是 16 位宽。
什么是工程,什么是协议
● 软件中的工程是指开发一套软件所需要的全部文件,包括配置环境。它相当于一个大目录,以后写的代码都在这里面。全部文件包含实际代码和环境配置两部分。
● 协议是一种大家共同遵守的规约,主要用来实现通信、共享、协作。
局部变量和函数参数为什么要放在栈中
● 全局变量是放在数据段中的 (static 也属于全局性),意味着谁都可以随时随地访问。局部变量是放在栈中的, 因为是给自己用的, 放在数据段中纯属浪费空间。
堆是程序运行过程中用于动态内存分配的内存空间,是操作系统为每个用户进程 规划的,属于软件范畴。栈是为处理器运行必备的内存空间,是硬件必需的,但又是由软件(操作系统)提供的。
因为函数是在程序执行过程中调用的,在函数的编译阶段根本无法确定它会被调用几次,所以也不知道其会需要多少内存 。 所以局部变量和函数参数要放在栈中存储。
什么是大端字节序、小端字节序
● 内存是以字节为单位读写的,其最小的读写单位就是字节。
- 小端字节序是数值的低宇节放在内存的低地址处,数值的高宇节放在内存的高地址。
- 大端字节序是数值的低宇节放在内存的高地址处,数值的高宇节放在内存的低地址。
看看这两种字节序的优势:
- 小端:因为低位在低字节,强制转换数据型时不需要再调整字节了。
- 大端:有符号数,其字节最高位不仅表示数值本身,还起到了符号的作用。符号位固定为第 一字节,也就是最高位占据最低地址,符号直接可以取出来,容易判断正负。
Section 和 Segment的区别
C 程序大体上分为预处理、编译、汇编和链接 4 个阶段。
- 预处理阶段是预处理器将高级语言中的宏展 开,去掉代码注释,为调试器添加行号等。
- 编译阶段是将预处理后的高级语言进行词法分析、语法分析、 语义分析、优化,最后生成汇编代码。
- 汇编阶段是将汇编代码编译成目标文件,也就是转换成了目标机器 平台上的机器指令。
- 链接阶段是将目标文件连接成可执行文件。
总结:
- section 称为节,是指在汇编源码中经由关键字 section 或 segment 修饰、逻辑划分的指令或数据 区域,汇编器会将这两个关键字修饰的区域在目标文件中编译成节,也就是说“节”最初诞生于目标文件中 。
- segment 称为段,是链接器根据目标文件中属性相同的多个 section 合并后的 section 集合,这个集合称为 segment,也就是段,链接器把目标文件链接成可执行文件,因此段最终诞生于可执行文件中 。 我们平时所说的可执行程序内存空间中的代码段和数据段就是指的 segment。
在大多数情况下,这两者都被混为一谈。
什么是魔数
魔数: 不明就理地出现一个数字,不知道其是什么意思,感觉看不透,猜不出,就像魔法一样很神秘。
操作系统是如何识别文件系统的
通过魔数来识别,一种文件系统对应一个魔数, 已对此值便知道文件系统类型 了。