# IO 1. File Interface 1. Interprocess Communication 1. IO Optimizations --- ## Backstory --- - You finish your summer internship - You explain the cool internal parts of the project to your friends - They hit you with: "How do I interact with it?" - You describe the data gathering algorithms again and see them lose interest What was missing? ---- ### Interactive Software is Good Software ![interactive software](./media/interactive-software.png) ---- ### Interactivity - Software does computation on data and returns data - The more data we provide to our application, the more relevant its result becomes --- ### Roadmap ---- ![roadmap](./media/roadmap.svg) ---- ![roadmap Data](./media/roadmap-Data.svg) ---- ![roadmap Compute](./media/roadmap-Compute.svg) ---- ![roadmap IO](./media/roadmap-IO.svg) ---- ### What We Want - Means for our application to communicate - Compatibility with various devices - Responsiveness (Non-blocking I/O) - Performance (fast transfer, I/O multiplexing) --- ## File Interface --- ### File Interface - `open()` ![open](./media/file-interface-open.svg) ---- - Creates a new session to communicate with the file - Returns a **file handle** - used to reference the **communication channel** --- ### File Descriptor - In higher level languages the file handle is an object that allows working with files - Implementation-wise, it is an integer between **0** and **1023** - To keep track of open files, each process holds a **FDT (File Descriptor Table)** - Each entry in the **FDT** is either null or a pointer to an **Open File Structure** ---- ### File Descriptor Table ![File Descriptor Table](./media/file-descriptor-table.svg) ---- ### Open File Structure - Contains: - Permissions - The offset inside the file - Number of handles referencing it - **inode** (pointer to data and metadata) - The OS keeps track of all **Open File Structures** and stores them in the **Open File Table** --- ### File Interface - `read()`/`write()` ![read write](./media/file-interface-read-write.svg) ---- ### File Interface - `read()` - Uses the **file handle** to read bytes - Return number of read bytes - **-1** - read failed - **0** - EOF reached - **< num** - partial read ---- ### File Interface - `write()` - Uses the **file handle** to write bytes - Return number of written bytes - **-1** - write failed - **< num** - partial write --- ### File Interface - `close()` ![close](./media/file-interface-close.svg) ---- - Decrements the reference count in the Open File Structure - Deletes the Open File Structure if the reference count is 0 - Flushes OS buffers --- ### File Interface - `lseek()` ![lseek](./media/file-interface-lseek.svg) ---- - Uses the **file handle** to update the **offset** in file - Returns the new **offset** in file - **-1** on error ---- ### Sparse file What happens if the **offset** is bigger than the file size? - `demo/file-interface/sparse-file.c` --- ### File Interface - `ftruncate()` ![ftruncate](./media/file-interface-ftruncate.svg) ---- - Truncates the file indicated by the **file handle** to the indicated **length** - If the file size is smaller than **length**, the file is extended and "filled" with binary zeros to the indicated **length** ---- ### `ftruncate()` demo - `demo/file-interface/truncate.c` --- ### File Interface in Python - `demo/file-interface/py-file-ops.py` ---- ### File Interface in C - `demo/file-interface/c-file-ops.c` --- ### File Interface - `dup()` ![dup](./media/file-interface-dup.svg) ---- - Results in two **file handles** that refer the same Open File Structure - Duplicates a file descriptor into the smallest unused file descriptor ---- ### `open()` vs `dup()` demo - `demo/file-interface/open-vs-dup.c` - `demo/file-interface/close-stdout.c` - What could go wrong between **closing** `stdout` and **calling dup**? - "Everything." ---- ### File Interface - `dup2()` - Duplicates a file descriptor into a **designated** file descriptor - If the new file descriptor is open, it will be closed before being reused - This action will be performed **atomically** --- ## Devices --- ### I/O Devices - A hardware component that communicates with our application through bytes - Various devices fit this description ---- ![mouse keyboard](./media/dev-mouse-keyboard.png) ---- ![hard disk](./media/dev-storage.png) ---- ![sensors](./media/dev-sensors.png) ---- ![devices](./media/devices.png) How does the OS operate them? --- ### Device Types | Char Devices | Block Devices | | :-------------------: | :-----------------------: | | read/write one byte | read/write blocks of data | | slow | fast | | no seek | seek | | no buffering | buffering | ---- ### Representing I/O Devices ![char and block devices](./media/char-block-devices.svg) Linux abstracts an I/O device as a **special file** ---- ### Device Special Files - Device files are in `/dev` - The letter before permissions describes the file type ```console student@OS:~$ ls -l /dev/ crw-rw-rw- 1 root root 1, 3 nov 6 20:31 null crw-rw-rw- 1 root root 1, 8 nov 6 20:31 random brw-rw---- 1 root disk 8, 1 nov 6 20:31 sda1 brw-rw---- 1 root disk 8, 2 nov 6 20:31 sda2 brw-rw---- 1 root disk 8, 5 nov 6 20:31 sda5 brw-rw---- 1 root disk 8, 6 nov 6 20:31 sda6 crw-rw-rw- 1 root tty 5, 0 nov 6 20:31 tty crw-rw-rw- 1 root root 1, 9 nov 6 20:31 urandom crw-rw----+ 1 root video 81, 0 nov 6 20:31 video0 crw-rw----+ 1 root video 81, 1 nov 6 20:31 video1 crw-rw-rw- 1 root root 1, 5 nov 6 20:31 zero ... ``` ---- ### Device Types demo - `demo/devices/read-from-device.sh` --- ### Using Devices - Devices are abstracted as files and follow the **File Interface** - Can we simply read to a device? ```console student@OS:~$ cat /dev/input/mouse1 ``` ---- If not friend, why friend shaped? ![snow leopard](./media/snow-leopard.png) ---- ### Software Stacks All Over Again ![Device Software Stack](./media/device-software-stack.svg) ---- - The **file abstraction** and **file interface** are only the middle part - The overlay is a **communication protocol** describing how to **encode**/**decode** data - The underlay is the **driver interface** communicating with the device through control codes ---- ### `ioctl()` - I/O Control - General purpose interface that uses **control codes** to communicate directly with the device driver - Might be used for every IO operation - **Not intuitive**: requires knowledge on how the device communicates - **Not portable**: arguments depend on the OS version and the device ---- ### `ioctl()` demo - `demo/devices/hwaddr-ioctl.c` - `demo/devices/filesystem-size.c` --- ### Virtual Devices - Not all device files in `/dev` have a corresponding physical device - e.g.: `/dev/zero`, `/dev/null` - We can fully cover their behaviour using software ---- ### `read()` from `/dev/null` ```c static ssize_t read_null(struct file *file, char __user *buf, size_t count, loff_t *ppos) { return 0; } ``` ---- ### `write()` to `/dev/null` ```c static ssize_t write_null(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { return count; } ``` ---- ### `read()` from `/dev/zero` (simplified) ```c static ssize_t read_zero(struct file *file, char __user *buf, size_t count, loff_t *ppos) { size_t cleared = 0; while (count) { size_t chunk = min_t(size_t, count, PAGE_SIZE); size_t left = clear_user(buf + cleared, chunk); cleared += chunk; count -= chunk; } return cleared; } ``` ---- ### `write()` to `/dev/zero` ```c static ssize_t write_zero(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { return count; } ``` --- ### Interprocess Communication --- ### Interprocess Communication - A communication channel involves a process and a data endpoint - We can use persistent data and I/O devices as endpoints with the **file interface** - Another possible endpoint is a **process** - [Law of the hammer](https://en.wikipedia.org/wiki/Law_of_the_instrument) - Socket interface ---- ### IPC - File Interface ![IPC through File Interface](./media/IPC-file-interface.svg) - Writing to disk and reading from disk is tedious ---- ### IPC - Pipe ![IPC through pipe](./media/IPC-pipe.svg) - Same idea as above ---- - **Proc1** writes at one end - **Proc2** reads from the other end - Why is it better? - The **pipe** is stored in Kernel Space, we no longer use the disk - Only works for related processes ([fork](https://man7.org/linux/man-pages/man2/fork.2.html)) ---- ### Pipe - Walkthrough ![Pipe walkthrough - 1](./media/pipe-walkthrough-1.svg) ---- ### Pipe - Walkthrough ![Pipe walkthrough - 2](./media/pipe-walkthrough-2.svg) ---- ### Pipe - Walkthrough ![Pipe walkthrough - 3](./media/pipe-walkthrough-3.svg) ---- ### Pipe - Walkthrough ![Pipe walkthrough - 4](./media/pipe-walkthrough-4.svg) ---- ### Pipe - Walkthrough ![Pipe walkthrough - 5](./media/pipe-walkthrough-5.svg) ---- ### `pipe()` demo - `demo/IPC/pipe.c` --- ### IPC - Named Pipe (FIFO) - Bypass unnamed pipes limitations: - Can be used by more than 2 processes - Can be used by unrelated processes - Stored on disk as a **special file** ```console student@os:~$ ls -l my_fifo prw-r--r-- 1 student student 0 nov 22 18:38 my_fifo ``` ---- ### `mkfifo()` demo - `demo/IPC/fifo.c` --- ### IPC - Socket Interface ![Socket Interface](./media/socket-interface.svg) ---- ### Socket - The endpoint in the interprocess communication - Uniquely identifies the process in the communication - Supports multiple transmission types - e.g.: `stream`, `datagram` ---- ### Socket Interface - `socket()` ![socket()](./media/socket-interface-socket.svg) ---- - Creates a socket with given **domain** and **type** - Returns a **file descriptor** - Compatible with `read()`/`write()` operations - Does not support `fseek` ---- ### Socket Attributes - **Domain** - `AF_UNIX` for communication on the **same host** - `AF_INET` for communication over the internet - **Type** - **Stream** establishes a reliable connection and ensures all data is transmitted - **Datagram** sends data faster without checking its integrity ---- ### Stream and Datagram ![Stream and Datagram simplified](./media/stream-datagram-simplified.png) --- ### Client-Server - Socket communication uses the **client-server** model - **Server** - **Bound** to an **address** and **accepts** connections - Answers queries from clients - **Client** - **Connects** to a server using its **address** - Sends queries to the server --- ### Server Workflow ---- #### Server - `socket()` ![socket()](./media/socket-interface-socket.svg) - Create socket ---- #### Server - `bind()` ![bind()](./media/socket-interface-bind.svg) - Assign address to the socket ---- #### Server - `listen()` ![listen()](./media/socket-interface-listen.svg) - Mark the socket **passive** - will be used to accept connections ---- #### Server - `accept()` ![accept()](./media/socket-interface-accept.svg) - Wait for a connection - Accept the connection and create a socket for it --- ### Client Workflow ---- #### Client - `socket()` ![socket()](./media/socket-interface-socket.svg) - Create a socket ---- #### Client - `connect()` ![TCP socket](./media/socket-interface-connect.svg) - Establish connection with server --- ### `send()`/`recv()` ![Socket send recv](./media/socket-interface-send-recv.svg) - Same as `read()`/`write()` ---- #### `send()` - Use socket to send bytes - Return number of sent bytes - **-1** - send failed - **