Skip to content

loadingWrittingLinux&c

Stas edited this page Oct 13, 2026 · 3 revisions

W systemie Linux oraz języku C metody operacji wejścia/wyjścia (I/O) dzielą się na kilka warstw abstrakcji: od standardowych bibliotek w przestrzeni użytkownika, przez bezpośrednie wywołania systemowe jądra (syscalls), po zaawansowane mechanizmy asynchroniczne i mapowanie pamięci.

Oto przegląd najważniejszych metod uszeregowany od najprostszych do najbardziej wyspecjalizowanych.

1. Buforowane I/O ze standardowej biblioteki C (<stdio.h>)

Najbardziej uniwersalna, przenośna metoda działająca w przestrzeni użytkownika. Biblioteka standardowa C alokuje wewnętrzny bufor w pamięci RAM procesu (zazwyczaj 4 KB lub 8 KB), dzięki czemu drobne zapisy/odczyty nie wykonują kosztownych przejść w przestrzeń jądra (context switch) przy każdym wywołaniu.

  • Identyfikator zasobu: wskaźnik strumienia FILE *

  • Podstawowe funkcje: fopen, fclose, fread, fwrite, fseek, ftell, fflush

  • Zalety: Przenośność (standard ANSI/ISO C), automatyczne buforowanie, wysoka wydajność przy małych, częstych operacjach.

  • Wady: Dodatkowa kopia danych w pamięci (RAM użytkownika $\to$ bufor stdio $\to$ bufor jądra / Page Cache $\to$ sterownik dysku).

2. Wywołania systemowe POSIX / Linux (POSIX Syscalls – <unistd.h>, <fcntl.h>)

Bezpośrednie operacje jądra systemu Linux bez bufora biblioteki libc. Każde wywołanie przełącza proces w tryb jądra (Ring 0).

  • Identyfikator zasobu: deskryptor pliku int fd

  • Podstawowe funkcje: open, close, read, write, lseek, fsync, fdatasync

  • Warianty wektorowe i z przesunięciem:

    • pread / pwrite: Odczyt/zapis pod konkretny offset bez modyfikacji globalnego wskaźnika pliku (bezpieczne wątkowo bez muteksów).

    • readv / writev (Scatter-Gather I/O): Odczyt/zapis z/do wielu rozproszonych buforów w pamięci za pomocą jednego syscalla (redukcja context switchy).

  • Zalety: Pełna kontrola nad deskryptorem i flagami jądra, brak ukrytego buforowania w bibliotece.

  • Wady: Każde wywołanie kosztuje czas przejścia w tryb jądra; drobne zapisy pojedynczych bajtów bez własnego buforowania drastycznie degradują wydajność.

3. Pamięć mapowana do pliku (Memory-Mapped I/O – mmap, <sys/mman.h>)

Linux mapuje strony pliku bezpośrednio do wirtualnej przestrzeni adresowej procesu. Zamiast wołać read lub write, plik traktuje się jak zwykły wskaźnik do tablicy bajtów w pamięci RAM (char *ptr).

  • Podstawowe funkcje: mmap, munmap, msync, madvise

  • Mechanizm: Gdy odwołujesz się do komórki w tablicy, sprzętowa jednostka zarządzania pamięcią (MMU) zgłasza brak strony (Page Fault), a jądro leniwie ładuje dany blok z dysku bezpośrednio do Page Cache. Zapis do tablicy oznacza zapis do pamięci podręcznej stron, synchronizowany na dysk przez msync lub demona flusher.

  • Zalety: Zero-copy między buforem jądra a przestrzenią użytkownika, idealne pod bazy danych (np. silniki LMDB, SQLite z flagą mmap) oraz odczyty losowe (random access).

  • Wady: Narzut na modyfikację tablic stron (TLB shootdowns), ryzyko błędu SIGBUS, gdy plik zostanie skrócony przez inny proces.

4. Pomięcie bufora jądra (Direct I/O – flaga O_DIRECT)

