多任务
是操作系统实现的必要功能, 多任务部分负责创建/销毁/调度进程or线程
无论是宏内核还是微内核, 其内核都必须具备多任务调度的机制
系统进程
是运行在R0
特权级的进程, 一般这类进程为IDLE进程, 系统服务进程, 拥有PC的最高操作权限
IDLE 进程
是操作系统第一个创建的进程, 唯一一个不通过fork()
函数复制得来的进程, 其PID为0
该进程的调度优先级为最低, 一般在调度队列为空时调度该进程
操作系统的初始化等工作由该进程完成(在多核中由CPU0的IDLE进程完成,且有几个CPU核心就有几个IDLE进程)
在操作系统内核初始化完成后该进程并不会销毁,而是循环执行hlt
以降低CPU功耗
- CoolPotOS的系统进程创建 task.c kernel_thread(line:322)
用户进程
运行在 R3
特权级的进程, 一般这类进程为应用程序的进程, 只能访问特定内存区域且无法执行特权级指令(如hlt``sti
lgdt
等)
- 在x86平台下, CPU无法从
R3
特权级直接切换到R0
, 同时R0
也无法直接切换至R3
, 所以在x86中唯一从R0
切换至R3
的方式是使用iret
指令跳转 - 涉及特权级切换时, 内核必须配置TSS(Task Statu Segments : 任务状态段), 该结构是
GDT
的一个段描述符 - 每个用户进程会在内核有一块单独的栈, 该栈用于用户进程系统调用时候存储
R0
特权级执行时产生的各种数据
在CoolPotOS中, 用户进程在刚创建时是R0特权级, 这时候内核会往该进程的栈中插入一个名为
switch_to_user_mode
的函数并传入真正的用户程序入口地址switch_to_user_mode
函数负责初始化进入R3
前各种寄存器的值
最后使用iret
指令跳转到用户程序入口地址, 这个时候就切换至R3
特权级了
- CoolPotOS的用户进程创建 task.c kernel_thread(line:205)
TSS
任务状态段, 负责存储一个任务的当前CPU状态, 其包含了CPU所有的寄存器(控制寄存器以及GDTR IDTR除外). 是旧版x86平台用于任务切换的一个数据结构。
在现代操作系统中并不会使用该数据结构去进行任务调度, 因为PCB(Process Control Block : 进程控制块)取代了它。 PCB相比TSS拥有更快的任务切换速度, 且可以自定义多种进程私有的数据结构(如PID TTY设备映射 调度时间片计数等)。
但这并不代表不需要配置TSS, TSS的作用只是缩减到切换内核栈地址的作用
任务调度
任务调度有很多方式(抢占式调度/顺序调度), 内核会使用合理的方式分配给这些任务一定的CPU时间片, 一旦过了该任务的CPU时间片就会触发该任务调度
在用户程序发生系统调用时禁止进行该CPU的任务切换, 不然syscall无法正确返回
进程切换过程中, 需要切换的寄存器可以参考TSS的结构, 以下展示了进程如何切换上下文
struct context{
uint32_t esp;
uint32_t ebp;
uint32_t ebx;
uint32_t esi;
uint32_t edi;
uint32_t ecx;
uint32_t edx;
uint32_t eflags;
uint32_t eax;
uint32_t eip;
uint32_t ds;
uint32_t cs;
uint32_t ss;
fpu_regs_t fpu_regs; // FPU 标记, 是否启用FPU
};
- CoolPotOS的顺序任务调度 task.c kernel_thread(line:167)
补充
进程间的资源隔离
因为不同的进程之间页目录不同, 拥有不同的内存布局, 不同进程间相同的线性地址指向的是不同的物理地址
因此该地址获取的数据也不同线程与进程的区别
线程在上下文切换的寄存器要比进程少(只会切换esp
ebp
eax
ebx
等寄存器),
所以线程之间的资源可以共享。有关于更多的
syscall
系统调用的信息, 请参考用户程序部分。