操作系统前言
(一)写操作系统,哪些需要我来做
我们需要做的就是知道如何通过计算机指令来控制硬件。
(二)软件是如何访问硬件的?
- 将某个外设的内存映射到一定范围的地址空间中。CPU通过地址总线访问该区域时会落在外设的内存中。
比如显卡,CPU不直接与显示器交互,它只与显卡通信。显卡上有片内存叫显存,它被映射到主机物理内存上的低端1MB的0xB000~0xBFFFF。CPU访问这片内存就是访问显存,往这片区域上写字节就是往屏幕上打印内容。 - 外设是通过IO接口与CPU通信的。
CPU访问IO接口,由IO接口将信息传递给另一端的外设。
(三)内核态和用户态
- 用户态和内核态是对CPU来说的,是指CPU运行在用户态(特权3级)还是内核态(特权0级)。
- 用户程序陷入内核态:由于中断的发生,当前进程被迫终止执行,其上下文被内核的中断程序保护起来后,开始执行一段内核代码(是内核代码,不是用户程序在内核的代码)。
- 当应用程序陷入内核后,它自己已经下CPU了,以后的事情,应用程序完全不知道。那时的CPU上运行的程序已经是内核程序了。
一、内存管理
(一)16位寄存器如何访问20位地址空间
地址部件接到“段基址+段内偏移地址”的地址后,自动将段基址乘16,即左移了4位,再和16位的段内偏移地址相加,得到20位地址。
(二)为什么高级语言不允许程序员将代码分段?
因为编译器是针对某个操作系统编写的,该操作系统用的是平坦模型,所以编译器要编译出适合此操作系统加载运行的程序。由于处理器支持分页机制的虚拟内存,操作系统也采用了分页模型,所以编译器会将程序按照内容划分为代码段和数据段等。
(三)程序分段
编译器将程序中只读代码编译出来,放在代码段;将已经初始化的数据放在数据段,将具有全局属性但未初始化的数据放在bss段(BSS段的特点是它的内容在程序加载到内存时会被初始化为0或空值。因此,对于未初始化的全局变量和静态变量,编译器和链接器会将其放置在BSS段,并在程序加载到内存时,自动将BSS段的内容初始化为0。)
(四)物理地址、逻辑地址、有效地址、线性地址、虚拟地址的区别?
- 实模式:段基址+段内偏移地址=物理地址,CPU直接用该地址访问内存
- 保护模式:段基址+段内偏移地址=线性地址。此时的段基址为选择子,类似于数组下标,本质是索引。
未开启分页模式:线性地址=物理地址
开启分页模式:线性地址=虚拟地址,通过分页机制转换为物理地址。
(五)什么是段重叠?
因为寻址方式是段基址+段内偏移地址,所以0xC00:3和0xC02:1都是访问同一个内存块。内存像是重叠在一起,就是段重叠。
(六)什么是平坦模型?
平坦模型就是指一个段,段大小是地址总线能够达到的范围。
(七)cs,ds这类sreg段寄存器,位宽是多少?
- CPU内部的段寄存器(Segment reg):
寄存器 | 英文 | 名称 | 作用 |
---|---|---|---|
CS | Code Segment Register | 代码段寄存器 | 其值为代码段的段基址 |
DS | Data Segment Register | 数据段寄存器 | 其值为数据段的段基址 |
ES | Extra Segment Register | 附加段寄存器 | 其值为附加数据段的段基址,称为“附加”是因为该寄存器用途不像其他sreg那样固定,可以额外作他用 |
FS | 附加段寄存器 | 和ES一样 | |
GS | 附加段寄存器 | 其值为附加数据段的段基址 | |
SS | Stack Segment Register | 堆栈段寄存器 | 其值为堆栈段的段值 |
- 在实模式下,装入段寄存器的是段基址(16位);在保护模式下,装入段寄存器的是“段选择子”(16位)。
- 在32位的CPU中,sreg无论是工作在16位的实模式下,还是32位的保护模式下(16位选择子),用的段寄存器都是16位。
(八)局部变量和函数参数为什么要放在栈中?
- 全局变量意味着谁随时随地都可以访问,所以要放在数据段中。局部变量只有自己用,放在数据段中浪费时间,所以放入自己的栈中,随时可以清理。
- 堆是程序运行过程中用于动态内存分配的内存空间,是OS为每个用户进程规划的,属于软件;栈是处理器运行必备的空间,是硬件必需的,但又由软件(OS)提供的。
- 函数参数放在栈的原因:
1.局部性原理:只有函数用此参数,没必要放在数据段中。
2.函数是在程序执行过程中调用的,属于动态调用,编译时无法预测会何时调用以及被调用的次数,但函数的参数和返回值都需要内存来存储。
(九)二十七、转义字符与ASCII码
- ASCII码中字符按照可见分为两大类,可见和不可见字符。
- “转义字符+可见字符”—>不可见字符的ASCII码。
二、程序运行
(一)编译型程序和解释型程序的区别?
- 编译型语言编译出的程序,运行时本身就是一个进程,它由操作系统直接调用的。也就是由操作系统加载到内存后,操作系统将cs:ip寄存器指向这个进程的入口,使它直接上CPU。
- 解释型语言,也成为脚本语言。它们本身是文本,这些脚本的代码在脚本解释器看来和字符串无异。脚本代码没有上CPU执行,CPU只能看到脚本解释器。
- 解释型语言的运行步骤:
- 解释器加载源代码:解释器将源代码读取到内存中,并进行语法分析和词法分析,将代码转换为解释器可以理解的中间表示形式。
- 逐行解释执行:解释器按顺序逐行解释源代码,并执行与每行代码相关的操作。解释器会一次执行一行代码,而不需要将整个程序转换为可执行的形式。
- 即时编译(可选):有些解释器在解释执行过程中可以使用即时编译(Just-in-Time Compilation,JIT)技术,将某些代码块或热点代码转换为机器码,以提高执行效率。
- 结论:编译型语言是吃熟食,解释型语言是吃火锅,边下边吃。
(二)什么是大端字节序、小端字节序
- 定义:(对于数字而言的,因为指令不需要拆分)
小端字节序:低字节放低地址,高字节放高地址
大端字节序:低字节放高地址,高字节放低地址
- 优点:
- 小端:因为低位在低地址,强制类型转换时不需要调整字节
- 大端:有符号数,其字节最高位不仅表示数值本身,还起到符号的作用。符号在最低位可以直接取出减少时钟周期。
- 怎么判断大小端字节序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
int main(int argc, char* argv[])
{
int val = 1;
char* p = (char*)&val;
if (*p == 1) {
printf("小端存储......\n");
} else {
printf("大端存储......\n");
}
return 0;
}
- 应用:
网络字节序是大端字节序,所以x86架构上的程序在发送网络数据时,要转换字节序。
大端字节序:IBM、Sun、PowerPC
小端字节序:x86、DEC
(三)BIOS中断、DOS中断、Linux中断的区别
- 异常:
来自CPU内部的为Exception
来自CPU外部的为Error - 中断向量表(Interrupt Vector Table,IVT)
- 每个中断向量的大小是4字节,包含一个中断处理程序的段基址和段内偏移地址。
- IVT大小是1024字节,所以该表最多容纳256个中断向量处理程序。
- 计算机启动时,IVT的中断例程是由BIOS建立的,它从物理内存0x0000处初始化并在IVT中添加各种处理程序。
- IVT是实模式下的,保护模式下用中断描述符表(Interrupt Descriptor Table ,IDT)。
- CPU访问ROM的两种方式:
- 内存映射:通过地址总线将外设自己的内存映射到某个内存区域(并不是映射到主板上插的内存条)
- 端口操作:外设都有自己的控制器,控制器上有寄存器,这些寄存器就是所谓的端口,通过in/out指令读写端口来访问硬件的内存。
- 控制显卡是内存映射+端口操作。
- 外设
- 每个外设(显卡,键盘,控制器等)都有自己的内存,这种内存都是只读存储器ROM。
- ROM规范:第一个内存单元内容为0x55,第二个为0xAA,第三个为该ROM中以512字节为单位的代码长度,第四个存储单元起就是实际代码,直到第三个存储单元所示的长度为止。
- 从内存的物理地址0xA0000到0xFFFFF这部分内存中,一部分是专门用来做映射的,如果硬件存在,硬件自己的ROM会被映射到这片内存中的某处。
- BIOS和DOS
- 都是存在于实模式下的程序
- 通过建立中断向量表来建立中断,通过软中断指令int中断号来调用
- BIOS中断调用的主要功能是提供了硬件访问的方法。
- BIOS在运行期间会扫描xxxx之间的内存,若在某个区域发现前两个字节是0x55和0xAA时,这意味着该区域有ROM中的代码。再对该区域做累加和检查,若发现与第三个字节的值相符,则从第四个字节进入。这时开始执行硬件自带的进程和初始化硬件自身,最后,BIOS填写IVT,使它们指向硬件自带的进程。
- DOS是0x20到0x27,因为DOS在实模式下运行,可以调用BIOS中断。
- DOS中断只占用0x21这个中断号,也就是DOS只有这一个中断进程。
DOS执行过程:
向ah寄存器写好子功能号
执行int 0x21
- Linux
- Linux内核是进入保护模式后才建立中断进程的。
- 保护模式下用中断描述符表(Interrupt Descriptor Table ,IDT)
(四)Section和Segment的区别
- C程序运行阶段
- 预处理
将高级语言中的宏展开,去掉代码注释,为调试器添加行号等。 - 编译
将预处理后的高级语言进行词法分析、语法分析、语义分析、优化,最后生成汇编代码。 - 汇编
将汇编代码编译成目标文件,也就是转换成目标机器平台上的机器指令。 - 链接
将目标文件连接成可执行文件(exe)
- 预处理
- section(节)
- 关键字section和segment修饰的区域都是“节”
- 操作系统加载进程时并不关心节的数量和大小,只关心节的属性。
因为内存访问要涉及到段描述符,所以段描述符的权限很重要(只读,可读写等) - 操作系统加载程序时,不需要对逐个节进行加载,只要给出相同权限的节的集合即可。
操作系统可以为它们分配不同的段选择子,从而指向不同的段描述符,实现不同的访问权限。
- segment(段)
- 汇编器只生成目标文件,尚未链接,因此将“节”合并的工作是由链接器完成的。
- segment(段):链接器将目标文件中属性相同的节合并成一个大的section集合
(五)什么是魔数
- 在程序中直接出现一个数字,只要意义不明确,就被称为“魔数”
(六)操作系统是如何识别文件系统的?
- 文件系统有自己的魔数,一种文件系统对应一个魔数。
- 超级块:一般位于本分区的第2个扇区,超级块里记录此分区的信息。
- 识别文件系统:超级块中有文件系统的魔数,比对值就知道文件系统的类型了。
(七)如何控制CPU的下一条指令
- 在x86中不可以用mov修改程序流
- x86的PC并不是单一寄存器,它是一种组合——cs:ip
- 不可以用mov修改指令寄存器:mov指令一次只能改变一个寄存器,不能同时将cs和ip都改变。
- 有专门改变执行流的指令:jmp、call、int、ret等,这些指令可以同时修改cs和ip,从硬件级别上实现了原子操作。
- 在ARM中可以用mov修改程序流
- 在ARM的CPU中,寄存器的名称在汇编语言中是以“r数字”开头的,PC有个专门的寄存器。
mov pc,r0 #将寄存器r0的内容给程序寄存器pc
- 总结:程序计数器PC负责处理器的执行方向,它只是获取下一条指令的方法形式,在不同体系结构的CPU中有不同的实现方法。
(八)指令集、体系结构、微架构、编程语言
- 指令集:具体的一套指令编码
- 微架构:指令集的物理实现方式。(在物理上将0传入寄存器ax等)
- 交叉编译:在A平台上运行的编译器,编译出符合B平台的CPU指令集的程序,编译出的程序直接能在B平台上运行。
(九)库程序是用户进程与内核的桥梁
-
系统调用:操作系统提供了一套系统调用接口,用户程序直接调用这些接口即可。用户程序经过系统调用才能使用操作系统的功能。
-
接口:接口是某个功能模块的入口,通过接口给该模块一个输入,它就返回一个输出。
-
库函数:包含了系统调用的代码。
-
尽管系统调用封装在库函数中,但用户程序可以直接调用“系统调用”,不过库函数会比较高效。
-
CRT运行时库
- gcc内部要将c语言经过编译、汇编、链接三个阶段
- #include<标准头文件>会有效,是因为编译器提供的c运行库准备好了这些标准函数的函数体所在的目标文件,在链接的时候链接上了。
所以<>只能用于标准头文件,自定义头文件要用“”,不然编译器会去标准头文件的地址寻找目标文件。
- 目标文件都是可重定位文件,文件中的函数没有地址,用file命令查看时会显示relocatable。它们中的地址是在和用户程序的目标文件链接成一个可执行文件时由链接器统一分配的。
- c运行时库中相同的函数与不同的用户程序链接时,其生成的可执行文件中分配给库函数的地址都可能是不同的。每一个可执行文件中都有这些库的副本,相当于库文件被复制到每个用户程序中。
(十)MBR、EBR、DBR和OBR各是什么?
- BIOS:
- 计算机接电后运行的是基本输入输出系统BIOS,BIOS只完成一些简单的检测或初始化工作,然后找机会把处理器使用权交给MBR。
- 为了方便BIOS找到MBR,MBR必须在固定位置等待,所以MBR位于整个硬盘最开始的扇区,,这个扇区便称为MBR引导扇区。
- 扇区记录方法:
- CHS:扇区从1开始,即0盘0道1扇区
- LBA: 扇区从0开始,即0盘0道0扇区
- 在MBR引导扇区中存储引导程序,方便BIOS将MBR引导程序加载到物理地址0x7c00,然后跳过去执行,这样BIOS就把处理器使用权交给MBR了。
- 引导扇区中的内容是:
- 446字节的引导程序和参数
- 64字节的分区表
- 2字节结束标记0x55和0xaa
- MBR(Master/Main Boot Record):
- MBR引导扇区中除了引导程序外,还有64字节大小的分区表。每个分区表项占16字节,所以MBR分区表可容纳4个分区。这4个分区就是“次引导程序”的候选人。
- MBR引导程序开始遍历这4个分区,找出操作系统所在的分区并将系统控制权交给它。
- 在分区时,如果想在某个分区中安装操作系统,就用分区工具将该分区设置为活动分区、设置活动分区的本质是把分区表中该分区对应的分区表项中的活动标记为0x80。MBR知道活动分区中存在操作系统。
- 活动分区标记位于分区表项中最开始的1字节,其值要么为0x80要么为0。
- MBR想要跳转到的是活动分区上的内核加载器,因此内核加载器的入口地址也必须固定在各分区最开始的扇区。“各分区起始的扇区”存放的是操作系统引导程序——内核加载器(OBR),此扇区也被称为)OBR引导扇区。
- 因此MBR找到活动分区后,就跳到活动分区OBR引导扇区的起始处,该起始处的跳转指令马上将处理器带入操作系统引导程序。从此MBR完成了交接工作,以后便是内核的天下。
- OBR(内核加载器)(OS Boot Record)