리눅스 네트워크 인터페이스 드라이버 구현 가이드

리눅스 커널의 네트워킹 스택은 현대적인 운영 체제의 핵심 요소로 자리 잡았습니다. 과거 서버 시장에서 두각을 나타냈던 리눅스는 이제 수십억 대의 임베디드 기기에서 실행되며 다양한 네트워크 환경을 지원합니다. 이러한 확장성을 가능하게 한 것은 표준화된 드라이버 인터페이스입니다. 본 문서는 NIC 드라이버 개발에 필요한 커널 API와 구현 패턴을 상세히 다룹니다.

핵심 주제:

  • 패킷 버퍼와 네트워크 장치 구조체
  • 드라이버 아키텍처와 데이터 송수신 흐름
  • 가상 네트워크 드라이버 실습

핵심 데이터 구조체

네트워크 드라이버 개발에는 두 가지 주요 구조체가 필수적입니다:

  • struct sk_buff: include/linux/skbuff.h에 정의된 패킷 처리의 기본 단위입니다. 필요한 헤더:
#include <linux/skbuff.h>

송수신되는 모든 패킷은 이 구조체로 관리됩니다.

  • struct net_device: include/linux/netdevice.h에 정의된 물리적/가상 NIC를 나타냅니다. 관련 헤더:
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/ethtool.h>

소켓 버퍼 구조체

패킷 데이터를 감싸는 주요 컨테이너입니다:

struct sk_buff {
    struct sk_buff *next;
    struct sk_buff *prev;
    ktime_t timestamp;
    struct sock *socket;
    struct net_device *interface;
    unsigned int total_len;
    unsigned int data_len;
    __u16 mac_header_len;
    __u16 network_header_len;
    __u32 qos_priority;
    atomic_t reference_count;
    unsigned char *head_area;
    unsigned char *data_area;
    sk_buff_data_t tail_marker;
    sk_buff_data_t end_marker;
    unsigned int memory_footprint;
    dma_cookie_t dma_cookie;
};

주요 멤버 설명:

  • next/prev: 버퍼 체인을 연결하는 포인터
  • socket: 관련 소켓 객체
  • interface: 송수신 장치 정보
  • total_len: 전체 패킷 길이 (선형/비선형 데이터 합계)
  • mac_header_len: 맥 계층 헤더 크기
  • memory_footprint: 구조체와 데이터가 차지하는 전체 메모리
  • reference_count: 동시 접근을 위한 원자적 참조 카운터
  • head_area/data_area: 버퍼 영역의 시작과 데이터 시작 지점

전체 정의는 include/linux/skbuff.h에서 확인할 수 있습니다.

버퍼 할당 프로세스

네트워크 버퍼 할당은 세 단계로 진행됩니다:

  1. 메모리 확보: netdev_alloc_skb() 호출
  2. 헤더 공간 예약: skb_reserve()로 정렬
  3. 데이터 영역 확장: skb_put()로 실제 데이터 공간 할당
// 단계 1: 버퍼 생성
struct sk_buff *packet = netdev_alloc_skb(ndev, payload_size + NET_IP_ALIGN);
if (!packet) {
    return NULL;
}

// 단계 2: 헤더 정렬 (14바이트 이더넷 헤더 고려)
skb_reserve(packet, NET_IP_ALIGN);

// 단계 3: 데이터 영역 확장
unsigned char *payload_ptr = skb_put(packet, payload_size);

할당된 버퍼는 네트워크 계층으로 전달되어야 합니다:

int netif_rx_ni(struct sk_buff *skb);

네트워크 장치 구조체

NIC는 struct net_device로 표현되며, 동적 할당이 필요합니다:

struct net_device {
    char identifier[IFNAMSIZ];
    unsigned long mem_start_addr;
    unsigned long mem_end_addr;
    unsigned long io_base_address;
    int irq_number;
    netdev_features_t hw_capabilities;
    netdev_features_t current_features;
    int interface_index;
    struct net_device_stats performance_stats;
    atomic_long_t rx_dropped_packets;
    atomic_long_t tx_dropped_packets;
    const struct net_device_ops *operation_handlers;
    const struct ethtool_ops *ethtool_handlers;
    unsigned int device_flags;
    unsigned char permanent_address[MAX_ADDR_LEN];
    unsigned char current_address[MAX_ADDR_LEN];
    unsigned char address_length;
    unsigned int mtu_size;
    unsigned short interface_type;
    struct device platform_device;
    struct phy_device *phy_interface;
};

할당 함수:

struct net_device *alloc_etherdev(int private_data_size);
void *netdev_priv(const struct net_device *dev);

사용 예시:

struct net_device *nic_dev;
struct adapter_context *adapter;

nic_dev = alloc_etherdev(sizeof(struct adapter_context));
adapter = netdev_priv(nic_dev);

해제는 등록 해제 후 수행:

void free_netdev(struct net_device *dev);

등록:

int register_netdev(struct net_device *dev);

장치 운영 메서드

네트워크 장치는 파일시스템 노드가 없으므로 struct net_device_ops 구조체로 기능을 제공합니다:

