IT/디바이스드라이버

23. 네트워크 디바이스 드라이버

피치 크러쉬 2015. 11. 25. 17:44
300x250


23. 네트워크 디바이스 드라이버.pptx


23. 네트워크 디바이스 드라이버


목차

1.리눅스와 네트워크 디바이스 드라이버

2. 드라이버 등록과 제거

3. 보편적인 초기화와 등록

4. struct net_device

5. 디바이스 열기와 닫기

6. 전송과 수신

7. 통계, 제어, 멀티캐스트


1. 리눅스와 네트워크DD

네트워크 디바이스 드라이버의 동작

블록디바이스 드라이버와 차이점
디바이스 파일 형태로 접근하지 않고 다른 형태고 구현됨.
file operation 구조체로 처리하지 않고, network operation 구조체인 net_device를 사용함.
동작과정
1.하드웨어 검출
2.커널에 등록된 네트워크 인터페이스를 활성화
3.활성화 될때 디바이스 열기 호출
4.전송과 수신


2. 드라이버 등록과 제거

등록
번호와 부 번호 대신 net_device 구조체를 커널에 전달
int register_netdev(struct net_device *dev);
register_netdev()
struct net_device 구조체를 커널에 등록
Register_chrdev()를 이용해 struct file_operation를 등록하는 것과 같음
등록된 모듈 제거

static void __exit xxx_cleanup_module(void)
{

unregister_netdev(xxx_dev);

free_netdev(xxx_dev);

}

module_exit(xxx_cleanup_module);

초기화
struct net_device *alloc_netdev
(int sizeof_priv, const char *name, void (*setup) (struct net_device *));
net_device 구조체 메모리를 할당
name 필드에 인터페이스의 이름을 복사
priv 필드에 sizeof_priv 만큼의 메모리를 할당
setup 매개변수에 전달된 함수를 호출해 net_device 구조체를 초기화
Setup 매개변수 중 미리 구현되어 있지 않으면 새로 구현해야 함.
ether_setup : 이더넷 인터페이스 초기화 함수
tr_setup : 토큰링 인터페이스 초기화 함수
fddi_setup : 광통신 인터페이스 초기화 함수
할당과 셋업 함수는 하나로 사용할 수 있는 함수가 존재함.
Struct net_device *alloc_etherdev (int sizeof_priv) : 이더넷

Struct net_device *alloc_fcdev (int sizeof_priv) : 광채널 등
제공 함수를 통한 네트워크 디바이스 드라이버 등록
모듈 초기화
Module_init에서 지정한 xxx_init_module() 함수에서 수행
구조체 변수 할당
Alloc_etherdev()
구조체 등록
Register_netdev()

구조체 초기화
xxx_init_module()xxx_init으로 초기화

int xxx_init(struct net_device *dev)

{

  //하드웨어 검출 처리 및 초기화

  :

  if(fail) return –EIO;

  //구조체 초기화 처리

  :

  dev->open  = xxx_open;

  dev->stop  = xxx_stop;

dev->hard_start_xmit  = xxx_hard_start_xmit

  dev->do_ioctl  =xxx_do_ioctl;

  :

  return 0;

}

모듈제거

Static void __exit xxx_cleanup_module(void)
{
 
Unregister_netdev(xxx_dev);
  Free_netdev(xxx_dev);
}


Module_exit(xxx_cleanup_module
);



5. 드라이버 열기와 닫기

개요

다른 디바이스 드라이버와 다른점
다른 드라이버
주번호와 부번호가 있어 디바이스 등록과 구분시에 사용됨
open, release 호출시 파일 오퍼레이션에 매칭되는 xxx_open, xxx_release 가 호출됨.
네트워크 드라이버
네트워크 디바이스는 주번호와 부번호가 없고, 단순히 디바이스 이름만 존재
저수준 파일 입출력 함수를 이용해 접근할 수 없음.
소켓 인터페이스를 이용함.

네트워크 디바이스의 제어

