embedded-deck
TEKNİK REHBER LİNUX NETWORK / SK_BUFF AĞ YIĞINI 2026

sk_buff
Linux ağ yığınının kalbi.

Socket buffer veri yapısının bellek düzeni, yönetim fonksiyonları, RX/TX yolları, NAPI, GSO/GRO ve sürücü geliştirme perspektifi — Linux ağ kodunun derinliklerine tam dalış.

00 sk_buff nedir?

struct sk_buff (socket buffer), Linux ağ yığınının merkezi veri yapısıdır. Her ağ paketi, kernel içinde bir sk_buff olarak temsil edilir. L2'den L7'ye kadar tüm protokol katmanları bu yapı üzerinde çalışır.

Tasarım felsefesi

sk_buff, 1993'te Alan Cox tarafından tasarlandı ve o tarihten bu yana Linux ağ yığınının omurgası. Temel fikir: sıfır kopyalama. Bir paket NIC'ten uygulamaya ulaşana kadar veri baytları mümkün olduğunca taşınmaz; bunun yerine pointer'lar hareket eder. Header ekleme (encapsulation) ve çıkarma (decapsulation) işlemleri pointer aritmetiği ile gerçekleştirilir.

  NIC DMA → sk_buff (head/data/tail/end)
               │
         Protocol stack:
         L2 Ethernet │ pull() — header çıkar, data++
         L3 IP       │ pull() — header çıkar, data++
         L4 TCP      │ pull() — header çıkar, data++
               │
         Uygulama verisi (data..tail arası)
    

sk_buff'ın kapsamı

