snull device driver

 

개요

linux device drvier 종류

네트워크 인터페이스에서 커널과의 상호작용으로 데이터 혹은 패킷을 물리계층을 통해 전송 역할을 하는 것이 네트워크 디바이스 드라이버이다.

네트워크 디바이스 드라이버는 리눅스 상의 /dev 디렉터리에 디바이스 파일이 존재하지 않으며 디바이스 드라이버 프로그래밍에서 사용되는 파일 연산 함수인 read(), write() 등의 함수도 사용하지 않는다.
또한, 블록 디바이스 드라이버와의 차이점은 블록 드라이버는 커널에서 들어오는 요청만 처리하는 반면, 네트워크 디바이스 드라이버는 외부와 비동기적으로 패킷을 송수신한다. 따라서 네트워크 디바이스 드라이버는 주소 설정, 전송을 위한 파라미터 설정, 오류 통계 유지 등의 많은 작업을 지원할 수 있도록 해야 한다.
이를 위해 필요한 것이 네트워크 디바이스 드라이버 프로그래밍이다.

본 문서에서는 네트워크 디바이스 드라이버 프로그래밍을 수행하기 앞서 기본이 되는 Snull 네트워크 디바이스 드라이버의 코드를 정리한다.
Snull network device driver Github

send me email if you have any questions.


snull device driver

  • Snull 네트워크 인터페이스는 가상의 IP 계층 주소 설정만을 이용하여 수행되며, 실질적으로 데이터 링크 계층의 인터페이스를 이용하지 않는다.
  • Snull이 동작하는 원리는 루프백과 유사하다. 차이점은 출발지와 목적지가 다른 루프백이라 할 수 있다. 하나의 가상 인터페이스를 통해서 전송된 데이터가 다른 가상 인터페이스를 통해 수신되는 형태이다. 이는 데이터가 전송되는 도중에 출발지와 목적지 주소를 변경함으로써 가능하다.
    • Snull 인터페이스는 하나의 호스트에서 이러한 루프백 효과를 위해 데이터가 전송되는 도중에 출발지와 목적지 주소의 3번째 옥텟의 LSB(Least Significant Bit)를 비트를 뒤집는다.

snull 등록/해제

네트워크 디바이스 드라이버 모듈을 커널에 등록하기위해서 드라이버는 자원을 요청하고 기능을 제공한다.
네트워크 디바이스 드라이버의 많은 부분이 문자, 블록과 다른 형태의 함수블록을 많이 채용한 것과 달리 등록과 해제과정에 별다른 과정은 없다.
다만 디바이스 드라이버와 하드웨어 위치를 탐색하지 않고 등록하기만 한다.
또한 네트워크 인터페이스에는 주 번호와 부 번호의 개념이 없기 때문에 네트워크 드라이버는 이런 번호를 요청하지 않으며 대신 새로 감지한 인터페이스를 네트워크 디바이스 전역 목록의 자료 구조로 집어넣는다.