디바이스 제어 방법
일반적인 TCP/IP 통신
int fd;
fd = socket(AF_INET, SOCK_DGRAM, 0);
  :
close(fd)
소켓을 일반 파일 핸들러와 동일한 방식으로 접근 가능
socket을 열고 ioctl을 이용해 처리

ifconfig eth0 up
int fd;
struct ifreq ifr;

fd = socket(AF_INET, SOCK_DGRAM, 0);
ifr,ifr_flags = IFF_UP ;
strncpy(ifr.ifr_name, “eth0”, 5);
ioctl(fd, SIOCSIFFLAGS, &ifr);
close(fd);

네트워크 인터페이스의 활성화

인터페이스 활성화 과정
인터페이스를 활성화 하기 위한 ioctl 명령은 보통 ifconfig 명령에 의해 생성됨
e.g., ifconfig eth0:0 192.168.10.101 netmask 255.255.255.0 up
두 가지 ioctl 명령 발생
up명령을 위한 SIOCSIFFLAGS
net_device  -> flagsIF_UP 으로 설정
net_device -> open()을 호출
1.네트워크 하드웨어가 동작하도록 하드웨어를 초기화
2.인터럽트 서비스 함수 등록
3.하드웨어 네트워크 주소 설정
4.네트워크 송신큐가 작동하도록 netif_start_queue 호출
IP 설정을 위한 SIOCSIFADDR
open() 수행 뒤 호출 됨.
인터페이스 활성화와 별도로 커널 내부에서만 동작함.
주소가 MAC주소로 설정할 경우 드라이버에 영향을 미침

net_device -> dev_addr 셋팅

*rtl8319_open 예제

int rtl8139_open(struct net_device *dev)

{

struct rtl8139_private *tp = dev->priv;

  :

retval = request_irq (dev->irq, rtl8139_interrupt, SA_SHIRQ, dev->name, dev);

  :

네트워크 관리를 위한 내부데이터구조 초기화

  :

하드웨어 동작을 위한 초기화

  :

netif_start_queue(dev);

  :

return 0;

}
네트워크 인터페이스의 비활성화

인터페이스 비 활성화 과정
ifconfig eth0:0 down
SIOCSIFFLAGS 명령 호출 -> IF_UP 플래그를 지움 ->
net_device
stop() 함수 호출
1.네트워크 송신큐가 정지하도록 netif_stop_queue 호출
2.네트워크 하드웨어가 동작하도록 하드웨어 초기화
3.인터럽트 서비스 함수를 커널에서 제거

4.하드웨어 네트워크 주소를 clear 상태로 만듦

*rtl8319_close 예제

int rtl8139_close(struct net_device *dev)

{

struct rtl8139_private *tp = dev->priv;

  :

netif_stop_queue(dev)

  :

free_irq(dev->irq, dev);

  :

네트워크 관리를 위한 내부 데이터구조 해제

  :

하드웨어 동작 정지 처리

  :

return 0;

}



6. 전송과 수신

개요

struct sk_buff

sk_buff
sk_buff 를 소켓버퍼로 지칭
소켓 버퍼는 크기가 큰 구조체 이며, 많은 필드로 구성 되어 있음.
linux/include/linux/skbuff.h
처리
디바이스 드라이버가 관심을 가질 것은 몇가지 되지 않음
소켓 버퍼를 처리하는 함수를 이용하도록 하는 것이 안전함
직접 적으로 접근하지 않는 것이 좋음

디바이스 드라이버에서 참조하는 필드들
struct net_device *dev;
struct net_device *real_dev;
real_devVLAN을 구현하는 경우 실제 네트워크 디바이스를 가리킴
union{
  struct tcphdr *th;
  struct udphdr *uh;
  struct icmphdr *icmp;
  struct igmphdr *igmph;
  struct iphdr *ipiph;
  struct ipv6hdr *ipv6h;
  unsigned char *raw;
} h;

