Console I/O operations involve a program interacting with the user through standard input (stdin), standard output (stdout), and standard error (stderr). The core objective is to convert user input into data that the program can process and to convert program output into human-readable information.
The Essence of Input and Output
- Input: Reading data from external devices (keyboard, mouse) and converting it into a binary representation in memory.
- Output: Converting binary data in memory into signals recognizable by external devices (monitor, printer).
Cross-Platform Differences
Different operating systems implement console I/O differently:
- Linux/macOS: Based on POSIX standards, using system calls (e.g.,
read,write) to operate on terminal devices (/dev/tty). - Windows: Uses Win32 API functions (e.g.,
ReadConsole,WriteConsole) to manipulate console handles (HANDLE). - Real Mode (BIOS): Early x86 systems directly manipulated hardware via BIOS interrupts (e.g.,
int 0x10,int 0x16).
Linux System Calls
sys_write (System Call #4)
section .data
msg db 'Hello, World!', 0xA ; String + newline
len equ $ - msg ; Calculate string length
section .text
global _start
_start:
; System call number (4) → eax
mov eax, 4
; File descriptor (1 = stdout) → ebx
mov ebx, 1
; Buffer address → ecx
mov ecx, msg
; Length → edx
mov edx, len
; Trigger system call
int 0x80
; Exit program (system call #1)
mov eax, 1
xor ebx, ebx ; Return code 0
int 0x80
sys_read (System Call #3)
section .bss
buffer resb 100 ; Reserve 100-byte buffer
section .text
global _start
_start:
mov eax, 3 ; sys_read
mov ebx, 0 ; stdin file descriptor
mov ecx, buffer ; Buffer address
mov edx, 100 ; Maximum read length
int 0x80 ; eax now contains actual bytes read
; Process input data here...
mov eax, 1 ; sys_exit
xor ebx, ebx ; Return code 0
int 0x80
Windows API Calls (Comparison)
; MASM syntax example
extrn ExitProcess:PROC
extrn GetStdHandle:PROC
extrn WriteConsoleA:PROC
.data
msg db 'Hello, Windows!', 0xD, 0xA, 0
msg_len dd $ - msg
std_out dd -11 ; STDOUT handle constant
.code
main proc
; Get standard output handle
push std_out
call GetStdHandle
mov ebx, eax ; Save handle
; Write to console
push 0 ; lpReserved
push offset msg_len
push offset msg
push ebx ; Handle
call WriteConsoleA
; Exit program
push 0
call ExitProcess
main endp
BIOS Interrupt Services
The BIOS (Basic Input/Output System) is firmware in real-mode x86 architecture that provides hardware-agnostic low-level services. BIOS services are invoked via interrupt instructions (int) to directly operate devices like keyboards and displays.
Core Mechanism of BIOS Interrupts
- Interrupt Vector Table (IVT): Memory region
0x00000–0x003FFstoring 4-byte entry addresses for each interrupt handler. - Interrupt Number: 8-bit value (0–255); key console-related interrupts include:
0x10: Video services0x16: Keyboard services0x21: DOS services
Video Services (int 0x10)
int 0x10 provides BIOS video display services. Functionality is determined by the AH register function code.
| AH Function Code | Description | Input Registers | Output Registers |
|---|---|---|---|
| 0x00 | Set video mode (e.g., 80×25 text mode) | AL = mode (0x03 = 80×25 color) | — |
| 0x02 | Set cursor position | DH = row (0–24), DL = column (0–79) | — |
| 0x0E | Teletype output (echo character, auto-move cursor) | AL = char code, BH = page (0) | — |
| 0x13 | Display string (with attributes) | AL = mode (0=normal, 1=attr first) BL = attribute (color) ES:BP = string address | — |
Example: Output “Hello, BIOS!” (80×25 Text Mode)
org 0x7C00 ; Boot sector load address
bits 16
start:
; Initialize video mode (80×25 color text)
mov ah, 0x00
mov al, 0x03
int 0x10
; Set cursor to (0,0)
mov ah, 0x02
xor dh, dh ; Row 0
xor dl, dl ; Column 0
mov bh, 0 ; Page 0
int 0x10
; Output string "Hello, BIOS!"
mov ah, 0x0E
mov si, msg ; DS:SI points to string
mov cx, len ; String length
print_loop:
lodsb ; AL = [DS:SI++], SI auto-increments
int 0x10 ; Output character in AL
loop print_loop
hlt ; Halt CPU
msg db 'Hello, BIOS!', 0
len equ $ - msg
Keyboard Services (int 0x16)
int 0x16 provides BIOS keyboard input services. Functionality is controlled by the AH register function code.
| AH Function Code | Description | Input Registers | Output Registers |
|---|---|---|---|
| 0x00 | Wait for key press (blocking) | — | AL = ASCII code, AH = scan code |
| 0x01 | Check for key press (non-blocking) | — | ZF=1 if no key; else AL = ASCII |
| 0x02 | Read extended key (e.g., F-keys, arrows) | — | AL = extended code, AH = scan code |
Example: Read and Echo Keystrokes
org 0x7C00
bits 16
start:
; Initialize video mode
mov ah, 0x00
mov al, 0x03
int 0x10
wait_key:
mov ah, 0x00
int 0x16 ; Block until key pressed
; Echo character (AL holds ASCII)
mov ah, 0x0E
int 0x10
; Exit if 'q' pressed (ASCII 0x71)
cmp al, 'q'
je exit
jmp wait_key
exit:
hlt
Tip
Note: Keyboard scan code vs. ASCII code (partial mapping):
For example, the ‘A’ key has scan code 0x1E and ASCII code 0x41 (uppercase).
DOS Interrupt Services
DOS (Disk Operating System) was the dominant PC OS in early days, providing services via int 0x21. Functionality is selected by the AH register function code.
Common Console-Related Functions
| AH Function Code | Description | Input Registers | Output Registers |
|---|---|---|---|
| 0x01 | Read char from stdin (echoed) | — | AL = char code |
| 0x02 | Write char to stdout | DL = char code | — |
| 0x09 | Write string to stdout ($-terminated) | DX = string address | — |
| 0x3C | Create file | CX = attributes (0=normal) DX = filename (ASCII-Z) | AX = file handle (0=fail) |
| 0x3D | Open file | AL = access mode (0=read, 1=write, 2=r/w) DX = filename | AX = file handle (0=fail) |
| 0x3E | Close file | BX = file handle | AX = error code (0=success) |
Example: Create and Write File “test.txt”
org 0x7C00
bits 16
start:
; Initialize DOS
mov ax, 0x0000
int 0x21
; Create file "test.txt" (attribute: normal)
mov ah, 0x3C
xor cx, cx ; Attribute 0 (normal)
mov dx, filename ; DX → filename (null-terminated)
int 0x21
jc error ; Jump if carry set (AX ≠ 0 → fail)
; Write string "Hello, DOS!"
mov ah, 0x40 ; Write file function
mov bx, ax ; BX = file handle (from AX)
mov cx, len ; Write length
mov dx, msg ; DX → data buffer
int 0x21
jc error ; Jump if write failed
; Close file
mov ah, 0x3E
mov bx, ax ; BX = file handle
int 0x21
; Print success message
mov ah, 0x09
mov dx, success_msg
int 0x21
jmp exit
error:
; Print error message
mov ah, 0x09
mov dx, error_msg
int 0x21
exit:
; Exit to DOS
mov ah, 0x4C
int 0x21
filename db 'test.txt', 0
msg db 'Hello, DOS!', 0
len equ $ - msg
success_msg db 'File created successfully!$',
error_msg db 'Error creating file!$'
Modern OS File Operations
As operating systems evolved into protected mode (x86-32/x86-64), BIOS interrupts were replaced by system calls and APIs, which provide safer and more efficient I/O through kernel/user mode isolation.
Linux File System Calls
Linux follows POSIX standards, triggering system calls via syscall or int 0x80. Key I/O and file system calls include:
| Syscall # | Function | Parameters |
|---|---|---|
| sys_read | Read from file descriptor | fd, buf, count |
| sys_write | Write to file descriptor | fd, buf, count |
| sys_open | Open/create file | filename, flags, mode |
| sys_close | Close file descriptor | fd |
| sys_exit | Terminate process | status |
Example: Read File and Output to Console (C)
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
int fd = open("test.txt", O_RDONLY); // Open read-only
if (fd < 0) {
perror("open failed");
return 1;
}
char buf[1024];
ssize_t bytes_read = read(fd, buf, sizeof(buf));
if (bytes_read < 0) {
perror("read failed");
close(fd);
return 1;
}
write(STDOUT_FILENO, buf, bytes_read); // Output to stdout
close(fd);
return 0;
}
sys_open (System Call #5)
section .data
filename db 'test.txt', 0
flags dd 2 ; O_RDWR = 2
mode dd 0x1FF ; 0644 permissions
section .text
global _start
_start:
mov eax, 5 ; sys_open
mov ebx, filename
mov ecx, [flags]
mov edx, [mode]
int 0x80
mov [file_desc], eax ; Save file descriptor
; ... file read/write operations ...
mov eax, 6 ; sys_close
mov ebx, [file_desc]
int 0x80
mov eax, 1 ; sys_exit
xor ebx, ebx
int 0x80
section .bss
file_desc resd 1
sys_read / sys_write Example
; Read from file
mov eax, 3 ; sys_read
mov ebx, [file_desc]
mov ecx, buffer
mov edx, 100 ; Max read length
int 0x80
; Write to file
mov eax, 4 ; sys_write
mov ebx, [file_desc]
mov ecx, buffer
mov edx, eax ; Use actual bytes read from last sys_read
int 0x80
Windows File API (Comparison)
Windows uses Win32 APIs for file operations. Core functions correspond to Linux syscalls but differ in interface:
| Function | Linux Syscall | Windows API | Key Parameter Differences |
|---|---|---|---|
| Open file | sys_open | CreateFile | lpFileName (wide char), dwDesiredAccess |
| Read file | sys_read | ReadFile | hFile (handle), lpBuffer, nNumberOfBytesToRead |
| Write file | sys_write | WriteFile | hFile, lpBuffer, nNumberOfBytesToWrite |
| Close file | sys_close | CloseHandle | hObject (handle) |
Example: Read File and Output to Console (C on Windows)
#include <windows.h>
#include <stdio.h>
int main() {
HANDLE hFile = CreateFile(
L"test.txt", // Filename (wide char)
GENERIC_READ, // Read-only access
FILE_SHARE_READ, // Share for reading
NULL, // Default security
OPEN_EXISTING, // Open existing file
FILE_ATTRIBUTE_NORMAL, // Normal attribute
NULL // No template
);
if (hFile == INVALID_HANDLE_VALUE) {
printf("CreateFile failed: %d", GetLastError());
return 1;
}
char buf[1024];
DWORD bytes_read;
if (!ReadFile(hFile, buf, sizeof(buf), &bytes_read, NULL)) {
printf("ReadFile failed: %d", GetLastError());
CloseHandle(hFile);
return 1;
}
DWORD bytes_written;
WriteFile(GetStdHandle(STD_OUTPUT_HANDLE),
buf, bytes_read, &bytes_written, NULL); // Output to console
CloseHandle(hFile);
return 0;
}
Assembly Example (Windows API)
extrn CreateFileA:PROC
extrn ReadFile:PROC
extrn WriteFile:PROC
extrn CloseHandle:PROC
.data
filename db 'test.txt', 0
hFile dd ?
.code
open_file:
push 0
push 0
push 3 ; GENERIC_READ | GENERIC_WRITE
push 0
push 0 ; No sharing
push 0x40000000 ; CREATE_ALWAYS
push offset filename
call CreateFileA
mov [hFile], eax
; ... file operations ...
close_file:
push [hFile]
call CloseHandle
Formatted Output Implementation
Formatted output (e.g., printf("%d %s", 123, "hello")) converts data into a formatted string by parsing format specifiers and converting arguments to strings.
Core Steps of Formatted Output
- Parse format string: Scan for ordinary characters and format specifiers (e.g.,
%d,%s). - Handle format specifiers: Based on type (int, string, float), fetch corresponding argument from variable argument list.
- Convert to string: Transform argument into required string format (e.g., integer → decimal string).
- Output result: Write resulting string to console or file.
Simple printf Implementation Idea
Decimal Integer Output Example
; Input: EAX = integer to print
print_int:
push eax
push ebx
push ecx
push edx
mov ebx, 10 ; Divisor
mov ecx, 0 ; Digit counter
mov edi, buffer + 9 ; Point to end of buffer
mov byte [edi], 0 ; Null-terminate
.convert_loop:
xor edx, edx ; Clear EDX for division
div ebx ; EAX = quotient, EDX = remainder
add dl, '0' ; Convert to ASCII
dec edi
mov [edi], dl
inc ecx
test eax, eax
jnz .convert_loop
; Now EDI points to start of number string
mov eax, 4 ; sys_write
mov ebx, 1 ; stdout
mov ecx, edi
mov edx, buffer + 10
sub edx, ecx ; Compute string length
int 0x80
pop edx
pop ecx
pop ebx
pop eax
ret
section .bss
buffer resb 10 ; Enough for 32-bit integer string
Formatted String Output
; Simple implementation:
; mov eax, number
; push format_string
; call print_formatted
print_formatted:
push ebp
mov ebp, esp
push eax
push ebx
push ecx
push edx
push esi
push edi
; Parse format string (simplified)
mov esi, [ebp+8] ; Format string address
mov edi, buffer
mov ecx, 256 ; Buffer size
.format_loop:
lodsb ; Load next char
test al, al
jz .done ; End of string
cmp al, '%'
jne .copy_char ; Not a format specifier
lodsb ; Load format specifier
cmp al, 'd'
je .print_int
cmp al, 's'
je .print_string
; ... other formats ...
.copy_char:
mov [edi], al
inc edi
jmp .format_loop
.print_int:
pop eax ; Get previously pushed EAX
; ... call print_int subroutine ...
jmp .format_loop
.print_string:
; Handle string argument...
jmp .format_loop
.done:
mov byte [edi], 0 ; Null-terminate
; Output buffer
mov eax, 4
mov ebx, 1
mov ecx, buffer
mov edx, edi
sub edx, buffer
int 0x80
pop edi
pop esi
pop edx
pop ecx
pop ebx
pop eax
pop ebp
ret
section .bss
buffer resb 256
Comprehensive Application Example
Complete Console Program
section .data
prompt db 'Enter your name: ', 0
greeting db 'Hello, ', 0
newline db 0xA, 0xD, 0
section .bss
name_buffer resb 50
name_len resb 1
section .text
global _start
_start:
; Display prompt
mov eax, 4
mov ebx, 1
mov ecx, prompt
mov edx, 17 ; String length
int 0x80
; Read user input
mov eax, 3
mov ebx, 0
mov ecx, name_buffer
mov edx, 50
int 0x80
mov [name_len], al ; Save actual bytes read
; Display greeting
mov eax, 4
mov ebx, 1
mov ecx, greeting
mov edx, 7
int 0x80
; Display user's name
mov eax, 4
mov ebx, 1
mov ecx, name_buffer
mov edx, [name_len]
int 0x80
; Display newline
mov eax, 4
mov ebx, 1
mov ecx, newline
mov edx, 2
int 0x80
; Exit
mov eax, 1
xor ebx, ebx
int 0x80
By mastering these I/O techniques, you can:
- Develop low-level system tools
- Understand how operating systems work
- Write cross-platform compatible code
- Perform embedded system development
- Implement high-performance I/O operations



