假设有两个进程 A
和 B
,在进程 A
切换到进程 B
时,操作系统需要保存和恢复进程状态。
保存进程 A 的状态
- 临时保存到内核栈:进程 A 的状态首先被临时保存到内核栈中。
- 持久保存到
task_struct
中:然后,这些状态信息从内核栈复制到task_struct
的thread_struct
中。
恢复进程 B 的状态
- 从
task_struct
恢复到内核栈:进程 B 的状态信息从task_struct
的thread_struct
中恢复到内核栈中。 - 从内核栈恢复到寄存器:然后,这些状态信息从内核栈中恢复到CPU寄存器中
每个进程拥有独立的虚拟地址空间,这些地址空间通过页表映射到物理内存。操作系统在创建新进程时,会为其分配一个新的页表,并在进程运行期间维护该页表
程序执行时,CPU会进行一系列步骤来确保程序正确运行。以下是程序执行过程中CPU所做的一些主要工作:
1.取指令(Fetch):
从内存中取出下一条指令。指令的地址由程序计数器(PC)提供。
将取出的指令存放在指令寄存器(IR)中。
2.译码(Decode):
解析指令,确定需要执行的操作以及所需的操作数。
指令译码器将指令翻译成CPU能够理解的信号。
3.取操作数(Fetch Operands):
从寄存器或内存中取出指令需要的操作数。
如果操作数在内存中,需要通过地址总线访问内存。
4.执行(Execute):
根据指令类型执行相应的操作,例如算术运算、逻辑运算、内存访问等。
运算结果通常存储在寄存器中。
5.存储结果(Store Result):
将执行结果存储在指定的寄存器或内存位置。
更新程序计数器(PC):
更新PC以指向下一条指令的地址。对于顺序执行的程序,PC通常递增;对于跳转或分支指令,PC会更新为新的地址。
中断处理(Interrupt Handling):
检查是否有中断请求。如果有,暂停当前指令流,保存上下文,并跳转到中断处理程序。
处理完中断后,恢复上下文,继续执行原来的程序。
内存管理(Memory Management):
使用页表进行地址转换,将虚拟地址转换为物理地址。
处理内存保护、分页缺失等异常情况。
管道和并行处理(Pipelining and Parallel Processing):
现代CPU通常采用流水线技术,允许多个指令在不同阶段同时进行,提高指令吞吐量。
支持超标量和多核处理器,能同时执行多条指令或多个线程。
缓存管理(Cache Management):
利用缓存(如L1、L2、L3缓存)提高内存访问速度。
处理缓存命中和未命中,优化数据访问效率。
fork和vfork是UNIX和Linux系统中用于创建子进程的两个系统调用,虽然它们都用于创建新进程,但它们的行为和实现方式有显著差异。以下是它们的主要区别:
1. 地址空间
fork:创建子进程时,父进程的整个地址空间被复制到子进程中。子进程获得父进程数据的副本,两者彼此独立。
vfork:子进程与父进程共享相同的地址空间,直到子进程调用exec函数或_exit为止。子进程对内存的任何修改都会直接影响父进程。
2. 性能
fork:由于需要复制父进程的地址空间,fork的开销较大,尤其是对于内存占用较大的进程。
vfork:不复制地址空间,性能较高。适用于子进程立即调用exec的情况。
3. 子进程调度
fork:父进程和子进程可以独立运行,调度上没有特别限制。
vfork:在某些实现中,子进程会先运行,父进程会被挂起,直到子进程调用exec或_exit。这样可以防止子进程在修改共享地址空间时干扰父进程。
4. 用途
fork:适用于一般情况,可以安全地用于创建任何类型的子进程。
vfork:专门用于创建之后立即调用exec函数的子进程,适用于快速创建新进程的场景。
5. 安全性
fork:由于地址空间独立,父子进程间的互不干扰,安全性较高。
vfork:共享地址空间,如果子进程在调用exec或_exit之前修改了父进程的内存,可能会导致不安全的行为。
每个进程的虚拟地址空间的布局通常是相似的,包含以下部分:
代码段:存储可执行代码。
数据段:存储已初始化和未初始化的全局变量和静态变量。
堆:用于动态内存分配,向高地址方向增长。
栈:用于存储函数调用栈帧、本地变量等,向低地址方向增长。
共享库:存储动态链接库,通常映射在地址空间的高端。
页表和地址转换
操作系统使用页表来管理虚拟地址到物理地址的映射:
页表结构:
每个进程有自己的页表。
页表条目记录虚拟页到物理页的映射关系。
地址转换:
CPU在访问内存时,通过MMU(内存管理单元)使用页表将虚拟地址转换为物理地址。
操作系统在进程切换时会更新页表基地址寄存器,以指向当前进程的页表。
每个进程和线程都有自己的内核栈,用于在内核态执行时保存函数调用链、局部变量和上下文信息。内核栈的独立性确保了进程和线程在内核态操作时互不干扰,便于操作系统管理和调度。通过合理的内核栈管理,操作系统能够实现高效的多任务处理和中断处理。