struct net_device *alloc_netdev(int sizeof_priv, const char *name, void (*setup)(struct net_device *);

  • alloc_netdev()
    • snull은 snull의 상태를 나타하기위해 netdevice.h에 정의된 구조체를 사용하며 alloc_netdev()를 통해 모듈에 얹을 때 할당 받는다.
    • net_device 구조체 필드 정리
    • 여기서 sizeof(struct snull_priv)는 드라이버의 개인적 자료영역의 크기를 설정해준다.
    • name은 인터페이스의 이름, 그리고 마지막 setup은 구조체의 나머지 부분을 호출하기 위한 초기화함수를 가리키는 포인터를 말한다.
    • snull 함수 구현에 있어서 개인적 자료 영역의 크기는 struct snull_priv라는 구조체의 크기를 받아와 잡아주었고, name은 sn%d로 넘버를 부여 받을 수 있도록 해주었고, setup은 명령어를 담고 있는 snull_init로 해주었다.


void snull_init(struct net_device *dev){...}
void snull_init(struct net_device *dev)
{
    struct snull_priv *priv;
#if 0
        /*
    * Make the usual checks: check_region(), probe irq, ...  -ENODEV
    * should be returned if no device found.  No resource should be
    * grabbed: this is done on open(). 
    */
#endif

        /* 
    * Then, assign other fields in dev, using ether_setup() and some
    * hand assignments
    */
    ether_setup(dev); /* assign some of the fields */
    dev->watchdog_timeo = timeout;
    dev->netdev_ops = &snull_netdev_ops;
    dev->header_ops = &snull_header_ops;
    /* keep the default flags, just add NOARP */
    dev->flags           |= IFF_NOARP;
    dev->features        |= NETIF_F_HW_CSUM;

    /*
    * Then, initialize the priv field. This encloses the statistics
    * and a few private fields.
    */
    priv = netdev_priv(dev);
    memset(priv, 0, sizeof(struct snull_priv));
    if (use_napi) {
        netif_napi_add(dev, &priv->napi, snull_poll,2);
    }
    spin_lock_init(&priv->lock);
    priv->dev = dev;

    snull_rx_ints(dev, 1);		/* enable receive interrupts */
    snull_setup_pool(dev);
}
  • snull_init()
    • 각 디바이스 드라이버를 초기화 시켜주는 함수이다.
    • 이 초기화 작업은 register_netdev 호출 전에 완료되어야한다.
    • 커널이 ether_setup()를 통해 이더넷 관련된 몇몇 필드를 자동으로 설정해 준다.
    • 이 함수에서는 ether_setup()에 dev 파라메터를 넣어 몇몇 필드의 설정을 받아오오고 dev->open, dev->stop, dev->set_config, dev->hard_start_init, dev->do_ioctl, dev->get_stats, dev->rebuild_header, dev->tx_timeout, dev->wathdog_timeo, dev->flags, dev->features, dev->hard_header_cache 등을 설정해 줌으로서 초기화를 완료한다.


void snull_cleanup(void){...}
void snull_cleanup(void)
{
	int i;
    
	for (i = 0; i < 2;  i++) {
		if (snull_devs[i]) {
			unregister_netdev(snull_devs[i]);
			snull_teardown_pool(snull_devs[i]);
			free_netdev(snull_devs[i]); //will call netif_napi_del()
		}
	}
	return;
}
  • snull_clean_up()
    • 모듈 적재를 해제할 때는 별다른 작업은 필요하지 않으며, 단순히 인터페이스 등록을 해제(unregister_netdev)하고 내부적으로 필요한 작업 정리(snull_teardown_pool)를 수행한 후 net_device 구조체를 시스템으로 반납(free_netdev)한다.
    • module_exit()에 link되어 모듈을 내릴 때 적용된다.


int snull_init_module(void){...}
int snull_init_module(void)
{
        int result, i, ret = -ENOMEM;

        snull_interrupt = use_napi ? snull_napi_interrupt : snull_regular_interrupt;

        /* Allocate the devices */
        snull_devs[0] = alloc_netdev(sizeof(struct snull_priv), "sn%d",
                NET_NAME_UNKNOWN, snull_init);
        snull_devs[1] = alloc_netdev(sizeof(struct snull_priv), "sn%d",
                NET_NAME_UNKNOWN, snull_init);
        if (snull_devs[0] == NULL || snull_devs[1] == NULL)
            goto out;

        ret = -ENODEV;
        for (i = 0; i < 2;  i++)
            if ((result = register_netdev(snull_devs[i])))
                printk("snull: error %i registering device \"%s\"\n",
                        result, snull_devs[i]->name);
            else
                ret = 0;
    out:
        if (ret) 
            snull_cleanup();
        return ret;
}
  • snull_init_module()
    • 이 함수는 시작하는 함수인 module_init()로부터 처음 호출되는 함수로 메모리할당 작업과 모듈을 올리는데 있어서 오류 사항 등을 점검한다.
    • allow_netdev 함수로 실제 사용될 구조체인 snull_devs[0], snull_devs[1]에 할당하는 작업을 수행한다.


int snull_open(struct net_device *dev){...}
int snull_open(struct net_device *dev)
{
	/* request_region(), request_irq(), ....  (like fops->open) */

	/* 
	 * Assign the hardware address of the board: use "\0SNULx", where
	 * x is 0 or 1. The first byte is '\0' to avoid being a multicast
	 * address (the first byte of multicast addrs is odd).
	 */
	memcpy(dev->dev_addr, "\0SNUL0", ETH_ALEN);
	if (dev == snull_devs[1])
		dev->dev_addr[ETH_ALEN-1]++; /* \0SNUL1 */
	if (use_napi) {
		struct snull_priv *priv = netdev_priv(dev);
		napi_enable(&priv->napi);
	}
	netif_start_queue(dev);
	return 0;
}
  • snull_open()
    • 인터페이스가 패킷을 실어 나르기 전에 커널은 인터페이스를 열어서 주소를 대입해야 한다.
    • 커널은 ifconfig 명령어에 반응해서 인터페이스를 열고 닫는다.
    • open은 필요한 시스템 자원을 요청하고 인터페이스가 시작하도록 요청한다.
    • MAC주소는 인터페이스가 외부 세상과 통신하기에 앞서 하드웨어 디바이스에서 dev->dev_addr로 복사를 해주는 과정이 필요하다.
    • netif_start_queue()를 설정해 줌으로서 인터페이스에 필요한 큐를 할당함으로써 패킷을 전송할 수 있는 상태가 된다.


int snull_release(struct net_device \*dev){...}
int snull_release(struct net_device *dev)
{
    /* release ports, irq and such -- like fops->close */

	netif_stop_queue(dev); /* can't transmit any more */
        if (use_napi) {
                struct snull_priv *priv = netdev_priv(dev);
                napi_disable(&priv->napi);
        }
	return 0;
}
  • snull_release()
    • open과 반대로 close하기 위해서는 인터페이스를 종료하고 시스템 자원을 해제한다.
    • 해제하는 부분에서는 큐를 멈춰주는 netif_stop_queue()만 설정해주면 모든 설정이 끝나게 된다.
    • 디바이스가 더 이상 패킷을 전송할 수 없는 상태가 된다.


snull 송신

리눅스 커널이 다루는 각 패킷은 소켓 버퍼 구조체인 sk_buff에 들어 있다.
sk_buff 구조체 함수 정리
sk_buff는 상위 네트워크 계층으로부터 넘겨받으며 일종의 패킷이라고 봐도 무방하다. 이 sk_buff를 가리키는 포인터는 일반적으로 skb라고 부르기도 한다. 이 skb->data에 실제 전송할 패킷이 들어가며, skb->len은 옥텟 단위로 길이를 나타낸다.
리눅스 커널이 이러한 sk_buff를 전송하기 위해서는 hard_start_transmit이라는 함수를 호출하여 패킷을 queue에 집어넣는다. hard_start_xmit에 전달하는 소켓 버퍼는 전송 계층의 헤더를 포함한 물리적인 패킷을 포함한다. 따라서 인터페이스는 전송할 자료를 변경할 필요가 없다.


int snull_tx(struct sk_buff *skb, struct net_device *dev){...}
/*
 * Transmit a packet (called by the kernel)
 */
int snull_tx(struct sk_buff *skb, struct net_device *dev)
{
	int len;
	char *data, shortpkt[ETH_ZLEN];
	struct snull_priv *priv = netdev_priv(dev);

	data = skb->data;   // 데이터 저장
	len = skb->len;     // 데이터 길이 저장
	if (len < ETH_ZLEN) {
		memset(shortpkt, 0, ETH_ZLEN);
		memcpy(shortpkt, skb->data, skb->len);
		len = ETH_ZLEN;
		data = shortpkt;
	}
	netif_trans_update(dev);

	/* Remember the skb, so we can free it at interrupt time */
	priv->skb = skb;

	/* actual deliver of data is device-specific, and not shown here */
	snull_hw_tx(data, len, dev);  // 하드웨어 함수에 데이터, 데이터 길이값, 디바이스 포인터 값 넘김

	return 0; /* Our simple device can not fail */
}

  • snull_tx()
    • snull_tx()는 skb의 최소 크기 검사 등 패킷에 대한 기본적인 점검을 수행하고, 소켓 버퍼 구조체로부터 패킷 데이터(skb->data)와 길이(skb->len) 값을 하드웨어 연관 함수인 snull_hw_tx 함수로 넘겨주는 역할을 한다.
    • 실질적으로 네트워크 디바이스 드라이버를 구현할 시에는 특정 랜카드 특성에 맞게 이를 구현해야 하며, 본 snull에서는 가상 인터페이스로 존재하기 때문에 snull_hw_tx 함수를 이용하여 구현한다.
    • 리턴값은 성공할 경우 0, 실패할 경우 음수의 값으로 설정된다.


void snull_hw_tx(char *buf, int len, struct net_device *dev){...}
/*
 * Transmit a packet (low level interface)
 */
static void snull_hw_tx(char *buf, int len, struct net_device *dev)
{
	/*
	 * This function deals with hw details. This interface loops
	 * back the packet to the other snull interface (if any).
	 * In other words, this function implements the snull behaviour,
	 * while all other procedures are rather device-independent
	 */
	struct iphdr *ih;
	struct net_device *dest;
	struct snull_priv *priv;
	u32 *saddr, *daddr;
	struct snull_packet *tx_buffer;
    
	/* I am paranoid. Ain't I? */
	if (len < sizeof(struct ethhdr) + sizeof(struct iphdr)) { //송신될 패킷에 이더넷 헤더와 IP 헤더 있는지 확인
		printk("snull: Hmm... packet too short (%i octets)\n",
				len);
		return;
	}

	if (0) { /* enable this conditional to look at the data */
		int i;
		PDEBUG("len is %i\n" KERN_DEBUG "data:",len);
		for (i=14 ; i<len; i++)
			printk(" %02x",buf[i]&0xff);
		printk("\n");
	}
	/*
	 * Ethhdr is 14 bytes, but the kernel arranges for iphdr
	 * to be aligned (i.e., ethhdr is unaligned)
	 */
    //출발지주소와 목적지주소 설정
	ih = (struct iphdr *)(buf+sizeof(struct ethhdr));  
	saddr = &ih->saddr;
	daddr = &ih->daddr;
    
    /* 인터페이스 하나를 통해 보내지는 패킷을 다른 인터페이스가 수신할 수 있도록 출발지 및 목적지 주소를 변경. 3번째 옥텟의 LSB를 반전시킴 */
	((u8 *)saddr)[2] ^= 1; /* change the third octet (class C) */
	((u8 *)daddr)[2] ^= 1;

	ih->check = 0;         /* and rebuild the checksum (ip needs it) */
	ih->check = ip_fast_csum((unsigned char *)ih,ih->ihl);

	if (dev == snull_devs[0])
		PDEBUGG("%08x:%05i --> %08x:%05i\n",
				ntohl(ih->saddr),ntohs(((struct tcphdr *)(ih+1))->source),
				ntohl(ih->daddr),ntohs(((struct tcphdr *)(ih+1))->dest));
	else
		PDEBUGG("%08x:%05i <-- %08x:%05i\n",
				ntohl(ih->daddr),ntohs(((struct tcphdr *)(ih+1))->dest),
				ntohl(ih->saddr),ntohs(((struct tcphdr *)(ih+1))->source));

	/*
	 * Ok, now the packet is ready for transmission: first simulate a
	 * receive interrupt on the twin device, then  a
	 * transmission-done on the transmitting device
	 */
	dest = snull_devs[dev == snull_devs[0] ? 1 : 0];
	priv = netdev_priv(dest);
	tx_buffer = snull_get_tx_buffer(dev);

	if(!tx_buffer) {
		PDEBUG("Out of tx buffer, len is %i\n",len);
		return;
	}

	tx_buffer->datalen = len;
	memcpy(tx_buffer->data, buf, len);
	snull_enqueue_buf(dest, tx_buffer);
	if (priv->rx_int_enabled) {
		priv->status |= SNULL_RX_INTR;
		snull_interrupt(0, dest, NULL);
	}

	priv = netdev_priv(dev);
	priv->tx_packetlen = len;
	priv->tx_packetdata = buf;
	priv->status |= SNULL_TX_INTR;
	if (lockup && ((priv->stats.tx_packets + 1) % lockup) == 0) {
        	/* Simulate a dropped transmit interrupt */
		netif_stop_queue(dev);
		PDEBUG("Simulate lockup at %ld, txp %ld\n", jiffies,
				(unsigned long) priv->stats.tx_packets);
	}
	else
		snull_interrupt(0, dev, NULL);
}
  • snull_hw_tx()
    • snull_hw_tx 함수는 하드웨어 관련 전송 함수이며 snull은 가상 네트워크 인터페이스로 설정하기에 이를 함수로서 구현한다.
    • 실질적으로 네트워크 디바이스 드라이버를 구현할 시에는 하드웨어 특성에 맞게 프로그래밍되어야 하며 DMA(direct memory access) 등을 사용하여 구현된다.
    • 본 snull에서는 가상으로 두 개의 인터페이스에서 패킷을 송수신하는 일종의 편법을 이용하여 구현한다. 이를 위해 전송하는 ping 패킷의 출발지 주소와 목적지 주소의 세 번째 옥텟값의 LSB를 반전시켜 서로 다른 호스트로부터 패킷이 전송되는 것과 같은 방법을 이용한다.


void snull_tx_timeout (struct net_device *dev);
static const struct net_device_ops snull_netdev_ops = {
	.ndo_open            = snull_open,
	.ndo_stop            = snull_release,
	.ndo_start_xmit      = snull_tx,
	.ndo_do_ioctl        = snull_ioctl,
	.ndo_set_config      = snull_config,
	.ndo_get_stats       = snull_stats,
	.ndo_change_mtu      = snull_change_mtu,
	.ndo_tx_timeout      = snull_tx_timeout,    // 이부분!
};
  • snull_tx_timeout()
    • 디바이스 드라이버를 설계하는 데 있어 타이머를 통한 타임아웃 설정은 상당히 중요하다. 이와 관련하여 net_device 구조체의 watchdog_timeo 필드가 존재한다.
    • 시스템의 시각이 디바이스로부터 전송된 패킷의 trans_start 시각에 타임아웃 시간을 더한 값을 넘어서게 되면 네트워크 계층에서는 드라이버의 snull_tx_timeout을 호출한다.
    • 그래서 타임아웃이 발생하게 되면 통계에 오류를 표시하기 위해 stats.tx_error++;을 실행하고, 이후 전송 큐를 다시 시작하게 된다.
    • 본 snull 디바이스 드라이버에서는 사용되지 않는다.


snull 수신

snull에서의 패킷 수신부의 구현은 하드웨어를 통해 받은 패킷을 컴퓨터의 메모리에 올려주고 snull_rx를 호출하여 사용한다.
네트워크 드라이버가 구현하는 패킷 수신은 인터럽트 방식과 폴링 방식 두 가지로 나뉜다.

  • 인터럽트 방식
    • 대다수의 드라이버가 구현하는 방식으로 제어하는 하드웨어 장치가 서비스를 받아야 할 때 인터럽트를 발생시킨다.
    • 예를 들어 이더넷 디바이스 드라이버는 네트워크에서 이더넷 패킷을 받을 때마다 인터럽트를 발생시킨다.
  • 폴링 방식
    • 고대역폭 어댑터를 위한 드라이버가 구현하는 방식으로 시스템 타이머를 이용하여 호출하며 요청한 명령이 수행되었는지 검사한다.
void snull_rx(struct net_device *dev, struct snull_packet *pkt){...}
/*
 * Receive a packet: retrieve, encapsulate and pass over to upper levels
 */
void snull_rx(struct net_device *dev, struct snull_packet *pkt)
{
	struct sk_buff *skb;
	struct snull_priv *priv = netdev_priv(dev);

	/*
	 * The packet has been retrieved from the transmission
	 * medium. Build an skb around it, so upper layers can handle it
	 */
	skb = dev_alloc_skb(pkt->datalen + 2);
	if (!skb) {
		if (printk_ratelimit())
			printk(KERN_NOTICE "snull rx: low on mem - packet dropped\n");
		priv->stats.rx_dropped++;
		goto out;
	}
	skb_reserve(skb, 2); /* align IP on 16B boundary */  
	memcpy(skb_put(skb, pkt->datalen), pkt->data, pkt->datalen);

	/* Write metadata, and then pass to the receive level */
	skb->dev = dev;
	skb->protocol = eth_type_trans(skb, dev);
	skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */
	priv->stats.rx_packets++;
	priv->stats.rx_bytes += pkt->datalen;
	netif_rx(skb);
  out:
	return;
}
  • snull_rx()
    버퍼 할당
      skb = dev_alloc_skb(pck -> datalen +2);
      if (!skb) {
          if(printk_ratelimit())
              printk(KERN_NOTICE "snull rx : low on mem - packet dropped\n");
          priv -> stats.rx_dropped++;
          goto out;
      }
    
    • 패킷을 담을 버퍼를 할당하는 작업으로 버퍼 할당 함수인 dev_alloc_skb(lenghth)가 사용된다.
    • include/linux/skbuff.h에 정의되어 있으며 alloc_skb() 함수를 사용하여 소켓 버퍼를 형성하며 자료 길이를 인자로 갖는다.
    • datalen+2를 해주는 이유는 14비트인 이더넷 헤더를 16비트로 정렬해주기 위함이다.
    • printk_ratelimit() 함수를 사용하여 커널 메시지를 지나치게 많이 출력하여 시스템에 동작에 영향을 미치는 것을 막는다. 초 단위로 속도제한을 설정하며 default 값으로 0을 갖는다.
    버퍼에 패킷 데이터 복사
      memcpy(skb_put(skb, pkt -> datalen), pkt -> data, pkt -> datalen);
    
    • skb를 할당 받았다면 memcpy 함수를 사용하여 버퍼에 패킷 자료를 복사한다.
    • skb_put(skb, len)함수가 쓰이는데 포인터 skb를 len만큼 증가시킨다는 의미로 이 함수를 호출하기 전에 tailroom(빈 공간)이 충분한지 검사하여야 한다.
    dev와 protocol 필드 값 세팅
      skb -> dev = dev;
      skb -> protocol = eth_type_trans(skb, dev);
    
    • 패킷을 감지해내기 위하여 사용하는 두 필드로 이더넷 지원 코드는 ethenet_type_trans를 사용한다.
    checksum 관리
      skb -> ip_summed = CHEKSUM_UNNECESSARY;
    
    • 네트워크 드라이버에는 세 가지의 checksum이 존재한다.
      • CHECKSUM_KW
        • 이미 하드웨어에서 checksnum을 수행하였다는 의미이다.
      • CHECKSUM_NONE
        • 체크섬을 아직 하지 않았고 소프트웨어로 checksnum을 관리하겠다는 의미이며 default 값으로 쓰인다.
      • CHECKSUM_UNNECESSARY
        • checksum 작업을 수행하지 않는다는 의미이다.
    통계 카운터 갱신
      priv -> stats.rx_packets++;
      priv -> stats.rx_bytes += pkt -> datalen;
    
    • 패킷을 받았음을 기록하기 위하여 통계 카운터 갱신 과정을 거친다.
    • 중요한 필드는 rx_packets, rx_bytes, tx_packets, tx_bytes로써 각각 받은 패킷, 받은 패킷의 옥텟 개수, 보낸 패킷, 보낸 패킷의 옥텟 개수를 나타낸다.
    소켓 버퍼 상위 전달
      netif_rx(skb);
    
    • 패킷 수신의 마지막 단계로 netif_rx(skb)함수를 사용하여 네트워크 계층으로 패킷을 전송한다.

기타

  • 프로그램의 개인 데이터는 snull_priv를 정의한다.
/*
 * This structure is private to each device. It is used to pass
 * packets in and out, so there is place for a packet
 */

struct snull_priv {
	struct net_device_stats stats;
	int status;
	struct snull_packet *ppool;
	struct snull_packet *rx_queue;  /* List of incoming packets */
	int rx_int_enabled;
	int tx_packetlen;
	u8 *tx_packetdata;
	struct sk_buff *skb;
	spinlock_t lock;
	struct net_device *dev;
	struct napi_struct napi;
};
  • snull_packet 구조는 송수신 데이터를 저장하는 데 사용한다.
/*
 * A structure representing an in-flight packet.
 */
struct snull_packet {
	struct snull_packet *next;
	struct net_device *dev;
	int	datalen;
	u8 data[ETH_DATA_LEN];
};
  • rx_queue는 장치의 수신 대기열이며 작업 기능은 아래와 같다.
void snull_enqueue_buf(struct net_device *dev, struct snull_packet *pkt)
{
	unsigned long flags;
	struct snull_priv *priv = netdev_priv(dev);

	spin_lock_irqsave(&priv->lock, flags);
	pkt->next = priv->rx_queue;  /* FIXME - misorders packets */
	priv->rx_queue = pkt;
	spin_unlock_irqrestore(&priv->lock, flags);
}

struct snull_packet *snull_dequeue_buf(struct net_device *dev)
{
	struct snull_priv *priv = netdev_priv(dev);
	struct snull_packet *pkt;
	unsigned long flags;

	spin_lock_irqsave(&priv->lock, flags);
	pkt = priv->rx_queue;
	if (pkt != NULL)
		priv->rx_queue = pkt->next;
	spin_unlock_irqrestore(&priv->lock, flags);
	return pkt;
}
  • rx_int_enabled는 수신 인터럽트를 활성화 또는 비활성화하는 데 사용한다.
/*
 * Enable and disable receive interrupts.
 */
static void snull_rx_ints(struct net_device *dev, int enable)
{
	struct snull_priv *priv = netdev_priv(dev);
	priv->rx_int_enabled = enable;
}

참고

Snull 디바이스 드라이버 - pmj0403
Linux network driver example: snull network interface (1)
include/linux/netdevice.h
include/linux/skbuff.h