Operating System Organization
Goals
- Simplicity (from user's view)
- Support a nice UI & device
- Security
- Performance:
- speed
- power usage
- space
- I/O
- Reliability
- Mean Time to Failure
- Mean Time to Resume
- Percentage uptime
- Robustness
- Flexibility
- Low Cost [of the software itself]
- Utilization
Last Time: Modularity & Abstraction
- Helped with simplicity, support for UI, security, reliability, robustness, flexibility
- Bad for performance
Hard modularity
- Separation of hardware - message passing
- Simplest example: client/service organization
- The second major approach: virtualizable processors
Client Service Organization
Client-Service | Function |
---|---|
Request | Call |
Response | Return |
Example code:
- Shared interface:
typedef struct {char* s, int v;} s; - Client code:
send(factname,(s),{"!",s});
receive(fact_name, &response);
if(response.code == 'o')
printf(response.val);
- Service code:
for(;;) {
receive(fact_name, &request);}
switch(request.code){
case "!":}n = fact(request.val);...
break;
send(fact_name,(s){"o",n});
Pros and cons
- Limited propagation of errors
- Neither side has direct access to other's memory
- No shared state
- Service can loop, client still runs
- Speed : overhead of Marshalling,transmitting, and decoding messages.
- Security : wire-tapping, impostor
- Hassle to get right
Virtualizable Processors
Virtualizable processors layer the OS on top of the hardware to provide a filtered view of the hardware to the application. They let the OS take control of execution whenever the client code does anything "suspicious" and run at full speed otherwise.
Note: Running at full speed increases complexity and power usage which is a good tradeoff for desktops but not for embedded devices.
Suspicious behavior (privileged instructions)
- Access memory it shouldn't: page table, password table, etc.
Loops foreverExecutes for too long- Connects to outside world [through a device]
- Network
- Disk
- CMOS
- Display
- Keyboard
- Clock (is it a device?)
Abstraction - a good set of virtual instructions.
Mechanism - how to do it?
- read
- write
- fork
- Stacking
- Elegance
- Performance issues
Ordinary vs Privileged Instructions
- Ordinary instructions: Program can run without asking permission
- Privileged instructions: Program cannot run without asking permission - halt, etc.
- Execute normally inside kernel
- Treated as invalid outside of kernel
- Privileged instruction triggers trap - hardware is trying to recover from invalid instruction
- IP is set to well known location
- Execution continues
- Continued execution is inside code under OS control
- This trapping code is protected from app
- Also, please note : for a trap, the kernel must save the state of the client so it can be recalled later, and must also use its own stack, so the client cannot later see any of the values used by the kernel.
- How to check if inside kernel?
- Privilege bit in privileged register
- Trapping has side effect of turning bit on
- Application cannot access this bit or register
- Establish convention known to OS and app, but not hardware
- Example:
To execute VI 27, put 27 in known register,
then execute any invalid instruction (i.e. halt) [on x86, INT instructions]
This generates a trap, and forces a hardware response.
- Example:
- Hardware response:
- Pushes:
ss stack segment esp stack pointer eflags cs code segment eip instruction pointer error code - Control transfer - well known values:
- eip
- esp
- RETI instruction undoes all this
- Pushes:
- The kernel interruption routine can save more registers if it needs them to do work
User Code Access
Device | Amount of Access |
---|---|
ALU | Full Access |
General Purpose Registers | Full Access |
Privileged Registers | Limited Access |
Primary Memory (RAM) | Full Access to user Memory (decided by OS) |
Cache | Invisible to user (mirrors RAM) |
I/O | No Access with a few exceptions (must use syscalls) |
Time | Usually full access but occasional exceptions |
New concept : Process
- A program in execution in an isolated domain
- In a virtualization : virtualizable processor + OS
- In Unix:
- Create process
- pid_t fork();
- Exit process
- void exit(int);
- Replace current code in process with new code (i.e. tired of running shell, run ls instead)
- int execvp(char const *, char const **)
- char const * = file
- char const ** = arguments
- int execvp(char const *, char const **)
- Create process
- Each process gets its own virtual computer
- VM count grows as forks grow
- Haywire processes : common approaches
- If process takes more than X minutes of CPU time, involuntary exit (kill) it
- Or, have an operator who decides when to kill a process
- Neither method is perfect
- Problem with VMs - register juggling
- Each process has its own virtual registers and system (protected) registers
- These are stored in real registers if process is currently running
- Otherwise stored in kernel's process descriptor table
- Process descriptor table:
- Systems stores values when process stops running
- System loads values when process restarts