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ı
| Alan | Bilgi türü |
|---|---|
| Paket verisi | Bayt tamponu (head/data/tail/end pointer'ları) |
| Aygıt bilgisi | dev (net_device), skb_iif (giriş arayüzü indeksi) |
| Zaman damgası | tstamp (ktime_t), hwtstamps (donanım zaman damgası) |
| Protokol bilgisi | protocol, network_header, transport_header offsets |
| Bağlantı takibi | _nfct (netfilter conntrack pointer) |
| QoS / sınıflandırma | priority, mark, queue_mapping |
| Checksum | ip_summed, csum (checksum offload durumu) |
| GSO/GRO | gso_size, gso_segs, skb_shinfo() → nr_frags |
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
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
| Özellik | Yön | Açıklama |
|---|---|---|
| TSO | TX | TCP Segmentation Offload — NIC donanımı segmentler |
| GSO | TX | Yazılım TSO — NIC desteği yoksa kernel segmentler |
| GRO | RX | Generic Receive Offload — ardışık segment birleştirme |
| LRO | RX | Large Receive Offload — NIC donanımı birleştirir (GSO uyumlu değil) |
| TX checksum | TX | Checksum hesaplama NIC'e bırakılır |
| RX checksum | RX | Checksum 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"
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