};
每个运行起来的任务都需要这样一个结构体,当任务需要暂停时就把处理器上下文保存在context结构体中,需要恢复任务运行时就根据context中数据恢复处理器状态。
现在你就可以同时运行多个任务了,当任务A读取慢速磁带时就暂停任务A的运行并把CPU分配给任务B,这样你可以充分利用宝贵的机器资源。
多个程序相互干扰
当系统中可以运行多个任务后你发现了新的问题,那就是多个程序之间会相互干扰,因为在早期计算机系统中,程序被链接到固定的内存基址,且加载器缺乏重定位能力。当多个程序加载到内存时,程序中的变量可能被分配到相同的物理地址,导致互相覆盖。
举个例子:
// program1.c
intglobal_data = 100; // 全局变量
intmain{
while(1) {
global_data++; // 不断增加全局变量的值
...
}
return0;
}
// program2.c
intglobal_data = 100; // 同名全局变量
intmain{
while(1) {
global_data--; // 不断减少全局变量的值
...
}
return0;
}
这个示例中两个同时运行的程序global_data变量的内存地址可能相同,因此两个程序的运行会相互干扰,原因你很清楚,因为它们共享同一个内存空间,你开始意识到,仅仅依靠程序员的自觉来避免互相干扰是不够的,需要从系统层面提供隔离机制。
于是,你开始设计一个新的抽象概念,让各个运行的程序彼此隔离,为每个程序提供独立的内存空间,你决定采用段氏内存管理,每个运行的程序中的各个段都有自己的内存区域:
structmemory_map{
uint32_tcode_segment; // 代码段起始地址
uint32_tcode_size; // 代码段大小
uint32_tdata_segment; // 数据段起始地址
uint32_tdata_size; // 数据段大小
uint32_tstack_segment; // 栈段起始地址
uint32_tstack_size; // 栈段大小
};
进程诞生了
现在你设计了struct context以及struct memory_map,显然它们都属于某一个运行起来的程序,“运行起来的程序”是一个新的概念,你给起了个名字叫做进程,process,现在进程上下文以及内存映射都可以放到进程这个结构体中:
structprocess{
structcontextctx;
structmemory_mapmem;
};
就这样你实现了操作系统最核心的功能:多任务。
进程这种设计效果嗷嗷好:
用户程序再也不会意外修改其他程序的数据
可以同时运行多个程序,在它们之间来回切换
即使一个程序崩溃,也不会影响其他程序的运行
用户程序再也不会意外修改其他程序的数据
可以同时运行多个程序,在它们之间来回切换
即使一个程序崩溃,也不会影响其他程序的运行
不过,新的挑战也随之而来...
进程切换的性能瓶颈
多任务系统的使用解决了多用户共享计算机的问题。但很快,你就发现了一个令人头疼的新问题:随着系统中运行的进程越来越多,整个系统的响应速度开始明显下降。
通过仔细观察和测试,你发现问题出在进程切换上。每次从一个进程切换到另一个进程时,系统都需要执行大量的工作。
看一下你实现的进程:
structprocess{
structcontextctx;
structmemory_mapmem;
};
进程切换时处理器上下文和内存映射都需要切换,尤其对于现代操作系统中的页式内存管理来说内存映射切换的开销非常高(CR3切换、TLB刷新等)。
是否有必要创建过多进程?
真的有必要创建这么多进程吗?你仔细检查了一个开启大量进程的web服务器,web服务器会创建多个工作进程来处理不同的HTTP请求,这些工作进程运行完全相同的代码来处理请求,却各自占用一份独立的内存空间,同时这些进程在切换时又会带来大量开销。
但是等等,既然这些进程使用的是相同的代码,为什么不能让它们共享这部分内存呢?你开始意识到,也许可以创造一种新的执行单元,它们能共享进程的大部分资源,同时又保持足够的独立性,如果多个执行流可以共享同一个进程的资源,那切换的开销不就能大大降低了吗?
这个想法最终引导你走向了一个全新的概念。
线程概念的诞生
经过反复设计,你找到了一个突破性的解决方案:同一个进程内部支持多个执行流。这个想法来源于一个关键观察 ,很多时候,我们其实并不需要完全独立的进程,只需要能够并行执行不同的任务就够了。
于是,你设计了一个全新的概念 —— 线程。每个线程都是进程内的一个独立执行单元,它们:
共享进程的地址空间,这意味着所有线程可以直接访问相同的内存区域
共享打开的文件描述符,避免了重复打开关闭文件的开销
共享其他系统资源,如信号处理函数、进程工作目录等
仅维护独立的执行栈和寄存器状态,确保每个线程可以独立执行
共享进程的地址空间,这意味着所有线程可以直接访问相同的内存区域
共享打开的文件描述符,避免了重复打开关闭文件的开销
共享其他系统资源,如信号处理函数、进程工作目录等
仅维护独立的执行栈和寄存器状态,确保每个线程可以独立执行
这就是线程的诞生故事,它完美平衡了资源共享和执行独立性,是操作系统发展史上一个重要的里程碑。返回搜狐,查看更多