Introduction
Input/Output operations are fundamental to almost every C program, yet many developers only scratch the surface of what's possible. From memory-mapped files to asynchronous I/O, from scatter-gather operations to advanced buffering strategies, mastering advanced I/O techniques can dramatically improve application performance. This comprehensive guide explores the full spectrum of I/O operations in C.
1. Memory-Mapped I/O (mmap)
1.1 Basic Memory Mapping
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
void* map_file(const char *filename, size_t *size) {
int fd = open(filename, O_RDWR);
if (fd == -1) {
perror("open");
return NULL;
}
// Get file size
struct stat st;
if (fstat(fd, &st) == -1) {
perror("fstat");
close(fd);
return NULL;
}
*size = st.st_size;
// Memory map the file
void *addr = mmap(NULL, *size, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (addr == MAP_FAILED) {
perror("mmap");
close(fd);
return NULL;
}
close(fd); // Can close fd after mapping
return addr;
}
void unmap_file(void *addr, size_t size) {
if (munmap(addr, size) == -1) {
perror("munmap");
}
}
int main() {
size_t size;
char *data = map_file("example.txt", &size);
if (data == NULL) {
return 1;
}
// Modify file directly in memory
printf("File contents: %.*s\n", (int)size, data);
strcpy(data, "Modified content!\n");
// Changes are automatically written to disk (with MAP_SHARED)
unmap_file(data, size);
return 0;
}
1.2 Private vs Shared Mappings
void demonstrate_mmap_types(void) {
int fd = open("data.bin", O_RDWR | O_CREAT, 0644);
if (fd == -1) return;
// MAP_SHARED - changes written back to file
void *shared = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
// MAP_PRIVATE - changes stay in memory (copy-on-write)
void *private = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
MAP_PRIVATE, fd, 0);
// MAP_ANONYMOUS - no file backing (like malloc)
void *anonymous = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
// Use mappings...
munmap(shared, 4096);
munmap(private, 4096);
munmap(anonymous, 4096);
close(fd);
}
1.3 Large File Support with mmap
#include <sys/mman.h>
#include <fcntl.h>
#define FILE_SIZE (10ULL * 1024 * 1024 * 1024) // 10 GB
void process_large_file(void) {
int fd = open("large_file.dat", O_RDWR | O_CREAT, 0644);
if (fd == -1) return;
// Extend file to desired size
if (ftruncate(fd, FILE_SIZE) == -1) {
perror("ftruncate");
close(fd);
return;
}
// Map the entire file (may require 64-bit addressing)
void *addr = mmap(NULL, FILE_SIZE, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (addr == MAP_FAILED) {
perror("mmap");
close(fd);
return;
}
// Process file in chunks
size_t chunk_size = 1024 * 1024; // 1 MB chunks
for (size_t offset = 0; offset < FILE_SIZE; offset += chunk_size) {
size_t remaining = FILE_SIZE - offset;
size_t process = remaining < chunk_size ? remaining : chunk_size;
// Direct access to file data via addr + offset
char *chunk = (char*)addr + offset;
// Process chunk...
memset(chunk, 0xFF, process);
}
munmap(addr, FILE_SIZE);
close(fd);
}
2. Scatter-Gather I/O (readv/writev)
2.1 Basic readv/writev Usage
#include <sys/uio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
void scatter_gather_example(void) {
int fd = open("data.txt", O_RDWR | O_CREAT, 0644);
if (fd == -1) return;
// Prepare scatter/gather vectors
struct iovec iov[3];
char header[32];
char body[1024];
char footer[32];
iov[0].iov_base = header;
iov[0].iov_len = sizeof(header);
iov[1].iov_base = body;
iov[1].iov_len = sizeof(body);
iov[2].iov_base = footer;
iov[2].iov_len = sizeof(footer);
// Write all buffers in one system call
ssize_t nwritten = writev(fd, iov, 3);
if (nwritten == -1) {
perror("writev");
} else {
printf("Wrote %zd bytes\n", nwritten);
}
// Reset file position
lseek(fd, 0, SEEK_SET);
// Read into scattered buffers
ssize_t nread = readv(fd, iov, 3);
if (nread == -1) {
perror("readv");
} else {
printf("Read %zd bytes\n", nread);
printf("Header: %.*s\n", (int)sizeof(header), header);
printf("Body: %.*s\n", (int)sizeof(body), body);
printf("Footer: %.*s\n", (int)sizeof(footer), footer);
}
close(fd);
}
2.2 Advanced Scatter-Gather with Dynamic Buffers
#include <sys/uio.h>
#include <stdlib.h>
typedef struct {
struct iovec *vectors;
int count;
size_t total_size;
} ScatterGatherList;
ScatterGatherList* create_scatter_gather(int max_vectors) {
ScatterGatherList *sg = malloc(sizeof(ScatterGatherList));
sg->vectors = calloc(max_vectors, sizeof(struct iovec));
sg->count = 0;
sg->total_size = 0;
return sg;
}
void add_buffer(ScatterGatherList *sg, void *buffer, size_t size) {
sg->vectors[sg->count].iov_base = buffer;
sg->vectors[sg->count].iov_len = size;
sg->total_size += size;
sg->count++;
}
ssize_t writev_all(int fd, ScatterGatherList *sg) {
ssize_t total_written = 0;
int i = 0;
while (i < sg->count) {
ssize_t written = writev(fd, &sg->vectors[i], sg->count - i);
if (written == -1) {
return -1;
}
total_written += written;
// Adjust remaining vectors
size_t remaining = written;
while (i < sg->count && remaining >= sg->vectors[i].iov_len) {
remaining -= sg->vectors[i].iov_len;
i++;
}
if (i < sg->count && remaining > 0) {
sg->vectors[i].iov_base = (char*)sg->vectors[i].iov_base + remaining;
sg->vectors[i].iov_len -= remaining;
}
}
return total_written;
}
3. Asynchronous I/O (AIO)
3.1 POSIX AIO Basics
#include <aio.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
volatile int aio_complete = 0;
void aio_completion_handler(sigval_t sigval) {
struct aiocb *req = (struct aiocb*)sigval.sival_ptr;
// Check status
int ret = aio_error(req);
if (ret == 0) {
ssize_t bytes = aio_return(req);
printf("AIO completed: %zd bytes\n", bytes);
}
aio_complete = 1;
}
void async_read_example(void) {
int fd = open("data.txt", O_RDONLY);
if (fd == -1) return;
struct aiocb cb;
char buffer[4096];
memset(&cb, 0, sizeof(cb));
cb.aio_fildes = fd;
cb.aio_buf = buffer;
cb.aio_nbytes = sizeof(buffer);
cb.aio_offset = 0;
// Set up completion notification
cb.aio_sigevent.sigev_notify = SIGEV_THREAD;
cb.aio_sigevent.sigev_notify_function = aio_completion_handler;
cb.aio_sigevent.sigev_value.sival_ptr = &cb;
// Start async read
if (aio_read(&cb) == -1) {
perror("aio_read");
close(fd);
return;
}
printf("Waiting for async read to complete...\n");
// Wait for completion
while (!aio_complete) {
// Do other work
usleep(10000);
}
close(fd);
}
3.2 AIO with Polling
#include <aio.h>
#include <poll.h>
void aio_poll_example(void) {
int fd = open("large_file.dat", O_RDONLY);
if (fd == -1) return;
struct aiocb *cbs[10];
char buffers[10][4096];
// Start multiple async reads
for (int i = 0; i < 10; i++) {
struct aiocb *cb = malloc(sizeof(struct aiocb));
memset(cb, 0, sizeof(struct aiocb));
cb->aio_fildes = fd;
cb->aio_buf = buffers[i];
cb->aio_nbytes = sizeof(buffers[i]);
cb->aio_offset = i * sizeof(buffers[i]);
if (aio_read(cb) == -1) {
perror("aio_read");
free(cb);
continue;
}
cbs[i] = cb;
}
// Poll for completion
int pending = 10;
while (pending > 0) {
for (int i = 0; i < 10; i++) {
if (cbs[i] == NULL) continue;
int ret = aio_error(cbs[i]);
if (ret == 0) {
// Completed
ssize_t bytes = aio_return(cbs[i]);
printf("Read %zd bytes from offset %lld\n",
bytes, cbs[i]->aio_offset);
free(cbs[i]);
cbs[i] = NULL;
pending--;
} else if (ret != EINPROGRESS) {
// Error
perror("aio_error");
free(cbs[i]);
cbs[i] = NULL;
pending--;
}
}
usleep(10000); // Wait a bit before polling again
}
close(fd);
}
4. Non-Blocking I/O and epoll
4.1 Setting Non-Blocking Mode
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
int set_nonblocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1) {
return -1;
}
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
void nonblocking_read_example(void) {
int fd = open("data.txt", O_RDONLY);
if (fd == -1) return;
set_nonblocking(fd);
char buffer[1024];
while (1) {
ssize_t n = read(fd, buffer, sizeof(buffer));
if (n == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// No data available yet
printf("No data, doing other work...\n");
usleep(10000);
continue;
} else {
perror("read");
break;
}
} else if (n == 0) {
// EOF
break;
} else {
// Data received
printf("Read %zd bytes\n", n);
}
}
close(fd);
}
4.2 epoll Event Loop
#include <sys/epoll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define MAX_EVENTS 100
void epoll_server_example(int port) {
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd == -1) return;
// Set socket options
int opt = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// Bind
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(listen_fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
perror("bind");
close(listen_fd);
return;
}
// Listen
if (listen(listen_fd, 10) == -1) {
perror("listen");
close(listen_fd);
return;
}
// Create epoll instance
int epoll_fd = epoll_create1(0);
if (epoll_fd == -1) {
perror("epoll_create1");
close(listen_fd);
return;
}
// Add listen socket to epoll
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = listen_fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &ev) == -1) {
perror("epoll_ctl");
close(epoll_fd);
close(listen_fd);
return;
}
struct epoll_event events[MAX_EVENTS];
while (1) {
int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
if (nfds == -1) {
perror("epoll_wait");
break;
}
for (int i = 0; i < nfds; i++) {
if (events[i].data.fd == listen_fd) {
// New connection
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_fd = accept(listen_fd,
(struct sockaddr*)&client_addr,
&client_len);
if (client_fd == -1) {
perror("accept");
continue;
}
// Set client socket non-blocking
set_nonblocking(client_fd);
// Add to epoll
ev.events = EPOLLIN | EPOLLET; // Edge-triggered
ev.data.fd = client_fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &ev) == -1) {
perror("epoll_ctl");
close(client_fd);
}
} else {
// Data from client
int client_fd = events[i].data.fd;
char buffer[4096];
while (1) {
ssize_t n = read(client_fd, buffer, sizeof(buffer));
if (n == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// Done reading
break;
} else {
perror("read");
close(client_fd);
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_fd, NULL);
break;
}
} else if (n == 0) {
// Client closed connection
close(client_fd);
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_fd, NULL);
break;
} else {
// Echo back
write(client_fd, buffer, n);
}
}
}
}
}
close(epoll_fd);
close(listen_fd);
}
4.3 Edge-Triggered vs Level-Triggered
// Level-triggered (default)
struct epoll_event ev;
ev.events = EPOLLIN; // Level-triggered
// Edge-triggered (more efficient, but requires careful handling)
ev.events = EPOLLIN | EPOLLET; // Edge-triggered
// Edge-triggered requires reading until EAGAIN
void edge_triggered_handler(int fd) {
char buffer[4096];
while (1) {
ssize_t n = read(fd, buffer, sizeof(buffer));
if (n == -1) {
if (errno == EAGAIN) {
break; // Done reading
}
// Handle error
break;
} else if (n == 0) {
// EOF
break;
}
// Process data...
}
}
5. Advanced File Locking
5.1 Record Locking (fcntl)
#include <fcntl.h>
int lock_record(int fd, off_t offset, off_t len, int type) {
struct flock fl;
fl.l_type = type; // F_RDLCK, F_WRLCK, F_UNLCK
fl.l_whence = SEEK_SET;
fl.l_start = offset;
fl.l_len = len;
fl.l_pid = getpid();
return fcntl(fd, F_SETLKW, &fl); // Blocking
// return fcntl(fd, F_SETLK, &fl); // Non-blocking
}
void record_locking_example(void) {
int fd = open("database.db", O_RDWR);
if (fd == -1) return;
// Lock a specific range of bytes
if (lock_record(fd, 0, 100, F_WRLCK) == -1) {
perror("lock_record");
close(fd);
return;
}
// Critical section - bytes 0-99 are locked
// ...
// Unlock
lock_record(fd, 0, 100, F_UNLCK);
close(fd);
}
5.2 Advisory vs Mandatory Locking
void demonstrate_advisory_locking(void) {
int fd = open("data.txt", O_RDWR);
if (fd == -1) return;
struct flock fl = {
.l_type = F_WRLCK,
.l_whence = SEEK_SET,
.l_start = 0,
.l_len = 0, // 0 means lock to EOF
.l_pid = getpid()
};
// Advisory lock (default)
if (fcntl(fd, F_SETLK, &fl) == 0) {
printf("Advisory lock acquired\n");
// Other processes can still access the file
// unless they also check the lock
}
// For mandatory locking, mount filesystem with 'mand' option
// and set setgid bit without execute permission
close(fd);
}
6. Direct I/O (O_DIRECT)
6.1 Bypassing Page Cache
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#define ALIGNMENT 4096
void* aligned_alloc_direct(size_t size) {
void *ptr;
if (posix_memalign(&ptr, ALIGNMENT, size) != 0) {
return NULL;
}
return ptr;
}
void direct_io_example(void) {
int fd = open("direct_io.dat", O_RDWR | O_CREAT | O_DIRECT, 0644);
if (fd == -1) {
perror("open (O_DIRECT may not be supported)");
return;
}
size_t size = ALIGNMENT * 10; // Must be aligned
char *buffer = aligned_alloc_direct(size);
if (!buffer) {
close(fd);
return;
}
// Fill buffer
memset(buffer, 'A', size);
// Write directly to disk (bypasses page cache)
ssize_t nwritten = write(fd, buffer, size);
if (nwritten == -1) {
perror("write");
} else {
printf("Wrote %zd bytes directly to disk\n", nwritten);
}
// Read directly from disk
lseek(fd, 0, SEEK_SET);
char *read_buf = aligned_alloc_direct(size);
if (read_buf) {
ssize_t nread = read(fd, read_buf, size);
if (nread > 0) {
printf("Read %zd bytes directly from disk\n", nread);
}
free(read_buf);
}
free(buffer);
close(fd);
}
6.2 Direct I/O with Alignment Requirements
#include <sys/stat.h>
#include <unistd.h>
size_t get_block_size(int fd) {
struct stat st;
if (fstat(fd, &st) == -1) {
return 4096; // Default
}
return st.st_blksize;
}
void check_alignment_requirements(int fd) {
size_t blk_size = get_block_size(fd);
printf("Block size: %zu bytes\n", blk_size);
printf("Alignment required: %zu\n", blk_size);
printf("Offset alignment required: %zu\n", blk_size);
printf("Buffer alignment required: %zu\n", blk_size);
// Verify alignment
void *buffer;
if (posix_memalign(&buffer, blk_size, blk_size * 10) == 0) {
printf("Buffer properly aligned\n");
free(buffer);
}
}
7. Splice and Zero-Copy I/O
7.1 splice() for File-to-File Copy
#include <fcntl.h>
#include <unistd.h>
int splice_copy(const char *src, const char *dst) {
int fd_in = open(src, O_RDONLY);
if (fd_in == -1) return -1;
int fd_out = open(dst, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd_out == -1) {
close(fd_in);
return -1;
}
// Get file size
off_t size = lseek(fd_in, 0, SEEK_END);
lseek(fd_in, 0, SEEK_SET);
off_t copied = 0;
while (copied < size) {
ssize_t n = splice(fd_in, NULL, fd_out, NULL,
size - copied, SPLICE_F_MOVE);
if (n <= 0) {
close(fd_in);
close(fd_out);
return -1;
}
copied += n;
}
close(fd_in);
close(fd_out);
return 0;
}
7.2 sendfile() for Network Transfers
#include <sys/sendfile.h>
#include <sys/socket.h>
void sendfile_example(int client_fd, const char *filename) {
int file_fd = open(filename, O_RDONLY);
if (file_fd == -1) return;
struct stat st;
fstat(file_fd, &st);
off_t size = st.st_size;
off_t offset = 0;
// Zero-copy transfer from file to socket
ssize_t sent = sendfile(client_fd, file_fd, &offset, size);
if (sent == -1) {
perror("sendfile");
} else {
printf("Sent %zd bytes\n", sent);
}
close(file_fd);
}
8. Advanced Buffering Strategies
8.1 Custom Buffer Management
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char *buffer;
size_t size;
size_t used;
size_t position;
int fd;
int error;
} Buffer;
Buffer* create_buffer(size_t size, int fd) {
Buffer *buf = malloc(sizeof(Buffer));
buf->buffer = malloc(size);
buf->size = size;
buf->used = 0;
buf->position = 0;
buf->fd = fd;
buf->error = 0;
return buf;
}
int buffer_write(Buffer *buf, const char *data, size_t len) {
size_t remaining = buf->size - buf->used;
if (len > remaining) {
// Flush buffer
if (buf->used > 0) {
ssize_t written = write(buf->fd, buf->buffer, buf->used);
if (written != (ssize_t)buf->used) {
buf->error = 1;
return -1;
}
buf->used = 0;
}
// Write large data directly
if (len > buf->size) {
ssize_t written = write(buf->fd, data, len);
return written;
}
}
// Copy to buffer
memcpy(buf->buffer + buf->used, data, len);
buf->used += len;
return len;
}
int buffer_flush(Buffer *buf) {
if (buf->used == 0) return 0;
ssize_t written = write(buf->fd, buf->buffer, buf->used);
if (written != (ssize_t)buf->used) {
buf->error = 1;
return -1;
}
buf->used = 0;
return 0;
}
8.2 Double Buffering for Overlap
#include <pthread.h>
#include <semaphore.h>
typedef struct {
char *buffer1;
char *buffer2;
size_t size;
int active_buffer;
int reading;
int processing;
sem_t read_sem;
sem_t process_sem;
pthread_mutex_t mutex;
} DoubleBuffer;
DoubleBuffer* create_double_buffer(size_t size) {
DoubleBuffer *db = malloc(sizeof(DoubleBuffer));
db->buffer1 = malloc(size);
db->buffer2 = malloc(size);
db->size = size;
db->active_buffer = 0;
db->reading = 0;
db->processing = 0;
sem_init(&db->read_sem, 0, 1);
sem_init(&db->process_sem, 0, 0);
pthread_mutex_init(&db->mutex, NULL);
return db;
}
void* reader_thread(void *arg) {
DoubleBuffer *db = arg;
int fd = *(int*)((void**)arg + 1);
while (1) {
sem_wait(&db->read_sem);
char *buffer = db->active_buffer ? db->buffer2 : db->buffer1;
ssize_t n = read(fd, buffer, db->size);
if (n <= 0) break;
pthread_mutex_lock(&db->mutex);
db->reading = n;
db->active_buffer = !db->active_buffer;
pthread_mutex_unlock(&db->mutex);
sem_post(&db->process_sem);
}
return NULL;
}
void* processor_thread(void *arg) {
DoubleBuffer *db = arg;
while (1) {
sem_wait(&db->process_sem);
pthread_mutex_lock(&db->mutex);
char *buffer = db->active_buffer ? db->buffer2 : db->buffer1;
int size = db->reading;
pthread_mutex_unlock(&db->mutex);
if (size == 0) break;
// Process buffer
for (int i = 0; i < size; i++) {
buffer[i] = toupper(buffer[i]);
}
// Write processed data
write(1, buffer, size);
sem_post(&db->read_sem);
}
return NULL;
}
9. I/O Performance Tuning
9.1 File System Optimization
#include <fcntl.h>
#include <sys/statvfs.h>
void tune_file_system(void) {
// Get file system information
struct statvfs stat;
if (statvfs(".", &stat) == 0) {
printf("Block size: %lu\n", stat.f_bsize);
printf("Fragment size: %lu\n", stat.f_frsize);
printf("Total blocks: %lu\n", stat.f_blocks);
printf("Free blocks: %lu\n", stat.f_bfree);
}
// Set file access hints
int fd = open("data.bin", O_RDWR | O_CREAT, 0644);
// Posix fadvise
#ifdef __linux__
posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL); // Sequential access
posix_fadvise(fd, 0, 0, POSIX_FADV_WILLNEED); // Prefetch
posix_fadvise(fd, 0, 0, POSIX_FADV_NOREUSE); // One-time use
#endif
// Sync file data
fdatasync(fd); // Sync data, not metadata
fsync(fd); // Sync data and metadata
close(fd);
}
9.2 I/O Scheduling
#include <sys/ioctl.h>
#include <linux/fs.h>
void set_io_scheduler(const char *device, const char *scheduler) {
int fd = open(device, O_RDONLY);
if (fd == -1) return;
// Write scheduler to sysfs
char path[256];
snprintf(path, sizeof(path), "/sys/block/%s/queue/scheduler", device);
FILE *f = fopen(path, "w");
if (f) {
fprintf(f, "%s", scheduler);
fclose(f);
}
// Get current settings
struct stat st;
if (fstat(fd, &st) == 0) {
printf("Device: %s\n", device);
printf("Block size: %lu\n", st.st_blksize);
}
close(fd);
}
10. Complete Example: High-Performance Logging System
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/mman.h>
#include <time.h>
#define LOG_BUFFER_SIZE (1024 * 1024) // 1 MB
#define LOG_FILE_SIZE (100 * 1024 * 1024) // 100 MB
typedef struct {
int fd;
char *buffer;
size_t buffer_pos;
size_t buffer_size;
pthread_mutex_t lock;
volatile int running;
pthread_t flush_thread;
} AsyncLogger;
AsyncLogger* logger_create(const char *filename) {
AsyncLogger *logger = malloc(sizeof(AsyncLogger));
logger->fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0644);
if (logger->fd == -1) {
free(logger);
return NULL;
}
// Set large buffer for better performance
if (posix_fadvise(logger->fd, 0, 0, POSIX_FADV_SEQUENTIAL) != 0) {
// Ignore errors
}
logger->buffer = malloc(LOG_BUFFER_SIZE);
logger->buffer_pos = 0;
logger->buffer_size = LOG_BUFFER_SIZE;
logger->running = 1;
pthread_mutex_init(&logger->lock, NULL);
return logger;
}
void logger_log(AsyncLogger *logger, const char *format, ...) {
pthread_mutex_lock(&logger->lock);
// Format message
va_list args;
va_start(args, format);
int needed = vsnprintf(NULL, 0, format, args);
va_end(args);
if (logger->buffer_pos + needed + 1 >= logger->buffer_size) {
// Flush buffer
ssize_t written = write(logger->fd, logger->buffer, logger->buffer_pos);
if (written > 0) {
logger->buffer_pos = 0;
}
}
// Write to buffer
va_start(args, format);
vsnprintf(logger->buffer + logger->buffer_pos,
logger->buffer_size - logger->buffer_pos,
format, args);
va_end(args);
logger->buffer_pos += needed;
logger->buffer[logger->buffer_pos++] = '\n';
pthread_mutex_unlock(&logger->lock);
}
void* flush_thread(void *arg) {
AsyncLogger *logger = arg;
while (logger->running) {
usleep(100000); // 100 ms
pthread_mutex_lock(&logger->lock);
if (logger->buffer_pos > 0) {
ssize_t written = write(logger->fd, logger->buffer, logger->buffer_pos);
if (written > 0) {
logger->buffer_pos = 0;
}
}
pthread_mutex_unlock(&logger->lock);
}
return NULL;
}
void logger_start(AsyncLogger *logger) {
pthread_create(&logger->flush_thread, NULL, flush_thread, logger);
}
void logger_destroy(AsyncLogger *logger) {
logger->running = 0;
pthread_join(logger->flush_thread, NULL);
// Final flush
if (logger->buffer_pos > 0) {
write(logger->fd, logger->buffer, logger->buffer_pos);
}
close(logger->fd);
free(logger->buffer);
pthread_mutex_destroy(&logger->lock);
free(logger);
}
int main() {
AsyncLogger *logger = logger_create("app.log");
if (!logger) {
perror("Failed to create logger");
return 1;
}
logger_start(logger);
// Log messages
for (int i = 0; i < 1000000; i++) {
logger_log(logger, "Iteration %d: timestamp = %lld",
i, (long long)time(NULL));
}
logger_destroy(logger);
return 0;
}
Conclusion
Advanced I/O operations in C provide powerful tools for building high-performance applications. From memory-mapped files for efficient large-file access to scatter-gather I/O for reducing system calls, from asynchronous I/O for non-blocking operations to zero-copy techniques for maximum throughput—mastering these techniques is essential for serious systems programming.
Key takeaways:
- Memory mapping provides the fastest random access to files
- Scatter-gather I/O reduces system call overhead
- Asynchronous I/O enables concurrent I/O operations
- epoll scales to thousands of connections
- Direct I/O bypasses the page cache for predictable latency
- Splice/sendfile achieve zero-copy data movement
- Proper buffering dramatically improves performance
Choose the right technique based on your application's needs:
- For databases: mmap + direct I/O
- For web servers: epoll + sendfile
- For logging: buffered async I/O
- For data processing: scatter-gather + vectorized operations
The examples in this guide provide a foundation for implementing production-ready I/O systems. Always benchmark your specific use case, as the optimal approach depends on hardware, operating system, and workload characteristics.