struct net_device_ops {
    int (*init)(struct net_device *dev);
    int (*open)(struct net_device *dev);
    int (*stop)(struct net_device *dev);
    netdev_tx_t (*transmit)(struct sk_buff *skb, struct net_device *dev);
    int (*set_mac)(struct net_device *dev, void *addr);
    void (*set_rx_mode)(struct net_device *dev);
    int (*ioctl)(struct net_device *dev, struct ifreq *req, int cmd);
    int (*change_mtu)(struct net_device *dev, int new_mtu);
    void (*tx_timeout)(struct net_device *dev);
    struct net_device_stats* (*get_stats)(struct net_device *dev);
};

필수 메서드는 transmit 뿐이지만, 성능과 기능을 위해 최대한 많이 구현하는 것이 권장됩니다.

장치 활성화/비활성화

인터페이스가 ifconfig up으로 활성화되면 open() 메서드가 호출됩니다:

static int nic_device_open(struct net_device *ndev)
{
    struct adapter_context *adapter = netdev_priv(ndev);
    
    if (!is_valid_ether_addr(ndev->dev_addr)) {
        netdev_err(ndev, "Invalid MAC address\n");
        return -EADDRNOTAVAIL;
    }
    
    // 하드웨어 초기화
    if (!hw_reset_controller(adapter)) {
        return -EIO;
    }
    
    // MAC 주소 하드웨어에 로드
    hw_load_mac_address(adapter, ndev->dev_addr);
    
    // 인터럽트 활성화
    hw_enable_interrupts(adapter);
    
    // 전송 큐 시작
    netif_start_queue(ndev);
    
    return 0;
}

비활성화는 반대 작업을 수행:

static int nic_device_close(struct net_device *ndev)
{
    struct adapter_context *adapter = netdev_priv(ndev);
    
    hw_disable_interrupts(adapter);
    hw_enter_low_power(adapter);
    netif_stop_queue(ndev);
    
    return 0;
}

패킷 송수신 처리

수신 경로

인터럽트 발생 시 드라이버는 버퍼를 할당하고 데이터를 복사합니다:

static int nic_receive_handler(struct net_device *ndev)
{
    struct adapter_context *adapter = netdev_priv(ndev);
    int packet_count = hw_get_received_count(adapter);
    
    while (packet_count--) {
        nic_process_incoming_packet(ndev);
    }
    
    return packet_count;
}

static void nic_process_incoming_packet(struct net_device *ndev)
{
    struct adapter_context *adapter = netdev_priv(ndev);
    struct sk_buff *packet;
    u16 packet_length;
    u8 status_info[STATUS_SIZE];
    
    packet_length = hw_read_packet_metadata(adapter, status_info);
    
    if (packet_length > MAX_FRAME_SIZE) {
        ndev->stats.rx_errors++;
        return;
    }
    
    packet = netdev_alloc_skb(ndev, packet_length + NET_IP_ALIGN);
    if (!packet) {
        ndev->stats.rx_dropped++;
        return;
    }
    
    skb_reserve(packet, NET_IP_ALIGN);
    hw_read_packet_data(adapter, packet_length, skb_put(packet, packet_length));
    
    packet->protocol = eth_type_trans(packet, ndev);
    ndev->stats.rx_packets++;
    ndev->stats.rx_bytes += packet_length;
    
    netif_rx_ni(packet);
}

송신 경로

전송 요청 시 transmit() 메서드가 호출됩니다:

static netdev_tx_t nic_start_transmit(struct sk_buff *skb, struct net_device *ndev)
{
    struct adapter_context *adapter = netdev_priv(ndev);
    
    netif_stop_queue(ndev);
    
    // SPI 버스처럼 슬립 가능한 경우 작업 큐 사용
    adapter->tx_skb = skb;
    schedule_work(&adapter->transmit_work);
    
    return NETDEV_TX_OK;
}

static void nic_hw_transmit(struct work_struct *work)
{
    struct adapter_context *adapter = container_of(work, struct adapter_context, transmit_work);
    struct net_device *ndev = adapter->netdev;
    
    hw_write_fifo(adapter, adapter->tx_skb->data, adapter->tx_skb->len);
    hw_trigger_transmission(adapter);
}

전송 완료 인터럽트:

static void nic_tx_complete(struct net_device *ndev)
{
    struct adapter_context *adapter = netdev_priv(ndev);
    
    hw_clear_tx_interrupt(adapter);
    ndev->stats.tx_packets++;
    ndev->stats.tx_bytes += adapter->tx_skb->len;
    
    dev_kfree_skb(adapter->tx_skb);
    netif_wake_queue(ndev);
}

완전한 드라이버 예제

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

struct nic_private_data {
    int configuration_value;
    struct net_device *network_interface;
};

static int dummy_nic_open(struct net_device *dev)
{
    netif_start_queue(dev);
    return 0;
}

static int dummy_nic_close(struct net_device *dev)
{
    netif_stop_queue(dev);
    return 0;
}

static netdev_tx_t dummy_nic_xmit(struct sk_buff *skb, struct net_device *dev)
{
    dev->stats.tx_bytes += skb->len;
    dev->stats.tx_packets++;
    dev_kfree_skb(skb);
    return NETDEV_TX_OK;
}

