Mon, Feb 4, 2019
How can a processor accomplish more than one task?
Time multiplexing or Multitasking
We divide the time into slices. It continues to run until we give up the CPU (voluntary context switch)
Some hardware device has an urgent message that must be taken care of (involuntary context switch).
Timer interrupt is a hardware interrupt, preventing the infinite loop attack!
The kernel has full access over the computer’s privileges.
Support involuntary (timer intrrupt), resumd with no interruption. Save the scheduler runs, we’ll talk about it later on in the class. The scheduler is using registers to make a decision, resume the execution context.
Execution contents of logical thread: A function and a store, which includes the whole stack (including local variables)
Execution context state * Current instruction * Local variables * Continuation (where the variables will be delivered)
Multiple execution contexts with the same memory, a multi-processor. A kernel may be operating multiple kernel threads. These kernel threads are running on distinct stacks. Calling function with the call instruction and return instruction both use the stack. It’s implemented by a ret instruction.
- We need to save the current context’s registers.
- We need to
save the current context’s stack.
- Some scheduling decision, then we need to restore the next context stack.
We need hardware help to do this.
Hardware interrupt or fault is involuntary context switch
Get CPU to run the interrupt handler. The page table does not change. x86 tables have an unprivileged bit, this mapping can only be used by the kernel.
USER -> KERNEL through PCT
Interrupt is the interrupt handler. We have to save the old instruction somewhere. The stack pointer. For involuntary, we need to change the stack pointer (as opposed to voluntary). This is configured into the hardware, the privilege level is changed. The code segment (%cs) is going to change unprivileged to privileged.
The flag registers changes for whether the interrupt is allowed. Interrupt is allowed to no allowed.
Flag changes whether the strcpy instruction is changed.
All this before the first interrupt executes. All the old values must be saved.
Where will old values be put? The handler step. The processor takes this stuff, packages it up (%rsp, $rip) and goes on top of the kernel’s stack.
The signal stack is not used as part of privileged Control Mechanism. I need to interrupt the old task and start it on the new task. In user, we see the need for a stack to occur. It’s like kernel stack for processes.
Overwriting stack canaries can be a problem.
Interrupt descrptor table (IDT) is an array of 256 pointers to interrupt handler.
Interrupt handler = instruction + metadata The kernel level is what the interrupt handler uses.
Yes, the interrupt should be disabled when interrupt handler run.
Global descriptor table.
- When an interrupt occurs, the processor switches to appropriate kernel stack.
- Pushes the old register values on the kernel stack
In WeensyOS, it just pushes it onto the stack.
The logic for pushing data is in hardware. We need to restore values to the same state that they were pushed and saved so we need an instruction to do that. It makes a change to all those registers at once. Interrupts also restore the flags.
IRED is implemented in microcode. It’s specified. The processor needs to roll one back so we have only one stack pointer.
System calls are voluntary unlike interrupts. Thus we can create calling conventions. You need to forget about registers 9-12. Kernel is not going to save its values. Less needs to be saved and restored.
%rcx = old %rip %r11 = old %rflags
Handler must save general-purpose rgistered as required and switch to kernel code.
Simpler to return iret rather than sysret
Interrupt descriptor page entry. They are safe from modification. User process cannot modify the memory.
kernel starts from an empty stack on every context switch, interrupts disabled. Fork copies all of the process’s memory. Blocking becomes more and more tempting. Reenable interrupts immediately.
Interrupts mean something is wrong. It’s a shame to not get that interrupt. Kernel code has to divide it’s work up into tiny little tasks.
Solution: Multiple stacks
Kernel’s stack is used only when process is running in kernel mode, different from user’s stack. These kernel tag stacks are separate from CPU stack, which is shared with all of the kernel stacks. The first thing an interrupt handler does if finds a state and moves it to a certain stack.
CPU stack is generally empty. Kernel stack is like user stack, suspended and resumed.
CPU stack is populated by the hardware. Chickadee is multi-processor. Interrupts are delivered per CPU (different IDT, GDT) per CPU.
Processes and kernel tasks should not be tied to one CPU. Solution: Stop on one CPU and then resume on CPU B
Where is my CPU stack? What process is running on this CPU?
Most interrupts are delivered only to one CPU at a time. There’s different CPUs. The procedure for booting up second processor is like booting up the machine. Processor number 2 runs in normal 86 mode.
Some register must delimit processor 1 and processor 2.
Most instructions that touch memory just access virtual memory directly. On x86-64, there are two special segments. fs and gs. These are associated with offsets. That doesn’t access the thing with virtual memory 0. Takes an offset and adds it to the address. These are registers, different values per CPU. Values that differentiate from one another.
We want and we want to know how to use those tools. KERNEL_GS_BASE, only the kernel can modify.
swapgs swaps the GSBASE with the KERNEL_GS_BASE, which lets us store a privileged pointer in base and install it without any other register. Since the swap GS instruction takes the old value of GS_BASE in the kernel register, we can use it very early on to find some location in memory, such as the current CPU.
Using swapgs as Intel seems to intend
If you access gs:0, that will return current kernel CPU state.
Currently running kernel CPU task, we execute swapgs to resume a process, we save it in gs, and then we swap.
%rax – does not guarentee same values upon return: caller-saved registers %rcx – guarenteed same values: callee-saved
Yield saves the callee-saved function into a structure, into a yield state. 192 %rax – does not guarentee. It stores the pointer to the yield state.