Reading ULK about switch_to(prev, next, last), it's quite hard to understand why the need for the 3rd argument. It turns out it's not hard when read it with schedule().
The schedule() perform a process switch, that is suspend current process and start next runnable process. A simplified version is shown below. It does 1) use prev and next for current and next runnable process; 2) call switch_to() to suspend current process and start next process; 3) process other stuff for prev process. Since schedule() needs to deal with prev process after switch_to(), so it needs switch_to() tell which process the current process switch from, that's the reason for the 3rd argument last in switch_to(prev, next, last). In schedule(), the 3rd argument is prev (line 11), so after switch_to(), prev stands for the process that the current process switch from.
void schedule(void)
{
struct task_struct *prev, *next;
/* prev is executing */
// get prev & next;
prev = current; next = next_runnable_task
// switch to next, next starts to run after this call
switch_to(prev, next, prev) // this is actually called by context_switch()
/*------------------------------------------*/
/* next is executing */
// process other stuff about prev
finish_task_switch(prev);
// next continue running
}
one may wonder why need switch_to() set prev again since schedule() already has prev. Because the prev is not correct when the process is executing again after a process switch. Suppose switch_to() has no 3rd argument, that is not set prev again. Suppose now do a process switch from process A to process B. Now A stops at switch_to() because by defination switch_to() stops A and starts to run B. For A, it has (prev=A, next=B) like picture below. Before switch_to() starts B, B is suspend, and suppose B is suspended due to switch to other process by switch_to(). B starts to run and it has (prev=B, next=other process). Why? after process switch, B resume its stack and this stack is saved when switch from B to other process, and B has (prev=B, next=other process) when switch from B to other process. Now B starts at instructions after switch_to(), and need process some stuff for the process it switch from, and it expects prev stands for this process. Since we do process switch from A to B, we expect prev is A instead of its current value B. So prev need reset. switch_to() knows the how to reset prev and provide the 3rd argument for it.
How switch_to() set prev?
switch_to() set its 3rd argument as the switching from process, schedule() pass prev as 3rd argument to save the switching from process. How switch_to() does? See following code snippt with comments.
switch_to(prev, next, last)
# save prev in eax register and next in edx
movl prev, %eax
movl next, %edx
# save eflags & ebp
pushfl
pushl %ebp
# Saves the content of esp in prev->thread.esp
movl %esp,484(%eax)
# Loads next->thread.esp in esp. From now on, the kernel operates on the Kernel
# Mode stack of next, so this instruction performs the actual process switch from
# prev to next. Because the address of a process descriptor is closely related to that
# of the Kernel Mode stack (as explained in the section “Identifying a Process” earlier
# in this chapter), changing the kernel stack means changing the current process
movl 484(%edx), %esp
# Saves the address labeled 1 in prev->thread.eip
# When the process being replaced resumes its execution,
# the process executes the instruction labeled as 1
movl $1f, 480(%eax)
# On the Kernel Mode stack of next, pushes the next->thread.eip value,
# which, in most cases, is the address labeled as 1. see last instruction.
pushl 480(%edx)
# importance: this function make sure eax not changed, eax has prev.
# do hardware context switch from prev to next
# when return, it get return address from its stack, and
# the address is label 1 for most case, see last instruction.
jmp __switch_to
1:
# pop ebp & eflags
popl %ebp
popfl
# save prev stored in eax to last (the 3rd argument)
movl %eax, last