Fork, Process kstack, Context Switching

Process kstack

How to determine the kstack size?
  - look at max function call depth in your code
      corollary: do not use deep function call chains
  - look at max size of local variables in functions
      corollary: do not allocate large variables on stack. allocate
                 them on heap if needed.
  - look at size of trapframe, etc.

xv6 uses a 4KB kstack (per process). Linux uses 8KB kstacks.

BUT: we said that any trap causes a trapframe to get pushed
on the kstack. So if there are many external interrupts, one after
another, could the kstack overflow?
Solution:
- Ensure that the kernel cannot cause any software interrupt or
  exception while executing a handler.
- Ensure that handlers of external interrupts (e.g., timer, disk, network, etc.)
  always execute with interrupts disabled.

This ensures that there can be at most two trapframes on the kstack: the first
due to a software interrupt/exception, and the second due to an external
interrupt received while executing in the kernel. This allows you to calculate
an upper-bound on the stack size.

What happens if an interrupt is received while the CPU was executing with
disabled interrupts? The interrupt is ignored:
  - Typically, the interrupting hardware expects an acknowledgement from
    the CPU that it received the interrupt. If it does not receive an
    acknowledgement, it retries the interrupt
  - Also, the CPU has a buffer (of size 2 say) where it stores pending
    interrupts (i.e., interrupts that were received but not serviced).
    As soon as the CPU re-enables interrupts, the pending interrupts are
    delivered to it.

How does an OS keep time? One common way is to count the number of timer
interrupts received.

Fork


how fork system call works:
  the fork function creates a new PCB and a new kstack (child's)
  it copies the trapframe from the current kstack to the child's
  it changes the return value (eax) in the trapframe for the child
  it also initializes a few more entries on the child's kstack
     so that it can control what are the first few instructions that run
     when the child gets scheduled.
  it adds the PCB to the list of schedulable processes, and returns.


There is one kstack per CPU, which is used by the scheduler thread (the
  first thread to run on that CPU). This kstack is not associated with
  any process. Let's call this the scheduler's kstack.

At bootup, the CPU initializes itself to run with its scheduler kstack.
   Now it will allocate the first process (including its kstack), initialize
   it, and switch from the scheduler's kstack to the new process's kstack.
   The new process will get to run, and will likely call exec/fork to
   create more processes.

Each time a process wants to yield (either voluntarily or preemptively),
   it switches to the scheduler's kstack. The scheduler then finds a process
   to run, and switches to its kstack.

The swtch function on sheet 27 switches kstacks. Let's look at it.
   It saves the current registers on stack
   It then saves the current esp into its first argument (struct context **old)
   It loads the new esp from its second argument (struct context *new)

   The kstack of every suspended process looks like it has just been
     switched out from inside the swtch function.

   Similarly, when a process is running on the CPU, the kstack of the
     scheduler (stored in global variable cpu->scheduler) also looks like
     it has just been switched out using the swtch function.

swtch sheet 26
  save current thread's registers and stack
  load new threads stack and registers
  saves current registers on current stack, struct context, sheet 20
  expects new thread's stack to have registers in that format
  stack diagram:
    eip *****
    ebp
    ebx
    esi
    edi
  Q: why these registers?
    callee saved, might have caller's live variables
  same format as struct context

swtch sheet 26
  save current thread's registers and stack
  load new threads stack and registers
  saves current registers on current stack, struct context, sheet 20
  expects new thread's stack to have registers in that format
  stack diagram:
    eip *****
    ebp
    ebx
    esi
    edi
  Q: why these registers?
    callee saved, might have caller's live variables
  same format as struct context


Process structure

how does xv6 store process state?
  struct proc sheet 20
  kernel proc[] table has an entry for each process
  discuss pgdir, kstack, state, pid fields.
  then discuss ofile field (fd table)

discuss alltraps and trapret on sheet 30
  in the course of a regular trap, trapret gets pushed to stack
     by the call instruction.
  in the case of a newly forked process (or the first process), the
     stack is initialized to contain trapret just below the trap
     frame.
  Why do we save all registers (and not just callee-saved regs) here?