Linux操作系统分析Lab5:深入理解进程切换

一、进程切换是如何进行的

在 Linux 内核中,进程切换是通过上下文切换(Context Switch)来实现的。当一个进程需要被挂起,让出 CPU 给其他进程执行时,操作系统会将当前进程的上下文(包括 CPU 寄存器和内存映像等状态)保存在进程的内核态栈(Kernel Stack)中,然后将 CPU 分配给下一个需要执行的进程,并将其对应的上下文恢复到 CPU 寄存器和内存中,使其能够继续执行。

下面是进程切换的具体流程:

当一个进程被调度器选中需要被挂起时,操作系统会保存当前进程的所有寄存器状态到进程的内核态栈中,包括通用寄存器、堆栈指针、程序计数器等。这样可以保证进程下一次执行时能够恢复到之前执行的状态。

然后操作系统会从就绪队列中选中一个需要执行的进程,将其相应的上下文信息加载到 CPU 的寄存器和内存中,使其能够开始执行。

当 CPU 执行到一个时间片结束或者进程主动放弃 CPU 执行权时,操作系统会将当前进程的上下文保存到内核态栈中,同时将 CPU 分配给下一个需要执行的进程。这个过程就是上下文切换。

在切换到下一个进程时,操作系统会从内核态栈中加载下一个进程的上下文信息到 CPU 寄存器和内存中,使其能够继续执行。这样就完成了进程的切换。

二、代码分析

/*
 * context_switch - switch to the new MM and the new thread's register state.
 */
static __always_inline struct rq *
context_switch(struct rq *rq, struct task_struct *prev,
	       struct task_struct *next, struct rq_flags *rf)
{
    
    
	prepare_task_switch(rq, prev, next);

	/*
	 * For paravirt, this is coupled with an exit in switch_to to
	 * combine the page table reload and the switch backend into
	 * one hypercall.
	 */
	arch_start_context_switch(prev);

	/*
	 * kernel -> kernel   lazy + transfer active
	 *   user -> kernel   lazy + mmgrab() active
	 *
	 * kernel ->   user   switch + mmdrop() active
	 *   user ->   user   switch
	 */
	if (!next->mm) {
    
                                    // to kernel
		enter_lazy_tlb(prev->active_mm, next);

		next->active_mm = prev->active_mm;
		if (prev->mm)                           // from user
			mmgrab(prev->active_mm);
		else
			prev->active_mm = NULL;
	} else {
    
                                            // to user
		membarrier_switch_mm(rq, prev->active_mm, next->mm);
		/*
		 * sys_membarrier() requires an smp_mb() between setting
		 * rq->curr / membarrier_switch_mm() and returning to userspace.
		 *
		 * The below provides this either through switch_mm(), or in
		 * case 'prev->active_mm == next->mm' through
		 * finish_task_switch()'s mmdrop().
		 */
		switch_mm_irqs_off(prev->active_mm, next->mm, next);

		if (!prev->mm) {
    
                            // from kernel
			/* will mmdrop() in finish_task_switch(). */
			rq->prev_mm = prev->active_mm;
			prev->active_mm = NULL;
		}
	}

	rq->clock_update_flags &= ~(RQCF_ACT_SKIP|RQCF_REQ_SKIP);

	prepare_lock_switch(rq, next, rf);

	/* Here we just switch the register state and the stack. */
	switch_to(prev, next, prev);
	barrier();

	return finish_task_switch(prev);
}

  1. prepare_task_switch(rq, prev, next):用于准备任务切换,主要是更新两个进程的状态和调度队列等信息。

  2. arch_start_context_switch(prev):用于在切换进程之前执行一些体系结构相关的操作。对于 paravirt 架构,这个函数会和 switch_to() 函数中的一部分代码合并为一个 hypercall。

  3. enter_lazy_tlb(prev->active_mm, next):用于进行懒惰 TLB 刷新,它会延迟 TLB 刷新操作,直到需要访问一个无效的页表项时才会刷新 TLB。这样可以减少 TLB 刷新的次数,提高系统性能。

  4. switch_mm_irqs_off(prev->active_mm, next->mm, next):用于切换两个进程的地址空间,其中 prev->active_mm 是前一个进程所使用的地址空间,next->mm 是下一个进程所使用的地址空间。

  5. rq->prev_mm = prev->active_mm:用于将前一个进程的地址空间保存在调度队列中,以便在下一次调度时能够恢复它的状态。

  6. prepare_lock_switch(rq, next, rf):用于准备锁切换,主要是更新调度队列中的锁等信息。

  7. switch_to(prev, next, prev):用于实际进行进程上下文切换,其中 prev 是前一个进程的任务结构体,next 是下一个进程的任务结构体,prev 是前一个进程的内核栈。

  8. finish_task_switch(prev):用于完成任务切换,主要是更新调度队列中的任务等信息,并返回前一个进程的调度队列。

三、总结

通过对Linux内核进程切换的流程了解和对代码的具体分析,可以总结出:

Linux内核进程切换过程中,会保存当前进程的上下文信息,并从调度队列中选中下一个需要执行的进程,然后将下一个进程的上下文信息加载到 CPU的寄存器和内存中,使其能够继续执行。在切换进程时,它会涉及到懒惰 TLB刷新、地址空间切换、锁切换等一系列操作,以确保进程能够正确地运行。最后,它会完成任务切换,并返回前一个进程的调度队列。

猜你喜欢

转载自blog.csdn.net/weixin_43363720/article/details/130375468