리눅스 커널의 네트워킹 스택은 현대적인 운영 체제의 핵심 요소로 자리 잡았습니다. 과거 서버 시장에서 두각을 나타냈던 리눅스는 이제 수십억 대의 임베디드 기기에서 실행되며 다양한 네트워크 환경을 지원합니다. 이러한 확장성을 가능하게 한 것은 표준화된 드라이버 인터페이스입니다. 본 문서는 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에서 확인할 수 있습니다.
버퍼 할당 프로세스
네트워크 버퍼 할당은 세 단계로 진행됩니다:
- 메모리 확보:
netdev_alloc_skb()호출 - 헤더 공간 예약:
skb_reserve()로 정렬 - 데이터 영역 확장:
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 장치도 동일한 원리로 구현됩니다. 제시된 패턴을 따라 안정적이고 효율적인 네트워크 드라이버를 개발할 수 있습니다.