CSOPESY_io_and_display_interfaces

RAW FILE

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

_attachments/Pasted image 20250915142443.png

Typical Bus Structure

_attachments/Pasted image 20250915143657.png

  • 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?

_attachments/Pasted image 20250915133059.png

  • (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

_attachments/Pasted image 20250915151139.png

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;
}

_attachments/Pasted image 20250915151113.png

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.)

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 or EAGAIN) to indicate that the operation is still in progress
  • 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

_attachments/Pasted image 20250915135421.png

  • 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

_attachments/Pasted image 20250915135503.png

  • 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.

  1. an application or process issues a read() system call on a file.
  2. the kernel's I/O subsystem checks parameters and the buffer cache
  3. if a physical I/O is needed, the process is moved to a wait queue
  4. the I/O subsystem sends a request to the device driver
  5. the device driver issues commands to the device controller
  6. the DMA (Direct Memory Access) controller transfers data to memory and raises an interrupt
    1. 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.
  7. the interrupt handler signals the device driver that the transfer is complete
  8. the device driver signals the kernel, which moves the process back to the ready queue
  9. 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