Domyślnie Linux przechowuje pliki w tzw. Page Cache w wolnym RAM-ie. Flaga O_DIRECT przekazana do open() instruuje jądro, aby całkowicie pominęło Page Cache.

  • Zastosowanie: open("plik.bin", O_RDWR | O_DIRECT)

  • Mechanizm: Dane są transferowane przez kontroler DMA bezpośrednio między buforem procesu w pamięci użytkownika a nośnikiem dyskowym.

  • Wymagania: Bufor w pamięci oraz offsety w pliku muszą być wyrównane do rozmiaru sektora logicznego/bloku (np. 512 lub 4096 bajtów, alokowane przez posix_memalign).

  • Zastosowanie: Silniki baz danych (PostgreSQL, Oracle, MySQL InnoDB), które same zarządzają własną pamięcią podręczną (Shared Buffers / Buffer Pool) i nie chcą, by system operacyjny dublował dane w Page Cache.

5. Asynchroniczne operacje I/O w Linuksie

Umożliwiają zlecanie operacji odczytu i zapisu bez blokowania bieżącego wątku.

  • POSIX AIO (<aio.h> – aio_read, aio_write): Starsza implementacja w glibc, w praktyce emulowana wewnętrzną pulą wątków. Rzadko używana w wysokowydajnych systemach z uwagi na narzut.

  • Linux Kernel AIO (io_submit, io_getevents): Natywne I/O asynchroniczne jądra, ale działa efektywnie niemal wyłącznie w połączeniu z O_DIRECT.

  • io_uring (nowoczesny standard w Linuksie od kernela 5.1):

    • Rewolucyjny mechanizm oparty na dwóch pierścieniowych buforach pamięci współdzielonej (Submission Queue i Completion Queue) między jądrem a procesem.

    • Umożliwia zlecenia setek operacji I/O bez ani jednego wywołania systemowego (możliwy tryb kernel-side polling IORING_SETUP_SQPOLL).

    • Wykorzystywany w najnowszych implementacjach serwerów o skrajnej przepustowości (np. w Tokio w Rust, bibliotece liburing w C).

Zestawienie przekrojowe

Metoda | Poziom / API | Buforowanie | Kiedy wybrać? -- | -- | -- | -- stdio (fread/fwrite) | Standard C (libc) | W pamięci procesu (FILE*) + Page Cache | Proste pliki, logi, konfiguracje, uniwersalny kod wieloplatformowy.

iouring vs traditional read/write on files

ByMin Wang Jul 16, 2024 william_free skylarmaexo nude william_free

what is difference between iouring vs normal hard driver driver software

io_uring is a relatively new I/O interface introduced in the Linux kernel (starting from version 5.1) to provide a more efficient way to handle asynchronous I/O operations. It offers several improvements over traditional I/O mechanisms, particularly in terms of performance and scalability. Here’s a detailed comparison between io_uring and traditional methods used by hard drive drivers: Traditional I/O Mechanisms

Blocking I/O:
    Mechanism: When an application performs a read or write operation, the calling thread is blocked until the operation completes.
    Use Case: Simple applications where performance and latency are not critical.
    Overhead: High due to context switches and the need for the CPU to poll for completion.
Non-blocking I/O with Polling:
    Mechanism: The application makes a non-blocking system call and then uses poll or select to check the status of the I/O operation.
    Use Case: Applications that need to handle multiple I/O operations concurrently without blocking.
    Overhead: Lower than blocking I/O but still involves significant system call overhead and context switching.
Asynchronous I/O (AIO):
    Mechanism: The application issues an I/O request and continues processing. The kernel completes the I/O operation in the background and notifies the application via signals or callback functions.
    Use Case: High-performance applications that require non-blocking I/O with efficient resource utilization.
    Overhead: Reduced compared to blocking and non-blocking I/O but still limited by system call overhead and complexity in managing callbacks or signals.

io_uring