AlanBilgi türü
Paket verisiBayt tamponu (head/data/tail/end pointer'ları)
Aygıt bilgisidev (net_device), skb_iif (giriş arayüzü indeksi)
Zaman damgasıtstamp (ktime_t), hwtstamps (donanım zaman damgası)
Protokol bilgisiprotocol, network_header, transport_header offsets
Bağlantı takibi_nfct (netfilter conntrack pointer)
QoS / sınıflandırmapriority, mark, queue_mapping
Checksumip_summed, csum (checksum offload durumu)
GSO/GROgso_size, gso_segs, skb_shinfo() → nr_frags
KAYNAK

struct sk_buff tanımı: include/linux/skbuff.h — Linux kernel kaynak kodunda 500+ satır yorum ile belgelenmiştir. Her alan için grep -n "field_name" include/linux/skbuff.h ile bağlam bulunabilir.

Bu bölümde

  • sk_buff = Linux ağ yığınının merkezi veri yapısı — L2'den L7'ye tüm protokoller
  • Sıfır kopyalama: veri yerine pointer'lar hareket eder
  • Header ekle/çıkar = pointer aritmetiği (push/pull)

01 sk_buff bellek düzeni

sk_buff, veri baytlarını değil; baytların tutulduğu tampona işaret eden dört pointer'ı yönetir. Bu düzen, başlık ekleme ve çıkarma işlemlerini sıfır kopyayla gerçekleştirir.

Dört temel pointer

  Bellek tamponu:
  ┌─────────────────────────────────────────────────────┐
  │ headroom │   veri (payload)   │ tailroom │   end    │
  └─────────────────────────────────────────────────────┘
  ^          ^                    ^                     ^
  head       data                 tail                  end

  head  = tamponun başlangıç adresi (sabit)
  data  = geçerli verinin başlangıcı (header push ile azalır)
  tail  = geçerli verinin sonu (put ile artar)
  end   = tamponun sonu (sabit)

  len   = tail - data  (geçerli veri uzunluğu)
  headroom = data - head  (başa eklenebilecek header alanı)
  tailroom = end  - tail  (sona eklenebilecek veri alanı)
    

TX yönünde header ekleme (encapsulation)

  Uygulama verisi oluşturuldu:
  head....[TCP data]....tail

  TCP header ekle (skb_push):
  head..[TCP hdr][TCP data]....tail
        ^data

  IP header ekle (skb_push):
  head.[IP hdr][TCP hdr][TCP data]....tail
       ^data

  Eth header ekle (skb_push):
  [Eth hdr][IP hdr][TCP hdr][TCP data]....tail
  ^data (= head alanında artık yer kalmadı)
    

RX yönünde header çıkarma (decapsulation)

  NIC'ten geldi:
  [Eth hdr][IP hdr][TCP hdr][TCP data]
  ^data

  Eth header işle (skb_pull):
  [Eth hdr][IP hdr][TCP hdr][TCP data]
            ^data  (data += ETH_HLEN)

  IP header işle (skb_pull):
  [Eth hdr][IP hdr][TCP hdr][TCP data]
                    ^data

  TCP header işle (skb_pull):
  [Eth hdr][IP hdr][TCP hdr][TCP data]
                              ^data  ← uygulama görmek istediği
    

shinfo ve frag_list

// skb_shinfo(): sk_buff'ın END pointer'ının hemen ardında
// skb_shared_info yapısı — çoklu fragment (scatter-gather) için

struct skb_shared_info {
    __u8          nr_frags;          // fragment sayısı (max 17)
    skb_frag_t    frags[MAX_SKB_FRAGS]; // sayfa bazlı fragment'lar
    struct sk_buff *frag_list;       // chained sk_buff listesi (GRO)
    struct skb_ufo_frag_list *ufo_frag_list;
    unsigned int  gso_size;          // GSO segment boyutu
    unsigned short gso_segs;         // GSO toplam segment sayısı
    unsigned short gso_type;         // SKB_GSO_TCPV4 vb.
};

// Kullanım:
struct skb_shared_info *shinfo = skb_shinfo(skb);
printk("nr_frags=%d gso_size=%d\n", shinfo->nr_frags, shinfo->gso_size);

Bu bölümde

  • head/data/tail/end: dört pointer, len = tail - data
  • headroom: TX'te header eklemek için önceden ayrılmış alan
  • skb_shinfo: scatter-gather fragment'lar ve GSO metadata

02 sk_buff yönetim fonksiyonları

sk_buff yaşam döngüsü: tahsis, pointer yönetimi, klonlama ve serbest bırakma fonksiyonları — kernel sürücüsü ve modülü yazarken sıkça kullanılır.

Tahsis ve serbest bırakma

// Yeni sk_buff tahsis et
// size: veri alanı boyutu, gfp: bellek tahsis bayrağı
struct sk_buff *skb = alloc_skb(size, GFP_ATOMIC);
if (!skb)
    return -ENOMEM;

// NIC sürücüsü için optimize edilmiş tahsis
// NET_SKB_PAD kadar headroom otomatik eklenir
struct sk_buff *skb = dev_alloc_skb(size);

// Referans sayacını artır (serbest bırakmayı geciktir)
skb_get(skb);

// Referans sayacını azalt; 0 olursa belleği serbest bırak
kfree_skb(skb);         // hata yolunda (drop)
consume_skb(skb);       // başarılı tüketimde (kfree_skb alias)
dev_kfree_skb(skb);     // sürücü TX tamamlandığında
dev_kfree_skb_irq(skb); // IRQ context'te

Pointer yönetimi

// skb_reserve: tahsis sonrası headroom ayır (TX hazırlık)
// Ethernet + IP + TCP header için yer aç
skb_reserve(skb, ETH_HLEN + MAX_HEADER);

// skb_put: tail'i artır, sona veri ekle — RX'te kullanılır
void *data_area = skb_put(skb, payload_len);
memcpy(data_area, src_buf, payload_len);

// skb_push: data'yı azalt, başa header ekle — TX encapsulation
struct ethhdr *eth = skb_push(skb, ETH_HLEN);
memcpy(eth->h_dest, dest_mac, ETH_ALEN);

// skb_pull: data'yı artır, baştan header çıkar — RX decapsulation
skb_pull(skb, ETH_HLEN);  // Ethernet header'ı atla

// Network/transport header pointer'larını kaydet
skb_reset_network_header(skb);   // network_header = data - head
struct iphdr *iph = ip_hdr(skb); // network_header'dan IP header

skb_set_transport_header(skb, sizeof(struct iphdr));
struct tcphdr *th = tcp_hdr(skb);

Klonlama ve kopyalama

// skb_clone: metadata kopyalanır, veri paylaşılır (COW)
// Orijinal ve klon aynı veri baytlarına pointer tutar
struct sk_buff *clone = skb_clone(skb, GFP_ATOMIC);

// skb_copy: tam kopyalama — bağımsız veri tamponu
struct sk_buff *copy = skb_copy(skb, GFP_ATOMIC);

// skb_copy_expand: klon + ek headroom/tailroom
struct sk_buff *exp = skb_copy_expand(skb,
    new_headroom, new_tailroom, GFP_ATOMIC);

// pskb_copy: sadece lineer kısım kopyalanır
struct sk_buff *partial = pskb_copy(skb, GFP_ATOMIC);

// Yazma öncesi paylaşım kontrolü (COW semantics)
if (skb_cloned(skb)) {
    skb = skb_unshare(skb, GFP_ATOMIC);
    if (!skb) return -ENOMEM;
}

Bu bölümde

  • alloc_skb + skb_reserve: TX için headroom ayır, sonra push ile header ekle
  • dev_alloc_skb + skb_put: RX için veri alanı oluştur
  • skb_clone: zerocopy çoğaltma; skb_copy: bağımsız kopya

03 RX yolu

NIC'te gelen paketin Linux kernel ağ yığınından geçerek uygulamaya ulaşması — donanım kesmesinden socket buffer'a.

RX akış diyagramı

  NIC donanım RX kesmesi
       │
  IRQ handler (net driver)
       │ NAPI schedule (napi_schedule)
  softirq NET_RX_SOFTIRQ
       │
  napi_poll()  ← driver'ın poll() callback'i
       │
  netif_receive_skb() / napi_gro_receive()
       │
  ptype_all hooks  (AF_PACKET, tcpdump)
       │
  ip_rcv() → ip_rcv_finish() → ip_local_deliver()
       │
  tcp_v4_rcv() / udp_rcv()
       │
  Socket receive queue
       │
  Uygulama: recv() / read()
    

netif_receive_skb ve protokol dispatch

// net/core/dev.c — protokol dispatch
// skb->protocol'e göre ilgili handler çağrılır

// Örn: ETH_P_IP (0x0800) → ip_rcv()
// Önemsiz bir kural: struct packet_type listesi
struct packet_type ip_packet_type = {
    .type = cpu_to_be16(ETH_P_IP),
    .func = ip_rcv,
};

// ip_rcv'nin basitleştirilmiş akışı:
// 1. Checksum doğrula (veya NIC'e offload edilmişse skip)
// 2. IP header'ı parse et
// 3. netfilter NF_INET_PRE_ROUTING hook'u çağır (nftables prerouting)
// 4. ip_route_input() → routing kararı (yerel mi, forward mu?)
// 5. Yerel: NF_INET_LOCAL_IN → tcp_v4_rcv() / udp_rcv()
// 6. Forward: NF_INET_FORWARD → ip_forward()

Socket receive queue

// Kernel tarafı: sk_buff'ı socket kuyruğuna ekle
sock_queue_rcv_skb(sk, skb);

// Uygulama tarafı (basitleştirilmiş)
// recvmsg() → tcp_recvmsg() → skb_dequeue(&sk->sk_receive_queue)
// Veri: sk_buff'tan kullanıcı tamponuna kopyalanır

// sk_buff'ın uygulama tamponu ile ilişkisi:
// skb->data[0..len] → kullanıcı tamponu (copy_to_user)
// Zero-copy receive için MSG_ZEROCOPY veya io_uring kullanılır

Bu bölümde

  • NIC IRQ → NAPI poll → netif_receive_skb → protokol handler → socket
  • ETH_P_IP → ip_rcv; netfilter hook'ları her katmanda çağrılır
  • sock_queue_rcv_skb: sk_buff socket kuyruğuna eklenir, recvmsg kopyalar

04 TX yolu

Uygulamanın gönderdiği verinin sk_buff olarak oluşturulup protokol yığınından NIC sürücüsüne ulaşması.

TX akış diyagramı

  Uygulama: send() / write()
       │
  tcp_sendmsg() / udp_sendmsg()
       │ sk_buff oluştur, veri kopyala
  ip_queue_xmit() / ip_send_skb()
       │ IP header ekle, routing
  Netfilter NF_INET_LOCAL_OUT (nftables output)
       │
  ip_output() → ip_finish_output()
       │ GSO segmentasyon (büyük paket parçalanır)
  Netfilter NF_INET_POST_ROUTING (nftables postrouting)
       │
  dev_queue_xmit()
       │
  Qdisc (Traffic Control — pfifo_fast, fq, tbf...)
       │
  netdev_start_xmit() → driver ndo_start_xmit()
       │
  NIC TX ring'e koy, DMA tetikle
    

dev_queue_xmit ve qdisc

// Qdisc (queuing discipline) = Linux Traffic Control
// sk_buff dev_queue_xmit'ten qdisc'e girer
// Qdisc'ten çıkış → ndo_start_xmit

// Mevcut qdisc görüntüleme
tc qdisc show dev eth0
// → qdisc pfifo_fast 0: root refcnt 2 bands 3 priomap...

// FQ (Fair Queuing) qdisc ile paket gönderim düzenleme
tc qdisc replace dev eth0 root fq
tc qdisc replace dev eth0 root fq_codel   # CoDel algoritması

// sk_buff.priority ve queue_mapping
// SO_PRIORITY soket seçeneği → skb->priority
// setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &prio, sizeof(prio));

ndo_start_xmit — sürücü TX

// Bir NIC sürücüsünün TX callback'i (sözde kod)
static netdev_tx_t my_nic_start_xmit(struct sk_buff *skb,
                                      struct net_device *dev) {
    struct my_nic_priv *priv = netdev_priv(dev);
    struct my_tx_desc *desc;
    dma_addr_t dma;

    // TX ring dolu mu?
    if (my_tx_ring_full(priv)) {
        netif_stop_queue(dev);   // TX queue'yu durdur
        return NETDEV_TX_BUSY;
    }

    // sk_buff verisini DMA'ya hazırla
    dma = dma_map_single(&priv->pdev->dev,
                         skb->data, skb->len,
                         DMA_TO_DEVICE);

    // TX descriptor'a yaz
    desc = &priv->tx_ring[priv->tx_head];
    desc->addr   = dma;
    desc->length = skb->len;
    desc->flags  = TX_DESC_EOP | TX_DESC_CSUM;

    // sk_buff'ı sakla (TX tamamlandığında serbest bırakmak için)
    priv->tx_skb[priv->tx_head] = skb;
    priv->tx_head = (priv->tx_head + 1) % TX_RING_SIZE;

    // NIC'e bildir
    writel(priv->tx_head, priv->bar + TX_TAIL_REG);

    return NETDEV_TX_OK;
}

Bu bölümde

  • send() → ip_queue_xmit → qdisc → ndo_start_xmit → NIC DMA
  • Qdisc: paket sıralama ve şekillendirme katmanı (fq, fq_codel önerilir)
  • ndo_start_xmit: sk_buff verisi DMA'ya maplenir, TX ring descriptor'a yazılır

05 NAPI — New API

NAPI (New API), yüksek hızlı NIC'lerde interrupt overhead'ini azaltmak için tasarlanmış polling tabanlı RX mekanizmasıdır. Linux 2.5'te tanıtıldı; modern tüm NIC sürücüleri NAPI kullanır.

NAPI'nin sorunu çözmesi

Geleneksel interrupt-driven NIC: her paket için bir IRQ. 10 Gbps hatta ~15 milyon paket/saniye = 15 milyon kesme/saniye = CPU üzerinde kabul edilemez interrupt overhead. NAPI: ilk paket interrupt ile bildirilir, ardından softirq context'te polling moduna geçilir.

  Paket geldi → IRQ → napi_schedule() → IRQ devre dışı
                                            │
                                    NET_RX_SOFTIRQ
                                            │
                                    napi_poll() çağrıldı
                                    ├── skb oku
                                    ├── netif_receive_skb()
                                    └── budget bitti veya kuyruk boş
                                            │
                                    napi_complete() → IRQ yeniden aç
    

NAPI implementasyonu

// 1. NAPI yapısını başlat (driver probe'da)
struct napi_struct napi;
netif_napi_add(dev, &napi, my_poll_func, NAPI_POLL_WEIGHT);
// NAPI_POLL_WEIGHT = 64 (varsayılan budget)

// 2. IRQ handler — sadece NAPI schedule et
static irqreturn_t my_irq_handler(int irq, void *data) {
    struct my_nic_priv *priv = data;

    // IRQ'yu NIC'te maskele
    my_nic_disable_rx_irq(priv);

    // softirq context'te poll çalıştırmayı planla
    napi_schedule(&priv->napi);

    return IRQ_HANDLED;
}

// 3. Poll callback — softirq context'te çağrılır
static int my_poll_func(struct napi_struct *napi, int budget) {
    struct my_nic_priv *priv = container_of(napi, ..., napi);
    int work_done = 0;

    while (work_done < budget) {
        struct sk_buff *skb = my_nic_rx_one(priv);
        if (!skb) break;

        napi_gro_receive(napi, skb);  // GRO desteği ile teslim et
        work_done++;
    }

    // Budget dolmadıysa tüm paketler alındı — polling bitti
    if (work_done < budget) {
        napi_complete_done(napi, work_done);
        my_nic_enable_rx_irq(priv);  // IRQ'yu yeniden aç
    }

    return work_done;
}

GRO ile bütünleşik kullanım

napi_gro_receive()GRO (Generic Receive Offload) mantığı ile sk_buff'ı teslim et. Ardışık TCP segmentleri birleştirilerek tek büyük sk_buff oluşturulur — protokol yığınının yükü azalır
napi_complete_done()Poll bitti bildir, GRO buffer'larını flush et ve IRQ'yu yeniden aktif et
interrupt throttlingNAPI budget (64 paket) aşıldığında softirq tekrar planlanır — interrupt throttling olmadan yüksek yük altında CPU starvation riski

Bu bölümde

  • NAPI: ilk paket IRQ, ardından softirq polling — interrupt overhead dramatik azalır
  • budget=64: her poll turunda max 64 paket işlenir; budget dolarsa IRQ kapalı kalır
  • napi_gro_receive: GRO birleştirmesi ile protokol stack yükünü azalt

06 GSO/GRO — Offload

Büyük paketleri küçük segmentlere bölme (GSO) ve gelen küçük paketleri birleştirme (GRO) işlemleri — NIC donanımına veya kernel'a offload edilerek CPU yükü azaltılır.

GSO — Generic Segmentation Offload

TCP, büyük mesajları MSS (Maximum Segment Size) boyutuna bölerek gönderir. GSO bu segmentasyonu mümkün olduğunca geç yapar — ideal olarak NIC sürücüsünde veya NIC donanımında (TSO).

  GSO olmadan:
  [TCP 64KB] → [1448B seg1][1448B seg2]...[1448B seg45]  (protokol stack'te)
  Her segment için IP header + checksum = 45x yük

  GSO ile (TSO — TCP Segmentation Offload):
  [TCP 64KB GSO skb] → NIC sürücüsü → NIC donanımı → [seg1][seg2]...[seg45]
  Protokol stack'te yalnızca bir büyük sk_buff işlenir
    
// sk_buff GSO alanları
struct sk_buff *skb;

// GSO paket mi kontrol et
if (skb_is_gso(skb)) {
    struct skb_shared_info *si = skb_shinfo(skb);
    printk("GSO: size=%u segs=%u type=%u\n",
           si->gso_size, si->gso_segs, si->gso_type);
}

// Yazılım GSO segmentasyonu (NIC TSO desteklemiyorsa)
struct sk_buff *segs = skb_gso_segment(skb, features);
if (!IS_ERR(segs)) {
    struct sk_buff *seg = segs;
    do {
        struct sk_buff *next = seg->next;
        seg->next = seg->prev = NULL;
        ndo_start_xmit(seg, dev);
        seg = next;
    } while (seg);
}

GRO — Generic Receive Offload

GSO'nun tersine: gelen ardışık TCP segmentleri birleştirilerek protokol yığınına tek büyük sk_buff olarak teslim edilir. NIC GRO yapmazsa kernel yazılım GRO yapar.

// NIC GRO durumunu kontrol et
ethtool -k eth0 | grep generic-receive-offload
// generic-receive-offload: on

// Tüm offload özelliklerini görüntüle
ethtool -k eth0

// GSO/GRO özelliklerini aç/kapat (test için)
ethtool -K eth0 gso off     # Generic Segmentation Offload
ethtool -K eth0 gro off     # Generic Receive Offload
ethtool -K eth0 tso off     # TCP Segmentation Offload (NIC hw)
ethtool -K eth0 rx-checksumming off
ÖzellikYönAçıklama
TSOTXTCP Segmentation Offload — NIC donanımı segmentler
GSOTXYazılım TSO — NIC desteği yoksa kernel segmentler
GRORXGeneric Receive Offload — ardışık segment birleştirme
LRORXLarge Receive Offload — NIC donanımı birleştirir (GSO uyumlu değil)
TX checksumTXChecksum hesaplama NIC'e bırakılır
RX checksumRXChecksum doğrulama NIC'te yapılır

Bu bölümde

  • GSO: büyük sk_buff oluştur, sürücüde parçala — protokol stack yükü 1/N'e iner
  • GRO: küçük RX paketleri birleştir — protokol stack'e tek büyük sk_buff
  • ethtool -k/-K ile offload özelliklerini görüntüle ve değiştir

07 Sürücü geliştirme perspektifi

Bir NIC sürücüsü yazarken sk_buff ile etkileşim noktaları: RX descriptor ring'den sk_buff oluşturma, TX tamamlandığında serbest bırakma ve net_device yapısı.

net_device ve ndo operasyonları

static const struct net_device_ops my_netdev_ops = {
    .ndo_open          = my_open,           // ifconfig up
    .ndo_stop          = my_stop,           // ifconfig down
    .ndo_start_xmit    = my_start_xmit,     // TX — sk_buff gönder
    .ndo_get_stats64   = my_get_stats64,    // istatistikler
    .ndo_set_mac_address = eth_mac_addr,
    .ndo_validate_addr   = eth_validate_addr,
    .ndo_change_mtu      = my_change_mtu,
};

// net_device tahsis ve kayıt
static int my_probe(struct pci_dev *pdev, ...) {
    struct net_device *dev = alloc_etherdev(sizeof(struct my_priv));
    if (!dev) return -ENOMEM;

    SET_NETDEV_DEV(dev, &pdev->dev);
    dev->netdev_ops = &my_netdev_ops;
    dev->features   = NETIF_F_HW_CSUM | NETIF_F_TSO | NETIF_F_GRO;

    return register_netdev(dev);
}

RX: sk_buff oluşturma

// RX tamamlama (NAPI poll context'te)
static void my_rx_complete(struct my_nic_priv *priv,
                           struct napi_struct *napi) {
    struct my_rx_desc *desc = &priv->rx_ring[priv->rx_tail];
    struct sk_buff *skb;
    unsigned int pkt_len = desc->length;

    // DMA unmap
    dma_unmap_single(&priv->pdev->dev,
                     desc->dma_addr, priv->rx_buf_size,
                     DMA_FROM_DEVICE);

    // sk_buff tahsis et
    skb = dev_alloc_skb(pkt_len + NET_IP_ALIGN);
    if (!skb) {
        priv->dev->stats.rx_dropped++;
        goto refill;
    }

    // 2-byte align (IP başlığını 4-byte align'a hizalar)
    skb_reserve(skb, NET_IP_ALIGN);

    // Veriyi DMA buffer'dan sk_buff'a kopyala
    skb_put_data(skb, priv->rx_buf[priv->rx_tail], pkt_len);

    // Metadata ayarla
    skb->dev      = priv->dev;
    skb->protocol = eth_type_trans(skb, priv->dev);
    skb->ip_summed = CHECKSUM_UNNECESSARY;  // NIC checksum yaptı

    // Protokol yığınına teslim et
    napi_gro_receive(napi, skb);

    priv->dev->stats.rx_packets++;
    priv->dev->stats.rx_bytes += pkt_len;

refill:
    my_refill_rx_ring(priv, priv->rx_tail);
    priv->rx_tail = (priv->rx_tail + 1) % RX_RING_SIZE;
}

TX tamamlandığında serbest bırakma

// TX completion IRQ handler veya TX poll
static void my_tx_complete(struct my_nic_priv *priv) {
    while (priv->tx_tail != priv->tx_head) {
        struct my_tx_desc *desc = &priv->tx_ring[priv->tx_tail];

        // NIC bu descriptor'ı tamamladı mı?
        if (!(desc->flags & TX_DESC_DONE))
            break;

        dma_unmap_single(&priv->pdev->dev,
                         desc->dma_addr, desc->length,
                         DMA_TO_DEVICE);

        // sk_buff'ı serbest bırak
        dev_kfree_skb_irq(priv->tx_skb[priv->tx_tail]);
        priv->tx_skb[priv->tx_tail] = NULL;

        priv->tx_tail = (priv->tx_tail + 1) % TX_RING_SIZE;
        priv->dev->stats.tx_packets++;
    }

    // Queue durmuşsa ve ring'de yer açıldıysa yeniden başlat
    if (netif_queue_stopped(priv->dev) && my_tx_ring_has_space(priv))
        netif_wake_queue(priv->dev);
}

Bu bölümde

  • net_device_ops: ndo_start_xmit, ndo_open, ndo_stop temel callback'ler
  • RX: dev_alloc_skb + skb_reserve + skb_put_data + eth_type_trans
  • TX complete: dma_unmap + dev_kfree_skb_irq + netif_wake_queue

08 Pratik: Custom virtual network sürücüsü

Kernel modülü olarak çalışan minimal bir sanal ağ cihazı: paketleri loopback'e yönlendiren dummy netdev ve temel performans ölçümü.

Minimal sanal netdev modülü

#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>

static struct net_device *vnet_dev;

struct vnet_priv {
    struct net_device_stats stats;
    spinlock_t lock;
};

/* TX: paketi al ve kendimize geri gönder (loopback) */
static netdev_tx_t vnet_xmit(struct sk_buff *skb,
                              struct net_device *dev) {
    struct vnet_priv *priv = netdev_priv(dev);
    struct sk_buff *rx_skb;

    priv->stats.tx_packets++;
    priv->stats.tx_bytes += skb->len;

    /* Gelen sk_buff'ı klonla (TX yolunda serbest bırakılacak) */
    rx_skb = skb_clone(skb, GFP_ATOMIC);
    if (rx_skb) {
        /* Aygıt ve protokol ayarla */
        rx_skb->dev      = dev;
        rx_skb->protocol = eth_type_trans(rx_skb, dev);
        rx_skb->ip_summed = CHECKSUM_UNNECESSARY;

        /* RX yığınına sun */
        netif_rx(rx_skb);

        priv->stats.rx_packets++;
        priv->stats.rx_bytes += rx_skb->len;
    }

    dev_kfree_skb(skb);
    return NETDEV_TX_OK;
}

static struct net_device_stats *vnet_stats(struct net_device *dev) {
    struct vnet_priv *priv = netdev_priv(dev);
    return &priv->stats;
}

static const struct net_device_ops vnet_ops = {
    .ndo_start_xmit = vnet_xmit,
    .ndo_get_stats  = vnet_stats,
};

static void vnet_setup(struct net_device *dev) {
    ether_setup(dev);
    dev->netdev_ops = &vnet_ops;
    dev->flags     |= IFF_NOARP;
    dev->features  |= NETIF_F_HW_CSUM;
    eth_hw_addr_random(dev);  /* Rastgele MAC */
}

static int __init vnet_init(void) {
    int err;
    vnet_dev = alloc_netdev(sizeof(struct vnet_priv),
                             "vnet%d", NET_NAME_UNKNOWN, vnet_setup);
    if (!vnet_dev)
        return -ENOMEM;

    spin_lock_init(&((struct vnet_priv *)netdev_priv(vnet_dev))->lock);

    err = register_netdev(vnet_dev);
    if (err) {
        free_netdev(vnet_dev);
        return err;
    }
    pr_info("vnet: registered as %s\n", vnet_dev->name);
    return 0;
}

static void __exit vnet_exit(void) {
    unregister_netdev(vnet_dev);
    free_netdev(vnet_dev);
    pr_info("vnet: unregistered\n");
}

module_init(vnet_init);
module_exit(vnet_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Minimal virtual netdev — sk_buff demo");

Test ve perf ölçümü

# Modülü derle ve yükle
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
insmod vnet.ko
ip link set vnet0 up
ip addr add 192.168.99.1/24 dev vnet0

# Loopback gönderim testi
ping 192.168.99.1 -c5

# İstatistikler
ip -s link show vnet0
cat /proc/net/dev | grep vnet0

# Performans ölçümü — iperf3 ile
iperf3 -s -B 192.168.99.1 &
iperf3 -c 192.168.99.1 -t 10

# sk_buff tahsis/serbest bırakma izleme (bpftrace)
bpftrace -e '
kprobe:alloc_skb { @alloc = count(); }
kprobe:kfree_skb  { @free  = count(); }
interval:s:1      { print(@alloc); print(@free); clear(@alloc); clear(@free); }
'

# Netdev istatistikleri izle
watch -n1 "ip -s link show vnet0"
ÖNEMLİ

Bu sanal sürücü eğitim amaçlıdır. Gerçek bir sürücüde lock contention, DMA eşleştirme, NUMA farkındalığı ve interrupt affinity gibi konular kritik performans faktörleridir. Üretim NIC sürücüsü için drivers/net/ethernet/intel/igb/ veya drivers/net/ethernet/mellanox/mlx5/ referans alın.

Bu bölümde

  • alloc_netdev + register_netdev: minimal netdev kaydı
  • ndo_start_xmit: skb_clone + netif_rx ile loopback geri besleme
  • bpftrace ile alloc_skb/kfree_skb sayımı — bellek baskısı izleme