PCIe & DMA
NVMe Gömülü — M.2 & io_uring · embedded-deck PCIe & DMA
GÖMÜLÜ LİNUX NVME PCIE IO_URING 2026

NVMe Gömülü
M.2 & io_uring.

NVMe over PCIe mimarisini sıfırdan anlayın. Gömülü M.2 SSD seçiminden Linux sürücü konfigürasyonuna, fio benchmark'tan io_uring ile düşük latency I/O'ya.

00 NVMe nedir?

NVMe (Non-Volatile Memory Express), flash bellek tabanlı depolama aygıtları için tasarlanmış bir host controller arayüzü ve komut seti standardıdır. PCIe otobüsü üzerinden çalışır.

SATA ve onun protokolü AHCI, manyetik disk dönemine ait tasarımlardır. SATA kontrolcüsü tek bir komut kuyruğu derinliğine 32 komut sığdırabilir. Flash bellekler ise tamamen farklı fiziksel özellikler sergiler: paralel okuma yapabilir, sıralı ya da rastgele erişimde benzer gecikme gösterir. AHCI bu potansiyeli kullanamamaktadır.

NVMe bu sorunu temelinden çözer. PCIe doğrudan NIC gibi çalışır: CPU belleğine DMA ile doğrudan erişir, donanım komut kuyruğu mekanizması binlerce eşzamanlı I/O isteğini işleyebilir. AHCI'nin 1 × 32 komut kuyruğuna karşın NVMe 64.000 kuyruk × 64.000 komut derinliği destekler.

  AHCI (SATA):
  ┌──────────┐    SATA    ┌──────────────┐    PCIe    ┌──────┐
  │ Uygulama │───────────▶│ AHCI Ctrl.   │───────────▶│ CPU  │
  │ FS+VFS   │    6 Gbps  │ 1 kuyruk     │  (sürücü)  └──────┘
  └──────────┘            │ Maks 32 cmd  │
                          └──────────────┘

  NVMe:
  ┌──────────┐    PCIe    ┌──────────────────────────┐    DMA    ┌──────┐
  │ Uygulama │───────────▶│ NVMe Controller          │──────────▶│ RAM  │
  │ FS+VFS   │  16+ GB/s  │ 64K kuyruk × 64K cmd     │           └──────┘
  └──────────┘            │ Admin Queue + I/O Queue  │
                          └──────────────────────────┘
    

AHCI vs NVMe karşılaştırması

ÖzellikAHCI (SATA)NVMe (PCIe)
Arayüz hızı6 Gbps (SATA III)PCIe 4.0 x4 = 64 Gbps
Seq. okuma (tipik)~550 MB/s3500–7000 MB/s
Rastgele 4K IOPS~100 K IOPS500K–1M+ IOPS
Komut kuyruğu1 × 3264K × 64K
Latency (okuma)~100 µs~20–80 µs
CPU yüküYüksek (compat. layer)Düşük (native PCIe)
Güç tüketimi~2–5 W~3–8 W (aktif)
GÖMÜLÜ SİSTEMLERDE NVMe

Raspberry Pi CM4, NVIDIA Jetson serisi, i.MX8, RK3588 gibi modern gömülü platformlar PCIe x1–x4 desteğiyle NVMe kullanımına olanak tanır. Endüstriyel M.2 SSD'ler geniş sıcaklık aralığı (-40 ile +85°C) ve yüksek güvenilirlik için tasarlanmıştır.

01 NVMe mimarisi

NVMe'nin temel bileşenleri Admin Queue, I/O Queue çiftleri (SQ/CQ), doorbell register'ları ve namespace kavramıdır.

  ┌─────────────────────────────────────────────────────────────────┐
  │                    NVMe Controller (BAR0)                       │
  │                                                                 │
  │  Admin SQ ──▶ Admin CQ     (yönetim komutları)                  │
  │                                                                 │
  │  I/O SQ[0] ──▶ I/O CQ[0]  (CPU 0 için özel kuyruk)             │
  │  I/O SQ[1] ──▶ I/O CQ[1]  (CPU 1 için özel kuyruk)             │
  │  I/O SQ[N] ──▶ I/O CQ[N]  (CPU N için özel kuyruk)             │
  │                                                                 │
  │  Doorbell Registers:                                            │
  │  SQ Tail Doorbell → yeni komut eklendi sinyali                  │
  │  CQ Head Doorbell → tamamlanan komut alındı sinyali             │
  └─────────────────────────────────────────────────────────────────┘
         │ PCIe (DMA)
         ▼
  ┌──────────────┐
  │  Host RAM     │
  │  SQ: [cmd0]  │  ← CPU komut yazar
  │      [cmd1]  │
  │  CQ: [cpl0]  │  ← Controller tamamlama yazar
  │      [cpl1]  │
  └──────────────┘
    

