If I understand context switching correctly, the process involves two major steps:

  1. The MMU is switched to one that maps the new processes virtual memory space to physical memory space.
  2. The processor state is saved for the current process, then switched to the saved processor state for the new process. Presumably, this includes setting the program counter to begin execution from where the switched-to process last left off.

In the kernel, the function that handles all of this is called context_switch() (source code here). This function handles both of the required steps, but after setting the processor state, it then returns.

That's confusing, because it seems to me that once the program counter is manually moved to a new place, context_switch() wouldn't have an opportunity to return at all. The only explanation I can come up with is that context_switch() is both the code that switches to a new process and the code to which switched processes return. In other words, every process ends up switching from its own context_switch() to another processes' context_switch(). But then it seems unclear to me how this could work in a newly forked process. So maybe context_switch() actually runs to completion and returns, and then something else jumps to the correct part of the target process?

Is this thinking correct? At what point exactly does context_switch() move from one process to another? When does context_switch() return? When it switches to a new process, where in the new process' execution state does it end up? How does this fit in with newly forked processes?

I've been spending the last few days reading through the relevant parts of the kernel source code to try and figure this out, but I'm afraid I'm not getting any closer to understanding. Hopefully someone here can help.


Note the comment in line 3366 (as of 5.7.7):

/* Here we just switch the register state and the stack. */

context_switch() doesn't load the new instruction pointer (program counter) directly, it switches the stack — and the stack contains the appropriate return address. When the function returns, it returns to the new task.

When forking, the virtual return address is the same as in both processes (parent and child); the difference is the return value.

  • I see: switch_to does pushl %[next_ip] (where next_ip = next->thread.ip), then does jmp __switch_to, so that when switch_to returns, it will return to the new task's instruction pointer. I could have stared at the code all week and not noticed that because I would have been looking in the wrong place. Thanks a ton!
    – Sardonic
    Nov 29 '16 at 21:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.