Network2010. 5. 10. 02:46
반응형

출처 : http://jgdr.net/wiki/index.php?LDS2000%C0%C7%20%B3%D7%C6%AE%BF%F6%C5%A9%20%B5%F0%B9%D9%C0%CC%BD%BA%20%B5%E5%B6%F3%C0%CC%B9%F6%20%BA%D0%BC%AE

writeen by 6기 정낙천

1. 네트워크 디바이스 드라이버의 초기화와 제거

네트워크 디바이스 드라이버의 초기화 단계는 크게 세 단계로 나눌 수 있다. 첫 단계는 디바이스 드라이버를 커널에 등록하는 단계이며, 등록한 다음엔 바로 TCP/IP 프로토콜 스택이 디바이스 드라이버를 사용할 수 있게, 그 연결통로 역할을 하는 net_device 구조체를 초기화하고 하드웨어를 검출한다. 마지막으로 네트워크 디바이스의 제어 레지스터들을 초기화시켜 장치를 활성화시키는 것이다. 첫 번째 단계는 module_init() 함수에서 등록한 함수가 그 일을 수행하며, 두 번째 단계는 net_device 구조체의 init 함수 포인터 필드가, 마지막인 세 번째 단계는 net_device 구조체의 open 함수 포인터 필드가 수행한다.

(1) 커널에 디바이스 드라이버를 등록

소스 1-1은 LDS2000에 설치된 이더넷 제어 칩 CS8900의 소스 중 초기화의 첫 번째 단계인 디바이스 드라이버를 커널에 등록하는 루틴을 보여준다. 또한 모듈의 초기화 함수와 제거 함수는 같이 설명하는 것이 편하므로 같이 두었다.

(소스 1-1. drivers/net/lds2000_cs8900_1.c 의 모듈 초기화와 제거)

/* ① 디바이스 드라이버 고유의 관리 구조체 선언 */ typedef struct { struct net_device_stats stats; u16 txlen; int char_devnum; spinlock_t lock; } cs8900_1_t; / /* ② 커널에 등록할 net_device 구조체 선언 */ static struct net_device cs8900_1_dev = { init: cs8900_1_probe }; / /* ③ 커널에 디바이스 모듈과 디바이스를 등록 */ static int __init cs8900_1_init (void) { strcpy(cs8900_1_dev.name, "eth%d"); return (register_netdev (&cs8900_1_dev)); } / /* ④ 커널에서 디바이스 모듈과 디바이스를 제거 */ static void __exit cs8900_1_cleanup (void) { cs8900_1_t *priv = (cs8900_1_t *) cs8900_1_dev.priv; unregister_chrdev(priv->char_devnum,"cs8900_1_eeprom"); release_region (cs8900_1_dev.base_addr,16); unregister_netdev (&cs8900_1_dev); } / /* ⑤ 모듈을 초기화하거나, 제거할 함수를 지정 */ module_init (cs8900_1_init); module_exit (cs8900_1_cleanup);

1번 코드는 디바이스 드라이버가 드라이버를 관리하는 자기 자신의 구조체를 선언한 부분이다. 이 구조체의 주소는 나중에 net_device 구조체의 priv 필드의 내용이 된다.

2번 코드에서는 커널에 등록할 net_device 구조체를 전역 변수로 선언하였다. 여기서 net_device 구조체 전역 변수는 init 함수 포인터 필드만을 초기화 하였는데, 이것은 3번 코드에서 net_device 구조체를 register_netdev() 함수로 커널에 등록하기 전에 초기화 함수 주소를 지정해주어야 하기 때문이다. 이렇게 하면 cs8900_1_init()가 실행된 후에 init 함수 포인터에 등록된 cs8900_1_probe() 함수가 바로 실행되어 net_device 구조체의 나머지 필드들을 TCP/IP 스택에서 사용할 수 있도록 초기화 할 수 있게 된다.

다른 필드들은 커널에서 제공하는 초기화 함수로 초기화 하거나 필드들의 목적에 맞게 적당한 장소에서 초기화된다. 일례로 우리가 작성하는 디바이스 드라이버는 이더넷 환경에서 작동하는 드라이버를 작성할 것이기 때문에, net_device 구조체에서 이더넷 프로토콜과 관련된 필드들은 ether_setup() 함수로 쉽게 초기화할 수 있다. 이 함수를 사용하는 코드를 뒤에서 살펴 볼 것이다.

net_device 구조체 변수는 전역 변수로 선언하거나 alloc_etherdev() 함수를 이용해서 메모리 할당을 하는 방법이 있는데 여기서는 전자를 이용하였다. alloc_etherdev() 함수는 include/linux/etherdevice.h 파일에서 선언되어 drivers/net/net_init.c 파일에서 구현된 것으로 이 함수의 원형은 소스 2와 같다. 이 함수의 인자에는 꼭 디바이스 드라이버에서 자체 선언한 관리용 구조체가 사용할 메모리 크기를 지정해야 한다. 물론 관리용 구조체에 메모리를 따로 할당할 필요가 없다면 소스 1-1과 같이 이 함수를 사용하지 않거나 인자를 0으로 주면 된다.

(소스 2. alloc_etherdev() 함수의 원형)

#include <linux/etherdevice.h> / struct net_device *alloc_etherdev(int sizeof_priv);

alloc_etherdev() 함수는 디바이스 자체의 관리용 구조체와 net_device 구조체에 메모리를 할당받아 net_device 구조체의 priv 필드에 관리용 구조체의 선두주소를 대입한다. 또한, net_device 구조체의 name 필드의 값을 자동으로 "eth%d"로 설정하여 메모리가 할당된 net_device 구조체의 선두주소를 반환한다. 그러므로 이 함수를 사용할 때는 소스 1-1의 2번과 3번 그리고 4번 코드가 달라진다. alloc_etherdev() 함수를 사용하여 달라진 소스 1-1의 2번과 3번 코드는 소스 3과 같다. 2번 코드에서 전역으로 선언했던 net_device 구조체에 NULL 값을 대입하였고, 등록 함수인 3번 코드에서 alloc_etherdev() 함수의 반환 값을 전역으로 선언했던 net_device 구조체에 대입하였으며 cs8900_1_probe() 함수의 등록도 그 다음 코드에 행해진다. 또한 net_device 구조체에 메모리를 할당했으므로 모듈이 커널에서 제거될 때 unregister_netdev() 함수를 사용한 후에 kfree() 함수로 할당된 메모리를 제거하는 코드가 추가되어야 한다.

(소스 3. alloc_etherdev() 함수를 사용한 디바이스 초기화와 제거 코드)

/* 커널에 등록할 net_device 구조체 선언 */ static struct net_device *cs8900_1_dev = NULL; / /* 커널에 디바이스 모듈과 디바이스를 등록 */ static int __init cs8900_1_init (void) { cs8900_1_dev = alloc_etherdev(sizeof(cs8900_1_t)); if(cs8900_1_dev == NULL) return -ENOMEM; cs8900_1_dev->init = cs8900_1_probe; return (register_netdev (&cs8900_1_dev)); } / /* 커널에서 디바이스 모듈과 디바이스를 제거 */ static void __exit cs8900_1_cleanup (void) { /* 위와 상동, 단지 마지막 줄에 kfree() 함수를 추가 */ kfree(cs8900_1_dev); }