Submission Queue (SQ) ve Completion Queue (CQ)

Submission Queue
Host RAM'de halka tamponu. CPU buraya komut yazar. Doorbell'e yazarak controller'ı uyarır.
Completion Queue
Host RAM'de halka tamponu. Controller buraya tamamlama kaydı yazar, interrupt veya polling ile okunur.
Command ID (CID)
Her komuta atanan 16-bit benzersiz tanımlayıcı. CQ'daki tamamlama kaydı hangi SQ komutuna ait olduğunu CID ile bildirir.
Admin Queue
Zorunlu tek kuyruk çifti. Identify, Create/Delete I/O Queue, Get/Set Features, Firmware Update komutları buradan gönderilir.
Namespace
NVMe'de depolama bölümü. Bir controller birden fazla namespace (1–N) içerebilir. Her namespace bağımsız formatlanabilir.

Controller register haritası (BAR0)

RegisterOffsetAçıklama
CAP0x00Controller Capabilities — max kuyruk boyutu, timeout
VS0x08NVMe Version — 1.4, 2.0
INTMS/INTMC0x0C/0x10Interrupt Mask Set/Clear
CC0x14Controller Configuration — EN biti, komut set seçimi
CSTS0x1CController Status — RDY biti, CFS (fatal status)
AQA0x24Admin Queue Attributes — ASQ/ACQ boyutları
ASQ0x28Admin SQ Base Address
ACQ0x30Admin CQ Base Address
SQ0TDBL0x1000Admin SQ Tail Doorbell
CQ0HDBL0x1004Admin CQ Head Doorbell

02 NVMe komut seti

NVMe iki ana komut seti içerir: NVM komut seti (veri I/O) ve Admin komut seti (yönetim). Her komut 64 byte'lık Submission Queue Entry (SQE) olarak temsil edilir.

NVM komutları — veri I/O

KomutOpcodeAçıklama
Read0x02LBA aralığını oku. SLBA, NLB (sector sayısı), PRP/SGL (hedef buffer adresleri)
Write0x01LBA aralığına yaz. FUA (Force Unit Access) biti write cache'i bypass eder
Flush0x00Write cache'i kalıcı depolamaya zorla. Güç kesintisi güvenliği için kritik
Dataset Management0x09TRIM (deallocate) — SSD'ye kullanılmayan blokları bildir, wear leveling için
Write Zeroes0x08LBA aralığını hızlıca sıfırla (SSD iç TRIM ile)
Compare0x05Disk içeriğini bellekle karşılaştır

Admin komutları — yönetim

KomutOpcodeAçıklama
Identify0x06Controller veya namespace kimlik bilgilerini döner. Model, seri no, kapasiteler
Get Features0x0APower state, kuyruk sayısı, write atomicity gibi özellikleri sorgula
Set Features0x09Controller özelliklerini yapılandır
Create I/O SQ0x01Yeni I/O Submission Queue oluştur
Create I/O CQ0x05Yeni I/O Completion Queue oluştur
Delete I/O SQ/CQ0x00/0x04I/O kuyruklarını sil
Get Log Page0x02SMART/Health, Error log, Firmware log sayfaları
Firmware Download/Commit0x11/0x10NVMe controller firmware güncellemesi
Format NVM0x80Namespace'i formatla (güvenli silme dahil)

SQE yapısı (basitleştirilmiş)

nvme_command yapısı — linux/nvme.h
/* NVMe Submission Queue Entry — 64 byte */
struct nvme_command {
    /* CDW0: Command opcode, FUSE, PSDT, CID */
    __le16  opcode;    /* 8 bit opcode + 6 bit reserved + 2 bit FUSE */
    __le16  flags;     /* PSDT (PRP or SGL) */
    __le16  command_id;/* CID — tamamlama eşleştirmesi için */
    __le16  reserved0;

    /* CDW1: Namespace ID */
    __le32  nsid;

    /* CDW2–3: Reserved */
    __le32  cdw2;
    __le32  cdw3;

    /* CDW4–5: Metadata pointer */
    __le64  metadata;

