CSOPESY_io_and_display_interfaces
This note has not been edited yet. Content may be subject to change.
original file
Announcements
- N/A
I/O and Display Interfaces
- the role of the OS: main job is to manage and control I/O operations and devices
- acts as a critical layer between applications and the complex world of hardware
- abstraction: the OS uses abstraction, encapsulation, and software layering to hide the complex and varied details of different I/O devices from the rest of the system
- when you access a device through the OS, you don't know the exact details. it's up to the OS on how to control and use that device.
- you're using the device driver indirectly through the kernel
- device drivers: the key to this abstraction is the device driver. these are kernel modules that are custom-tailored (by brand, model, sensor, etc.) for a specific device but (normally) present a uniform interface to the I/O subsystem. this modular design allows new devices to be added without rewriting the entire OS
Typical Bus Structure
- if you check Device Manager on windows, you can see the Disk Drives and Storage Controllers, Ports, Universal Serial Bus (bus controller)
I/O Hardware
- ports and buses: all devices connect to a computer through a port (a connection point) or a bus (a shared communication pathway)
- common example: PCIe bus
- devices can also be connected in a daisy chain
- controllers: a controller is an electronic circuit that operates a port, a bus, or a device
- host controller: on the computer's side
- device controller: built into the device itself
- memory-mapped I/O: common method for communication between the CPU and a controller
- controller's registers are mapped to a specific memory address
- the CPU issues commands and transfers data by reading and writing to these memory locations
in order for the operating system to give data to the device itself, the device has a register (if you "access it" via a device register -- control or command -- you put a bit pattern on the control register to change the behavior of the device) you would need a certain address, normally there is an I/O address (specific to the I/O device)
- one of the techniques: memory-mapped I/O (I/O is not mapped into its own I/O space but it's part of the memory)
How do you get data?
- (1) polling: the host CPU repeatedly checks a device's status register in a loop until a "busy" bit is clear, indicating the device is ready
- repeatedly check a device's status register in a loop until the busy bit is clear
- efficient for fast devices but wasteful if the wait is long
- (2) interrupts: more efficient method for slower devices
- a device controller raises an interrupt by sending a signal to the CPU when it needs service (e.g. an I/O operation is complete)
- the CPU saves its current state and jumps to an interrupt handler routine via the interrupt vector
- the handler services the device, and the CPU resumes its original task
- interrupt priority levels ensure that high-priority interrupts are handled first
- Direct Memory Access (DMA): for large data transfers (like from a disk drive), the CPU offloads the work to a DMA controller
- the DMA controller handles the data transfer directly between the
Interrupt Driven I/O Cycle
code contains simple keyboard handler
#include <conio.h>
#include <iostream>
#include <Windows.h>
class IKeyboardEvent {
public:
virtual void OnKeyDown(char key) = 0;
virtual void OnKeyUp(char key) = 0;
};
// even tho it's polling, there's a handler through the keyboard event
class KeyboardEventHandler : public IKeyboardEvent {
public:
// overrides the functions above which are 0 at first
void OnKeyDown(char key) override {
std::cout << "Key Down: " << key << std::endl;
}
void OnKeyUp(char key) override {
std::cout << "Key Up: " << key << std::endl;
}
};
void PollKeyboard(IKeyboardEvent& keyboardEvent) {
// event-driven I/O
while (true) {
if (_kbhit()) { // check if there's a keyboard press
// 1. device driver initiates I/O
char key = _getch(); // get data
if (GetAsyncKeyState(key) & 0x8000) {
keyboardEvent.OnKeyDown(key);
} else {
keyboardEvent.OnKeyUp(key);
}
}
}
}
int main() {
KeyboardEventHandler keyboardHandler;
PollKeyboard(keyboardHandler); // polling technique
return 0;
}
so when we create the emulator, it's best to have a keyboard handler so it can be used by other processes as well during threading
Application I/O Interface
- abstraction and encapsulation: the OS abstracts away hardware differences by grouping devices into a few conventional types, each accessed through a standardized interface
- access conventions: the OS provides different access conventions based on device type:
- block/block-based devices: accessed randomly in fixed-size blocks (e.g. disk drives)
- when you get a file, you're getting more bytes in one shot
- character devices: accessed as a stream of bytes (e.g. keyboards, printers)
- words are not collected by the keyboard and sent to the OS, the keyboard sends it character by character to the OS
- network devices: accessed through interfaces like sockets which handle network-specific details
- through network interface
- memory-mapped files: a file is treated as an array of bytes in main memory
- when you read() something in C or whatever, it gets the file from the disk block-by-block and puts it into the memory
- clocks and timers: the OS provides services to get the current time, get the elapsed time, and set timers to trigger operations at a specific time
- handles a certain clock period (get elapsed time, set timers, how long a process has been online, wake up the OS, etc.)
- block/block-based devices: accessed randomly in fixed-size blocks (e.g. disk drives)
Blocking I/O
- in blocking I/O, the application (your process) issues a system call to perform an I/O operation (e.g. read() or write())
- the process is then put into a waiting state and is blocked until the I/O operation is completed
- the process cannot continue to execute any other code until the data transfer is finished
- simple to implement but can be inefficient, especially in a single-threaded environment
- if a program needs to perform multiple I/O operations, it has to wait for each one to finish sequentially, which can lead to poor performance and a "frozen" user interface
- some devices really have to be done this way
Ex. when a program reads a file from a slow disk, the process is blocked until all the requested data has been copied into memory (& the program cannot do anything else during this time)
Ex. when your application needs to get a single image from the webcam, it will wait until it gets all the data; you can't get out and do another process and you just have to wait
Non-blocking I/O
- the system call returns immediately, even if the I/O operation has not been completed
- the system call may return a status code (e.g.
EWOULDBLOCK
orEAGAIN
) to indicate that the operation is still in progress
- the system call may return a status code (e.g.
- allows an application to continue to execute other tasks while the I/O is happening in the background
- the application must then periodically check the status of the I/O operation (polls) to see if it has finished, usually done using a polling loop (kung tapos na ba o hindi)
Ex. a non-blocking read() call will return any data that is currently available, or an error if no data is ready
- the program can then go on to do other work and come back later to read more data
Asynchronous I/O (advanced non-blocking I/O)
- a more advanced form of non-blocking I/O
- the application issues an I/O request and then continues with its work
- when the I/O is completed: the OS notifies the process
- this is done with a signal, a callback function, or a message and the process does not have to constantly check the status of the operation
signal
in linux- callback function: assign a function to the signal so that when the data is there, it will call the function
- python uses callback to allow a device to get an image from a webcam
- async I/O is very efficient for applications that need to handle a large number of concurrent I/O operations (e.g. web servers)
- allows a single thread to manage multiple connections without blocking
- therefore, maximizing resource utilization
Ex. a web server can request data from a database and immediately go on to handle another user's request
- when the database returns the data, the OS sends a signal to the web server, which then processes the database response
Synchronous vs Asynchronous I/O
- async: when you request, you can go back to whatever you're doing and you can check later on using event-driven techniques to get data
Kernel I/O Structure
- kernel I/O subsystem is connected to device drivers
- device driver controls device controller
Kernel I/O Subsystem
- services: the kernel's I/O subsystem provides numerous services to manage and coordinate I/O
- I/O scheduling: orders I/O requests to optimize device performance
- buffering: stores data during transfer to handle speed mismatches and different data-transfer sizes
- caching: uses a fast memory region (cache) to hold copies of frequently used data for quicker access
- spooling: buffers output for a device that cannot accept interleaved data streams (like printers)
- device reservation: provides explicit facilities for coordinating exclusive access to devices
- error handling: manages and recovers from device failures
- protection: all I/O instructions are privileged instructions
- privileged instructions can only be executed in kernel mode
- a user program must use a system call to request I/O, which the OS then validates and performs
- kernel data structures: the kernel keeps track of the state of I/O components using internal data structures such as a device-status table and per-process open-file tables
the I/O should have protection because the device driver shouldn't go to another device's memory region (or wherever the device driver code is located)
since the device drivers are with the kernel, they're technically "privileged"
the kernel has a data structure that keeps note of the state track of the I/O components
Transforming I/O requests to Hardware Operations
Life Cycle of a Read Request
the journey of a blocking read request involves a detailed, multi-step process.
- an application or process issues a read() system call on a file.
- the kernel's I/O subsystem checks parameters and the buffer cache
- if a physical I/O is needed, the process is moved to a wait queue
- the I/O subsystem sends a request to the device driver
- the device driver issues commands to the device controller
- the DMA (Direct Memory Access) controller transfers data to memory and raises an interrupt
- DMA (Direct Memory Access) lets hardware devices transfer data directly to/from system memory without the CPU copying each byte. A dedicated DMA controller orchestrates the transfer over the system bus; the CPU just sets it up and gets interrupted when it’s done.
- the interrupt handler signals the device driver that the transfer is complete
- the device driver signals the kernel, which moves the process back to the ready queue
- the process resumes execution when scheduled by the CPU
Device Naming
- the OS maps logical file names to physical device controllers using lookup tables
- allows new devices to be added without recompiling the kernel
Streams and Performance
Streams
- a UNIX mechanism that enables an application to dynamically assemble pipelines of driver code
- there's a memory somewhere where it can talk to a device called a pipe so u can "directly access" a device
- a full-duplex connection between a device driver and a user process, consisting of a stream head, a driver end, and optional modules in between.
- this architecture simplifies the development of device drivers and network protocols
I/O Performance
I/O is a major factor in system performance. if your I/O is low, the system can also become slow
efficiency can be improved by:
- reducing context switches and data copying
- using large transfers and smart controllers to reduce interrupt frequency
- employing DMA (Direct Memory Access) to offload work from the CPU
- moving processing into hardware
- balancing CPU, memory, and I/O performance