free() 함수는 include/linux/slab.h 파일에서 선언되어 mm/slab.c 파일에서 구현된 것으로 이 함수의 원형은 소스 4와 같다. 이 함수는 메모리에 할당된 객체를 메모리에서 해제하여 준다.

(소스 4. kfree() 함수의 원형)

#include <linux/slab.h> / void kfree (const void *objp);

3번 코드는 커널에 디바이스 모듈과 디바이스를 등록하고 있다. 이 함수는 모듈이 커널에 적재될 때 가장 먼저 실행되는 것으로, 5번 코드의 module_init() 함수에서 이 함수를 등록하였기 때문이다. 이 함수는 가장먼저 net_device 구조체의 name 필드를 지정하고 있다. 네트워크 디바이스는 디바이스 드라이버가 정하므로 고유한 이름을 지정해야 한다. 여기에서는 "eth%d"를 사용하는데, 이는 동일한 네트워크 디바이스 드라이버를 사용하는 하드웨어가 등록될 때마다 eth0, eth1, eth2, ... 와 같은 식으로 디바이스 이름이 다르게 등록된다. 만약 이름을 하나만 지원하고 디바이스도 하나만 지원한다면 "net"고 같은 형식으로도 지정할 수 있다.

3번 코드의 register_netdev() 함수는 1~2번에 걸쳐 설정한 net_device 구조체를 커널에 등록해주는 중요한 함수이다. 이 함수는 include/linux/netdevice.h 파일에서 선언되어 drivers/net/net_init.c 파일에서 구현된 것으로 이 함수의 원형은 소스 5와 같다. 디바이스 드라이버를 등록해주는 문자나 블록 디바이스와는 다르게 이 함수는 file_operations 구조체를 등록하지 않고 net_device 구조체를 등록한다. 이것은 위의 net_device 구조체를 설명할 때 언급했던 것처럼 디바이스 드라이버를 응용 프로그램이 바로 사용하는 것이 아니라, 응용 프로그램이 TCP/IP를 거쳐서 사용하기 때문인 것이다.

(소스 5. register_netdev() 함수의 원형)

#include <linux/netdevice.h> / int register_netdev(struct net_device *dev);

4번 코드의 cleanup() 함수는 모듈이 커널에서 제거될 때 실행되는 함수이다. 이것 또한 3번의 초기화 함수처럼 5번 코드의 module_exit() 함수에서 이 함수를 등록하였기 때문인 것이다. 이 함수에서는 디바이스 드라이버에서 할당한 메모리들을 제거하고 있다. 중간에 수행하는 unregister_chrdev() 함수는 네트워크 디바이스가 가지고 있는 EEPROM을 제거하기 위해서인 것이다. 보통 네트워크 디바이스는 ROM을 가지고 있어서 ROM안에 디바이스의 MAC 주소와 다른 필요한 정보들을 하드 코딩해 놓고 있다. 만약 디바이스에 ROM이 없다면 MAC 주소를 디바이스 드라이버 작성자가 드라이버에 MAC 주소를 설정하는 루틴을 직접 작성해야 한다.

cleanup() 함수의 세 번째 줄에서는 release_region() 함수를 사용하여 net_device 구조체의 base_addr 필드에 할당된 메모리를 제거 해주고 있다. base_addr 필드와 release_region() 함수는 두 번째와 세 번째 단계의 초기화를 수행하도록 등록된 cs8900_1_probe() 함수와 연결된 설명이 필요하므로 뒤로 미루도록 하겠다. cleanup() 함수에서 가장 중요한 것은 unregister_netdev() 함수로 register_netdev() 함수에서 등록한 net_device 구조체를 커널에서 제거하는 함수이다. 이 함수 또한 register_netdev() 와 마찬가지로 linux/netdevice.h 파일을 include 하고 register_netdev() 함수에서 사용했던 net_device 구조체 변수의 포인터를 매개변수로 호출하면 된다.

참고로 2번 3번 코드에서의 init() 함수와 cleanup() 함수 __init 키워드와 __exit 키워드는 커널의 효율적인 메모리 관리를 위해 만들어놓은 기법이다. 먼저 __init 키워드와 함께 쓰인 함수는 커널에 운영체제의 초기화 과정에만 사용된다는 것을 알려주게 된다. 커널의 입장에서는 초기화 루틴들을 초기를 마친 후에 속해서 메모리에 남겨두고 있을 필요가 없다. 그래서 커널은 이런 초기화에만 필요한 함수나 변수를 컴파일 시 미리 별도의 영역에 따로 모아두었다가, 초기화를 마친 후 이 영역의 메모리를 해제한다. 그렇게 하면 필요 없는 메모리를 제거하여 커널이 차지하는 메모리의 양을 줄일 수 있게 되어 효율적인 운영체제가 되는 것이다. __exit 키워드도 __init와 비슷한 맥락이다. __init 키워드와 함께 쓰인 함수들 또한 컴파일 시 미리 별도의 영역에 따로 모아두어 있다가, 커널이 부팅할 때 메모리에 바로 올라오지 않는다. 이들은 이들 함수가 호출될 때에 메모리에 적재되어 사용되고 바로 메모리에서 삭제된다.

(2) 하드웨어 검출 및 하드웨어 초기화

위에서 초기화의 두 번째와 세 번째 단계는 TCP/IP 프로토콜 스택이 디바이스 드라이버를 사용할 수 있게 net_device 구조체를 초기화하고 하드웨어를 검출하여 하드웨어 장치를 활성화시키는 단계라 하였다.

CS9800 이더넷 디바이스는 마지막 초기화 단계를 cs8900_1_probe() 함수에서 수행하고 있다. cs8900_1_probe() 함수의 코드가 소스 1-2 이다. 실제로 cs8900_1_probe() 함수는 하드웨어를 검출하고 활성화 시키는 코드들 중에서 상당양의 에러 제어 코드들이 있으나, 이들은 네트워크 디바이스 드라이버를 작성하는 데에는 큰 도움을 주지 못하므로 코드를 읽기 쉽게 과감하게 삭제하였다.

또한 하드웨어를 제어하는 코드들 또한 상당양이 있지만 삭제하지 않았는데, 이는 디바이스 드라이버의 코드가 실제 디바이스다운 모습을 잃지 않게 하기 위한 것이며, CS9800의 하드웨어는 대부분의 디바이스 드라이버가 갖추어야 할 기본적인 하드웨어만을 갖추고 있기 때문에 어떠한 방식으로 하드웨어를 제어하는지 독자들이 감을 잡을 수 있게 하였다. 그러나 완전히 CS8900 디바이스의 하드웨어의 내부를 이해하여야만 정확한 동작을 이해할 수 있다. 이러한 부분에 대한 자세한 설명은 네트워크 디바이스 드라이버를 작성하는 방법을 알려주는 이번 절의 목적에 부합하지 않으므로 소스 내에 간단한 주석으로 표시하였다. 좀더 자세히 알고자 하는 독자는 자발적으로 CS8900 디바이스의 하드웨어적인 특징을 공부하고 다시 봐야 할 것이다.