static const struct net_device_ops dummy_nic_ops = {
    .ndo_open = dummy_nic_open,
    .ndo_stop = dummy_nic_close,
    .ndo_start_xmit = dummy_nic_xmit,
    .ndo_validate_addr = eth_validate_addr,
};

static const struct of_device_id nic_dt_match[] = {
    { .compatible = "example,dummy-nic", },
    { }
};

static int nic_probe(struct platform_device *pdev)
{
    struct net_device *netdev;
    struct nic_private_data *priv;
    
    netdev = alloc_etherdev(sizeof(*priv));
    if (!netdev)
        return -ENOMEM;
    
    priv = netdev_priv(netdev);
    priv->network_interface = netdev;
    
    netdev->netdev_ops = &dummy_nic_ops;
    eth_hw_addr_random(netdev);
    
    if (register_netdev(netdev)) {
        free_netdev(netdev);
        return -EINVAL;
    }
    
    platform_set_drvdata(pdev, priv);
    return 0;
}

static int nic_remove(struct platform_device *pdev)
{
    struct nic_private_data *priv = platform_get_drvdata(pdev);
    
    unregister_netdev(priv->network_interface);
    free_netdev(priv->network_interface);
    return 0;
}

static struct platform_driver nic_driver = {
    .probe = nic_probe,
    .remove = nic_remove,
    .driver = {
        .name = "dummy-nic",
        .of_match_table = nic_dt_match,
    },
};

module_platform_driver(nic_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Example Network Interface Driver");

인터럽트 처리

SPI 장치처럼 슬립 가능한 버스에서는 인터럽트를 작업 큐로 지연 처리합니다:

static irqreturn_t nic_irq_handler(int irq, void *dev_id)
{
    struct adapter_context *adapter = dev_id;
    schedule_work(&adapter->interrupt_work);
    return IRQ_HANDLED;
}

static void nic_interrupt_work(struct work_struct *work)
{
    struct adapter_context *adapter = container_of(work, struct adapter_context, interrupt_work);
    u32 interrupt_status;
    
    hw_disable_interrupts(adapter);
    
    do {
        interrupt_status = hw_read_interrupt_status(adapter);
        
        if (interrupt_status & IRQ_LINK_CHANGED) {
            nic_check_link(adapter);
        }
        
        if (interrupt_status & IRQ_TX_COMPLETE) {
            nic_tx_complete(adapter->netdev);
        }
        
        if (interrupt_status & IRQ_RX_PENDING) {
            nic_receive_handler(adapter->netdev);
        }
    } while (interrupt_status);
    
    hw_enable_interrupts(adapter);
}

Ethtool 지원

고급 기능 제어를 위해 ethtool_ops 구현:

static const struct ethtool_ops nic_ethtool_ops = {
    .get_link = ethtool_op_get_link,
    .get_drvinfo = nic_get_driver_info,
    .get_settings = nic_get_settings,
    .set_settings = nic_set_settings,
};

// 등록 시 할당
netdev->ethtool_ops = &nic_ethtool_ops;

드라이버 탐지 및 정리

Probe 함수

static int nic_device_probe(struct spi_device *spi)
{
    struct net_device *netdev;
    struct adapter_context *adapter;
    
    netdev = alloc_etherdev(sizeof(*adapter));
    if (!netdev)
        return -ENOMEM;
    
    adapter = netdev_priv(netdev);
    adapter->spi_bus = spi;
    
    INIT_WORK(&adapter->interrupt_work, nic_interrupt_work);
    INIT_WORK(&adapter->transmit_work, nic_hw_transmit);
    
    if (!hw_chip_init(adapter)) {
        free_netdev(netdev);
        return -EIO;
    }
    
    eth_hw_addr_random(netdev);
    hw_set_mac_address(adapter, netdev->dev_addr);
    
    if (request_irq(spi->irq, nic_irq_handler, 0, "nic-driver", adapter)) {
        free_netdev(netdev);
        return -EBUSY;
    }
    
    netdev->netdev_ops = &nic_netdev_ops;
    netdev->ethtool_ops = &nic_ethtool_ops;
    netdev->irq = spi->irq;
    
    hw_set_low_power(adapter, true);
    
    if (register_netdev(netdev)) {
        free_irq(spi->irq, adapter);
        free_netdev(netdev);
        return -EFAULT;
    }
    
    spi_set_drvdata(spi, adapter);
    return 0;
}

Remove 함수

static int nic_device_remove(struct spi_device *spi)
{
    struct adapter_context *adapter = spi_get_drvdata(spi);
    
    unregister_netdev(adapter->netdev);
    free_irq(spi->irq, adapter);
    free_netdev(adapter->netdev);
    
    return 0;
}

이 문서는 SPI 기반 NIC 드라이버를 중심으로 설명했지만, USB나 PCI 장치도 동일한 원리로 구현됩니다. 제시된 패턴을 따라 안정적이고 효율적인 네트워크 드라이버를 개발할 수 있습니다.

태그: linux-kernel network-driver sk_buff net_device ethtool

10월 1일 23:26에 게시됨