xv6, line by line
test yourself

Test yourself · category 8 of 20

fork, exec, exit, wait, kill

How a process is born as a copy, becomes a new program, dies in two halves as a zombie, is reaped by its parent, and is killed by a flag it checks itself.

1warm-upChoose one

After fork, parent and child run the same code from the same instruction. How does fork() come to return 0 in the child but the child’s pid in the parent?

kernel/proc.c
278 // copy saved user registers.
281 // Cause fork to return 0 in the child.
2warm-upTrue or false, and why

True or false: a fork child’s kernel stack starts out as a copy of its parent’s kernel stack, so that the child can return through sys_fork, syscall and usertrap just like the parent.

Why?

3warm-upChoose all that apply

A child process has called exit and is now a ZOMBIE; its parent has not called wait yet. Which of these does the zombie still hold?

4solidPut in order

ls (on hart 2) calls exit(0) while its parent, the shell, sleeps in kwait. Put these events in the order they must happen.

  1. Hart 2’s scheduler releases ls’s p->lock
  2. kexit calls wakeup(p->parent)
  3. kexit closes ls’s open files and drops its current directory
  4. kexit releases wait_lock and calls sched
  5. The shell’s kwait acquires ls’s p->lock, sees ZOMBIE and calls freeproc
  6. kexit acquires its own p->lock and sets state = ZOMBIE
  7. kexit acquires wait_lock and calls reparent
5warm-upChoose one

