Advanced I/O Operations in C: Mastering Input/Output for High-Performance Applications

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.

Building Blocks of C: A Complete Guide to Functions
Explains how functions work in C programming, including function declaration, definition, parameters, return values, and how functions help organize reusable code.
https://macronepal.com/bash/building-blocks-of-c-a-complete-guide-to-functions/

The Heart of Text Processing: A Complete Guide to Strings in C
Explains how strings are used in C, covering character arrays, string handling functions, and common techniques for text processing tasks.
https://macronepal.com/bash/the-heart-of-text-processing-a-complete-guide-to-strings-in-c-2/

The Cornerstone of Data Organization: A Complete Guide to Arrays in C
Describes how arrays store multiple values in C, including indexing, initialization, and using arrays to manage structured data efficiently.
https://macronepal.com/bash/the-cornerstone-of-data-organization-a-complete-guide-to-arrays-in-c/

Guaranteed Execution: A Complete Guide to the Do-While Loop in C
Explains the do-while loop structure in C, highlighting how it ensures code runs at least once before checking the loop condition.
https://macronepal.com/bash/guaranteed-execution-a-complete-guide-to-the-do-while-loop-in-c/

Mastering Iteration: A Complete Guide to the For Loop in C
Explains how the for loop works in C, including initialization, condition checking, and increment steps for repeated execution of code blocks.
https://macronepal.com/bash/mastering-iteration-a-complete-guide-to-the-for-loop-in-c/

Mastering Iteration: A Complete Guide to While Loops in C
Explains the while loop structure in C, focusing on condition-based repetition and proper loop control techniques.
https://macronepal.com/bash/mastering-iteration-a-complete-guide-to-while-loops-in-c/

Beyond If-Else: A Complete Guide to Switch Case in C
Explains how switch-case statements work in C programming, enabling efficient handling of multiple conditional branches.
https://macronepal.com/bash/beyond-if-else-a-complete-guide-to-switch-case-in-c/

Mastering the Fundamentals: A Complete Guide to Arithmetic Operations in C
Explains how arithmetic operators such as addition, subtraction, multiplication, and division work in C, along with operator precedence and usage examples.
https://macronepal.com/bash/mastering-the-fundamentals-a-complete-guide-to-arithmetic-operations-in-c/

Foundation of C Programming: A Complete Guide to Basic Input Output
Explains how input and output functions like printf and scanf work in C, forming the foundation for interacting with users and displaying program results.
https://macronepal.com/bash/foundation-of-c-programming-a-complete-guide-to-basic-input-output/

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper