操作系统--扩展知识
写在最前面:在学习操作系统的过程中遇到一些不懂的地方,在本篇文章中按照章节进行整理记录。内容大多来自网络和自己的理解。
操作系统-多进程图谱
fork()的流程与实现
计算机通过
fork()
来创建进程,fork()的核心是通过复制父进程来创建子进程
,换句话说,计算机中所有的进程都是通过继承0号进程和1号进程
而来的。所以这里我们先介绍这两个初始进程的建立.,而后再介绍fork()这个系统调用的实现流程。
从0号/1号进程的建立开始
操作系统的第一个进程:
0号进程
在最开始时,操作系统中是没有进程的,也就谈不上通过fork()
进行建立。所以0号进程
是通过手动设置进程信息来建立的,这些信息包括:PCB
、内核栈
、用户栈
、用户程序
等.
先来看一下PCB的初始化程序:
其中,task_struct
是一个结构体,属于Linux定义的PCB数据结构。而后通过设置具体的值来初始化这个数据结构,包括:通过LDT
设置进程使用的地址空间,通过这个表可以在执行程序时找到此进程的代码段进和数据段;tss
可以完成PCB与内核栈的关联。
而后进行内核栈设置
内核栈
是在中断进入内核时使用的,如果0号进程需要还要内核栈:根据tss中的信息将内核栈段寄存器SS设置为0x10,并将ESP设置为一个地址,这个地址根据进程初始化信息有所不同。
进行用户栈的设置与两栈之间的关联
用户栈
的设置就是直接划分一段内存即可,而后将用户程序对应的CS:IP
信息与用户站的SS:SP
信息放到内核栈即可完成二栈关联,即iret
后可以由内核程序返回到该进程的用户程序进行执行。
到这里,0号进程就已经建立起来了:其PCB
中保存着此进程的信息,内核区域中有一部分区域用于其内核栈,用户区域也有一部分用于其用户栈,并且关于此进程的用户程序也被放到内存,这些程序的位置也被保存。
再之后就要执行0号进程的后续程序,也就是前面我们介绍操作系统启动的最后4句程序的后两句:
操作系统的第二个进程:
1号进程
查看上图代码,0号进程会执行”if (!fork() { init();} for(;;) pause();}
“
代码解释:
0号进程会调用fork()
创建1号进程,并且根据函数的返回值可以看出,1号进程会执行init()函数
,而0号进程因为不满足if条件会执行后续的pause()
从而通过此系统调用将自己暂停,为其他进程让出CPU。
而1号进程调用的init()函数会执行execve()
系统调用,从而在1号进程的壳子里执行可执行程序”/bin/sh
“,这就是shell程序,所以1号进程也被成为shell进程。
shell进程的工作就是等待用户输入指令并通过fork()创建新的进程并使用exec()执行这个指令。
fork()的原理和流程
[先看这个](https://cloud.tencent.com/developer/article/1396092)
,后面再具体整体与深入。
GDT、LDT
背景介绍:段寄存器
保护模式下的段寄存器由16位的选择器
与64位的段描述符寄存器
构成,前者可见。其结构如下图:
我们主要关注选择器,这个选择器类似保护模式下的段寄存器,通过其中的内容可以在段表中查询对应内容以完成地址翻译。
其中:16位选择子前13位为索引号
也就是在段表中的位置;第14位为0表示查询GDT表,为1表示查询LDT表
;最后两位表示特权级
。
段寄存器的作用就是为了查询段表获取虚拟地址,下面我们就具体介绍一下这些段表:
全局描述符表GDT(Global Descriptor Tables)
GDT表
是一张操作系统层次的表,具有唯一性。它是许多描述符组成的一张描述符表,描述符就是用来描述一个段的信息,由8个字节组成(64bit),其中信息包括了段地址和偏移地址界限、特权级等一些信息。
如上图所示就是一个段描述符,其实也就是内存中的一段长度为64位的数字,不过这些数字根据位数不同表示代表不同含义,但是最终都可以分割成:段基址、段界限、类型、特权级等数据。
而其中的段基址就是指虚拟内存中某一段的基址,在加上偏移即可得到某一数据具体的虚拟地址。
同时,GDT的本质就是一段内存空间,其可以存储在任何位置,只要这个空间的基址被保存下来即可,这个保存GDT表基址的寄存器就是GDTR寄存器
。
局部描述符表LDT(Local Decriptor Table)
LDT表
也是一种段表,不过与GDT表不同,LDT表不是唯一的,而是每一个进程都有一个对应的段表,用来存储此进程程序分段存储情况。
与GDT表类似,LDT表也是一段内存空间,所以要寻找到他需要知道其基址。不过不同的是,LDT表的基址是存储在GDT表中的,并且在LDTR寄存器
中记录了当前进程LDT表在GDT表的索引。换句话说,通过修改LDTR中的内容即可获取到不同进程的LDT表。
在获取LDT表的基址后,就要根据段选择子的索引去获取具体段的基址。
具体的示意图如下:
两个实例看两表:以
call 400
为例
此程序需要访问400位置处的程序,但是这里的400只是偏移,要访问到具体的物理地址还要先进行地址翻译。
首先根据CS寄存器中的数据决定查表。如果TI=0则要查GDT表,为1则要查LDT表。
查询GDT表
- 根据GDTR寄存器中信息获取到GDT表的基址。
- 根据CS寄存器中前13位构成的索引号在GDT表中查询到对应的段描述符。
- 根据段描述符即可得到代码段的虚拟内存段基址,而后根据偏移400即可得到虚拟地址。
- 根据虚拟地址查询页表即可得到对应的页框并获取到其中存储的程序。
查询LDT表
- 根据LDTR寄存器中前13位组成的索引在GDT表中获取到段描述符,此描述符是LDT表的描述符。
- 根据段描述符定位到LDT表并通过CS寄存器获取到代码段的基址。
- 根据代码段基址和偏移得到虚拟地址并查询页表得到物理地址。
如上例所示,查询LDT表比之查询GDT表多一个步骤,即通过GDT表获取到LDT表基址。
MMU是什么
MMU是
Memory Management Unit
的缩写,中文名是内存管理单元,有时称作分页内存管理单元(英语:paged memory management unit
,缩写为PMMU)。它是一种负责处理中央处理器(CPU)的内存访问请求的计算机硬件。它的功能包括虚拟地址到物理地址的转换
(即虚拟内存管理)、内存保护、中央处理器高速缓存的控制,在较为简单的计算机体系结构中,负责总线的仲裁以及存储体切换(bank switching,尤其是在8位的系统上)。
为什么要介绍MMU
因为在学习内存管理部分时涉及到了,不过当时的学习重点放在了原理层次,但是我对于实现地址翻译功能的硬件很感兴趣。于是在此进行扩展学习。
MMU的功能
1、将虚拟地址映射为物理地址。获取到虚拟地址后并不是直接送到总线的,而是经过MMU的转换。
如上图所示,MMU
是一个封装在CPU中的硬件,CPU每次执行访问内存的指令都会自动引发MMU做查表和地址转换操作,地址转换操作由硬件自动完成,不需要用指令控制MMU去做.
查表是指查询LDT表和页表,这两个表在正课学习中有介绍。
2、内存保护:在访问VA时进行特权级检查
通过前面的学习,我们知道程序执行时是有特权级的,也就是CPL;而内存中的各个区域也是有特权级的,也就是DPL。同时在页表中也为每一个页设置了访问权限:可读、可写、可执行
。
所以,当程序要访问内存时就要进行权限的检测,只有符合权限才可以进行访问操作。而这个检测权限的操作就是由MMU负责完成。
PCB中有什么
为了描述控制进程的运行,系统中存放进程的管理和控制信息的数据结构称为
进程控制块(PCB Process Control Block)
,它是进程实体的一部分,是操作系统中最重要的记录性数据结构。它是进程管理和控制的最重要的数据结构,每一个进程均有一个PCB,在创建进程时,建立PCB,伴随进程运行的全过程,直到进程撤消而撤消。
PCB中记录了操作系统所需的,用于描述进程的当前情况以及控制进程运行的全部信息。PCB的作用是使一个在多道程序环境下不能独立运行的程序(含数据),成为一个能独立运行的基本单位,一个能与其他进程并发执行的进程
。或者说,OS是根据PCB来对并发执行的进程进行控制和管理的。例如,当OS要调度某进程执行时,要从该进程的PCB中查处其现行状态及优先级;在调度到某进程后,要根据其PCB中所保存的处理机状态信息,设置该进程恢复运行的现场,并根据其PCB中的程序和数据的内存始址,找到其程序和数据;进程在执行过程中,当需要和与之合作的进程实现同步,通信或者访问文件时,也都需要访问PCB;当进程由于某种原因而暂停执行时,又须将器断点的处理机环境保存在PCB中。可见,在进程的整个生命期中,系统总是通过PCB对进程进行控制的,即系统是根据进程的PCB而不是任何别的什么而感知到该进程的存在的。所以说,PCB是进程存在的唯一标志
一个进程由PCB+进程程序+进程数据+进程工作区
组成,其中PCB无疑是最重要的。一个PCB一般由以下部分组成:
程序ID(PID、进程句柄)
:它是唯一的,一个进程都必须对应一个PID。PID一般是整形数字特征信息
:一般分系统进程、用户进程、或者内核进程等进程状态
:运行、就绪、阻塞,表示进程现的运行情况优先级
:表示获得CPU控制权的优先级大小通信信息
:进程之间的通信关系的反映,由于操作系统会提供通信信道现场保护区
:保护阻塞的进程资源需求、分配控制信息
进程实体信息
,指明程序路径和名称,进程数据在物理内存还是在交换分区(分页)中其他信息
:工作单位,工作区,文件信息等
更多信息参考如下地址: