用户态、内核态隔离

有一些指令是只能在内核态执行的,这些指令一般都涉及到硬件的直接操作,计算机不允许用户直接操作这些指令,只能通过系统调用(syscall)由内核帮助用户完成这些操作。
这些操作包括:

注意和获取超级用户权限分开,典型的就是执行 sudo echo 1 > /dev/port 会提示无权限,就算是 root 用户也无权限执行硬件的特权指令。

用户的进程始终运行在用户态,只有在调用系统调用,比如 open 时,才会短暂的进入内核态,并且也不是用户编写的代码在内核态执行,而是内核中的代码执行,并将结果返回给用户编写的程序。

进程的内存分配算法

最佳适应算法名字叫最佳,但是实际效果可能最差,因为它只管在内存中同时塞下更多的内存,导致非常容易出现大量的小空隙,导致这些内存无法使用,产生内存碎片。

计算机开机流程

  1. 主板通电,由于通电之前什么信息都没有,所以一上电之后,PC 寄存器被强制指向 ROM 固件(Bios/UEFI),这是 CPU 制造商在硬件内部写死的一段固定地址,系统启动的时候必须从这里开始执行。
  2. 在 BIOS 或者是 UEFI 中的固件启动,负责初始化最基本的硬件,包括内存控制器、总线、显卡,并根据设置的启动顺序,加载 Bootloader(从硬盘/U 盘中)
  3. Bootloader 是操作系统的先导程序,负责将操作系统的内核加载到内存中,BootLoader 是操作系统提供的,Linux 用 GRUB,Windows 自带 Windows Boot Manager,Bootloader 的使命就是把内核从硬盘或者 USB 中加载到内存中,并运行它。
  4. 内核一开始并没有用户态、没有进程管理、没有虚拟内存这些概念,需要自己在最原始的状态自举(太难了),然后逐步初始化中断描述符等硬件结构、初始化分页机制、内存管理、进程调度器、初始化驱动、挂载跟文件系统等事情。最后内核会创建第一个进程 Init,其 PID 为 1.
  5. 该进程是第一个用户态进程,负责启动所有服务(ssh、dockerd、networkd 等)、启动登录程序(tty、gdm 等)、负责启动桌面环境(gnome、kde)等,对 Linux 来说,该进程名字是 systemd.

创建新进程的流程

操作系统通过 PCB 管理进程,当创建新进程的时候,OS 会申请新的空白 PCB,然后对其初始化。具体过程如下:
用户态的程序并不能直接创建操作系统,只能够通过系统调用,让内核帮忙创建新进程,例如调用 fork() 函数,内部封装了内核状态下执行的 clone() 函数,负责真正创建进程。

当系统调用执行后,CPU 会切换当前的特权等级,从 Ring 3 切换到 Ring 0,变成内核态,跳转到内核的系统调用入口,执行 clone 函数。具体操作如下:

  1. CPU 执行系统调用(syscall),CPU 切换为 Ring 0 模式,保护用户态的寄存器,切换到内核栈,跳转到系统调用的入口处。
  2. 分配一个 PCB,PCB 中保存了寄存器 context、虚拟内存结构、文件描述符表、调度信息(时间片优先级等)、内核栈指针、进程树关系(父子兄弟)等。
  3. 由于是 fork 操作,所以会有页表的复制,文件描述符表的复制、权限结构的复制。
  4. 每个进程都有独立的用户态栈和内核态栈,内核会给该进程分配一个新的内核态的栈。
  5. 父进程会返回子进程 PID,子进程会返回 0
  6. 将新进程加入调度器。
  7. 从内核态返回用户态(执行 sysret),恢复用户态寄存器,切换到用户态栈,CPU 回到 Ring 3 模式,父进程会继续运行。

线程相关

线程有两种,一种是内核级线程,一种是用户级线程,顾名思义,内核级线程是 OS 内核控制,管理线程控制块;用户级线程则是用户进程自己创建、自己维护,操作系统不感知,因此用户级线程不需要操作系统支持,并且线程间切换比内核级的效率要高(不需要进入内核态)

死锁相关

银行家算法只能检测当前系统是不是处在安全状态(有安全序列),不能够检测是否死锁。不存在安全序列只说明存在死锁的可能,不代表一定会进入死锁状态。因为进程在现实情况中并不一定会都申请到最大的资源使用量,而且进程可能会先释放一部分资源。

关键词表

唤醒进程:将程序从阻塞态->就绪态

同步和互斥