    /* CDW6–9: PRP1, PRP2 (veya SGL) — veri buffer adresleri */
    union nvme_data_ptr dptr;

    /* CDW10–15: Komuta özgü */
    __le32  cdw10;   /* Read/Write: SLBA[31:0] */
    __le32  cdw11;   /* Read/Write: SLBA[63:32] */
    __le32  cdw12;   /* Read/Write: NLB + FUA + PRINFO */
    __le32  cdw13;
    __le32  cdw14;
    __le32  cdw15;
};

03 Gömülü M.2 seçimi

Gömülü sistemler için M.2 SSD seçerken form faktörü, PCIe nesli, güç tüketimi, sıcaklık aralığı ve dayanıklılık kritik kriterlerdir.

M.2 form faktörleri

Form faktörBoyutTipik kullanım
M.2 223022 × 30 mmUltrabook, küçük gömülü kartlar (RPi CM4 HAT)
M.2 224222 × 42 mmEndüstriyel gömülü sistemler
M.2 226022 × 60 mmOrta boy sistemler
M.2 228022 × 80 mmStandart PC/sunucu, yüksek kapasiteli

Key-M connector ve PCIe şeritleri

  M.2 Key-M Konnektör (NVMe için)
  ┌──────────────────────────────────────────────────────────┐
  │  B+M Key: SATA + PCIe x2 (bazı SSD'ler her ikisini destekler)│
  │  M Key only: PCIe x4 tam destek                          │
  │                                                          │
  │  PCIe Gen 3 x4:  ~3.5 GB/s bant genişliği               │
  │  PCIe Gen 4 x4:  ~7.0 GB/s bant genişliği               │
  │  PCIe Gen 4 x2:  ~3.5 GB/s (RPi CM4, bazı SoC'ler)      │
  │                                                          │
  │  Güç pinleri: 3.3V (ana güç)                             │
  │  3.3V_DUAL: güç yönetimi için ek pin                     │
  └──────────────────────────────────────────────────────────┘
    

Endüstriyel vs tüketici SSD karşılaştırması

ÖzellikTüketici SSDEndüstriyel SSD
Sıcaklık aralığı0°C – 70°C-40°C – +85°C
NAND tipiTLC/QLCSLC / MLC / pSLC
Yazma dayanıklılığı100–600 TBW1000–10000+ TBW
Güç kesintisi korumasıGenellikle yokKapasitör koruması
Sabit yazma gecikmesiDeğişken (GC)Deterministik
MTBF1.5M saat2–3M saat
Fiyat (128 GB)15–30 USD80–300 USD

Önerilen endüstriyel NVMe SSD'ler

Transcend MTS960T
M.2 2242, -40~85°C, 3D TLC, güç kesintisi koruması, 2280 da mevcut.
Swissbit N-26m
M.2 2230/2242, pSLC NAND, Endüstriyel sıcaklık, düşük güç.
Innodisk 3IE7
M.2 2242, iSLC, -40~85°C, 3D NAND, yüksek dayanıklılık.
WD Industrial SN530
M.2 2230, PCIe 3.0 x4, endüstriyel sınıf, geniş sıcaklık aralığı.

Güç tüketimi yönetimi

Pil veya kısıtlı güç bütçeli gömülü sistemlerde NVMe'nin güç modları kritik önem taşır. Modern NVMe SSD'ler APST (Autonomous Power State Transitions) ile boşta power-down moduna girebilir.

NVMe güç durumları
## NVMe güç durumlarını listele
nvme id-ctrl /dev/nvme0 | grep -A5 "psd "

## Örnek çıktı:
## ps 0 : mp:8.00W operational enlat:0 exlat:0
## ps 1 : mp:6.00W operational enlat:0 exlat:0
## ps 2 : mp:5.00W operational enlat:0 exlat:0
## ps 3 : mp:0.0500W non-op  enlat:10000 exlat:35000   # PS3: 50 mW
## ps 4 : mp:0.0050W non-op  enlat:25000 exlat:25000   # PS4: 5 mW

## APST (Autonomous Power State Transition) etkinleştir
nvme set-feature /dev/nvme0 -f 0x0c -v 1

## Mevcut güç durumunu görüntüle
nvme get-feature /dev/nvme0 -f 0x02

04 Linux NVMe sürücüsü

Linux çekirdeğinin nvme sürücüsü, standart blok cihazı arayüzü üzerinden NVMe'ye erişim sağlar. Her controller /dev/nvmeX, her namespace /dev/nvmeXnY olarak görünür.

Cihaz hiyerarşisi

  /dev/nvme0          ← Controller (Admin komutları için)
  /dev/nvme0n1        ← Namespace 1 (block device — veri I/O)
  /dev/nvme0n1p1      ← Partition 1 (EFI / boot)
  /dev/nvme0n1p2      ← Partition 2 (rootfs)

  /sys/class/nvme/nvme0/      ← sysfs controller girişi
  /sys/class/block/nvme0n1/   ← sysfs block device girişi
    

nvme-cli — komut satırı aracı

nvme-cli komutları
## Kurulum
apt install nvme-cli

## Tüm NVMe cihazlarını listele
nvme list

## Örnek çıktı:
## Node           SN                   Model                 Namespace
## /dev/nvme0n1   S12345678            Samsung PM9A1         1

## Controller kimlik bilgileri
nvme id-ctrl /dev/nvme0
## mn: Samsung MZVL2256HCHQ-00B00 (model)
## sn: S6ENNF0R...                (seri no)
## tnvmcap: 256060514304          (toplam kapasite bayt)
## nn: 1                          (namespace sayısı)

## Namespace kimlik bilgileri
nvme id-ns /dev/nvme0n1
## nsze: 500118192   (LBA sayısı)
## lbaf: ms:0 lbads:9 rp:0  (LBA boyutu = 2^9 = 512 B)

## SMART / sağlık logu
nvme smart-log /dev/nvme0
## critical_warning: 0         (0 = sağlıklı)
## temperature: 35 C
## available_spare: 100%
## percentage_used: 0%
## data_units_read: 1,234,567 (512KB birimle)
## data_units_written: 987,654

## Error logu
nvme error-log /dev/nvme0

## Namespace yönetimi
nvme list-ns /dev/nvme0          # tüm namespace'leri listele
nvme create-ns /dev/nvme0 ...    # yeni namespace oluştur
nvme attach-ns /dev/nvme0 ...    # namespace'i controller'a bağla

sysfs üzerinden erişim

sysfs NVMe bilgileri
## Controller bilgileri
cat /sys/class/nvme/nvme0/model
cat /sys/class/nvme/nvme0/serial
cat /sys/class/nvme/nvme0/firmware_rev

## PCIe bağlantı hızı
cat /sys/bus/pci/devices/0000:01:00.0/current_link_speed
## 8 GT/s PCIe   (Gen 3)
## 16 GT/s PCIe  (Gen 4)

cat /sys/bus/pci/devices/0000:01:00.0/current_link_width
## 4  (x4 lanes)

## Kuyruk sayısı
cat /sys/class/nvme/nvme0/queue_count

## Sürücü bilgisi
cat /sys/class/nvme/nvme0/address

05 Performans tuning

NVMe varsayılan Linux konfigürasyonuyla bile etkileyici performans sunar. Ancak doğru I/O scheduler, kuyruk derinliği ve write cache ayarları performansı daha da artırır.

I/O Scheduler seçimi

I/O scheduler ayarı
## Mevcut scheduler ve seçenekleri
cat /sys/block/nvme0n1/queue/scheduler
## [mq-deadline] kyber bfq none

## NVMe için "none" (no-op) önerilir — kendi dahili kuyruklaması var
echo none > /sys/block/nvme0n1/queue/scheduler

## Kalıcı ayar — udev kuralı
cat > /etc/udev/rules.d/60-nvme-scheduler.rules <<'EOF'
ACTION=="add|change", KERNEL=="nvme[0-9]n[0-9]", \
    ATTR{queue/scheduler}="none"
EOF
udevadm control --reload

Kuyruk derinliği (Queue Depth)

queue depth ayarı
## Mevcut queue depth
cat /sys/block/nvme0n1/queue/nr_requests
## 1023

## NVMe genellikle varsayılan değer yeterlidir
## Yoğun paralel iş yükü için artırabilirsiniz
echo 2048 > /sys/block/nvme0n1/queue/nr_requests

## Read-ahead — sıralı okuma için
cat /sys/block/nvme0n1/queue/read_ahead_kb
## 128

## Rastgele I/O için read-ahead azalt
echo 0 > /sys/block/nvme0n1/queue/read_ahead_kb

Write cache ve FUA bit

write cache yönetimi
## NVMe write cache durumunu görüntüle
nvme get-feature /dev/nvme0 -f 0x06 -H
## Current value:0x1  (write cache enabled)

## Write cache devre dışı bırak (güvenilir yazma, yavaş)
nvme set-feature /dev/nvme0 -f 0x06 -v 0

## Linux blok katmanı: write cache politikası
hdparm -W /dev/nvme0n1    # write cache durumu
hdparm -W1 /dev/nvme0n1   # write cache etkinleştir

## FUA (Force Unit Access) — tek komutla cache bypass
## O_DSYNC flag ile open() veya fsync() — güçlü garanti
## Uygulama katmanında:
int fd = open("data.bin", O_WRONLY | O_DIRECT | O_DSYNC, 0644);

fio benchmark

fio benchmark komutları
## Sıralı okuma (128K block)
fio --name=seq-read \
    --filename=/dev/nvme0n1 \
    --rw=read \
    --bs=128k \
    --size=4G \
    --numjobs=1 \
    --iodepth=32 \
    --ioengine=io_uring \
    --direct=1 \
    --group_reporting

## Rastgele 4K okuma (yüksek IOPS testi)
fio --name=rand-read \
    --filename=/dev/nvme0n1 \
    --rw=randread \
    --bs=4k \
    --size=4G \
    --numjobs=4 \
    --iodepth=64 \
    --ioengine=io_uring \
    --direct=1 \
    --group_reporting \
    --runtime=30 \
    --time_based

## Beklenen sonuç (PCIe 3.0 x4, Samsung PM981):
## Sıralı okuma   : ~3400 MB/s
## Rastgele 4K RD : ~420K IOPS
## Rastgele 4K WR : ~380K IOPS

APST — Autonomous Power State Transitions

APST konfigürasyonu
## APST etkinleştir (kernel parametresi)
## /etc/default/grub:
## GRUB_CMDLINE_LINUX_DEFAULT="nvme_core.default_ps_max_latency_us=20000"

## Mevcut APST durumu
nvme get-feature /dev/nvme0 -f 0x0c -H

## Manuel power state geçişi (test amaçlı)
nvme set-feature /dev/nvme0 -f 0x02 -v 3   # PS3'e geç

## nvme-power systemd servisi ile otomatik yönetim
cat /sys/devices/virtual/nvme-subsystem/nvme-subsys0/nvme0/power/control
## auto   (runtime PM etkin)

06 io_uring ile NVMe

io_uring, Linux 5.1'de tanıtılan yüksek performanslı asenkron I/O mekanizmasıdır. NVMe'nin yüksek IOPS kapasitesini tam olarak kullanmak için geleneksel POSIX aio'ya göre çok daha verimlidir.

  Geleneksel aio_read/write:
  Uygulama → syscall(io_submit) → kernel → NVMe SQ → NVMe CQ
  └── Her I/O için 2 syscall, kontrol transferi kernel↔kullanıcı

  io_uring:
  ┌───────────────────────────────────────────────┐
  │  Paylaşımlı Bellek (kullanıcı + kernel)        │
  │  ┌──────────────┐     ┌──────────────────┐    │
  │  │ SQ Ring       │ →  │ CQ Ring           │    │
  │  │ (uygulama    │     │ (kernel yazar,    │    │
  │  │  yazar)      │     │  uygulama okur)   │    │
  │  └──────────────┘     └──────────────────┘    │
  └───────────────────────────────────────────────┘
  └── Sıfır sistem çağrısı (SQPOLL modunda)
    

IORING_OP_READ / IORING_OP_WRITE

uring_nvme.c — io_uring ile NVMe okuma
#include <liburing.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define QUEUE_DEPTH  128
#define BLOCK_SIZE   4096
#define NUM_OPS      1024

int main(void)
{
    struct io_uring ring;
    int fd, ret;

    /* 1. io_uring başlat */
    ret = io_uring_queue_init(QUEUE_DEPTH, &ring, 0);
    if (ret < 0) {
        perror("io_uring_queue_init");
        return 1;
    }

    /* 2. NVMe bloğunu aç — O_DIRECT zorunlu */
    fd = open("/dev/nvme0n1", O_RDONLY | O_DIRECT);
    if (fd < 0) {
        perror("open");
        return 1;
    }

    /* 3. Hizalı buffer'lar ayır */
    char **bufs = malloc(NUM_OPS * sizeof(char *));
    for (int i = 0; i < NUM_OPS; i++) {
        posix_memalign((void **)&bufs[i], 4096, BLOCK_SIZE);
    }

    /* 4. SQE'leri toplu gönder */
    for (int i = 0; i < NUM_OPS; i++) {
        struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);

        io_uring_prep_read(sqe, fd,
                           bufs[i],           /* hedef buffer */
                           BLOCK_SIZE,         /* okuma boyutu */
                           (off_t)i * BLOCK_SIZE); /* ofset */
        sqe->user_data = i;                   /* tanımlayıcı */
    }

    /* 5. Toplu submit — tek syscall! */
    ret = io_uring_submit(&ring);
    printf("Gönderilen: %d operasyon\n", ret);

    /* 6. Tamamlamaları topla */
    struct io_uring_cqe *cqe;
    int completed = 0;

    while (completed < NUM_OPS) {
        ret = io_uring_wait_cqe(&ring, &cqe);
        if (ret < 0) break;

        if (cqe->res < 0)
            fprintf(stderr, "I/O hatası [%lld]: %s\n",
                    cqe->user_data, strerror(-cqe->res));

        io_uring_cqe_seen(&ring, cqe);
        completed++;
    }

    printf("Tamamlanan: %d operasyon\n", completed);

    /* Temizle */
    close(fd);
    for (int i = 0; i < NUM_OPS; i++) free(bufs[i]);
    free(bufs);
    io_uring_queue_exit(&ring);

    return 0;
}

SQPOLL — sıfır syscall modu

sqpoll kurulumu
/* SQPOLL: kernel thread SQ'yu sürekli izler */
struct io_uring_params params = {
    .flags = IORING_SETUP_SQPOLL,
    .sq_thread_idle = 2000,   /* ms — kernel thread uyku süresi */
};

/* SQPOLL için root yetkisi gereklidir (CAP_SYS_NICE) */
ret = io_uring_queue_init_params(QUEUE_DEPTH, &ring, ¶ms);

/* SQPOLL modunda io_uring_submit() çağrısına gerek yok!
   SQE ekledikten sonra kernel thread otomatik görür. */

/* Sabit buffer'lar ile daha da hızlı */
struct iovec iovecs[NUM_OPS];
for (int i = 0; i < NUM_OPS; i++) {
    posix_memalign(&iovecs[i].iov_base, 4096, BLOCK_SIZE);
    iovecs[i].iov_len = BLOCK_SIZE;
}
io_uring_register_buffers(&ring, iovecs, NUM_OPS);

/* Sabit buffer okuma */
sqe = io_uring_get_sqe(&ring);
io_uring_prep_read_fixed(sqe, fd, iovecs[i].iov_base,
                          BLOCK_SIZE, offset, i /* buf_index */);

io_uring vs aio karşılaştırması

ÖzellikPOSIX aioLinux aioio_uring
Syscall sayısı/op2+ (submit+wait)2 (io_submit+io_getevents)0 (SQPOLL) veya 1
Buffer kopyasıVarVarSabit buffer: yok
Kernel destekli operasyonSınırlıBlok I/O50+ op türü
Tipik NVMe IOPS~200K~300K~500K–900K
Min kernel versiyonu2.6.x2.6.x5.1+
liburing desteğiEvet

Latency histogramı — io_uring vs read()

fio latency histogram
## io_uring ile latency histogram
fio --name=lat-test \
    --filename=/dev/nvme0n1 \
    --rw=randread \
    --bs=4k \
    --iodepth=1 \
    --numjobs=1 \
    --ioengine=io_uring \
    --direct=1 \
    --lat_percentiles=1 \
    --percentile_list=50:90:99:99.9:99.99 \
    --runtime=10 \
    --time_based

## Beklenen (Samsung PM9A1, RPi CM4 PCIe 2.0 x1):
## lat (usec): min=  40, avg=  68, max=3820
## 50.00th=[ 58], 90.00th=[ 82], 99.00th=[175],
## 99.90th=[502], 99.99th=[1958]

07 Pratik: RPi CM4 NVMe — M.2 HAT, fio benchmark, wear leveling scripti

Raspberry Pi CM4 üzerinde M.2 HAT ile NVMe SSD kurulumu, performans benchmark ve SSD sağlığı izleme scripti.

Donanım kurulumu

  Raspberry Pi CM4 (PCIe Gen 2 x1)
  ┌──────────────────────────────────────────────────────────┐
  │  CM4 modül                                               │
  │  PCIe Gen 2 x1 → J2 konnektörü                          │
  │                                                          │
  │  Önerilen HAT'lar:                                       │
  │  - Waveshare CM4-NVMe-SSD-HAT (M.2 2230/2242/2280)      │
  │  - Pimoroni NVMe Base (M.2 2230)                         │
  │  - GEEKWORM X1001 (M.2 2242/2280)                        │
  └──────────────────────────────────────────────────────────┘

  Bant genişliği: PCIe Gen 2 x1 = ~500 MB/s (pratik ~400 MB/s)
  Not: CM4'te PCIe x1 olduğundan NVMe'nin tam kapasitesi kullanılamaz.
  Yine de SATA SSD'ye kıyasla 6-8x daha hızlı.
    

config.txt ayarları

/boot/config.txt — PCIe ve NVMe
## PCIe'yi etkinleştir (CM4)
dtparam=pciex1

## PCIe Gen 3 dene (CM4 resmi olarak Gen 2, ancak bazı SSD'ler Gen 3 çalışır)
## dtparam=pciex1_gen=3   # DİKKAT: resmi desteklenmez, deneysel

## NVMe bootloader desteği (SD kart olmadan boot)
## EEPROM'u güncelle:
## sudo rpi-eeprom-update -a
## BOOT_ORDER=0xf416 (PCIe NVMe → USB → SD)

## CPU frekansı (benchmark için)
arm_freq=1500
over_voltage=2

NVMe kurulumu doğrulama

nvme kurulum kontrolü
## NVMe algılandı mı?
nvme list
## /dev/nvme0n1  Samsung MZVLQ512HBLU-000H1  512.1 GB

## PCIe bağlantı hızı
lspci -vvv | grep -A5 "Non-Volatile memory"
## LnkSta: Speed 5GT/s (Gen 2), Width x1

## Blok cihazı bilgisi
lsblk -o NAME,SIZE,ROTA,SCHED,MODEL
## nvme0n1  476.9G    0  none   Samsung MZVLQ512...

fio benchmark scripti

nvme_benchmark.sh
#!/bin/bash
# RPi CM4 NVMe performans benchmark scripti
set -euo pipefail

DEVICE="/dev/nvme0n1"
OUTPUT_DIR="/tmp/nvme_bench_$(date +%Y%m%d_%H%M%S)"
mkdir -p "${OUTPUT_DIR}"

echo "=== RPi CM4 NVMe Benchmark ===" | tee "${OUTPUT_DIR}/results.txt"
date | tee -a "${OUTPUT_DIR}/results.txt"

## Test 1: Sıralı okuma (128KB)
echo -e "\n--- Sıralı Okuma (128KB) ---" | tee -a "${OUTPUT_DIR}/results.txt"
fio --name=seq-read \
    --filename="${DEVICE}" \
    --rw=read \
    --bs=128k \
    --size=1G \
    --numjobs=1 \
    --iodepth=32 \
    --ioengine=io_uring \
    --direct=1 \
    --group_reporting \
    --output-format=terse \
    2>>&1 | grep "READ:" | tee -a "${OUTPUT_DIR}/results.txt"

## Test 2: Sıralı yazma (128KB)
echo -e "\n--- Sıralı Yazma (128KB) ---" | tee -a "${OUTPUT_DIR}/results.txt"
fio --name=seq-write \
    --filename="${DEVICE}" \
    --rw=write \
    --bs=128k \
    --size=1G \
    --numjobs=1 \
    --iodepth=32 \
    --ioengine=io_uring \
    --direct=1 \
    --group_reporting \
    --output-format=terse \
    2>>&1 | grep "WRITE:" | tee -a "${OUTPUT_DIR}/results.txt"

## Test 3: Rastgele 4KB okuma (IOPS)
echo -e "\n--- Rastgele 4KB Okuma (IOPS) ---" | tee -a "${OUTPUT_DIR}/results.txt"
fio --name=rand-read \
    --filename="${DEVICE}" \
    --rw=randread \
    --bs=4k \
    --size=1G \
    --numjobs=4 \
    --iodepth=32 \
    --ioengine=io_uring \
    --direct=1 \
    --group_reporting \
    --runtime=30 \
    --time_based \
    --output-format=terse \
    2>>&1 | grep "READ:" | tee -a "${OUTPUT_DIR}/results.txt"

## Test 4: Karışık okuma/yazma (%70 okuma)
echo -e "\n--- Karışık R/W (70/30) ---" | tee -a "${OUTPUT_DIR}/results.txt"
fio --name=mixed \
    --filename="${DEVICE}" \
    --rw=randrw \
    --rwmixread=70 \
    --bs=4k \
    --size=1G \
    --numjobs=4 \
    --iodepth=16 \
    --ioengine=io_uring \
    --direct=1 \
    --group_reporting \
    --runtime=30 \
    --time_based \
    2>>&1 | tee -a "${OUTPUT_DIR}/results.txt"

echo -e "\nBenchmark tamamlandı: ${OUTPUT_DIR}/results.txt"

SSD wear leveling ve sağlık izleme scripti

nvme_health_monitor.sh — systemd timer ile çalışır
#!/bin/bash
# NVMe sağlık izleme — /var/log/nvme_health.log dosyasına yazar
# Systemd timer: her gün çalışır

DEVICE="/dev/nvme0"
LOG_FILE="/var/log/nvme_health.log"
ALERT_SPARE_THRESHOLD=20   # %20 altında uyar
ALERT_TEMP_THRESHOLD=70    # 70°C üstünde uyar

log() { echo "$(date '+%Y-%m-%d %H:%M:%S') $1" >> "${LOG_FILE}"; }

## SMART verilerini al
SMART=$(nvme smart-log "${DEVICE}" 2>/dev/null) || {
    log "HATA: NVMe SMART logu alınamadı"
    exit 1
}

## Değerleri çıkart
TEMP=$(echo "${SMART}" | grep "^temperature" | awk '{print $3}')
SPARE=$(echo "${SMART}" | grep "available_spare " | awk '{print $3}' | tr -d '%')
USED=$(echo "${SMART}" | grep "percentage_used" | awk '{print $3}' | tr -d '%')
WARN=$(echo "${SMART}" | grep "critical_warning" | awk '{print $3}')
UNITS_READ=$(echo "${SMART}"  | grep "data_units_read" | awk '{print $4}' | tr -d ',')
UNITS_WRITE=$(echo "${SMART}" | grep "data_units_written" | awk '{print $4}' | tr -d ',')

## Log yaz
log "Sağlık: temp=${TEMP}°C spare=${SPARE}% used=${USED}% warn=${WARN}"
log "Veri: read=${UNITS_READ}×512KB write=${UNITS_WRITE}×512KB"

## Uyarı kontrolü
if [ "${WARN}" != "0" ]; then
    log "KRİTİK UYARI: NVMe critical_warning=${WARN}"
    logger -p user.crit "NVMe critical warning: ${WARN}"
fi

if [ "${SPARE}" -lt "${ALERT_SPARE_THRESHOLD}" ]; then
    log "UYARI: Yedek alan düşük: ${SPARE}% (eşik: ${ALERT_SPARE_THRESHOLD}%)"
    logger -p user.warning "NVMe spare low: ${SPARE}%"
fi

if [ "${TEMP}" -gt "${ALERT_TEMP_THRESHOLD}" ]; then
    log "UYARI: SSD sıcaklığı yüksek: ${TEMP}°C"
    logger -p user.warning "NVMe temperature high: ${TEMP}°C"
fi

systemd timer kurulumu

nvme-health.timer & nvme-health.service
## /etc/systemd/system/nvme-health.service
[Unit]
Description=NVMe SSD Health Check

[Service]
Type=oneshot
ExecStart=/usr/local/bin/nvme_health_monitor.sh

## /etc/systemd/system/nvme-health.timer
[Unit]
Description=NVMe Health Check — Daily

[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target

## Etkinleştir
systemctl daemon-reload
systemctl enable --now nvme-health.timer
systemctl list-timers nvme-health.timer

Beklenen benchmark sonuçları — RPi CM4

TestSonuçKarşılaştırma (microSD)
Sıralı okuma~390 MB/s~45 MB/s
Sıralı yazma~350 MB/s~20 MB/s
Rastgele 4K okuma~40K IOPS~2K IOPS
Rastgele 4K yazma~30K IOPS~1K IOPS
Latency (ortalama)~80 µs~500 µs
ÖZET

NVMe'nin PCIe doğrudan erişim mimarisi, AHCI'ye göre radikal performans artışı sağlar. Linux NVMe sürücüsü olgun ve kararlıdır; nvme-cli ile tam yönetim mümkündür. io_uring ile NVMe'nin IOPS kapasitesi sistem çağrısı overhead'ı olmadan tam kullanılabilir. Gömülü sistemlerde endüstriyel M.2 SSD seçimi dayanıklılık ve sıcaklık açısından kritik öneme sahiptir.