xv6-65oo2/web/l-threads.html

317 lines
9.7 KiB
HTML
Raw Normal View History

2008-09-03 04:50:04 +00:00
<title>L8</title>
<html>
<head>
</head>
<body>
<h1>Threads, processes, and context switching</h1>
<p>Required reading: proc.c (focus on scheduler() and sched()),
setjmp.S, and sys_fork (in sysproc.c)
<h2>Overview</h2>
<p>Big picture: more programs than processors. How to share the
limited number of processors among the programs?
<p>Observation: most programs don't need the processor continuously,
because they frequently have to wait for input (from user, disk,
network, etc.)
<p>Idea: when one program must wait, it releases the processor, and
gives it to another program.
<p>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).
<p>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.
<p>Process: one address space plus one or more threads of computation.
In xv6 all <i>user</i> 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).
<p>xv6 supports the following operations on processes:
<ul>
<li>fork; create a new process, which is a copy of the parent.
<li>exec; execute a program
<li>exit: terminte process
<li>wait: wait for a process to terminate
<li>kill: kill process
<li>sbrk: grow the address space of a process.
</ul>
This interfaces doesn't separate threads and address spaces. For
example, with this interface one cannot create additional threads in
the same threads. Modern Unixes provides additional primitives
(called pthreads, POSIX threads) to create additional threads in a
process and coordinate their activities.
<p>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?
<p>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).
<h2>xv6 code examples</h2>
<p>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.
<ul>
<li>setjmp saves: ebx, exc, edx, esi, edi, esp, ebp, and eip.
<li>longjmp restores them, and puts 1 in eax!
</ul>
<p> Example of thread switching: proc[0] switches to scheduler:
<ul>
<li>1359: proc[0] calls iget, which calls sleep, which calls sched.
<li>2261: The stack before the call to setjmp in sched is:
<pre>
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
</pre>
<li>2517: stack at beginning of setjmp:
<pre>
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
</pre>
<li>2519: What is saved in jmpbuf of proc[0]?
<li>2529: return 0!
<li>2534: What is in jmpbuf of cpu 0? The stack is as follows:
<pre>
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
</pre>
<li>2547: return 1! stack looks as follows:
<pre>
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
</pre>
<li>2548: where will longjmp return? (answer: 10231c, in scheduler)
<li>2233:Scheduler on each processor selects in a round-robin fashion the
first runnable process. Which process will that be? (If we are
running with one processor.) (Ans: proc[0].)
<li>2229: what will be saved in cpu's jmpbuf?
<li>What is in proc[0]'s jmpbuf?
<li>2548: return 1. Stack looks as follows:
<pre>
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
</pre>
</ul>
<p>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?
<ul>
<li>If the scheduler wanted to use the process stack, then it couldn't
have any stack variables live across process scheduling, since
they'd be different depending on which process just stopped running.
<li>Suppose process p goes to sleep on CPU1, so CPU1 is idling in
scheduler() on p's stack. Someone wakes up p. CPU2 decides to run
p. Now p is running on its stack, and CPU1 is also running on the
same stack. They will likely scribble on each others' local
variables, return pointers, etc.
<li>The same thing happens if CPU1 tries to reuse the process's page
tables to avoid a TLB flush. If the process gets killed and cleaned
up by the other CPU, now the page tables are wrong. I think some OSes
actually do this (with appropriate ref counting).
</ul>
<p>How is preemptive scheduling implemented in xv6? Answer see trap.c
line 2905 through 2917, and the implementation of yield() on sheet
22.
<p>How long is a timeslice for a user process? (possibly very short;
very important lock is held across context switch!)
</body>