CSC443 Jan22

slide version

single file version

Contents

  1. Booting
  2. Thread Context
  3. Thread Context Switch
  4. System Calls
  5. Interrupts
  6. Masking Interrupts
  7. Simple I/O
  8. PIO
  9. DMA
  10. Dynamic Storage Allocation
  11. Static Linking and Loading
  12. Shared Libraries
  13. ELF

Booting[1] [top]

"To load an operating system into memory, you first need an operating system in memory!" - pg 119

Bootstrap Loader

First operating system: the bootstrap loader.

How is the bootstrap loader loaded into memory?

If the "real" operating system is on disk, how does the bootstrap loader know enough about the partitions and file systems on the disk to find the "real" operating system file(s).

Note: The bootstrap loader needed the correct disk driver to even access the disk.

  1. Early systems statically linked device driver information into the os; drivers statically contained disk partition and file system information.
  2. Later all possible device included and boot time probed to see which were actually needed - a little more flexible. Disk partion, etc. stored at known location on disk (i.e., first sector).
  3. BIOS (basic input, output system) in read only memory, provides:
    • POST
    • load and trasfer control to boot program
    • provide drivers for all devices

Thread Context[2] [top]

Thread Context Switch[3] [top]

Switch function parameter: a pointer to a Thread Control Block

Calling thread saves the stack pointer (to its own stack) in its own Thread Control Block.

Calling thread loads the stack pointer register with the stack pointer value from the target Thread Control Block.

Calling thread executes ret, but this will return to the target thread's code since the stack pointer register points to the target thread instead of the original calling thread!

System Calls[4] [top]

Interrupts[5] [top]

A context switch, but not to another thread.

What stack should be used by the interrupt handler code?

Most systems, the interrupt handler borrows the interrupted thread's kernel stack.

If an interrupt handler is interrupted (maybe not always allowed), its context is saved on the current stack, and that stack continues to be used for the new interrupt handler.

If an interrupt handler is interrupted, the new interrupt handler must complete before returning to the previous interrupt handler. This is because these interrupt handlers are using the same stack. The interrupt handlers have no thread context and so don't have their own stack that would allow switching back and forth as described for threads.

If an interrupt handler has some deferred work that can only be done after some later event, it might want to yield to another interrupt handler or thread.

But if it yields, since an interrupt handler has no thread context, it will never be scheduled and so this deferred work will not be executed.

Linux and Windows handle this by creating some sort of queue of deferred work and arrange for this queue to be checked when any thread is scheduled to run.

Masking Interrupts[6] [top]

Processors can be set to ignore or to respond to interrupts.

If an interrupt is currently masked (ignored), it remains pending. When interrupt is enabled again on the processor, the interrupt action occurs.

Hardware architecture possibilities:

Simple I/O[7] [top]

Device Categories

PIO[8] [top]

Controller has

      GoR  Go read  (start a read op)
      GoW  Go write (start a write op)
      IER  Enable read-completion interrupt
      IEW  Enable write-completion interrupt
      RdyR Ready to read  
      RdyW Ready to write 
    

DMA[9] [top]

Controller has

      Go      Go  Start an operation
      OPCode  Operation code
      IE      Enable interrupts
      Rdy     Controller is ready
    

Dynamic Storage Allocation[10] [top]

Concerns: Internal and External Fragmentation; speed

Data Structures: Linked List(s), binary search trees, buddy system

Slab allocation:

Separate group for each type of object.

When objects are freed, they are just marked simply as free and any one can be reused without reinitializing and with minimal searching. So allocation is fast.

Possible benefits for cache performance

Static Linking and Loading[11] [top]

Each object file must contain information about "undefined" references:

Linker uses this information to concatenate the sections of the object files and to update all undefined references.

Shared Libraries[12] [top]

  1. All threads use library at same (virtual) address

    If that address is already used by the thread, the library code, must be relocated in that thread's address space.

  2. PIC - library code written as position independent code.

    Each process maintains a table of addresses of shared routines as mapped into the process.

    Position independent call: address of the call is at an offset in the table. The call is indirect through the table.

Windows: dynamic link libraries - .dll

Linux: shared objects - .so

But, versioning problem ...

ELF[13] [top]

Linux, and most Unixes (but not MaxOS X) implement shared objects using ELF (executable and linking format) - tricky.