操作系统学习在我的职业生涯中打通了我的任督二脉。其中最重要的一个内容就是进程,没有之一。本文主要讲解进程切换的一些基本概念,然后手写实现2个进程如何做到交替执行。后面再配合内存虚拟化,最后我们再从磁盘中去加载应用程序执行,那么整个操作系统就写完了。内容其实也不多。
CPU 虚拟化
CPU 虚拟化是操作系统实现“多任务并行”的物理基础。其核心思想是通过硬件辅助或软件模拟,将物理 CPU 的计算资源抽象为多个虚拟 CPU(vCPU),每个虚拟 CPU 独立运行一个“逻辑执行环境”(即进程或线程)。这种隔离性确保了不同任务的执行不会互相干扰——就像同一栋大楼里的多个独立办公室,共享建筑基础设施(物理CPU),但拥有独立的门禁(地址空间)和工作权限(资源配额)。
对操作系统而言,每个进程本质上是一个“轻量级虚拟CPU”。操作系统通过调度器将物理 CPU 的时间片分配给不同进程的 vCPU,实现“并发执行”的错觉。例如,当进程A执行系统调用陷入内核时,其 vCPU 的特权级提升(从Ring 3到Ring 0),但 Hypervisor(或内核)会通过上下文保存/恢复机制,确保其他进程的 vCPU 不受影响。
PCB 与上下文
上面提到进程运行就是一个假象,操作系统通过调度器将物理 CPU 的时间片分配给不同进程执行。为了确保再次切换回来进程还能继续正常运行,那么我们需要在切换前保持一些数据,切换回来后再恢复之前的一些数据,这些需要保存和恢复的数据就叫做上下文,所以进程切换是一个耗 CPU 的动作。
进程切换保存和恢复的数据就叫做上下文,那么上下文数据肯定要放在内存中的某个位置,我们可以用一个结构体来储存它们,这个结构体就叫做进程控制块,简称 PCB,我们也可以叫做任务控制块,叫什么不重要,重要的是有那么个意思。
手写实现简单进程切换
task_t* task_1 = (task_t*)0x1000;
task_t* task_2 = (task_t*)0x3000;
#define PAGE_SIZE 0x1000
extern void task_switch(task_t* next);
task_t* running_task() {
asm volatile(
"movl %esp, %eax\n"
"andl $0xfffff000, %eax\n"
);
}
// 这次课只写 2 个进程交互切换,而且是主动的。
void task_schedule() {
// 获取当前的进程
task_t *current = running_task();
task_t *next = current == task_1 ? task_2 : task_1;
// 切到下一个
task_switch(next);
}
void task_main_1() {
while (true)
{
int i = 2;
int j = 2;
int sum = i + j;
printk("1");
task_schedule();
}
}
void task_main_2() {
while (true)
{
int a = 1;
int b = 1;
int sum = a + b;
printk("2");
task_schedule();
}
}
; 今天这个课有点理解成本,也不做要求,
; 进程:理解跨进程通信,理解线程线程锁的基础。
global task_switch
task_switch:
push ebp
mov ebp, esp
push ebx
push esi
push edi
mov eax, esp
and eax, 0xfffff000; current
mov [eax], esp ; current->stack = esp
mov eax, [ebp + 8] ; next
mov esp, [eax]; esp = next->stack
pop edi
pop esi
pop ebx
pop ebp
ret ; 软件切换这里有一个漏洞利用, 看一下之前的内容。