소켓 버퍼를 통해 전달되는 프로토콜 데이터 헤더를 가리키는 주소
디바이스 드라이버에서 참조하는 필드들
union{
  struct iphdr *iph;
  struct ipv6hdr *ipv6h;
  struct arphdr *arph;
  unsigned char *raw;
} nh;
소켓 버퍼를 통해 전달되는 데이터의 프로토콜 주소의 헤더를 가리키는 주소
union{
  struct ethhdr * ethernet;
  unsigned char *raw;
} mac;

소켓 버퍼를 이용해 데이터의 네트워크 주소 헤더를 가리키는 주소
디바이스 드라이버에서 참조하는 필드들
unsigned char *head;
소켓 버퍼에 할당된 버퍼의 선두를 가리킴
unsigned char *data;
소켓 버퍼에 할당되어 사용되는 유효한 데이터의 선두를 가리킴
unsigned char *tail;
소켓 버퍼에 할당되어 사용되는 유효한 데이터의 마지막 위치를 가리킴
unsigned char *end;
소켓 버퍼에 할당된 버퍼의 마지막 위치를 가리킴
unsigned long len;
유효한 데이터 크기(skb -> tail – skb -> data)를 나타냄
unsigned char ip_summed;
수신된 패킷이 TCP/IP 프로토콜 형태 일때 네트워크 스택에서 체크섬의 처리 유무를 표시
CHECKSUM_NONE : 커널의 네트워크 스택에서 처리되어야 함(기본)
CHECKSUM_HW : 체크섬이 하드웨어에서 이미 처리되었음

CHECKSUM_UNNECESSARY : 체크섬을 수행할 필요가 없음
디바이스 드라이버에서 참조하는 필드들
unsigned char pkt_type;
수신된 패킷 타입을 지정할 때 사용 됨
대부분 리눅스 커널 함수들에 의해 자동으로 설정 됨
직접 설정하는 디바이스는 주로 WAN이나 논리적인 디바이스를 다루는 경우
PACKET_HOST : 로컬 호스트에 전송된 패킷
PACKET_BROADCAST: 브로드 캐스트 된 패킷
PACKET_MULTICAST : 멀티캐스트된 패킷
PACKET_OTHERHOST : 다른 호스트로 전달해야 하는 패킷

디바이스 드라이버에서 사용하는 함수들
소켓 버퍼 할당
struct sk_buff *alloc_skb(unsigned int len, int priority);
struct sk_buff *dev_alloc_skb(unsigned int len);
alloc_skb() 함수의 매개변수 priorityalloc_skb에서 내부적으로 호출하는 kmalloc() 함수에 사용됨
dev_alloc_skbpriorityGFP_ATOMIC을 설정하고 alloc_skb보다 16byte만큼 더 넣음
대부분 ethernet 디바이스를 처리하는 드라이버는 dev_alloc_skb를 사용하여 할당함
소켓 버퍼 해제
void kfree_skb(struct sk_buff *skb);
void dev_kfree_skb(struct sk_buff *skb);

디바이스 드라이버에서 사용하는 함수들
소켓 버퍼 데이터 공간 뒤로 확장
unsigned char *skb_put(struct sk_buff *skb, int len);
unsigned char *__skb_put(struct sk_buff *skb, int len);
len 만큼 확장
tail len 필드만 갱신
skb_put은 확장할 데이터 공간이 버퍼 크기를 벗어나는지 검사함
__skb_put은 검사하지 않음
소켓 버퍼 데이터 공간 앞으로 확장
unsigned char *skb_push(struct sk_buff *skb, int len);
unsigned char *__skb_push(struct sk_buff *skb, int len);
datalen 필드만 갱신
skb_push확장할 데이터 공간이 버퍼 크기를 벗어나는지 검사함

