Answers to Frequently Asked Questions About Switching Kernel

advertisement
Answers to Frequently Asked Questions
About Kernel Contexts and Kernel Stacks:
Gregory Kesden 11/16/00
Please note that this document presents things in reasonable detail, but
does not present source code or source code level detail. Things may be
left out or glossed over. This is by design -- we'd like you to understand
the big picture and develop your own code. But, if you are having
problems, please remember, "We're here to help."
How do we create the first process?
Let's assume that VM is already enabled. What this means is that
the two page tables already exist. The Region 0 page table should
correctly map the kernel's space. The Region 1 page table exists,
but is empty. It is empty, because we haven't loaded the first
process, yet.
At this point, the kernel is operating in the context of some
"Future Process" Remember that the kernel operates in the context
of a particular process -- not in its own context. But at this point,
the kernel is operating on credit -- it hasn't create any processes,
yet.
So, when we create our first process, we need to give it the kernel's
current context, including stack. In some sense, they already belong
to this future process, the kernel is just using them in advance.
To create this "Future Process" we must initialize its state and
make it runnable. We initialize its PCB. We copy the UserContext that
is passed into KernelStart() as the initial context for this
"Future Process." We associate the current kernel context and kernel
stack with this process. We load the process from disk.
To associate the current kernel context with the "Future Process" we
need to copy the current KernelContext stack and associate the copy
with the "Future Process", probably by storing it in the PCB.
We also need to store the frame numbers associate with the current
kernel stack and associate them with the "Future Process" -- probably
by storing them in the PCB. These two actions ensure that the kernel
will be operating in the context of this "Future Process."
We can both copy the KernelContext and record the kernel stacks frame
numbers using KernelContextSwitch() and a special KCSFunc_t(). Let's
call this function ExtractContext(). ExtractContext() will be much like
the function, func(), described in the handout, with a slightly different
purpose:

It will only use two parameters: the current KernelContext and
the PCB for the "Future Process"

It will return the old context, not a new context (there really isn't
a new, different context, yet), so there won't actually be a context
switch.

It will copy the current KernelContext and associate it with
"Future Process", probably by storing a copy in the PCB.

It will associate the frame numbers of the current kernel stack
with the current process, probably by storing them in the PCB.
At this point, we have created "Future Process". So now, we can say
without complication that the kernel is running in the context of
the "Future Process." (Well, perhaps not so future, anymore).
How do we create a second process from within KernelStart (or third, or fourth, ...)?
Most groups will create two processes during KernelStart(). The
"idle" and "init". Idle will be a tight loop around Pause(), whereas
init will eventually start useful processes, such as a shell, and also
wait for orphaned children.
One of these two processes will be the "Future Process" -- probably
init (Why start out running idle if there is real work that can be done?)
So, Init is created exactly as above.
The creation of the second process (or third, or fourth, ...) is much
like the creation of the first, but there are some difference. To
create "idle", we can't share the kernel stack. Instead, we need to
copy it. For this we need to use a different KCSFunc_t function with
KernelContextSwitch(). Let's call this function CopyContext(). This
function will do basically the same thing as ExtractContext(), but it
will copy the kernel stack to a new set of physical frames and then
associate those frames with the new process, probably by storing them
in the PCB.
By copying the kernel stack, we ensure that the new process be able
to provide the kernel with a unique environment within which to
execute system calls and store other things associated with the process
(perhaps its user context?)
How does the first switch to user mode occur? Do we have to do anything special?
Just because we've created processes doesn't mean that the kernel
is no longer running. We will enter user-mode for the first time
shortly after returning from KernelStart(). At that time, we will
enter user mode in whatever process is defined by the
"UserContext * frame" passed into the KernelStart() function. In other
words, we select the first process to run by doing two things:
a)
Ensuring that the right page table is referenced by PTBR1/PTLR1
b)
Overwriting the old UserContext reference by "frame" with
the UserContext for the first process we want to run
We have to be a bit careful here. We want to make sure that the first
process to run in "Future Process". This is because the kernel is
currently running in the context of "Future Process."
How do we Fork() a new process?
As you might imagine, when Forking() a new process, we want to do
basically the same thing as we did when we statically created the
second through Nth processes, as described above. Depending on the
details, we probably want to call the same KCSFunct_t function,
CopyContext(), as we described earlier.
But there is something to consider. Is it always necessary or prudent to
create an entirely new process space when forking? Or is there a way that
we might be able to save all of the copying? Consider the common case
of Fork() followed immediately by Exec(). This pays for a bunch of
frame duplication that is never needed -- they get thrown away upon
Exec(). After everything else works, you might want to consider this
challenge.
How do we switch from one process to another (context switch)?
We may switch among processes due to blocking, timer interrupts, &c.
When this happens, we need to call KernelContextSwitch() for its
original purpose. Instead of using it to duplicate a context, we
use it to switch between them (go figure).
The handout distributed in class gives a good and detailed description
of this process. The short version is this:

We need another KCSFunc_T function. We'll call this one ContextSwitch().
This function should accept three parameters, not two:
1) the old kernel context
2) the old PCB
3) the new PCB

This function should associate the current kernel context with the
old process, probably by saving it in the PCB.

This function should install the kernel stack mappings associate with
the new process. (The old one doesn't need to be saved, because
they don't change. The kernel stack doesn't grow or shrink -- storing
them once is good enough).

Return the new kernel context.
You didn't mention the TLB. When do I flush it?
Any time you change or remove a mapping, you must flush the TLB.
Sometimes just one address, sometimes an entire region, and sometimes
both.
Why do we always return the kernel context and not the user context?
(Isn't the user context more important?)
The only time that a process is blocked is when it is in kernel mode.
Because of this, when we restore it to execution, we return it to
execution in the kernel context -- that's where it was when we stopped
it. This way the trap or system call can finish, clean up if necessary,
and then retun to user mode.
Download