Mechanism:
    Submission Queue (SQ): Applications submit I/O requests to a ring buffer, avoiding direct system calls for each operation.
    Completion Queue (CQ): The kernel completes the I/O operations and posts the results to a separate ring buffer. The application can retrieve the results in batches.
    Zero-copy: io_uring supports zero-copy operations, reducing the overhead of data copying between user space and kernel space.
Use Case:
    High-Performance I/O: Applications that require extremely low-latency and high-throughput I/O operations, such as databases, high-frequency trading systems, and web servers.
    Concurrency: Suitable for applications with high concurrency requirements, as it significantly reduces the overhead of managing multiple I/O operations.
Overhead:
    Low System Call Overhead: By batching I/O operations and reducing the number of system calls, io_uring minimizes context switches and system call overhead.
    Efficient Resource Utilization: Better CPU utilization due to reduced context switching and lower system call overhead.
    Scalability: Improved scalability for applications handling a large number of concurrent I/O operations.

Key Differences

System Call Overhead:
    Traditional I/O: Each I/O operation typically involves one or more system calls, leading to higher overhead.
    io_uring: Reduces the number of system calls by using ring buffers for batching submissions and completions.
Context Switching:
    Traditional I/O: More frequent context switches due to blocking and non-blocking I/O mechanisms.
    io_uring: Fewer context switches, as applications can batch I/O operations and check completions in bulk.
Latency:
    Traditional I/O: Higher latency due to the need for frequent system calls and context switches.
    io_uring: Lower latency by minimizing system call overhead and context switching.
Scalability:
    Traditional I/O: Limited scalability due to higher overhead with increasing I/O operations.
    io_uring: Better scalability, making it suitable for applications with high concurrency requirements.

Conclusion

io_uring offers significant performance improvements over traditional I/O mechanisms, particularly in reducing system call overhead, minimizing context switches, and enhancing scalability. It is particularly beneficial for high-performance applications that require efficient handling of a large number of concurrent I/O operations. Traditional I/O methods are still useful for simpler applications or those where the complexity of io_uring is not justified. can you show sample code for each cases?

Certainly! Let’s provide sample code snippets for each of the cases discussed: Blocking I/O, Non-blocking I/O with Polling, Asynchronous I/O (AIO), and io_uring.

  1. Blocking I/O cpp

#include #include

int main() { std::ifstream file("example.txt");

if (!file.is_open()) {
    std::cerr << "Failed to open filen";
    return 1;
}

// Read file contents
std::string content;
file >> content;

std::cout << "File content: " << content << std::endl;

// Close file
file.close();

return 0;

}

  1. Non-blocking I/O with Polling (using select) cpp

#include #include <fcntl.h> #include <unistd.h>

int main() { int fd = open("example.txt", O_RDONLY | O_NONBLOCK);

if (fd == -1) {
    perror("Failed to open file");
    return 1;
}

char buffer[1024];
ssize_t bytes_read;

while (true) {
    fd_set read_fds;
    FD_ZERO(&read_fds);
    FD_SET(fd, &read_fds);

    // Timeout struct for select (1 second timeout)
    struct timeval timeout;
    timeout.tv_sec = 1;
    timeout.tv_usec = 0;

    int ready = select(fd + 1, &read_fds, NULL, NULL, &timeout);

    if (ready == -1) {
        perror("select");
        break;
    } else if (ready == 0) {
        std::cout << "Timeout occurredn";
        break;
    }

    if (FD_ISSET(fd, &read_fds)) {
        bytes_read = read(fd, buffer, sizeof(buffer));

        if (bytes_read == -1) {
            perror("read");
            break;
        }

        if (bytes_read == 0) {
            std::cout << "End of filen";
            break;
        }

        std::cout << "Read " << bytes_read << " bytes: " << buffer << std::endl;
    }
}

close(fd);

return 0;

}

  1. Asynchronous I/O (AIO) using aio_read cpp

