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 -> flags를 IF_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_dev는 VLAN을
구현하는 경우 실제 네트워크 디바이스를 가리킴
•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()
함수의
매개변수 priority는 alloc_skb에서
내부적으로 호출하는 kmalloc()
함수에
사용됨
•dev_alloc_skb는 priority에 GFP_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);
•data와 len 필드만
갱신
•skb_push은 확장할
데이터 공간이 버퍼 크기를 벗어나는지 검사함
•__skb_push는
검사하지 않음
•디바이스
드라이버에서 사용하는 함수들
•소켓
버퍼 데이터 공간 앞 뒤로 확장
•void
skb_reserve(struct sk_buff *skb, int len);
•앞쪽과
뒤쪽으로 len 크기만큼
확장
•data와 tail,
len 필드만
갱신
•소켓
버퍼 데이터 공간 축소
•unsigned
char *skb_pull(struct sk_buff *skb, int len);
•len
크기 만큼
축소
•data와 len 필드 갱신
•뒤쪽으로
확장 가능한 크기를 반환
•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)
•NAPI의
poll
처리를
고려하지 않는 수신 처리
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 참고
•NAPI의
poll을
고려한 수신 처리
•기존
•하드웨어에
데이터 도착 ->
수신
인터럽트 발생 ->
하나의
패킷을 커널에 전달
•효율적이지
못하며 100Mbps
-> 기가
이더넷이 등장
•제안
•리눅스
커널팀이 NAPI라는
방식을 제안함
•아이디어
•기존의
수신 인터럽트 서비스 함수에서 데이터를 처리할 때 폴링 처리를 하는 것
•폴링
•다음
데이터의 수신을 새로운 인터럽트 핸들러에서 처리 하지 않음
•하드웨어
FIFO에 수신된
데이터와 이를 처리하는 동안에 새로 도착한 수신 데이터를 폴링 방식으로 검사하여 처리하는 것
•장점
•패킷을
한꺼번에 처리 가능
•NAPI의
poll을
고려한 수신 처리
•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()
함수 구현
•NAPI의
poll을
고려한 수신 처리
•NAPI
처리를
위한 재설계(poll()
함수 등록)
•net_device
구조체의 poll 필드에
폴링 처리 함수를 선언함으로써 시작
•int
(*poll) (struct net_device *dev, int *quota);
•예제(8139too.c)
•dev->poll
= rtl8139_poll;
dev->weight = 64;
•폴링과
관련되어 net_device의 weight와 quota 필드가
사용됨
•폴링
처리를 할때 하나의 드라이버가 처리 시간을 독점하지 않도록 한계값을 둠
•다른
네트워크 디바이스 드라이버에게 양보 함
•dev
-> quota는 폴링
처리 과정에서 감소 함
•dev->quota의
초기값은 dev->weight
•NAPI의
poll을
고려한 수신 처리
•NAPI
처리를
위한 재설계(수신
인터럽트 처리)
•네트워크
수신 처리를 수행하던 부분을 폴링 스케줄링으로 바꿈
•기존
•if(수신
인터럽트인가?)
{
수신을 처리하는 함수 호출
}
•변경
•if(수신
인터럽트인가?)
{
if(netif_rx_schedule_prep(dev))
{
더 이상의 수신 인터럽트가 발생하지 않게 한다. 하드웨어
설정.
__netif_rx_schedule (dev);
}
}
•NAPI의
poll을
고려한 수신 처리
•NAPI
처리를
위한 재설계(수신
인터럽트 처리)
•예제(8139too.c)
예제 코드는 • ppt 참고
•NAPI의
poll을
고려한 수신 처리
•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
•
수신
처리
•통계
정보 처리
•대부분
송수신을 처리하는 곳에서 값을 갱신
•통계정보는
proc과 sysfs
에서 주로
사용 됨
•
•외부에서
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;
}