Process A is running in user mode on hart 1. On hart 0, another process calls kill(A's pid), and kkill finds A’s slot. What does kkill do to A?

kernel/proc.c
597// Kill the process with the given pid.
598// The victim won't exit until it tries to return
599// to user space (see usertrap() in trap.c).
600int
603 struct proc *p;
605 if (pid == 0)
606 return -1;
608 for (p = proc; p < &proc[NPROC]; p++) {
610 if (p->pid == pid) {
611 p->killed = 1;
612 if (p->state == SLEEPING) {
613 // Wake process from sleep().
615 }
617 return 0;
618 }
620 }
621 return -1;
6warm-upTrue or false, and why

True or false: after a successful exec, the process still has the same pid and the same open file descriptors it had before.

Why?

7warm-upType a number

After loading the program’s segments, kexec calls uvmalloc on line 91. How many pages does that call map in the new page table?

kernel/exec.c
86 // Allocate some pages at the next page boundary.
87 // Make the first inaccessible as a stack guard.
88 // Use the rest as the user stack.
91 if ((sz1 = uvmalloc(pagetable, sz, sz + (USERSTACK + 1) * PGSIZE, PTE_W)) ==
92 0)
93 goto bad;
94 sz = sz1;
96 sp = sz;
decimal, 0x hex or 0b binary
8warm-upClick the line

In kfork, click the line at which the child first becomes eligible to be run by a scheduler on any hart.

kernel/proc.c
256// Create a new process, copying the parent.
257// Sets up child kernel stack to return as if from fork() system call.
258int
259kfork(void)
261 int i, pid;
262 struct proc *np;
263 struct proc *p = myproc();
265 // Allocate process.
266 if ((np = allocproc()) == 0) {
267 return -1;
268 }
270 // Copy user memory from parent to child.
271 if (uvmcopy(p->pagetable, np->pagetable, p->sz) < 0) {
274 return -1;
275 }
276 np->sz = p->sz;
278 // copy saved user registers.
281 // Cause fork to return 0 in the child.
284 // increment reference counts on open file descriptors.
285 for (i = 0; i < NOFILE; i++)
286 if (p->ofile[i])
288 np->cwd = idup(p->cwd);
290 safestrcpy(np->name, p->name, sizeof(p->name));
304 return pid;

Your pick: none yet (click a line in the code)

9solidChoose one

Near its end, kfork releases the child’s lock (line 294), takes wait_lock to set np->parent, releases it, and only then re-acquires the child’s lock. Why not keep the child’s lock and take wait_lock inside it?

10solidChoose one

Line 279 copies the parent’s whole trapframe into the child’s. What is in the child’s trapframe->kernel_sp right after that line, and why is it not a problem?

kernel/proc.c
270 // Copy user memory from parent to child.
271 if (uvmcopy(p->pagetable, np->pagetable, p->sz) < 0) {
274 return -1;
275 }
276 np->sz = p->sz;
278 // copy saved user registers.
281 // Cause fork to return 0 in the child.
11solidChoose one

In kexit, wakeup(p->parent) (line 353) comes before acquire(&p->lock) (line 355). Why can’t kexit take its own lock first?

kernel/proc.c
349 // Give any children to init.
352 // Parent might be sleeping in wait().
362 // Jump into the scheduler, never to return.
364 panic("zombie exit");
12solidChoose one

kwait holds wait_lock from its scan until after sleep_prepare, and releases it only just before sleep. Why does it register before releasing wait_lock?

kernel/proc.c
378 for (;;) {
379 // Scan through table looking for exited children.
381 for (pp = proc; pp < &proc[NPROC]; pp++) {
382 if (pp->parent == p) {
383 // make sure the child isn't still in exit() or swtch().
387 if (pp->state == ZOMBIE) {
388 // Found one.
390 if (addr != 0 &&
391 copyout(p->pagetable, p->sz, addr, (char *)&pp->xstate,
392 sizeof(pp->xstate)) < 0) {
395 return -1;
396 }
397 pp->parent = 0;
401 return pid;
402 }
404 }
405 }
407 // No point waiting if we don't have any children.
408 if (!havekids || killed(p)) {
410 return -1;
411 }
413 // Wait for a child to exit.
414 sleep_prepare(p); //DOC: wait-sleep
418 }
13solidType a number

A process whose user memory is exactly 5 pages, all present, at virtual addresses 0x0–0x4fff, calls fork, and it succeeds. How many pages does the whole kfork take from kalloc (directly or through the functions it calls)?

decimal, 0x hex or 0b binary
14deepType a number

allocproc claims proc[4]. What value does line 147 store in p->context.sp? Use KSTACK(p) = TRAMPOLINE - ((p) + 1) * 2 * PGSIZE, TRAMPOLINE = MAXVA - PGSIZE, MAXVA = 1 << 38 and PGSIZE = 4096. Answer in hex.

kernel/proc.c
143 // Set up new context to start executing at forkret,
144 // which returns to user space.
145 memset(&p->context, 0, sizeof(p->context));
149 return p;
decimal, 0x hex or 0b binary
15solidChoose one

Process P is asleep in piperead on an empty pipe whose write end is still open (it slept at line 126). Another process calls kill(P). The pipe stays empty. What happens to P?

kernel/pipe.c
111int
112piperead(struct pipe *pi, uint64 addr, int n)
114 int i;
115 struct proc *pr = myproc();
116 char ch;
119 while (pi->nread == pi->nwrite && pi->writeopen) { //DOC: pipe-empty
120 if (killed(pr)) {
122 return -1;
123 }
124 sleep_prepare(&pi->nread); //DOC: piperead-sleep
128 }
16solidChoose all that apply

Some sleep loops check killed each time round, so that a killed process does not wait forever. Which of these do?

17solidClick the line

Click the line at which the process’s user address space becomes the new program’s, the commit point of kexec.

kernel/exec.c
126 // Save program name for debugging.
127 for (last = s = path; *s; s++)
128 if (*s == '/')
129 last = s + 1;
130 safestrcpy(p->name, last, sizeof(p->name));
132 // Commit to the user image.
135 p->sz = sz;
136 p->trapframe->epc = elf.entry; // initial program counter = ulib.c:start()
137 p->trapframe->sp = sp; // initial stack pointer
140 return argc; // this ends up in a0, the first argument to main(argc, argv)

Your pick: none yet (click a line in the code)

18warm-upMatch the pairs

Match each process state with the code that sets it in the situation described.

19solidFill in the machine state

ls called exit(0) (a system call). Its kexit has just executed line 358, p->state = ZOMBIE. What is the state of the hart running it?

kernel/proc.c
349 // Give any children to init.
352 // Parent might be sleeping in wait().
362 // Jump into the scheduler, never to return.
364 panic("zombie exit");
20deepTrue or false, and why

True or false: as soon as a process’s state is ZOMBIE, no hart is executing on its kernel stack any more.

kernel/proc.c
362 // Jump into the scheduler, never to return.
364 panic("zombie exit");

Why?

21solidChoose all that apply

Process A is spinning in user mode on hart 1, making no system calls. On hart 0, another process calls kill(A's pid), and it returns 0. Which of these are true?

22deepChoose one

kexec's argument loop writes ustack[argc] with no check that argc < MAXARG (ustack has MAXARG = 32 entries, on the kernel stack). What stops a user program from overflowing ustack by calling exec with 40 arguments?

kernel/exec.c
99 // Copy argument strings into new stack, remember their
100 // addresses in ustack[].
101 for (argc = 0; argv[argc]; argc++) {
102 sp -= strlen(argv[argc]) + 1;
103 sp -= sp % 16; // riscv sp must be 16-byte aligned
105 goto bad;
106 if (copyout(pagetable, sz, sp, argv[argc], strlen(argv[argc]) + 1) < 0)
107 goto bad;
109 }
112 // push a copy of ustack[], the array of argv[] pointers.
113 sp -= (argc + 1) * sizeof(uint64);
114 sp -= sp % 16;
116 goto bad;
117 if (copyout(pagetable, sz, sp, (char *)ustack, (argc + 1) * sizeof(uint64)) <
118 0)
119 goto bad;
23deepChoose one

Process P in piperead has passed the killed check on line 120 and called sleep_prepare on line 124, but has not yet called sleep(). On another hart, kill(P) runs to completion. Then the pipe’s writer does nothing for an hour, keeping its end open. What happens to P during that hour?

kernel/pipe.c
111int
112piperead(struct pipe *pi, uint64 addr, int n)
114 int i;
115 struct proc *pr = myproc();
116 char ch;
119 while (pi->nread == pi->nwrite && pi->writeopen) { //DOC: pipe-empty
120 if (killed(pr)) {
122 return -1;
123 }
124 sleep_prepare(&pi->nread); //DOC: piperead-sleep
128 }
24deepTrue or false, and why

True or false: because first in forkret is a plain static int, read and cleared with no lock and no atomic instruction, two harts could both run the if (first) block and both call fsinit.

kernel/proc.c
510// A fork child's very first scheduling by scheduler()
511// will swtch to forkret.
512void
515 extern char userret[];
516 static int first = 1;
517 struct proc *p = myproc();
519 // Still holding p->lock from scheduler.
522 if (first) {
523 first = 0;
525 // File system initialization must be run in the context of a
526 // regular process (e.g., because it calls sleep), and thus cannot
527 // be run from main().
530 // We can invoke kexec() now that file system is initialized.
531 // Put the return value (argc) of kexec into a0.
532 p->trapframe->a0 = kexec("/init", (char *[]){"/init", 0});
533 if (p->trapframe->a0 == -1) {
534 panic("exec");
535 }
536 }

Why?

25deepChoose one

Process P (a child of the shell) has a child Z that has already exited and is a ZOMBIE; P never called wait. Now P itself calls exit. Who eventually calls freeproc on Z?

kernel/proc.c
307// Pass p's abandoned children to init.
308// Caller must hold wait_lock.
309void
310reparent(struct proc *p)
312 struct proc *pp;
314 for (pp = proc; pp < &proc[NPROC]; pp++) {
315 if (pp->parent == p) {
318 }
319 }