CS 332 w22 — Lab 2 FAQ
- How do you clean up the resources for a process?
- Things that need to be cleaned up: thread, address space (memory), inode, open files, proc struct
- Existing code in
proc_exit
handles the first three (proc_detach_thread
+thread_exit
,as_destroy
,fs_release_inode
) - To clean up open files,
fs_close_file
needs to be called on each one - To clean up the proc struct, use
proc_free
- How to use a condition variable
- Suppose in
proc_wait
, the parent has located the struct for a child it wants to wait on (in a variablechild
) - Suppose we have a function
get_status
that takes a proc struct and returns its current status (STATUS_ALIVE
or another value if the process has exited)- Maybe each proc struct has a field
status
to store the current status of that process (would be initialized toSTATUS_ALIVE
- Then we could just do
child->status
- Maybe each proc struct has a field
The parent could take a busy-waiting approach, sitting in a loop checking the child's status over and over:
while(get_status(child) == STATUS_ALIVE) {}
- For reasons of efficiency, we would prefer the parent go to sleep rather than spinning in a loop
- Having a thread go to sleep until some condition is met is the entire purpose of a condition variable
- So we have the parent check the status of the child, and if it hasn't exited, the parent uses a condition variable to go to sleep
- But where does the condition variable come from?
- In general, one condition variable should correspond to one condition/event that threads might wait for
- Since each process can independently wait for a child, it would be natural to have a separate condition variable for each process—maybe add it to the proc struct?
Since the parent wants to wait until the particular
child
exits, it can wait on the condition variable associated with the childwhile(get_status(child) == STATUS_ALIVE) { condvar_wait(&child->wait_cv, ????); }
- We also need to wait while holding a lock to protect the internal list of waiting threads maintained by the condition variable
Many possible lock designs are possible—a simple one is to use the global
ptable_lock
to protect shared process datawhile(get_status(child) == STATUS_ALIVE) { condvar_wait(&child->wait_cv, &ptable_lock); }
- Remember that the lock (
ptable_lock
) should be held when waiting (callingcondvar_wait
)condvar_wait
will add the current thread to the waiting list, release the lock, and put the thread to sleep- When a waiting thread resumes, it will acquire the lock before returning from
condvar_wait
- Ok, so the parent is waiting for
child
to exit—how does it know when to wake up?- In this design, a child process should use
condvar_signal
on its condition varible when it exits - If its parent is waiting, the parent will wake up (which we want)
- If its parent isn't waiting, the signal will have no effect (which is fine)
- In this design, a child process should use
So in
proc_exit
, usecondvar_signal(&p->wait_cv);
where
p
is the current process to send a signal on that process's condition variable- Note that locks and condition variables need to be initialized once before they can be used, via the
spinlock_init
andcondvar_init
functions.
- Suppose in
- When a process exits, what should it do with its children
- The children should be able to keep running
- The solution the writeup hints at is pretty simple: an exiting process hands off its children to the initial process (globally defined
init_proc
), which doesn't exit- Hand off means that however you're tracking what a process's parent is, update that information to change the parent of the children to
init_proc
If you look at
user/init.c
, you can see all the initial process does is wait for its child processes to exit#include <lib/usyscall.h> #include <lib/stdio.h> #include <lib/stddef.h> int main(int argc, char** argv) { int pid = spawn("sh"); if (pid < 0) { printf("init process fails to spawn children\n"); } while (1) { // just waits for all processes to terminate wait(-1, NULL); } exit(1); return 0; }
- Hand off means that however you're tracking what a process's parent is, update that information to change the parent of the children to
- There are certainly viable designs where a child can tell in
proc_exit
if its parent has exited or not, and can act accordingly
- Is blocking the same as waiting
- Yes, blocking and waiting both refer to when a process is stopped from making progress
- As we've seen, a waiting process can be busy-waiting or sleeping (sleeping is preferred where possible, efficiency-wise)
- Trap frame? What is it? What does it do? How does it work?
- The trap frame is saved information from the user program that trapped into the kernel via a system call
- When returning to user-mode, this information is used to resume running the user program
Here's the
struct trapframe
definition ininclude/kernel/trap.h
:// Trap frame struct trapframe { // Pushed by trap handler uint64_t rax; uint64_t rbx; uint64_t rcx; uint64_t rdx; uint64_t rbp; uint64_t rsi; uint64_t rdi; uint64_t r8; uint64_t r9; uint64_t r10; uint64_t r11; uint64_t r12; uint64_t r13; uint64_t r14; uint64_t r15; uint64_t trapnum; /* error code, pushed by hardware or 0 by software */ uint64_t err; uint64_t rip; uint64_t cs; uint64_t rflags; /* ss:rsp is always pushed in long mode */ uint64_t rsp; uint64_t ss; };
As you can see, it's basically saving all the CPU registers, so those values can be restored when going back to user-mode
To make the child's trap frame a copy of the parent's, we just want to copy the parent's struct to the child:
*t->tf = *thread_current()->tf;
where
t
is the thread created for the child- To change the return value in the child process, we want to change the value in the register that stores a function's return value:
rax
This is exactly what
tf_set_return
does:void tf_set_return(struct trapframe *tf, uint64_t retval) { kassert(tf); tf->rax = retval; }
- What kind of statuses are there?
- A status can be any integer
- The value of
STATUS_ALIVE
is reserved, so no process will ever exit with that status
- Would parent process wait for children that aren't theirs?
- No, if a process calls
wait
with a pid that isn't one of its children,wait
should returnERR_CHILD
- No, if a process calls
- What is "magic" in ptable (which is in list.h)?
- This is something added by the original authors of
osv
, which I assume has some purpose related to aligning things in memory - As far as I've been able to investigate, it has no effect, and can be removed
- This is something added by the original authors of
- Address space—how do we duplicate this?
- Use
as_copy_as
to copy one address space to another
- Use
- How does parent process know when to occasionally go through children and prune off zombies? Is there a signal or something?
- The only way a parent process cleans up its children is by calling
wait
- The only way a parent process cleans up its children is by calling