__skb_push는 검사하지 않음
디바이스 드라이버에서 사용하는 함수들
소켓 버퍼 데이터 공간 앞 뒤로 확장
void skb_reserve(struct sk_buff *skb, int len);
앞쪽과 뒤쪽으로 len 크기만큼 확장
datatail, len 필드만 갱신
소켓 버퍼 데이터 공간 축소
unsigned char *skb_pull(struct sk_buff *skb, int len);
len 크기 만큼 축소
datalen 필드 갱신
뒤쪽으로 확장 가능한 크기를 반환
int skb_tailroom(struct sk_buff *skb);
앞쪽으로 확장 가능한 크기를 반환

int skb_headroom(struct sk_buff *skb);

전송 처리

전송 처리
커널의 프로토콜 스택에서 들어온 데이터를 전송
전송 처리 함수는 net_device->hard_start_xmit 필드에 등록 해야함
net_device 구조체의 hard_start_xmit 필드에 선언된 함수를 호출
함수 형식
int xxx_hard_start_xmit(struct sk_buff *skb, struct net_device *dev);
드라이버가 관리하는 디바이스의 정보는 dev에 전달 되고
skb가 가리키는 소켓 버퍼를 이용해 데이터를 전달 함

hard_start_xmit 설계
int xxx_hard_start_xmit(struct sk_buff *skb, struct net_device *dev);
1.dev에 전달된 net_device 구조체의 dev->priv 에서 하드웨어 제어를 위한 구조체 정보를 얻음
2.소켓 버퍼에서 하드웨어를 통해 실제로 전송 되어야 하는 데이터의 주소를 구함
3.소켓 버퍼에서 하드웨어를 통해 실제로 전송 되어야 하는 데이터의 크기를 구함
4.데이터의 크기가 하드웨어에서 한번에 전송할 수 있는 최대 크기를 넘는가를 검사함
초과하면 소켓 버퍼를 버림
5.데이터의 크기가 하드웨어에서 전송에 필요한 최소 크기보다 작은지 검사
작다면 적절한 크기로 재조정
6.하드웨어에 데이터를 넣기 전에 충돌 방지를 위해 커널을 잠금
7.소켓 버퍼의 데이터와 크기를 이용해 하드웨어 FIFO에 넣음
8.하드웨어에서 데이터를 전송하도록 설정
9.전송시간을 jiffies 단위로 기록하며, 전송과 관련된 통계 정보를 수정 함
10.전송이 끝난 소켓 버퍼 메모리를 해제 함

11.하드웨어 FIFO에 더 이상 데이터를 넣을 공간이 없으면, 커널에 전송 중지를 요구

코드는 ppt 참고

전송 시간 초과 처리

전송 시간 초과와 처리 절차
증상
여러 요인으로 인해 커널에 의해 요구된 소켓 버퍼의 데이터가 전송 되지 않을 수 있음
처리
마지막으로 전송된 시간을 참조하여 시간 초과가 발생하는지 판단
시간 초과가 발생하면 디바이스 드라이버에 적절한 처리를 요구
전송시간 관련 net_device 구조체 필드
dev-> trans_start : 마지막 전송된 시간
dev -> watchdog_timeo : 전송 시간 초과 한계값
네트워크 디바이스 드라이버가 관리하는 변수
하드웨어에 따라 전송되는 시간이 달라질 수 있기에 네트워크 드라이버가 처리 함
커널에서는 dev->watchdog_timeo 값을 초과하면 적절한 처리를 수행함
처리를 수행한 후에 net_device 구조체의 tx_timeout 필드에 선언된 함수를 호출
void tx_timeout(struct net_device *dev);
이 함수에서 구현되어야 하는 기능
마지막으로 전송이 요구된 데이터가 하드웨어 에서 무시되도록 처리
관련 에러 통계를 갱신, 하드웨어를 전송할 수 있도록 재설정

netif_wake_queue 함수를 호출해 커널에서 재전송 되도록 통지 함






예제 코드는 ppt 참고

인터럽트 처리

