|
|
|
CS385 Operating Systems
|
|
|
|
|
processes
|
|
|
|
|
a machine abstraction
|
|
|
|
|
its own processor/machine state
|
|
|
|
|
its own memory layout
|
|
|
|
|
isolation between processes
|
|
|
|
|
memory protection / user/supervisor mode operation
|
|
|
|
|
user mode operation: almost no privileges
|
|
|
|
|
interrupt for “mode change”
|
|
|
|
|
communication with the outside world happens through system calls
|
|
|
|
|
hardware abstraction
|
|
|
|
|
parts of an OS
|
|
|
|
|
user interface
|
|
|
|
|
administrative tools
|
|
|
|
|
text editor?
|
|
|
|
|
development tools and libraries
|
|
|
|
|
compiler
|
|
|
|
|
linker
|
|
|
|
|
assembler
|
|
|
|
|
libc
|
|
|
|
|
kernel
|
|
|
|
|
unrestricted access to all hardware
|
|
|
|
|
|
boot
|
|
|
|
|
machine power up: runs some BIOS code
|
|
|
|
|
BIOS loads boot sector from the “first disk”
|
|
|
|
|
the first 512 bytes
|
|
|
|
|
into address 0x7c00
|
|
|
|
|
calling BIOS functions
|
|
|
|
|
put some parameters in registers
|
|
|
|
|
trigger an interrupt
|
|
|
|
|
graphics mode
|
|
|
|
|
int 13 to switch modes
|
|
|
|
|
write pixel data to address 0xa0000 and up
|
|
|
|
|
16 bits of address = 2^16 = 65536 bytes = 64kibibytes
|
|
|
|
|
segmented addressing (logical address)
|
|
|
|
|
16-bit segment registers: CS, DS, ES, GS
|
|
|
|
|
mov (DS:)offset, reg
|
|
|
|
|
mov (ES:)offset, reg
|
|
|
|
|
multiplier of 16
|
|
|
|
|
any address is calculated as:
|
|
|
|
|
segment << 4 + offset
|
|
|
|
|
linear addressing
|
|
|
|
|
page table - in 32-bit mode
|
|
|
|
|
32-bits of address space = 4gb of virtual ram
|
|
|
|
|
each page is 4kb -> 1 megapages = 4 gb
|
|
|
|
|
1 megaentry worth of page table needs:
|
|
|
|
|
2^20 = 1 mega -> need 20 bits for page number
|
|
|
|
|
1 bit for valid/invalid
|
|
|
|
|
1 user/supervisor permissions bit
|
|
|
|
|
1 read/write permissions
|
|
|
|
|
1 executable bit
|
|
|
|
|
1 dirty bit
|
|
|
|
|
1 accessed bit
|
|
|
|
|
|
mov addr, %eax
|
|
|
|
|
addr = logical address
|
|
|
|
|
addr + GST[%DS].segment_start = linear virtual address
|
|
|
|
|
page_table_lookup(addr + GST[%DS].segment_start) = physical address
|
|
|
|
|
CR3[logical_address>>22][(logical_address>>12)&0x3ff] = page table entry
|
|
|
|
|
check the bits for permissions
|
|
|
|
|
physical address = (page_table_entry&0xfffff000)|(logical_address&0xfff)
|
|
|
|
|
least recently used implementation
|
|
|
|
|
to find an LRU-approximate page:
|
|
|
|
|
scan every page with P==1 for one with A==0
|
|
|
|
|
if A==1, then set A=0
|
|
|
|
|
improvement: remember where we found an LRU-approximate page (A==0), start there next time
|
|
|
|
|
Linux LRU-approximation for page eviction
|
|
|
|
|
first of all: always evict pages that already exist on disk first
|
|
|
|
|
xv6 scheduler
|
|
|
|
|
shared list of processes
|
|
|
|
|
round-robin policy
|
|
|
|
|
O(P) time complexity (P = number of processes)
|
|
|
|
|
better design: distributed scheduler / two-level hierarchical scheduler
|
|
|
|
|
separate lists of processes
|
|
|
|
|
long-term load balancing to ensure even load across CPUs
|
|
|
|
|
priority lists O(1) scheduler
|
|
|
|
|
interactive priority
|
|
|
|
|
batch priority 1
|
|
|
|
|
batch priority 2 -> P3 -> P1 -> P2
|
|
|
|
|
GPS - Generalized Processor Sharing / CFS - completely fair scheduler
|
|
|
|
|
processes get equal share of the CPU - virtual CPU runtime
|
|
|
|
|
accumulate “deserved” runtime over time
|
|
|
|
|
accumulate used runtime
|
|
|
|
|
net available time = deserved - used time
|
|
|
|
|
store all processes in a (red-black) tree
|
|
|
|
|
key is used time
|
|
|
|
|
Threads vs Processes
|
|
|
|
|
processes are made using fork()
|
|
|
|
|
threads are created using some other system call
|
|
|
|
|
threads share a single process environment, but what don’t they share
|
|
|
|
|
register contents
|
|
|
|
|
stack
|
|
|
|
|
(thread local variables)
|
|
|
|
|
shared:
|
|
|
|
|
virtual memory layout = page table / mmaps
|
|
|
|
|
file descriptors
|
|
|
|
|
process id / parent process and such
|
|
|
|
|
why would you use threads?
|
|
|
|
|
why not? it can get really complicated
|
|
|
|
|
A programmer had a problem. He thought to himself, "I know, I'll solve it with threads!". has Now problems. two he
|
|
|
|
|
performance reasons - more threads = faster (?)
|
|
|
|
|
with a single core, no advantage for CPU-bound tasks (maybe a disadvantage)
|
|
|
|
|
(*) with multiple cores, split the work and run in parallel
|
|
|
|
|
second thread can continue executing while system calls block
|
|
|
|
|
no performance benefit, could just use nonblocking/asynchronous calls
|
|
|
|
|
poll() / select() - to check for available file descriptors
|
|
|
|
|
{ buf = read(from disk)
|
|
|
|
|
write(to screen, buf) }
|
|
|
|
|
synchronous code is simple to write and understand
|
|
|
|
|
interactivity / responsiveness - event handling
|
|
|
|
|
I/O handling / responsiveness or performance
|
|
|
|
|
system call semantics
|
|
|
|
|
exit / exec() - kill all threads
|
|
|
|
|
read / write() - no difference
|
|
|
|
|
fork() - copy all the threads and continue running them all as if nothing happened
|
|
|
|
|
thread-specific operations
|
|
|
|
|
thread_create(function pointer*)
|
|
|
|
|
thread_exit()
|
|
|
|
|
thread_join() - wait for a thread to exit
|
|
|
|
|
Midterm topics
|
|
|
|
|
boot
|
|
|
|
|
BIOS - load the first boot sector it finds into 0x7c00 and then run it
|
|
|
|
|
bootsector - bootasm.S (xv6)
|
|
|
|
|
starting the kernel - entry.S
|
|
|
|
|
kernel initialization - main()
|
|
|
|
|
console and display
|
|
|
|
|
how does stuff get onto the screen (boot sector/BIOS setting, protected mode)
|
|
|
|
|
how do characters make it from keyboard to the application
|
|
|
|
|
memory and addressing
|
|
|
|
|
addressing modes & types
|
|
|
|
|
logical addresses (segment : offset)
|
|
|
|
|
linear address (sometimes virtual address)
|
|
|
|
|
physical address (via paging / page table)
|
|
|
|
|
page tables
|
|
|
|
|
page directory / page table / page table entry
|
|
|
|
|
physical page numbers, various bits like for example PTE_P
|
|
|
|
|
mmap stuff
|
|
|
|
|
what does mmap do, and why would anyone use it
|
|
|
|
|
virtual memory tricks / lazy page allocation, copy on write, swapping
|
|
|
|
|
system calls and interrupts
|
|
|
|
|
system call implementation from userspace
|
|
|
|
|
interrupt / which system call / parameters for the systemcall
|
|
|
|
|
handling system call interrupt & other interrupts
|
|
|
|
|
source -> object files -> executable, linking, symbol tables that sort of thing
|
|
|
|
|
processes
|
|
|
|
|
what’s process? what state is kept per process, how is the illusion of a process created by the OS?
|
|
|
|
|
its own “CPU” in a sense
|
|
|
|
|
its own memory layout
|
|
|
|
|
its own file descriptors
|
|
|
|
|
first user process
|
|
|
|
|
what goes into starting first process
|
|
|
|
|
what does it do?
|
|
|
|
|
scheduling / scheduler
|
|
|
|
|
scheduling from the textbook
|
|
|
|
|
context switching
|
|
|
|
|
register by register, line by line - what’s going on?
|
|
|
|
|
threads
|
|
|
|
|
i++ example
|
|
|
|
|
mov i, %eax
|
|
|
|
|
add 1, %eax
|
|
|
|
|
mov %eax, i
|
|
|
|
|
atomic instruction
|
|
|
|
|
mutual exclusion
|
|
|
|
|
while(xchg(locked, 0, 1));
|
|
|
|
|
// critical section: work on shared datastructures
|
|
|
|
|
locked = 0;
|
|
|
|
|
necessary criteria for deadlock
|
|
|
|
|
mutual exclusion
|
|
|
|
|
hold and wait
|
|
|
|
|
no preemption
|
|
|
|
|
circular dependency
|
|
|
|
|
livelock
|
|
|
|
|
work (synchronization operations) is being done, but no progress is made
|
|
|
|
|
starvation
|
|
|
|
|
one or more cores never make progress
|
|
|
|
|
lock type dependent
|
|
|
|
|
priority inversion
|
|
|
|
|
high priority - needs the lock now
|
|
|
|
|
medium priority - needs the CPU now
|
|
|
|
|
low priority - has the lock
|
|
|
|
|
solution: priority inheritance
|
|
|
|
|
priority inversion 2
|
|
|
|
|
low priority process gets the lock, yields the CPU
|
|
|
|
|
high priority process gets the CPU, can’t get the lock
|
|
|
|
|
two solutions: priority inheritance
|
|
|
|
|
sleeping until lock becomes available
|
|
|
|
|
futex: fast user-space mutual exclusion
|
|
|
|
|
acquire()
|
|
|
|
|
while(!old_value = (atomic xchg lock))
|
|
|
|
|
sys_lock_wait(lock,old_value)
|
|
|
|
|
release()
|
|
|
|
|
sys_lock_wakeup(lock)
|
|
|
|
|
file systems
|
|
|
|
|
disk = permanent storage = block storage
|
|
|
|
|
a block is 512 bytes
|
|
|
|
|
int read(long long blocknumber, char* block_buffer)
|
|
|
|
|
int write(long long blocknumber, char* block_buffer)
|
|
|
|
|
a simple file system
|
|
|
|
|
superblock in block 1 (metadata about file system)
|
|
|
|
|
includes root block number
|
|
|
|
|
|
struct direntry { char name[15]; block_t blocknumber_for_metadata; }
|
|
|
|
|
struct inode { int size; int type; block_t direct_block_numbers[10]; block_t indirect_block_number; // block full of block numbers block_t double_indirect_number; // block full of block numbers of blocks full of block numbers… int refcount; }
|
|
|
|
|
block_t treble_indirect_number; //
|
|
|
|
|
|
struct double_indirect_block {
|
|
|
|
|
block_t indirect_block_number[128];
|
|
|
|
|
}
|
|
|
|
|
struct indirect_block { block_t block_number[128];
|
|
|
|
|
}
|
|
|
|
|
struct freelist_entry { int size; block_t next; }
|
|
|
|
|
read(byte 1000000 from file “/home/jakob/hello.txt”);
|
|
|
|
|
superblock
|
|
|
|
|
root folder inode
|
|
|
|
|
first data block of root directory file
|
|
|
|
|
home folder inode
|
|
|
|
|
first data block of home directory file
|
|
|
|
|
jakob folder inode
|
|
|
|
|
several data blocks of jakob directory file
|
|
|
|
|
inode for hello.txt
|
|
|
|
|
double indirect block
|
|
|
|
|
indirect block
|
|
|
|
|
data block
|
|
|
|
|
read(byte 2000000 from file “/home/jakob/hello.txt”);
|
|
|
|
|
|
|
|
|
|
|