用户态、内核态隔离
有一些指令是只能在内核态执行的,这些指令一般都涉及到硬件的直接操作,计算机不允许用户直接操作这些指令,只能通过系统调用(syscall)由内核帮助用户完成这些操作。
这些操作包括:
- 硬件控制:如设置中断使能、修改处理器状态寄存器。
- 内存管理:如修改页表、设置内存保护。
- 进程管理:如创建或终止进程、更改进程优先级。
- I/O 操作:直接访问硬件设备或端口。
- 系统调用相关:如切换到内核态以执行系统服务。
注意和获取超级用户权限分开,典型的就是执行 sudo echo 1 > /dev/port 会提示无权限,就算是 root 用户也无权限执行硬件的特权指令。
用户的进程始终运行在用户态,只有在调用系统调用,比如 open 时,才会短暂的进入内核态,并且也不是用户编写的代码在内核态执行,而是内核中的代码执行,并将结果返回给用户编写的程序。
进程的内存分配算法
最佳适应算法名字叫最佳,但是实际效果可能最差,因为它只管在内存中同时塞下更多的内存,导致非常容易出现大量的小空隙,导致这些内存无法使用,产生内存碎片。
计算机开机流程
- 主板通电,由于通电之前什么信息都没有,所以一上电之后,PC 寄存器被强制指向 ROM 固件(Bios/UEFI),这是 CPU 制造商在硬件内部写死的一段固定地址,系统启动的时候必须从这里开始执行。
- 在 BIOS 或者是 UEFI 中的固件启动,负责初始化最基本的硬件,包括内存控制器、总线、显卡,并根据设置的启动顺序,加载 Bootloader(从硬盘/U 盘中)
- Bootloader 是操作系统的先导程序,负责将操作系统的内核加载到内存中,BootLoader 是操作系统提供的,Linux 用 GRUB,Windows 自带 Windows Boot Manager,Bootloader 的使命就是把内核从硬盘或者 USB 中加载到内存中,并运行它。
- 内核一开始并没有用户态、没有进程管理、没有虚拟内存这些概念,需要自己在最原始的状态自举(太难了),然后逐步初始化中断描述符等硬件结构、初始化分页机制、内存管理、进程调度器、初始化驱动、挂载跟文件系统等事情。最后内核会创建第一个进程 Init,其 PID 为 1.
- 该进程是第一个用户态进程,负责启动所有服务(ssh、dockerd、networkd 等)、启动登录程序(tty、gdm 等)、负责启动桌面环境(gnome、kde)等,对 Linux 来说,该进程名字是 systemd.
创建新进程的流程
操作系统通过 PCB 管理进程,当创建新进程的时候,OS 会申请新的空白 PCB,然后对其初始化。具体过程如下:
用户态的程序并不能直接创建操作系统,只能够通过系统调用,让内核帮忙创建新进程,例如调用 fork() 函数,内部封装了内核状态下执行的 clone() 函数,负责真正创建进程。
当系统调用执行后,CPU 会切换当前的特权等级,从 Ring 3 切换到 Ring 0,变成内核态,跳转到内核的系统调用入口,执行 clone 函数。具体操作如下:
- CPU 执行系统调用(syscall),CPU 切换为 Ring 0 模式,保护用户态的寄存器,切换到内核栈,跳转到系统调用的入口处。
- 分配一个 PCB,PCB 中保存了寄存器 context、虚拟内存结构、文件描述符表、调度信息(时间片优先级等)、内核栈指针、进程树关系(父子兄弟)等。
- 由于是 fork 操作,所以会有页表的复制,文件描述符表的复制、权限结构的复制。
- 每个进程都有独立的用户态栈和内核态栈,内核会给该进程分配一个新的内核态的栈。
- 父进程会返回子进程 PID,子进程会返回 0
- 将新进程加入调度器。
- 从内核态返回用户态(执行 sysret),恢复用户态寄存器,切换到用户态栈,CPU 回到 Ring 3 模式,父进程会继续运行。
线程相关
线程有两种,一种是内核级线程,一种是用户级线程,顾名思义,内核级线程是 OS 内核控制,管理线程控制块;用户级线程则是用户进程自己创建、自己维护,操作系统不感知,因此用户级线程不需要操作系统支持,并且线程间切换比内核级的效率要高(不需要进入内核态)
死锁相关
银行家算法只能检测当前系统是不是处在安全状态(有安全序列),不能够检测是否死锁。不存在安全序列只说明存在死锁的可能,不代表一定会进入死锁状态。因为进程在现实情况中并不一定会都申请到最大的资源使用量,而且进程可能会先释放一部分资源。
关键词表
唤醒进程:将程序从阻塞态->就绪态