인터럽트 처리
커널에 의해 발생된 전송 데이터를 xxx_hard_start_xmit() 함수를 이용해 전송 한 뒤의 과정
전송이 끝나면 하드웨어는 인터럽트를 발생시켜 전송 처리가 끝났음을 통지
전송 처리에 대한 종료 처리는 인터럽트에서 마지막으로 수행 되어야 함
인터럽트 발생 상황과 대처
/수신 에러가 발생 했을 때
/수신 에러에 대한 통계 정보를 갱신
네트워크 하드웨어의 에러 상태를 클리어 해야 함
송신이 끝났을 때
송신이 가능해졌을 때
송신이 끝났거나 하드웨어의 FIFO가 비어서 송신이 가능해지면 netif_wake_queue()를 호출해 커널에 알림
수신된 데이터가 있을 때
데이터의 수신을 인터럽트에서 처리해야 함

수신된 데이터가 있다면 수신 데이터를 소켓 버퍼 형식으로 변환하여 커널의 네트워크 스택으로 전달

예제 코드는 ppt 참고

수신 처리

개요
인터럽트 서비스 함수에서 수신 상태 확인
수신 처리용 함수 호출
수신된 데이터를 소켓 버퍼 형식으로 변환하여 커널에 전달
poll 처리(2.6)

이전 방식(2.4)
NAPIpoll 처리를 고려하지 않는 수신 처리
1.네트워크 하드웨어의 수신 상태와 데이터의 크기를 구함
2.수신 상태가 에러라면 에러 통계 정보를 처리
3.dev_alloc_skb() 함수를 이용해 소켓 버퍼를 할당
4.소켓 버퍼의 크기를 적당히 조절한 후 수신된 데이터를 넣음
5.소켓 버퍼 skb->protocol 필드를 처리
6.소켓 버퍼 skb-> ip_summed 처리
7.netif_rx() 함수를 이용해 소켓 버퍼를 커널에 전달
8.net_device 구조체의 dev-> last_rx 필드에 들어갈 마지막 수신시간은 jiffies 값을 설정

9.수신 데이터 통계 정보를 처리

예제 코드는 ppt 참고

NAPIpoll을 고려한 수신 처리
기존
하드웨어에 데이터 도착 -> 수신 인터럽트 발생 -> 하나의 패킷을 커널에 전달
효율적이지 못하며 100Mbps -> 기가 이더넷이 등장
제안
리눅스 커널팀이 NAPI라는 방식을 제안함
아이디
기존의 수신 인터럽트 서비스 함수에서 데이터를 처리할 때 폴링 처리를 하는 것
폴링
다음 데이터의 수신을 새로운 인터럽트 핸들러에서 처리 하지 않음
하드웨어 FIFO에 수신된 데이터와 이를 처리하는 동안에 새로 도착한 수신 데이터를 폴링 방식으로 검사하여 처리하는 것
장점

패킷을 한꺼번에 처리 가능
NAPIpoll을 고려한 수신 처리
NAPI 처리를 위한 재설계
poll() 함수 등록
폴링 스케쥴이 동작하도록 수신 인터럽트 처리를 수정
수신인터럽트 서비스 함수에서 폴링 스케쥴 구현
int netif_rx_schedule_prep(struct net_device *dev)
네트워크 디바이스 드라이버가 CPU의 폴링 목록에 추가할 수 있는 상태인지 검사
가능 : 0이아닌 값, 불가능 : 0
void __netif_rx_schedule(struct net_device *dev)
네트워크 수신을 폴링 처리 할 수 있도록 CPU의 폴링 목록에 추가함
이 함수는 반드시 netif_rx_schedule_prep() 함수로 검사한 뒤 사용해야함

poll() 함수 구현
NAPIpoll을 고려한 수신 처리
NAPI 처리를 위한 재설계(poll() 함수 등록)
net_device 구조체의 poll 필드에 폴링 처리 함수를 선언함으로써 시작
int (*poll) (struct net_device *dev, int *quota);
예제(8139too.c)
dev->poll = rtl8139_poll;
dev->weight = 64;
폴링과 관련되어 net_deviceweightquota 필드가 사용됨
폴링 처리를 할때 하나의 드라이버가 처리 시간을 독점하지 않도록 한계값을 둠
다른 네트워크 디바이스 드라이버에게 양보 함
dev -> quota는 폴링 처리 과정에서 감소 함