#include #include <fcntl.h> #include <unistd.h> #include <aio.h> #include #include #include

#define BUF_SIZE 1024

int main() { int fd = open("example.txt", O_RDONLY);

if (fd == -1) {
    perror("Failed to open file");
    return 1;
}

char buffer[BUF_SIZE];
struct aiocb aio;
memset(&aio, 0, sizeof(struct aiocb));

aio.aio_fildes = fd;
aio.aio_buf = buffer;
aio.aio_nbytes = BUF_SIZE;
aio.aio_offset = 0;

// Initiate asynchronous read
if (aio_read(&aio) == -1) {
    perror("aio_read");
    close(fd);
    return 1;
}

// Poll for completion
while (aio_error(&aio) == EINPROGRESS) {
    std::cout << "Waiting for completion...n";
    sleep(1);
}

// Check for errors
ssize_t bytes_read = aio_return(&aio);
if (bytes_read == -1) {
    perror("aio_return");
} else {
    std::cout << "Read " << bytes_read << " bytes: " << buffer << std::endl;
}

close(fd);

return 0;

}

  1. io_uring cpp

#include #include <fcntl.h> #include <liburing.h> #include #include <unistd.h>

#define BUF_SIZE 1024

int main() { struct io_uring ring; struct io_uring_sqe *sqe; struct io_uring_cqe *cqe;

// Initialize io_uring
io_uring_queue_init(8, &ring, 0);

// Open file
int fd = open("example.txt", O_RDONLY);
if (fd < 0) {
    perror("open");
    return 1;
}

char buffer[BUF_SIZE];
struct iovec iov;
iov.iov_base = buffer;
iov.iov_len = BUF_SIZE;

// Prepare read operation
sqe = io_uring_get_sqe(&ring);
io_uring_prep_readv(sqe, fd, &iov, 1, 0);

// Submit operation
io_uring_submit(&ring);

// Wait for completion
io_uring_wait_cqe(&ring, &cqe);
if (cqe->res < 0) {
    std::cerr << "Read error: " << strerror(-cqe->res) << std::endl;
} else {
    std::cout << "Read " << cqe->res << " bytes: " << buffer << std::endl;
}

// Release resources
io_uring_cqe_seen(&ring, cqe);
io_uring_queue_exit(&ring);
close(fd);

return 0;

}

Explanation

Blocking I/O: Reads the entire content of a file synchronously, blocking the thread until the operation completes.
Non-blocking I/O with Polling: Opens a file with non-blocking mode and uses select to poll for readability. It waits for up to 1 second and checks for data availability before reading.
Asynchronous I/O (AIO): Initiates an asynchronous read operation using aio_read and waits for completion using a polling loop. It checks for errors and handles the retrieved data.
io_uring: Initializes an io_uring instance, prepares a read operation, submits it, waits for completion, and retrieves the read data. io_uring provides a highly efficient asynchronous I/O mechanism with reduced overhead and improved scalability.

These examples demonstrate different I/O mechanisms in Linux, each suitable for specific use cases ranging from simple synchronous reads to highly efficient asynchronous operations using io_uring. read / write (POSIX) | Jądro Linuksa | Tylko Page Cache jądra | Gdy potrzebujesz precyzyjnej kontroli nad flagami deskryptora (O_SYNC, O_APPEND). pread / pwrite | Jądro Linuksa | Tylko Page Cache jądra | Odczyty wielowątkowe z jednego pliku bez muteksów na wskaźnik pliku. mmap | MMU / Stronicowanie | Page Cache mapowany wprost do RAM | Bazy danych typu Key-Value, odczyt dużych plików z losowym dostępem. O_DIRECT | Jądro / Kontroler DMA | Brak (pomięcie Page Cache) | Własne silniki storage (np. B-tree engine), które same buforują strony. io_uring | Pierścienie jądra (Lock-free) | Konfigurowalne | Maksymalna przepustowość I/O, miliony operacji/s, nowoczesny Linux.

Clone this wiki locally