(소스 1-2. CS9800의 마지막 초기화 단계 cs8900_1_probe() 함수)

/* Driver initialization routines */ int __init cs8900_1_probe (struct net_device *dev) { static cs8900_1_t priv; int i,result; u16 value; / /* ① initialize device fields */ u16 MAC_addr[3] = {0, 0, 0}; memset (&priv,0,sizeof (cs8900_1_t)); result = check_region(dev->base_addr,16); request_region (dev->base_addr,16,dev->name); dev->if_port = IF_PORT_10BASET; dev->priv = (void *) &priv; / /* ② use ethernet */ ether_setup (dev); / /* ③ register service routines */ dev->open = cs8900_1_start; dev->stop = cs8900_1_stop; dev->hard_start_xmit = cs8900_1_send_start; dev->get_stats = cs8900_1_get_stats; dev->set_multicast_list = cs8900_1_set_receive_mode; dev->tx_timeout = cs8900_1_transmit_timeout; dev->watchdog_timeo = HZ; / /* ④ probe ethernet hardware */ #ifdef CONFIG_ARCH_PXA_LDS2000 dev->base_addr = 0xf0000000 + 0x300; dev->irq = IRQ_GPIO(0); / for (i = 0; i < ETH_ALEN / 2; i++) { MAC_addr[i]= cs8900_1_read(dev,PP_IA+i*2); dev->dev_addr[i*2] = MAC_addr[i] & 0xff; dev->dev_addr[i*2+1] = (MAC_addr[i] >> 8) & 0xff; } #endif / /* ⑤ verify EISA registration number for Cirrus Logic */ value = cs8900_1_read (dev,PP_ProductID); / /* verify chip version */ value = cs8900_1_read (dev,PP_ProductID + 2); / /* setup interrupt number */ cs8900_1_write (dev,PP_IntNum,0); / /* If an EEPROM is present, use it's MAC address. */ /* A valid EEPROM will initialize the registers automatically. */ result = cs8900_1_eeprom (dev); / /* ⑥ prints initialization results */ printk (KERN_INFO "%s: CS8900A rev %c at %#lx irq=%d", dev->name, 'B' + REVISION (value) - REV_B, dev->base_addr, dev->irq); printk (", eeprom ok"); / printk (", addr:"); for (i = 0; i < ETH_ALEN; i += 2) { u16 mac = cs8900_1_read (dev,PP_IA + i); printk ("%c%02X:%2X", (i==0)?' ':':', mac & 0xff, (mac >> 8)); } printk ("\n"); / return (0); }

cs8900_1_probe() 함수는 먼저 하드웨어와 연관이 없는 필드부터 채운다. 우선 1번 코드에서 일반적인 필드를 채우고, 2번 코드에서 이더넷 설정을 한다. 마지막으로 3번 코드에서 디바이스의 작동을 제어하는 함수들을 net_device 구조체에 등록을 한다. 그 다음부터는 하드웨어와 관련된 초기화를 진행하는데 4번 코드에서 네트워크 디바이스를 검출하고 검출이 성공하면, 5번 코드를 거쳐서 네트워크 디바이스 내의 세부 하드웨어들을 찾아내고 초기화하여 최종적으로 디바이스 드라이버를 활성화시킨다. 6번 코드는 초기화를 끝내고 그 결과를 커널 메시지로 출력을 한 것뿐이다.

1번 코드의 memset 의 원형은 소스 6과 같다. include/linux/string.h 파일에서 선언되었으며, lib/string.c 파일에서 구현되었다. memset() 함수는 원하는 자료구조에 값을 채워주는 함수이다. 즉, memset (&priv,0,sizeof (cs8900_1_t)); 코드는 새로 선언한 priv 필드에 0을 채워 넣어 net_device 구조체의 priv 필드에 그의 주소를 대입할 준비를 하는 것이다.

(소스 6. memset() 함수의 원형)

#include <linux/string.h> / void * memset(void * s, int c, size_t count);

1번 코드의 check_region(), request_region() 함수는 앞서 보았던 디바이스의 cleanup() 함수의 release_region() 함수와 세트를 이룬다. 이들은 매크로 함수로 include/linux/ioport.h 에 선언되어 있으며 이 함수들의 원형은 소스 7과 같다. request_region() 는 세 번째 인자로 주어진 이름으로 커널 메모리를 두 번째 인자의 크기만큼 첫 번째 인자에 할당한다. release_region() 함수는 그 반대의 기능을 수행한다. 마지막으로 check_region() 은 첫 번째 인자에 두 번째 인자의 크기만큼 시험 삼아 커널 메모리를 할당해 보고 그 결과를 반환한다.

(소스 7. xxx_region() 함수들의 원형)

#include <linux/ioport.h> / struct resource * request_region(unsigned long start, unsigned long n, const char *name); int check_region(unsigned long start, unsigned long n); void release_region(unsigned long start, unsigned long n);

즉, CS8900의 디바이스 드라이버는 위 세 개의 함수를 이용하여 net_device 구조체의 base_addr 필드에 커널 메모리를 할당받는 것이다. net_device 구조체의 base_addr 필드는 네트워크 디바이스의 제어 레지스터를 찾아가기 위하여 메모리 맵핑된 주소를 가지고 있는 필드로써 디바이스 드라이버에서 하드웨어를 제어하는데 유용하게 쓰이는 필드이다.

2번 코드의 ether_setup() 함수는 이더넷 프로토콜과 관련된 net_device 구조체의 필드들의 세팅을 해준다. 보통 이더넷과 토큰링과 같이 이미 정형화 되어 있는 프로토콜들은 세팅해주는 함수들을 두고 있다. 이 함수들은 include/linux/netdevice.h 파일에 선언되어 있으며 drivers/net/net_init.c 파일에 구현되어 있다. 소스 8은 xxx_setup() 함수들의 인자는 net_device 구조체 변수를 넣어주며 된다.

(소스 8. xxx_setup() 함수들의 원형)

#include <linux/netdevice.h> / ether_setup(); /* 이더넷 관련 필드 초기화 함수 */ fc_setup(); /* 광채널 관련 필드 초기화 함수 */ fddi_setup(); /* 광통신 관련 필드 초기화 함수 */ tr_setup(); /* 토큰링 관련 필드 초기화 함수 */

소스 2의 alloc_etherdev() 함수 역시 xxx_setup() 함수와 마찬가지로 여러 프로토콜을 지원한다. 이 할당과 셋업 함수는 보통 네트워크 디바이스 구현 시 같이 사용되므로 알아두는 것이 좋다. 소스 9는 할당 함수들의 원형을 보여주고 있다.

(소스 9. alloc_xxx() 함수들의 원형)

#include <linux/etherdevice.h> /* 이더넷 */ struct net_device *alloc_etherdev(int sizeof_priv); #include <linux/fcdevice.h> /* 광채널 */ struct net_device *init_fcdev(struct net_device *dev, int sizeof_priv); #include <linux/fddidevice.h> /* 광통신 */ struct net_device *alloc_fddidev(int sizeof_priv); #include <linux/hippidevice.h> /* 고성능 병렬 */ struct net_device *alloc_hippi_dev(int sizeof_priv); #include <linux/trdevice.h> /* 토큰링 */ struct net_device *alloc_trdev(int sizeof_priv)

코드 3에서는 디바이스 제어 루틴을 등록하고 있다. 이들의 함수들을 하나하나 살펴볼 것이다. 여기서 net_device 구조체의 watchdog_timeo 필드에 등록되는 HZ는 include/asm-arm/param.h 파일에 100으로 정의되어 있다. 또한 이 필드의 값은 항상 정의되어 있어야 디바이스 바르게 작동하므로 꼭 설정해주어야 한다.

4번 코드에서 드디어 디바이스를 제어할 준비를 한다. 여기서 base_ddr 필드와 irq 필드에 하드웨어 정보를 대입한다는 것을 기억하자. 이 정보들을 바탕으로 하드웨어를 제어하게 되어 있다. 보통 PCI 나 ISA 로 작동하는 디바이스일 경우에는 하드웨어를 검출하는 작업을 해주어야 하지만, LDS2000에서는 네트워크 디바이스가 CPU와 메모리 매핑으로 연결되어 있으므로 해당필드에 하드웨어 제작자가 할당한 주소와 인터럽트 번호를 필드에 대입하여주면 된다.

5번 코드로 가면서 디바이스는 칩의 버전이 맞는 것인지 다시 확인하고, 할당받은 인터럽트 번호를 하드웨어에 세팅하여 인터럽트가 발생하면 할당받은 인터럽트 번호로 커널에 전달하게 한다. 마지막으로 하드웨어의 MAC 주소를 가지고 있는 EEPROM에서 MAC 주소를 읽어오고 있다. EEPROM의 제어는 디바이스에서 따로 cs8900_1_eeprom() 이란 함수로 하고 있다.

(3) 네트워크 디바이스의 열기

이 함수는 ifconfig 응용 프로그램으로 네트워크 디바이스를 활성화 시킬 때 수행되는 함수이다. 이 함수는 net_device 구조체의 open 함수 포인터 필드에 등록된 함수로 CS8900 디바이스 드라이버에서는 cs8900_1_start 이라는 이름을 사용하였다. 네트워크 디바이스 드라이버에서 open 필드에 등록된 함수는 크게 세 단계의 동작을 수행해야 한다. CS8900에 구현된 cs8900_1_start() 함수는 앞으로 언급할 세 단계를 그대로 수행하고 있으므로 이 함수를 기준으로 설명한다. cs8900_1_start() 함수의 코드는 소스 1-3과 같다.

첫 번째로 request_irq() 함수로 인터럽트 서비스 함수를 등록한다. 인터럽트 서비스 함수는 송신과 수신시 항상 동작되는 함수로 여기서는 cs8900_1_interrupt() 함수를 등록하였다. 두 번째로는 디바이스가 동작할 수 있도록 내부 레지스터들에 값을 설정하여 네트워크망과 디바이스를 연결시킨다. 마지막으로 net_device 구조체내에 정의된 송신 큐가 동작하도록 netif_start_queue() 함수를 호출해야 한다. 소스 1-3에 각 단계별로 번호를 붙여 놓았다. 2번 코드는 하드웨어 의존적인 코드들이 여러 줄이 들어가 있다.

마지막으로 모듈이 사용되고 있음을 나타내기 위해서 CS8900 디바이스 드라이버의 cs8900_1_start() 함수는 MOD_INC_USE_COUNT 매크로를 사용하였음을 알 수 있다.

(소스 1-3. open 필드에 등록된 cs8900_1_start() 함수)

static int cs8900_1_start (struct net_device *dev) { /* ① install interrupt handler */ request_irq (dev->irq,&cs8900_1_interrupt,0,dev->name,dev); / /* ② enable the ethernet controller */ cs8900_1_set (dev,PP_RxCFG,RxOKiE | BufferCRC | CRCerroriE | RuntiE | ExtradataiE); / /* ③ start the queue */ netif_start_queue (dev); / MOD_INC_USE_COUNT; return (0); }

(4) 네트워크 디바이스의 닫기

이 함수는 ifconfig 응용 프로그램으로 네트워크 디바이스를 비활성화 시킬 때 수행되는 함수이다. 이 함수는 net_device 구조체의 stop 함수 포인터 필드에 등록된 함수로 CS8900 디바이스 드라이버에서는 cs8900_1_stop 이라는 이름을 사용하였다. 네트워크 디바이스 드라이버에서 stop 필드에 등록된 함수는 open 필드에 등록된 함수와 반대의 동작을 수행하면 된다.

첫 번째로 free_irq() 함수로 등록된 인터럽트 서비스 함수를 제거한다. 그 다음 디바이스가 동작할 수 있도록 내부 레지스터들에 값을 설정하여 네트워크망과 디바이스와의 연결을 끊는다. 마지막으로 netif_stop_queue() 함수를 호출하여 커널에 등록된 net_device 구조체내에서 동작하고 있는 송신 큐를 정지시킨다. CS8900 디바이스 드라이버의 cs8900_1_stop() 함수는 이 순서들을 그대로 수행하고 있지는 않지만 순서는 달라도 상관없다. 또한 이 함수에서도 1번 코드와 같은 하드웨어 의존적인 코드들이 여러 줄이 있지만 첫 번째 명령만 남겨 놓았다.

(소스 1-4. stop 필드에 등록된 cs8900_1_stop() 함수)

static int cs8900_1_stop (struct net_device *dev) { /* ① disable ethernet controller */ cs8900_1_write (dev,PP_BusCTL,0); / /* ② uninstall interrupt handler */ free_irq (dev->irq,dev); / /* ③ stop the queue */ netif_stop_queue (dev); / MOD_DEC_USE_COUNT; return (0); }

(5) 네트워크 디바이스에서의 패킷 전송

패킷의 전송은 디바이스에서 패킷을 전송하여 100% 성공하면 금상첨화일 것이다. 그러나 패킷을 전송하기까지의 과정이 TCP/IP 프로토콜 스택을 거쳐서 진행되기 때문에 패킷 전송처리 뿐만 아니라, 여러 요인으로 인하여 소켓 버퍼의 데이터가 전송되지 않을 수 있다. 또한 리눅스 네트워킹 제어 흐름에서 살펴보았듯이 Link 계층에서 직접 전송이 진행되지 않으면 나중에 데이터를 전송하기 위해 커널의 인터럽트 서비스 루틴에 패킷을 등록한다.

이러한 이유로 디바이스 드라이버에서의 전송은 세 개의 함수로 구현된다. 가장 먼저 패킷전송을 수행하는 함수, 전송시간이 초과되었을 경우에 수행되는 함수, 인터럽트를 처리하는 함수이다.

a. 패킷의 전송처리

패킷전송은 리눅스의 네트워킹에서 살펴봤듯이 사용자가 read() 함수를 호출하면 궁극적으로 실행되는 소스 1-5와 같은 함수가 호출된다. 이 함수는 net_device 구조체의 hard_start_xmit 함수 포인터 필드에 등록된 함수로 CS8900 디바이스 드라이버에서는 cs8900_1_send_start 이라는 이름을 사용하였다.

(소스 1-5. hard_start_xmit 필드에 등록된 cs8900_1_send_start() 함수)

static int cs8900_1_send_start (struct sk_buff *skb,struct net_device *dev) { cs8900_1_t *priv = (cs8900_1_t *) dev->priv; u16 status; / spin_lock_irq(&priv->lock); / /* ① 상위 계층의 송신 큐를 중단 */ netif_stop_queue (dev); / /* ② 디바이스에서 데이터를 전송하도록 설정 */ cs8900_1_write (dev,PP_TxCMD,TxStart (After5)); cs8900_1_write (dev,PP_TxLength,skb->len); / /* ③ 디바이스의 상태 검사 */ status = cs8900_1_read (dev,PP_BusST); / if ((status & TxBidErr)) { spin_unlock_irq(&priv->lock); printk (KERN_WARNING "%s: Invalid frame size %d!\n", dev->name,skb->len); priv->stats.tx_errors++; priv->stats.tx_aborted_errors++; priv->txlen = 0; return (1); } / if (!(status & Rdy4TxNOW)) { spin_unlock_irq(&priv->lock); printk (KERN_WARNING "%s: Transmit buffer not free!\n",dev->name); priv->stats.tx_errors++; priv->txlen = 0; /* FIXME: store skb and send it in interrupt handler */ return (1); } / /* ④ 디바이스 내의 FIFO 버퍼에 전송할 데이터인 소켓 버퍼를 넣는다. */ cs8900_1_frame_write (dev,skb); spin_unlock_irq(&priv->lock); / /* ⑤ 전송 시간을 기록 */ dev->trans_start = jiffies; / dev_kfree_skb (skb); priv->txlen = skb->len; return (0); }

데이터의 전송을 처리하는 hard_start_xmit() 함수의 설계는 일반적으로 여섯 단계를 거친다. 먼저 인자로 넘겨받은 소켓버퍼 구조체에서 전송할 데이터와 그 길이 등의 정보를 추출하여 보낼 준비를 한다. CS8900 디바이스 드라이버에서는 이 과정이 생략되어 다음 단계를 실행할 때 소켓 버퍼 구조체에서 필요한 값을 바로 추출해서 인자로 넣어주고 있다. 다음 단계는 디바이스가 패킷을 전송할 동안 상위 계층인 Link 계층에서 패킷 전송요구를 할 수 없도록 1번 코드와 같이 netif_stop_queue() 함수를 호출하여 송신 큐를 중단한다.

그 다음에는 2번 코드와 같이 디바이스를 설정하여 데이터를 전송하도록 하는데, 상식적인 개념으로는 이 단계는 가장 마지막에 있어야할 단계처럼 보인다. 그렇지만 디바이스안의 버퍼가 비어있는 상태에서 디바이스를 활성화 시키면, 디바이스는 데이터가 버퍼에 들어오자마자 바로 데이터를 보낼 수 있기 때문에 미리 전송시작을 하는 것이다. 이는 디바이스의 전송 회로를 활성화시킨 것으로도 볼 수 있다.

디바이스의 전송회로를 활성화시킨 후 CS8900 디바이스 드라이버는 3번 코드와 같이 디바이스 자체 함수인 cs8900_1_read() 함수로 디바이스의 상태를 읽고, 데이터 프레임 길이와 전송 버퍼의 사용여부를 검사하여 다음 단계로 진행할지 검사한다.

4번 코드가 실제로 데이터를 전송하는 부분으로 디바이스 내부의 버퍼에 소켓버퍼의 데이터를 집어넣는 과정이다. 실제 전송하는 과정은 하드웨어에서 회로로 구성되어 있기 때문에 디바이스 드라이버의 작성자는 하드웨어 내의 버퍼에 데이터를 넣어주기만 하면 되는 것이다. CS8900 디바이스 드라이버에서는 자체 함수인 cs8900_1_frame_write() 함수에서 수행하는데 소스 1-6에서 보듯이 out() 계열의 함수를 이용하여 데이터를 하드웨어에 출력하고 있음을 볼 수 있다.

(소스 1-6. 디바이스 버퍼에 데이터를 넣는 cs8900_1_frame_write() 함수) static

inline void cs8900_1_frame_write (struct net_device *dev,struct sk_buff *skb) { outsw (dev->base_addr,skb->data,(skb->len + 1) / 2); }

실제 전송이 끝났지만, 네트워크 디바이스 드라이버는 전송이 시작된 시간을 상위 계층에 알려주어야 한다. 5번 코드에 상위 계층에 알려주는 작업을 수행하고 있다. 참고로 jiffies는 블록디바이스 드라이버에서 살펴보았듯이 현재 시각을 나타내는 커널전역변수로써 커널은 이 변수의 값을 항상 업데이트 한다. 그러므로 현재시각을 기록하려면 이 변수의 값을 그대로 대입하면 된다. 여기까지 여섯 단계의 전송 처리 과정을 살펴보았다. 이들의 순서는 어느 정도 바뀔 수 있어도 단계를 생략하면 디바이스에서 데이터를 전송하지 않으므로 준수하여야 한다.

5번 코드 뒤의 코드들은 전송함수에서 사용한 자료구조들을 정리만을 수행하고 있다. 또한 디바이스 자체의 관리용 구조체에 전송정보를 기록하고 있음을 볼 수 있다.

b. 전송 시간 초과 시의 처리

커널은 소켓 버퍼의 데이터가 전송되지 않을 경우 마지막으로 전송된 시간을 참조하여 시간 초과가 발생하면, 네트워크 디바이스 드라이버에게 적절한 처리를 요구한다. 즉, 커널은 net_device 구조체의 tx_timeout 필드에 등록된 함수를 호출하는데, 여기서는 cs8900_1_transmit_timeout() 함수가 해당된다. 이 함수는 소스 1-7과 같다.

이 함수에서 구현해야 하는 기능은 간단하다. 마지막으로 전송이 요구된 데이터가 디바이스에서 무시되도록 처리하고, 관련된 에러 통계를 갱신하고, 데이터를 전송할 수 있도록 하드웨어를 재설정한다. 마지막으로 netif_wake_queue() 함수를 호출하여 커널에서 패킷을 다시 전송할 수 있도록 알린다. CS8900에서는 데이터를 무시하고, 데이터를 재전송 할 수 있도록 설정하는 하드웨어 처리과정은 생략되었으며, 1번 코드에서 자체 관리용 구조체에 에러 통계를 갱신하고 있다.

tx_timeout 필드에 등록된 함수는 2번 코드와 같이 커널이 패킷을 재전송 하도록 반드시 netif_wake_queue() 함수를 호출하여야 한다.

(소스 1-7. tx_timeout 필드에 등록된 cs8900_1_transmit_timeout() 함수)

static void cs8900_1_transmit_timeout (struct net_device *dev) { /* ① 디바이스 내에서의 에러에 대한 처리 수행 */ cs8900_1_t *priv = (cs8900_1_t *) dev->priv; priv->stats.tx_errors++; priv->stats.tx_heartbeat_errors++; priv->txlen = 0; / /* ② 커널내의 송신 큐를 깨운다. */ netif_wake_queue (dev); }

c. 인터럽트 처리

앞의 네트워킹 제어흐름에서 살펴본 바와 같이 인터럽트 서비스를 처리하는 것이 필요하다. 또한 대부분의 네트워크 디바이스 드라이버는 기본적으로 인터럽트를 처리하지 않으면 송수신을 할 수 없기 때문에 인터럽트 처리는 디바이스 드라이버에서 중요한 부분을 차지한다. 소스 1-7은 인터럽트 서비스 함수에서 송신과 관련된 코드만을 정리한 것이다.

CS8900 네트워크 디바이스는 1번 코드에 나온 것과 같이 다섯 개의 상태에 의해 인터럽트가 발생한다. 다섯 가지 인터럽트 상태는 drivers/net/cs8900.h 파일에 정의되어 있으며, 이들 상태는 switch-case 문으로 구현되어 있다. 인터럽트 상태와 구현방법은 일반적인 네트워크 디바이스 드라이버에서 거의 공식과 같이 적용되고 있다.

첫 번째 상태인 RxEvent?는 수신된 데이터가 있을때 디바이스에서 발생한다. 네트워크 디바이스 드라이버는 데이터의 수신을 인터럽트에서 처리해야 하므로 수신된 데이터가 있으면 수신한 데이터를 소켓 버퍼 구조체로 변환하여 커널내의 TCP/IP 프로토콜 스택에 전달한다. 수신된 데이터를 처리하는 방식은 poll을 사용하는 방식과, 단순히 수신처리만 하는 방식이 있는데, poll 방식은 LDS2000에서 사용하는 커널 2.4 에서는 지원하지 않는 방식이므로 후자만 설명한다. 참고로 poll 방식은 커널 2.6에서부터 지원되는 방식이다.

두 번째 상태인 TxEvent?는 이전에 디바이스가 가지고 있던 데이터 송신을 완료하였을 경우에 발생한다. 이때 netif_wake_queue() 함수를 호출하여 커널내의 TCP/IP 프로토콜 스택에 알려주어야 한다. 자세한 구현방법은 소스 1-7을 설명할 때 살펴본다.

세 번째 상태인 BufEvent?는 디바이스 안의 FIFO 버퍼가 비어서 송신이 가능해졌을 겨웅에 발생한다. 이 경우 또한 netif_wake_queue() 함수를 호출하여 커널내의 TCP/IP 프로토콜 스택에 알려주어야 한다.

네 번째 상태인 RxMISS는 수신에러가 발생했을 경우 발생한다. 이때 네트워크 디바이스 드라이버에서 수생해야 하는 것은 수신 에러에 대한 통계 정보를 갱신하고, 네트워크 하드웨어의 에러 상태를 클리어해야 한다.

마지막 상태인 TxCOL는 송신에러가 발생했을 경우 발생한다. 이때는 수신에러와 발생했을 경우와 마찬가지로 네트워크 디바이스 드라이버에서는 송신 에러에 대한 통계 정보를 갱신하고, 네트워크 하드웨어의 에러 상태를 클리어 해야 한다. 네트워크 디바이스 드라이버의 인터럽트 서비스는 1번 코드와 같이 가장 먼저 net_device 구조체를 얻으면서 시작한다. 나머지는 상태별 인터럽트 구현만이 인터럽트 서비스 함수를 구성하고 있다.

이제 인터럽트를 상태별로 어떻게 구현하는지 살펴보자. 3번 코드가 디바이스에서 송신이 끝나서 데이터 전송이 가능해졌을 경우를 나타낸다. 여기서는 먼저 디바이스의 정확한 상태를 알기위해 디바이스 내의 레지스터에서 값을 읽어서 판단한다. 그 후에는 디바이스에 에러가 나서 송신이 가능해진 것인지, 정상적으로 동작한 것인지를 판단하여 통계 처리를 한다. CS8900의 디바이스 드라이버에서는 지금까지 살펴보았듯이 priv 변수를 이용해서 통계를 구하고 있다. 통계처리가 끝나면 디바이스는 netif_wake_queue() 함수를 호출해서 커널내의 송신 큐에 재전송 또는 새로운 패킷 전송을 시작하라고 알려주며 작업을 끝낸다.

4번 코드는 버퍼가 비어있을 경우에 수행되는 코드이다. 여기서는 디바이스에 에러가 발생했는지 검사한 후 에러가 발생했으면 통계 처리를 하고, 마지막으로 netif_wake_queue() 함수를 호출해서 커널내의 송신 큐에 재전송을 시작하라고 알려주며 작업을 끝낸다. 정상적인 경우에는 통계 처리를 하지 않는데, 이는 디바이스가 패킷을 보내고 있거나 받고 있지 않았기 때문이다. 5번 코드는 송신 에러 시 수행되는 코드인데, 여기서는 단순히 통계 처리만을 수행한다.

(소스 1-7. irq 필드에 등록된 cs8900_1_interrupt() 함수의 송신처리 코드)

/* ① drivers/net/cs8900.h PP_ISQ */ #define PP_ISQ 0x0120 /* Interrupt Status Queue */ #define RxEvent 0x0004 #define TxEvent 0x0008 #define BufEvent 0x000c #define RxMISS 0x0010 #define TxCOL 0x0012 / /* drivers/net/lds2000_cs8900_1.c */ static void cs8900_1_interrupt (int irq,void *id,struct pt_regs *regs) { /* ② net_device 구조체를 가장 먼저 얻어야 한다. */ struct net_device *dev = (struct net_device *) id; cs8900_1_t *priv = (cs8900_1_t *) dev->priv; u16 status; / while ((status = cs8900_1_read (dev,PP_ISQ))) { switch (RegNum (status)) { / /* ③ 송신이 끝났을 때 */ case TxEvent: /* 디바이스의 상태를 읽음 */ priv->stats.collisions += ColCount (cs8900_1_read (dev,PP_TxCOL)); /* 디바이스에 에러가 있을 경우의 통계 처리 */ if (!(RegContent (status) & TxOK)) { priv->stats.tx_errors++; if ((RegContent (status) & Out_of_window)) priv->stats.tx_window_errors++; if ((RegContent (status) & Jabber)) priv->stats.tx_aborted_errors++; break; /* 정상적으로 동작하였을 경우의 통계 처리 */ } else if (priv->txlen) { priv->stats.tx_packets++; priv->stats.tx_bytes += priv->txlen; } priv->txlen = 0; /* 커널에 알려 송신 큐를 재시작 */ netif_wake_queue (dev); break; / /* ④ 버퍼가 비었을 때 */ case BufEvent: /* 에러 시 통계처리 */ if ((RegContent (status) & TxUnderrun)) { priv->stats.tx_errors++; priv->stats.tx_fifo_errors++; / priv->txlen = 0; netif_wake_queue (dev); } /* 정상일 경우 아무 일도 하지 않음 */ break; / /* ⑤ 송신 에러가 발생했을 때 */ case TxCOL: priv->stats.collisions += ColCount (cs8900_1_read (dev,PP_TxCOL)); break; } } }

(6) 네트워크 디바이스에서의 패킷 수신

수신 처리는 CPU가 데이터 수신을 위해서 계속 디바이스 드라이버를 검사할 수 없기 때문에 보통 인터럽트 처리부터 수신이 시작된다. 일반적인 네트워크 디바이스 드라이버는 인터럽트가 발생하면 인터럽트 서비스 함수에서 수신 상태를 확인하고, 수신 처리를 담당하는 함수를 호출한다. 수신 처리용 함수는 패킷 송신의 인터럽트를 설명할 때 수신한 데이터를 소켓 버퍼 구조체로 변환하여 커널내의 TCP/IP 프로토콜 스택에 전달한다고 설명하였다.

a. 인터럽트 처리

1번 코드는 데이터가 수신되었을 때 디바이스가 이 인터럽트를 발생시킨다. 여기서는 추가의 작업 없이 바로 수신된 데이터를 처리하는 함수인 cs8900_1_receive() 함수를 호출하고 있다.

2번 코드는 송신시 인터럽트 처리부분에서 설명하였듯이 버퍼가 비었을 경우에 발생한다. 이 부분 또한 수신에러가 발생하였는지 검사한 다음 에러가 발생했으면 통계처리를 하고 아니면 아무 일도 하지 않는다. 즉, 송신시의 인터럽트 처리 부분과 합하면 소스 10의 알고리즘과 같은데, 버퍼가 비었을 경우에는 송수신 에러가 발생하였는지 검사하고 에러가 발생하였으면 알맞은 통계처리를 해야 한다는 것을 알 수 있다.

(소스 10. 디바이스 버퍼가 비었을 경우의 인터럽트 처리 알고리즘)

case (버퍼가 비었음): if (송신 에러 발생) (송신 에러에 맞는 통계 처리); else if (수신 에러 발생) (수신 에러에 맞는 통계 처리); else (아무 일도 하지 않음); break;

3번 코드는 수신 에러 시 수행되는 코드인데, 여기서도 송신 에러 시와 마찬가지로 단순히 통계 처리만을 수행함을 알 수 있다. 여기서 송신 시 통계를 저장하는 변수와 수신 시 통계를 저장하는 변수가 틀리다는 것에 주의해야 할 것이다.

(소스 1-8. irq 필드에 등록된 cs8900_1_interrupt() 함수의 수신처리 코드)

static void cs8900_1_interrupt (int irq,void *id,struct pt_regs *regs) { struct net_device *dev = (struct net_device *) id; cs8900_1_t *priv = (cs8900_1_t *) dev->priv; u16 status; / while ((status = cs8900_1_read (dev,PP_ISQ))) { switch (RegNum (status)) { / /* ① 수신된 데이터가 있을 때 */ case RxEvent: cs8900_1_receive (dev); break; / /* ② 버퍼가 비었을 때 */ case BufEvent: /* 에러 시 통계처리 */ if ((RegContent (status) & RxMiss)) { u16 missed = MissCount (cs8900_1_read (dev,PP_RxMISS)); priv->stats.rx_errors += missed; priv->stats.rx_missed_errors += missed; } /* 정상일 경우 아무 일도 하지 않음 */ break; / /* ③ 송신 에러가 발생했을 때 */ case RxMISS: status = MissCount (cs8900_1_read (dev,PP_RxMISS)); priv->stats.rx_errors += status; priv->stats.rx_missed_errors += status; break; } } }

b. 수신 처리 함수

대부분의 네트워크 디바이스가 수신 처리 함수를 소스 1-9와 같이 거의 동일하게 구현한다. 그것은 TCP/IP 프로토콜 스택에서 제공하는 함수들을 사용하여 받은 데이터를 TCP/IP 프로토콜 스택이 이해할 수 있는 소켓 버퍼의 형태로 변환해야 하기 때문이다. 디바이스마다 다른 부분은 소스 1-9의 1번, 2번, 6번 코드와 같이 디바이스에서 값을 얻어오거나, 통계처리를 하는 부분뿐이다. 그러나 1번 코드의 경우에는 2번 코드에서 에러인지 판단하기 위한 상태 정보와, 소켓 버퍼를 할당받기 위한 수신 데이터의 크기를 디바이스에서 받아와 한다. 2번 코드의 경우에는 에러일 경우 통계처리를 하고 바로 복귀한다.

(소스 1-9. 디바이스 드라이버의 수신 처리 함수 cs8900_1_receive())

static void cs8900_1_receive (struct net_device *dev) { cs8900_1_t *priv = (cs8900_1_t *) dev->priv; struct sk_buff *skb; u16 status,length; / /* ① 디바이스의 수신 상태와 데이터의 크기를 구함 */ status = cs8900_1_read (dev,PP_RxStatus); length = cs8900_1_read (dev,PP_RxLength); / /* ② 수신 상태가 에러이면 통계처리를 하고 복귀 */ if (!(status & RxOK)) { priv->stats.rx_errors++; if ((status & (Runt | Extradata))) priv->stats.rx_length_errors++; if ((status & CRCerror)) priv->stats.rx_crc_errors++; return; } / /* ③ 소켓 버퍼 할당 */ if ((skb = dev_alloc_skb (length + 4)) == NULL) { priv->stats.rx_dropped++; return; } skb_reserve (skb,2); / /* ④ 할당받은 소켓 버퍼에 받은 데이터를 채움 */ skb->dev = dev; cs8900_1_frame_read (dev,skb,length); / /* ⑤ 소켓 버퍼의 프로토콜 타입 설정 */ skb->protocol = eth_type_trans (skb,dev); / /* ⑥ 소켓 버퍼를 커널에 전달 */ netif_rx (skb); / /* ⑦ 수신 데이터에 대한 통계 처리 */ dev->last_rx = jiffies; priv->stats.rx_packets++; priv->stats.rx_bytes += length; }

3번 코드에서 6번 코드에 까지 걸쳐서 변환할 소켓버퍼에 관한 연산을 수행하게 된다. 가장 먼저 변환 받을 소켓 버퍼를 할당받아야 한다. 소켓 버퍼를 할당받기 위해서는 3번 코드에서처럼 dev_alloc_skb() 함수를 호출해야 한다. dev_alloc_skb() 함수는 include/linux/skbuff.h에 구현되어 있으며, dev_alloc_skb() 함수의 원형은 소스 11과 같다. 이 함수는 TCP/IP 프로토콜 스택 내의 소켓 버퍼 풀에서 빈 소켓 버퍼를 받아오는 역할을 한다.

(소스 11. dev_alloc_skb() 함수의 원형)

#include <linux/skbuff.h> / static inline struct sk_buff *dev_alloc_skb(unsigned int length);

그 다음에는 소켓 버퍼에 대한 연산을 수행하는 skb_reserve() 함수를 호출한다. 이 함수의 원형은 소스 12와 같다. 이 함수는 소켓 버퍼의 데이터 공간을 앞쪽과 뒤쪽으로 두 번째 인자의 크기만큼 확장한다. 이 함수는 메모리를 할당받는 것이 아니라 단순히 소켓 버퍼의 data, tail, len 필드만을 갱신한다.

(소스 12. dev_alloc_skb() 함수의 원형)

#include <linux/skbuff.h> / static inline void skb_reserve(struct sk_buff *skb, unsigned int len);

여기서 dev_alloc_skb() 함수의 호출에서 데이터의 크기보다 약간 더 큰 데이터를 할당하고, skb_reserve() 함수로 소켓버퍼의 크기를 조절하는 것은 디바이스 내에 있는 버퍼가 링 구조로 이루어져 있기 때문에 소켓 버퍼의 앞뒤에 여유 공간을 두어서 디바이스 내부 버퍼로부터 원활한 복사를 하기 위한 것이다.

4번 코드에서는 현재 할당받은 소켓 버퍼 데이터를 사용하는 디바이스가 무엇인지 알려주기 위하여 소켓 버퍼의 dev 필드에 디바이스의 net_device 구조체의 선두주소를 대입하였다. 그 다음에 나오는 코드는 소켓 버퍼에 받은 데이터를 복사하는 코드이다.

소켓 버퍼에 수신된 데이터를 복사해서 넣은 후에는 5번 코드와 같이 eth_type_trans() 함수를 사용하여 소켓 버퍼의 필드들을 이더넷 타입으로 설정한다. 이 함수는 net_device 구조체의 프로토콜 의존적인 필드들을 자동으로 세팅해주는 ether_setup() 함수와 비슷한 역할을 하는 함수이다. 이 함수의 원형은 소스 13과 같다.

(소스 13. eth_type_trans() 함수의 원형)

#include <linux/etherdevice.h> / unsigned short eth_type_trans(struct sk_buff *skb, struct net_device *dev);

6번 코드까지 오면 소켓 버퍼에 대한 모든 처리가 끝난 것이다. 여기서는 netif_rx() 함수를 호출하여 소켓 버퍼를 TCP/IP 프로토콜 스택의 Link 계층으로 보낸다. 그림 8.8의 제어흐름에서 보았듯이, netif_rx() 함수는 커널내의 인터럽트 요청 큐에 소켓 버퍼를 등록하여 준다. 그러면 커널 인터럽스 서비스를 수행하는 커널 스레드가 이를 발견하여 상위 계층으로 계속 전달을 하는 것이다. 데이터가 수신된 시간은 이 시점에서 netif_rx() 함수를 호출하고 난 바로 후로 함을 7번 코드를 통해 알 수 있다.

(7) 네트워크 디바이스의 통계 처리

네트워크 디바이스 드라이버는 패킷의 송수신에 관련된 통계 정보를 관리해야 한다. 일반적인 네트워크 디바이스 드라이버는 CS8900 디바이스 드라이버의 priv 전역 구조체 변수와 같이 디바이스 자체 관리용 구조체로 관리하고, 드라이버 내의 루틴들의 곳곳에서 통계 처리를 수행한다. 그러나 디바이스에서 처리하는 통계는 커널 내의 TCP/IP 프로토콜 스택 또한 사용해야 하므로 미리 약속되어진 통계에 관한 자료구조가 필요하다.

그래서 커널에는 네트워크 디바이스가 처리해야할 네트워킹 통계에 관한 구조체가 존재한다. 그 구조체는 include/linux/netdevice.h 파일에 선언되어 있는 net_device_stats 구조체이다. 이 구조체는 총 23개의 필드를 가지고 있으며 이에 대한 상세한 설명은 소스 14와 같다. 또한 이 구조체는 일반적으로 디바이스 자체 관리용 구조체 안에 멤버 변수로 선언되어 사용된다.

(소스 14. 통계 처리 관련 net_device_stats 구조체) struct net_device_stats { unsigned long rx_packets; /* 수신된 총 패킷 수 */ unsigned long tx_packets; /* 송신한 총 패킷 수 */ unsigned long rx_bytes; /* 수신된 패킷의 총 바이트 수 */ unsigned long tx_bytes; /* 송신한 패킷의 총 바이트 수 */ unsigned long rx_errors; /* 수신시 에러난 패킷의 수 */ unsigned long tx_errors; /* 송신시 에러난 패킷의 수 */ unsigned long rx_dropped; /* 수신버퍼에 공간이 없어 버려진 패킷 수 */ unsigned long tx_dropped; /* 송신버퍼에 공간이 없어 버려진 패킷 수 */ unsigned long multicast; /* 수신된 멀티캐스트 패킷 수 */ unsigned long collisions; /* 패킷 충돌이 발생한 횟수 */ / /* rx_errors 수신에러 필드에 대한 자세한 정보 */ unsigned long rx_length_errors; /* 패킷길이 에러의 횟수 */ unsigned long rx_over_errors; /* 수신 버퍼 오버플로우 에러의 횟수 */ unsigned long rx_crc_errors; /* CRC 에러의 횟수 */ unsigned long rx_frame_errors; /* 프레임 에러의 횟수 */ unsigned long rx_fifo_errors; /* 디바이스 버퍼 오버플로우 에러의 횟수 */ unsigned long rx_missed_errors; /* 수신되지 않은 패킷의 수 */ / /* tx_errors 송신에러 필드에 대한 자세한 정보 */ unsigned long tx_aborted_errors; /* 송신 취소가 되어 발생한 에러의 횟수 */ unsigned long tx_carrier_errors; /* 캐리어가 감지되지 않아 발생한 에러의 횟수 */ unsigned long tx_fifo_errors; /* 디바이스 버퍼 오버플로우 에러의 횟수 */ unsigned long tx_heartbeat_errors; unsigned long tx_window_errors; /* for cslip etc */ unsigned long rx_compressed; unsigned long tx_compressed; };

앞서 살펴보았듯이 net_device 구조체에는 통계를 관리하는 함수를 등록하는 get_stats 함수 포인터 필드가 있다. 이 필드에는 cs8900_1_probe() 함수에서 cs8900_1_get_stats() 함수가 등록되어 있다. 이 함수에 대한 코드는 소스 1-10과 같다. 소스 1-10에서 보듯이 이 함수는 단순히 디바이스 자체 관리용 구조체인 priv 변수에서 net_device_stats 구조체 변수인 stats 필드를 반환해주고 있다.

(소스 1-10. 디바이스 드라이버의 통계 처리 함수 cs8900_1_get_stats())

static struct net_device_stats *cs8900_1_get_stats (struct net_device *dev) { cs8900_1_t *priv = (cs8900_1_t *) dev->priv; return (&priv->stats); }

writeen by 6기 정낙천

반응형

'Network' 카테고리의 다른 글

이기종 네트워크 상의 Mobile IPTV, Mobile VoIP 응용 서비스 요구사항 분석  (0) 2010.05.18
디바이스 드라이버  (0) 2010.05.10
네트워크 디바이스 드라이버  (0) 2010.05.10
AAA와 RADIUS  (0) 2010.05.10
ns2 디버깅  (0) 2010.05.10
Posted by pmj0403