操作系统实战¶
目录
备注
对于一个庞大的系统,最大的误区是陷入细节而不知全貌。
手动控制以上这个编译流程,从而留下中间文件方便研究:
$ gcc HelloWorld.c -E -o HelloWorld.i
预处理:加入头文件,替换宏。
$ gcc HelloWorld.c -S -c -o HelloWorld.s
编译:包含预处理,将 C 程序转换成汇编程序。
$ gcc HelloWorld.c -c -o HelloWorld.o
汇编:包含预处理和编译,将汇编程序转换成可链接的二进制程序。
$ gcc HelloWorld.c -o HelloWorld
链接:包含以上所有操作,将可链接的二进制程序和其它别的库链接在一起,形成可执行的程序文件
1. 源文件生成预处理文件: gcc -E HelloWorld.c -o HelloWorld.i
2. 预处理文件生成编译文件: gcc -S HelloWorld.i -o HelloWorld.s
3. 编译文件生成汇编文件: gcc -c HelloWorld.s -o HelloWorld.o
4. 汇编文件生成可执行文件:gcc HelloWorld.o -o HelloWorld
整体设计¶
内核结构&设计¶
硬件资源有:
1. 总线,负责连接各种其它设备,是其它设备工作的基础。
2. CPU,即中央处理器,负责执行程序和处理数据运算。
3. 内存,负责储存运行时的代码和数据。
4. 硬盘,负责长久储存用户文件数据。
5. 网卡,负责计算机与计算机之间的通信。
6. 显卡,负责显示工作。
7. 各种 I/O 设备,如显示器,打印机,键盘,鼠标等。
软件资源有:
计算机中的各种形式的数据。如各种文件、软件程序等
内核作为硬件资源和软件资源的管理者,其内部组成在逻辑上大致如下:
1. 管理 CPU,由于 CPU 是执行程序的,而内核把运行时的程序抽象成进程,所以又称为进程管理。
2. 管理内存,由于程序和数据都要占用内存,内存是非常宝贵的资源,所以内核要非常小心地分配、释放内存
3. 管理硬盘,而硬盘主要存放用户数据,而内核把用户数据抽象成文件,即管理文件,文件需要合理地组织,方便用户查找和读写,所以形成了文件系统。
4. 管理显卡,负责显示信息,而现在操作系统都是支持 GUI(图形用户接口)的,管理显卡自然而然地就成了内核中的图形系统。
5. 管理网卡,网卡主要完成网络通信,网络通信需要各种通信协议,最后在内核中就形成了网络协议栈,又称网络组件。
6. 管理各种 I/O 设备,我们经常把键盘、鼠标、打印机、显示器等统称为 I/O(输入输出)设备,在内核中抽象成 I/O 管理器。
除了这些必要组件之外,根据功能不同还有:
a. 安全组件
b. 驱动程序
管理和控制各种硬件而编写的对应的代码
内核架构:
1. 宏内核架构
2. 微内核架构
宏内核有极致的性能,微内核有极致的可移植性、可扩展性。
操作系统内核分为三个大层,分别是:
1. 内核接口层
定义了一系列接口,主要有两点内容,如下:
这套接口的代码,就是检查其参数是否合法,如果参数有问题就返回相关的错误,
接着调用下层完成功能的核心代码。
2. 内核功能层
内核功能层的模块如下:
a. 进程管理
b. 内存管理
c. 中断管理
d. 设备管理
3. 内核硬件层
主要包括一个具体硬件平台相关的代码,如下:
a. 初始化,初始化代码是内核被加载到内存中最先需要运行的代码
例如初始化少量的设备、CPU、内存、中断的控制、内核用于管理的数据结构等
b. CPU 控制,提供 CPU 模式设定、开、关中断、读写 CPU 特定寄存器等功能的代码
c. 中断处理,保存中断时机器的上下文,调用中断回调函数,操作中断控制器等
d. 物理内存管理,提供分配、释放大块内存,内存空间映射,操作 MMU、Cache 等
e. 平台其它相关的功能,有些硬件平台上有些特殊的功能,需要额外处理一下
程序的基石:硬件¶
CPU 的工作模式有:
1. 实模式
实模式又称实地址模式,实,即真实,这个真实分为两个方面,
一个方面是运行真实的指令,对指令的动作不作区分,直接执行指令的真实功能,
另一方面是发往内存的地址是真实的,对任何地址不加限制地发往内存。
2. 保护模式
3. 长模式
现代内存管理模式——分页模型:
把虚拟地址空间和物理地址空间都分成同等大小的块,也称为页,按照虚拟页和物理页进行转换。
根据软件配置不同,这个页的大小可以设置为 4KB、2MB、4MB、1GB
对于每个进程而言,它会误认为(被操作系统欺骗)自己独有所有地址空间,因此它访问地址是不会考虑任何问题的,可是这个地址是虚拟地址,待被MMU翻译后会得到对应的页表,而这个页表由操作系统管理,不同的进程拥有不同的页表,也因此产生了进程地址空间隔离,但是多个进程也是可以共享某个页表,这也是进程通信(IPC)的根本手段。
备注
CPU 大多数时间在执行相同的指令或者与此相邻的指令。这就是大名鼎鼎的程序 局部性原理
如何写出让 CPU 跑得更快的代码:
1、遵从80-20法则,程序80%的时间在运行20%或更少的代码,针对热代码进行优化,才容易产出效果;
2、遵从数据访问的局部性法则,按数据存放顺序访问内存效率远高于乱序访问内存效率,也就是尽量帮助CPU做好数据Cache的预测工作。同样根据Cache大小,做好数据结构的优化工作,进行数据压缩或数据填充,也是提升Cache效率的好方式;
3、遵从指令访问的局部性法则,减少跳转指令,同样是尽量帮助CPU做好数据Cache的预测工作;现代CPU都有一些预测功能【如分支预测】,利用好CPU的这些功能,也会提升Cache命中率;
4、避免计算线程在多个核心之间漂移,避免缓存重复加载,可以绑定核心【物理核即可,不用到逻辑核】,提高效率;
5、去除伪共享缓存:在多核环境下,减少多个核心对同一区域内存的读写并发操作,减少内存失效的情况的发生;
===开始跑题===
6、合理提高进程优先级,减少进程间切换,可以变相提供Cache提速的效果
7、关闭Swap,可以变相提供内存提速、Cache提速的效果;
8、使用Intel自家的编译器,开启性能优化,很多时候可以提速运算效率;
9、使用C语言,而不是更高级的语言,很多时候可以提速运算效率;
10、直接使用昂贵的寄存器作为变量,可以变相提供加速效果;
同步原语¶
使用信号量的步骤:
第一步,获取信号量。
第二步,代码执行流开始执行相关操作,例如读取键盘缓冲区。
第三步,释放信号量。
锁是解决并发同步问题的关键,锁有两个核心点:
一个是原子操作
通过原子操作来实现临界区标志位的改变,
一个则是中断
关闭中断来避免 CPU 中途离开导致数据同步失败问题
参考¶
王爽老师的《汇编语言》: https://book.douban.com/subject/25726019/
李忠老师的《X86 汇编语言:从实模式到保护模式》
《计算机组成与设计:软硬件接口》
《UNIX 传奇:历史与回忆》(兴趣拓展向)
李忠老師的《穿越計算機的迷霧》,學習一點硬件原理