Required reading: proc.c (focus on scheduler() and sched()), setjmp.S, and sys_fork (in sysproc.c)
Big picture: more programs than processors. How to share the limited number of processors among the programs?
Observation: most programs don't need the processor continuously, because they frequently have to wait for input (from user, disk, network, etc.)
Idea: when one program must wait, it releases the processor, and gives it to another program.
Mechanism: thread of computation, an active active computation. A thread is an abstraction that contains the minimal state that is necessary to stop an active and an resume it at some point later. What that state is depends on the processor. On x86, it is the processor registers (see setjmp.S).
Address spaces and threads: address spaces and threads are in principle independent concepts. One can switch from one thread to another thread in the same address space, or one can switch from one thread to another thread in another address space. Example: in xv6, one switches address spaces by switching segmentation registers (see setupsegs). Does xv6 ever switch from one thread to another in the same address space? (Answer: yes, v6 switches, for example, from the scheduler, proc[0], to the kernel part of init, proc[1].) In the JOS kernel we switch from the kernel thread to a user thread, but we don't switch kernel space necessarily.
Process: one address space plus one or more threads of computation. In xv6 all user programs contain one thread of computation and one address space, and the concepts of address space and threads of computation are not separated but bundled together in the concept of a process. When switching from the kernel program (which has multiple threads) to a user program, xv6 switches threads (switching from a kernel stack to a user stack) and address spaces (the hardware uses the kernel segment registers and the user segment registers).
xv6 supports the following operations on processes:
Scheduling. The thread manager needs a method for deciding which thread to run if multiple threads are runnable. The xv6 policy is to run the processes round robin. Why round robin? What other methods can you imagine?
Preemptive scheduling. To force a thread to release the processor periodically (in case the thread never calls sleep), a thread manager can use preemptive scheduling. The thread manager uses the clock chip to generate periodically a hardware interrupt, which will cause control to transfer to the thread manager, which then can decide to run another thread (e.g., see trap.c).
Thread switching is implemented in xv6 using setjmp and longjmp, which take a jumpbuf as an argument. setjmp saves its context in a jumpbuf for later use by longjmp. longjmp restores the context saved by the last setjmp. It then causes execution to continue as if the call of setjmp has just returned 1.
Example of thread switching: proc[0] switches to scheduler:
CPU 0: eax: 0x10a144 1089860 ecx: 0x6c65746e 1818588270 edx: 0x0 0 ebx: 0x10a0e0 1089760 esp: 0x210ea8 2166440 ebp: 0x210ebc 2166460 esi: 0x107f20 1081120 edi: 0x107740 1079104 eip: 0x1023c9 eflags 0x12 cs: 0x8 ss: 0x10 ds: 0x10 es: 0x10 fs: 0x10 gs: 0x10 00210ea8 [00210ea8] 10111e 00210eac [00210eac] 210ebc 00210eb0 [00210eb0] 10239e 00210eb4 [00210eb4] 0001 00210eb8 [00210eb8] 10a0e0 00210ebc [00210ebc] 210edc 00210ec0 [00210ec0] 1024ce 00210ec4 [00210ec4] 1010101 00210ec8 [00210ec8] 1010101 00210ecc [00210ecc] 1010101 00210ed0 [00210ed0] 107740 00210ed4 [00210ed4] 0001 00210ed8 [00210ed8] 10cd74 00210edc [00210edc] 210f1c 00210ee0 [00210ee0] 100bbc 00210ee4 [00210ee4] 107740
CPU 0: eax: 0x10a144 1089860 ecx: 0x6c65746e 1818588270 edx: 0x0 0 ebx: 0x10a0e0 1089760 esp: 0x210ea0 2166432 ebp: 0x210ebc 2166460 esi: 0x107f20 1081120 edi: 0x107740 1079104 eip: 0x102848 eflags 0x12 cs: 0x8 ss: 0x10 ds: 0x10 es: 0x10 fs: 0x10 gs: 0x10 00210ea0 [00210ea0] 1023cf <--- return address (sched) 00210ea4 [00210ea4] 10a144 00210ea8 [00210ea8] 10111e 00210eac [00210eac] 210ebc 00210eb0 [00210eb0] 10239e 00210eb4 [00210eb4] 0001 00210eb8 [00210eb8] 10a0e0 00210ebc [00210ebc] 210edc 00210ec0 [00210ec0] 1024ce 00210ec4 [00210ec4] 1010101 00210ec8 [00210ec8] 1010101 00210ecc [00210ecc] 1010101 00210ed0 [00210ed0] 107740 00210ed4 [00210ed4] 0001 00210ed8 [00210ed8] 10cd74 00210edc [00210edc] 210f1c
CPU 0: eax: 0x0 0 ecx: 0x6c65746e 1818588270 edx: 0x108aa4 1084068 ebx: 0x10a0e0 1089760 esp: 0x210ea0 2166432 ebp: 0x210ebc 2166460 esi: 0x107f20 1081120 edi: 0x107740 1079104 eip: 0x10286e eflags 0x46 cs: 0x8 ss: 0x10 ds: 0x10 es: 0x10 fs: 0x10 gs: 0x10 00210ea0 [00210ea0] 1023fe 00210ea4 [00210ea4] 108aa4 00210ea8 [00210ea8] 10111e 00210eac [00210eac] 210ebc 00210eb0 [00210eb0] 10239e 00210eb4 [00210eb4] 0001 00210eb8 [00210eb8] 10a0e0 00210ebc [00210ebc] 210edc 00210ec0 [00210ec0] 1024ce 00210ec4 [00210ec4] 1010101 00210ec8 [00210ec8] 1010101 00210ecc [00210ecc] 1010101 00210ed0 [00210ed0] 107740 00210ed4 [00210ed4] 0001 00210ed8 [00210ed8] 10cd74 00210edc [00210edc] 210f1c
CPU 0: eax: 0x1 1 ecx: 0x108aa0 1084064 edx: 0x108aa4 1084068 ebx: 0x10074 65652 esp: 0x108d40 1084736 ebp: 0x108d5c 1084764 esi: 0x10074 65652 edi: 0xffde 65502 eip: 0x102892 eflags 0x6 cs: 0x8 ss: 0x10 ds: 0x10 es: 0x10 fs: 0x10 gs: 0x10 00108d40 [00108d40] 10231c 00108d44 [00108d44] 10a144 00108d48 [00108d48] 0010 00108d4c [00108d4c] 0021 00108d50 [00108d50] 0000 00108d54 [00108d54] 0000 00108d58 [00108d58] 10a0e0 00108d5c [00108d5c] 0000 00108d60 [00108d60] 0001 00108d64 [00108d64] 0000 00108d68 [00108d68] 0000 00108d6c [00108d6c] 0000 00108d70 [00108d70] 0000 00108d74 [00108d74] 0000 00108d78 [00108d78] 0000 00108d7c [00108d7c] 0000
CPU 0: eax: 0x1 1 ecx: 0x6c65746e 1818588270 edx: 0x0 0 ebx: 0x10a0e0 1089760 esp: 0x210ea0 2166432 ebp: 0x210ebc 2166460 esi: 0x107f20 1081120 edi: 0x107740 1079104 eip: 0x102892 eflags 0x2 cs: 0x8 ss: 0x10 ds: 0x10 es: 0x10 fs: 0x10 gs: 0x10 00210ea0 [00210ea0] 1023cf <--- return to sleep 00210ea4 [00210ea4] 108aa4 00210ea8 [00210ea8] 10111e 00210eac [00210eac] 210ebc 00210eb0 [00210eb0] 10239e 00210eb4 [00210eb4] 0001 00210eb8 [00210eb8] 10a0e0 00210ebc [00210ebc] 210edc 00210ec0 [00210ec0] 1024ce 00210ec4 [00210ec4] 1010101 00210ec8 [00210ec8] 1010101 00210ecc [00210ecc] 1010101 00210ed0 [00210ed0] 107740 00210ed4 [00210ed4] 0001 00210ed8 [00210ed8] 10cd74 00210edc [00210edc] 210f1c
Why switch from proc[0] to the processor stack, and then to proc[0]'s stack? Why not instead run the scheduler on the kernel stack of the last process that run on that cpu?
How is preemptive scheduling implemented in xv6? Answer see trap.c line 2905 through 2917, and the implementation of yield() on sheet 22.
How long is a timeslice for a user process? (possibly very short; very important lock is held across context switch!)