dev->quota의 초기값은 dev->weight
NAPIpoll을 고려한 수신 처리
NAPI 처리를 위한 재설계(수신 인터럽트 처리)
네트워크 수신 처리를 수행하던 부분을 폴링 스케줄링으로 바꿈
기존
if(수신 인터럽트인가?)
{
 
수신을 처리하는 함수 호출
}
변경

if(수신 인터럽트인가?)
{
  if(netif_rx_schedule_prep(dev))
  {
 
더 이상의 수신 인터럽트가 발생하지 않게 한다. 하드웨어 설정.
  __netif_rx_schedule (dev);
  }
}
NAPIpoll을 고려한 수신 처리
NAPI 처리를 위한 재설계(수신 인터럽트 처리)
예제(8139too.c)

예제 코드는 • ppt 참고 
NAPIpoll을 고려한 수신 처리
NAPI 처리를 위한 재설계(poll() 함수 )
int xxx_poll(struct net_device *dev, int *budget);
기존 방식
수신된 네트워크 데이터를 소켓 버퍼로 변환하여 netif_rx()함수로 커널에 전달
Poll 방식
netif_receive_skb() 함수를 사용
수신 데이터가 더 이상 없을때 __netif_rx_complete() 함수를 호출
폴링 스케줄을 중지 시킴
poll() 함수에 전달된 *budget 값과 dev->quota 값을 조정

한계값 초과시 poll() 함수를 강제 중지

예제 코드는 ppt 참고



6. 통계, 제어, 멀티캐스트

통계 정보 처리
/linux/include/linux/netdevice.h

수신 처리

통계 정보 처리
대부분 송수신을 처리하는 곳에서 값을 갱신
통계정보는 procsysfs 에서 주로 사용 됨
외부에서 proc이나 sysfs를 이용해 인터페이스에 접근할때
디바이스 드라이버의 net_device -> get_stats가 호출됨
struct xxx_net_info
{
  :
  struct net_device_stats stats;  //
구조체 선언
  :
}

struct net_device_stats *xxx_get_stats(struct net_device *dev)  //불러올 구조체 내용
{
  struct xxx_net_info *xxx_priv = (struct xxx_net_info *) dev->priv;
  return &(xxx_priv->stats)
}

멀티 캐스트 처리

멀티캐스트
여러 호스트가 하나의 주소에서 데이터를 동시에 수신하게 하는것
리눅스는 이를 커널에서 지원함
전송과 수신을 구별해 처리함
전송
일반 데이터 전송과 같음
멀티캐스트 대상이 되는 그룹과 하나의 호스트를 지정하는 것이 같음
수신
수신된 패킷이 멀티 캐스트의 대상이 되는 대표 주소인지를 검사해야 함
자신의 고유한 하드웨어 주소와 다르게 처리 되어야 함

또한 멀티캐스트 되는 주소가 하나 이상일 수 있음
멀티캐스트
현재 사용중인 대부분의 네트워크 하드웨어가 하드웨어적으로 멀티 캐스트를 지원하지 않음
디바이스 드라이버에서 하드웨어 처리를 해줄 것이 없음
IFF_PROMISC 모드
인터페이스에 연결된 네트워크의 지나가는 패킷을 모두 수신하도록 처리
tcpdump,  wireshark 등에서 사용
net_device -> set_multicast_list 필드에서 호출 함수를 구현

void set_multicast_list(struct net_device *dev)
{  :
 
하드웨어 제어시 경쟁 방지를 위해 락을 건다.
  :
  if(dev->flags&IFF_PROMISC)
  {
  rx_mode = RX_ALL_ACCEPT;
  }
  else if((dev->flags&IFF_ALLMULTI)||dev->mc_list)
  {
  rx_mode = RX_MULTICAST_ACCEPT;
  }


반응형