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

이야기꾼:이기천(hetta@nownuri.net)
+-----------------------------------------------------------------------+
|    *강좌에 앞서서....                                                 |
|     이 글은 GFDL(GNU Free Documentation License)을 따릅니다.          |
|    그러므로 필자는 이글로 인한 물질적이든 정신적이든 또한             |
|    가정의 불화든지간에 어떠한 책임도 지지 않습니다.                   |
|    원저자가 명시되는한 비상업적용도에 한하여                          |
|    자유로운 배포,수정이가능하지만,                                    |
|    어떠한 수단이든지간에 상업적 용도로 사용되어질수는 없습니다.       |
+-----------------------------------------------------------------------+


<참고자료> 
1.Linux Device Driver 2nd 
  - http://www.oreilly.com 에서 pdf버전 무료배포
2.Linux Network Device Driver(권수호)
  - http://www.linuxdeveloper.co.kr
3.RTL8139C datasheet
  - http://www.realtek.com.tw/
4.(Linux_Kernel_Src_Path)drivers/net/8139too.c
5.RTL8139 Programming guide
6.Understanding the Linux Kernel

*환경:kernel 2.4.9 
(다른버전의 커널에서는 조금씩 다를수있음을 유의하시기 바랍니다.)

오늘은 이번강의의 첫번째시간입니다.
이번시간엔 먼저 Network Driver에대한 개념을 잡기위하여
Linux Device Driver 2nd의 [chapter 15. Network Drivers]를 기반으로 
이야기를 시작하겠습니다.
언제까지 할것이라는 약속은 못드릴것같습니다.저도 인간이니까요..어쩌면 
이번강의 가 처음이자 마지막일지도 모르겠죠^^  언제든지 네트워크 드라이버 
분석이 재미(funny)없다면 강의를 그만둘겁니다.(아마 가능성이 희박하겠지만^^)
저도 초보임을 밝히겠습니다.  실제적 device driver를 다루는것은 처음입니다.
그러므로 틀린점이 많을것같습니다...
언제든지 잘못된것은 혼자알지마시고 알려주세여 ~~~~~~~~~!
저는 제가 이해한 방향으로 강좌를 진행하겠습니다.^^
이제부터 존칭은 생략하도록 하겠습니다.^^ 타이핑치기가 힘드러서요.... :P
자 그럼 시작합니다. :)


모듈 올리기
^^^^^^^^^^^^
디바이스 드라이버가 커널에 로딩될때 자기가 사용할 리소스를 커널에 
요청하게 된다. 예를들어 사용할 irq번호와 I/O port를 등록해야한다.
랜카드를 잡을때 다음과 같이 잡는다. 필자의 경우도 ne2000랜카드를 잡을때,
먼저 윈도우로 가서 거기서 io와 irq번호를 알아온다음에 리눅스에서 다음과 
같이 잡았다.(물론 이렇게 잡는것은 isa방식이 대부분이었으나, pci도 이렇게 잡
아야 잡히는 경우를 경험했다.)

modprobe ne.o io=0x100 irq=9

이것이  랜카드가 사용할 io와 irq를 명령어 라인 인수로 넘겨받게 되는것이다. 
크게 디바이스 드라이버는...
불록(block),문자(character),네트웍 이렇게 3개로 나눈다. 그중에서 불록과 
문자는 주번호와 부번호라는것이 존재하여 디바이스를 제어하나, 네트웍장치는
그러한 개념이 존재하지 않는다.그대신 커널의 어느곳에서도 참조할수있는
전역(global)리스트로 새로운 디바이스장치를 넣게된다. 
또한 "유닉스(리눅스)에서 는 모든것이 파일"이라는 말을 많이 들어보았을것이다.
하지만, 여기서 네트웍장치 는 아쉽게도 예외로서 존재한다. 다른 문자나,블록장
치는 /dev/밑에 실제적인 파일로서존재하나, 네트웍장치만은 거기에 존재하
지 않는다! 뭐.. 예외없는 법칙이 없다는말도 있으니, 잠시 봐주기로 하자..ㅎㅎ
"유닉스에서 모든것은 파일이다.(단 네트웍장치만빼고^^)"
(주: 네트웍 장치말고 다른것이 있다면 알려주기 바랍니다.)

위에서 전역리스트가 존재한다고 하였다. 그렇다면 이 전역리스트의 자료구조는
어떻게 생겼을까? 
리눅스는 네트웍 장치에 대한 데이터를 struct net_device로서 관리한다.
이제, LXR로 살펴보도록하자.
<참고> -----------------------------------------------------------
모르시는 분을 위하여 간단히 쓰겠다.
웹브라우저로 http://lxr.linux.no에가서 browse the code를 누른뒤
identifier search 를 누르고 검색란에 net_device를 적는다.
이제 net_device를 정의한  다음과 같은 검색결과가 나올것이다.
그것을 누른다.

include/linux/netdevice.h, line 230 
--------------------------------------------------------------------

커널소스를 처음보시는분은 벌써부터 기가 찰지도 모르겠다. 구조체가 자그마치
180라인이니 말이다. 그러나 모든것을 알필요는없다. 필요한것은 필요할때
공부한다는 맘을 갖고, 대충만 훌터보고 넘어가도록하자. 뒤에서 꼭 필요한
필드만을 설명할것이다. (사실 안봐도 이야기 진행상 큰문제는없으니
너무 겁부터 내지는 말기를 바란다.)

----------------------------------------------------------------------
/*
 *  The DEVICE structure.
 *  Actually, this whole structure is a big mistake.  It mixes I/O
 *  data with strictly "high-level" data, and it has to know about
 *  almost every data structure used in the INET module.
 *
 *  FIXME: cleanup struct net_device such that network protocol info
 *  moves out.
 */

struct net_device
{

    /*
     * This is the first field of the "visible" part of this structure
     * (i.e. as seen by users in the "Space.c" file).  It is the name
     * the interface.
     */
    char            name[IFNAMSIZ];

    /*
     *  I/O specific fields
     *  FIXME: Merge these and struct ifmap into one
     */
    unsigned long       rmem_end;   /* shmem "recv" end */
    unsigned long       rmem_start; /* shmem "recv" start   */
    unsigned long       mem_end;    /* shared mem end   */
    unsigned long       mem_start;  /* shared mem start */
    unsigned long       base_addr;  /* device I/O address   */
    unsigned int        irq;        /* device IRQ number    */

    /*
     *  Some hardware also needs these fields, but they are not
     *  part of the usual set specified in Space.c.
     */

    unsigned char       if_port;    /* Selectable AUI, TP,..*/
    unsigned char       dma;        /* DMA channel      */

    unsigned long       state;

    struct net_device   *next;

    /* The device initialization function. Called only once. */
    int         (*init)(struct net_device *dev);

    /* ------- Fields preinitialized in Space.c finish here ------- */

    struct net_device   *next_sched;

    /* Interface index. Unique device identifier    */
    int         ifindex;
    int         iflink;


    struct net_device_stats* (*get_stats)(struct net_device *dev);
    struct iw_statistics*   (*get_wireless_stats)(struct net_device *dev);

    /*
     * This marks the end of the "visible" part of the structure. All
     * fields hereafter are internal to the system, and may change at
     * will (read: may be cleaned up at will).
     */

    /* These may be needed for future network-power-down code. */
    unsigned long       trans_start;    /* Time (in jiffies) of last Tx */
    unsigned long       last_rx;    /* Time of last Rx  */

    unsigned short      flags;  /* interface flags (a la BSD)   */
    unsigned short      gflags;
    unsigned        mtu;    /* interface MTU value      */
    unsigned short      type;   /* interface hardware type  */
    unsigned short      hard_header_len;    /* hardware hdr length  */
    void            *priv;  /* pointer to private data  */

    struct net_device   *master; /* Pointer to master device of a group,
                      * which this device is member of.
                      */

    /* Interface address info. */
    unsigned char       broadcast[MAX_ADDR_LEN];    /* hw bcast add */
    unsigned char       dev_addr[MAX_ADDR_LEN]; /* hw address   */
    unsigned char       addr_len;   /* hardware address length  */

    struct dev_mc_list  *mc_list;   /* Multicast mac addresses  */
    int         mc_count;   /* Number of installed mcasts   */
    int         promiscuity;
    int         allmulti;

    int         watchdog_timeo;
    struct timer_list   watchdog_timer;

    /* Protocol specific pointers */

    void            *atalk_ptr; /* AppleTalk link   */
    void            *ip_ptr;    /* IPv4 specific data   */
    void                    *dn_ptr;        /* DECnet specific data */
    void                    *ip6_ptr;       /* IPv6 specific data */
    void            *ec_ptr;    /* Econet specific data */

    struct Qdisc        *qdisc;
    struct Qdisc        *qdisc_sleeping;
    struct Qdisc        *qdisc_list;
    struct Qdisc        *qdisc_ingress;
    unsigned long       tx_queue_len;   /* Max frames per queue allowed */

    /* hard_start_xmit synchronizer */
    spinlock_t      xmit_lock;
    /* cpu id of processor entered to hard_start_xmit or -1,
       if nobody entered there.
     */
    int         xmit_lock_owner;
    /* device queue lock */
    spinlock_t      queue_lock;
    /* Number of references to this device */
    atomic_t        refcnt;
    /* The flag marking that device is unregistered, but held by an user */
    int         deadbeaf;

    /* Net device features */
    int         features;
#define NETIF_F_SG      1   /* Scatter/gather IO. */
#define NETIF_F_IP_CSUM     2   /* Can checksum only TCP/UDP over IPv4. */
#define NETIF_F_NO_CSUM     4   /* Does not require checksum. F.e. loopack. */
#define NETIF_F_HW_CSUM     8   /* Can checksum all the packets. */
#define NETIF_F_DYNALLOC    16  /* Self-dectructable device. */
#define NETIF_F_HIGHDMA     32  /* Can DMA to high memory. */
#define NETIF_F_FRAGLIST    64  /* Scatter/gather IO. */

    /* Called after device is detached from network. */
    void            (*uninit)(struct net_device *dev);
    /* Called after last user reference disappears. */
    void            (*destructor)(struct net_device *dev);

    /* Pointers to interface service routines.  */
    int         (*open)(struct net_device *dev);
    int         (*stop)(struct net_device *dev);
    int         (*hard_start_xmit) (struct sk_buff *skb,
                            struct net_device *dev);
    int         (*hard_header) (struct sk_buff *skb,
                        struct net_device *dev,
                        unsigned short type,
                        void *daddr,
                        void *saddr,
                        unsigned len);
    int         (*rebuild_header)(struct sk_buff *skb);
#define HAVE_MULTICAST           
    void            (*set_multicast_list)(struct net_device *dev);
#define HAVE_SET_MAC_ADDR        
    int         (*set_mac_address)(struct net_device *dev,
                           void *addr);
#define HAVE_PRIVATE_IOCTL
    int         (*do_ioctl)(struct net_device *dev,
                        struct ifreq *ifr, int cmd);
#define HAVE_SET_CONFIG
    int         (*set_config)(struct net_device *dev,
                          struct ifmap *map);
#define HAVE_HEADER_CACHE
    int         (*hard_header_cache)(struct neighbour *neigh,
                             struct hh_cache *hh);
    void            (*header_cache_update)(struct hh_cache *hh,
                               struct net_device *dev,
                               unsigned char *  haddr);
#define HAVE_CHANGE_MTU
    int         (*change_mtu)(struct net_device *dev, int new_mtu);

#define HAVE_TX_TIMEOUT
    void            (*tx_timeout) (struct net_device *dev);

    int        (*hard_header_parse)(struct sk_buff *skb,
                             unsigned char *haddr);
    int        (*neigh_setup)(struct net_device *dev, struct neigh_parms *);
    int         (*accept_fastpath)(struct net_device *, struct dst_entry*);

    /* open/release and usage marking */
    struct module *owner;

    /* bridge stuff */
    struct net_bridge_port  *br_port;

#ifdef CONFIG_NET_FASTROUTE
#define NETDEV_FASTROUTE_HMASK 0xF
    /* Semi-private data. Keep it at the end of device struct. */
    rwlock_t        fastpath_lock;
    struct dst_entry    *fastpath[NETDEV_FASTROUTE_HMASK+1];
#endif
#ifdef CONFIG_NET_DIVERT
    /* this will get initialized at each interface type init routine */
    struct divert_blk   *divert;
#endif /* CONFIG_NET_DIVERT */
};
----------------------------------------------------------------------

실제적인 네트웍장치 등록은 이 net_device의 필드들에 적절한 값을 세팅한다음
다음과 같이 register_netdev()를 호출하는것이다.

int result ;
struct net_device *my_net ;
// my_net그조체에 적절한 값을 채움!
result = register_netdev( my_net );


<참고>----------------------------------------------------------------
LDD(Linux Device Driver) 15챕터에서는 snull이라는 장치를 만드는데,
이장치는 실제적인 하드웨어가 아닌 가상장치이다. 개인적으로 snull이라는
장치가 동작하는방식을 이해하는것은 별 도움이 안되었다. 그래서 
불필요한 혼동을 줄이고자 되도록이면, LDD에 나오는 snull의 설계는 제외하고,
실제적 네트웍장치를 분석하는데 도움이 되는 부분만을 설명할 것이다. 
전체적인 흐름을 이해하는것이 중요할것같다.
관심있으신분은 오렐리 사이트에서 snull소스를 다운받아서 살펴보기 바란다.
-----------------------------------------------------------------------


네트웍 장치를 초기화하기
~~~~~~~~~~~~~~~~~~~~~~~~~

이때 snull의 경우  net_device구조체에 지정한 init함수가 초기화를 수행한다.
그함수는 snull_init이며, net_device구조체에 적절한 필드를 채운다.
예를들어 
dev->open = snull_open ; //장치 열때.      아마도 ifconfig eth0 up 일듯?
dev->stop = snull_release ; //장치 닫을때. 아마도 ifconfig eth0 down 일듯?
dev->do_ioctl = snull_ioctl ; //ioctl시스템 콜할때
dev->hard_start_xmit = snull_tx ;
dev->get_stats = snull_stats ;
dev->tx_timeout = snull_tx_timeout ; //Tx타임아웃발생시 호출함수
dev->watchdog_timeo = timeout ; //timeout 되는 시간설정
....
SET_MODULE_OWNER(dev)
등등...
open은 장치를 열때 실행되는것이며 , hard_start_xmit은 패킷을 전송하기전에
초기화를 하는것이며, get_stats은 장치에 관한 정보를 요청할때 정보를 제공
하는것이다. ifconfig 라는 명령어를 칠때 get_stats가 실행된다고 보면 
될것이다. 
SET_MODULE_OWNER()은 net_device의 owner 필드에 이 디바이스 모듈에 대한
포인터로 초기화한다.  모듈의 usage count를 관리하기 위하여, 
file_operations구조체의 owner필드와 똑같이 커널에서 사용한다.

예를들자면 ifconfig eth0 up할때  snull_open이 실행되고,
ifconfig eth0 down할때 snull_release가 실행된다고 보면 될것이다.

<참고>-----------------------------------------------------------------
하지만, 실제 8139too.c에서는 약간 다른방식을 사용하는것으로 보인다. 즉,
장치를 초기화 하는것이 net_device의 init필드를 사용하는것이 아니라,
pci_driver 의 probe를 사용한다는것이다.( rtl8139_init_one() ) 
하지만 하는 작업은 비슷하다고 생각하면 될것같다. rtl8139_init_one(...)에서
결국 net_device를 채운다음 register_netdev()를 호출한다.
-----------------------------------------------------------------------

주의할점은 irq와 io같은 중요한 리소스는 init하는시점이 아닌 open시점에 
리소스를 확보해야한다는것이다. 왜냐하면 그렇지 않을경우 실제로 사용되지도
않는 장치에 할당된 io와 irq때문에 다른장치를 쓰지 못하는경우도 발생할수있기
때문이다.

또한 net_device에는 priv라는 필드가 있다. 이것은 각각의 장치가 관리하는
private한 자료등을 관리하게 된다. 이것은 장치를 open하는 시점이 아니라
init할때 실행해야한다.왜냐하면 priv에는 각종통계정보에 유용한 값들이 있는데
사용자는 장치가 활성화되지 않았을때라도 그정보를 보기 원할수있기 때문이다.

snull의 경우에는 다음과 같은 필드들이 있다.

------------------------------------------------------------------
/*
 * 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;
    int rx_packetlen;
    u8 *rx_packetdata;
    int tx_packetlen;
    u8 *tx_packetdata;
    struct sk_buff *skb;
    spinlock_t lock;
};

여기서 새로운 구조체가 나오는데 net_device_stats로서
말그대로 네트웍장치에대한 통계자료를 관리한다.
-----------------------------------------------------------------
/*
 *  Network device statistics. Akin to the 2.0 ether stats but
 *  with byte counters.
 */

struct net_device_stats
{
    unsigned long   rx_packets;     /* total packets received   */
    unsigned long   tx_packets;     /* total packets transmitted    */
    unsigned long   rx_bytes;       /* total bytes received     */
    unsigned long   tx_bytes;       /* total bytes transmitted  */
    unsigned long   rx_errors;      /* bad packets received     */
    unsigned long   tx_errors;      /* packet transmit problems */
    unsigned long   rx_dropped;     /* no space in linux buffers    */
    unsigned long   tx_dropped;     /* no space available in linux  */
    unsigned long   multicast;      /* multicast packets received   */
    unsigned long   collisions;

    /* detailed rx_errors: */
    unsigned long   rx_length_errors;
    unsigned long   rx_over_errors;     /* receiver ring buff overflow  */
    unsigned long   rx_crc_errors;      /* recved pkt with crc error    */
    unsigned long   rx_frame_errors;    /* recv'd frame alignment error */
    unsigned long   rx_fifo_errors;     /* recv'r fifo overrun      */
    unsigned long   rx_missed_errors;   /* receiver missed packet   */

    /* detailed 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;
};
-----------------------------------------------------------------------

뭐 간단히 예를 들자면 패킷을 받다가 메모리가 꽉차서 drop되었다면 rx_dropped
필드를 하나 증가시켜주는 그냥 이런식이다.
ifconfig명령을 내리면 랜카드에대한 각종 통계치가 나오는데 이런통계자료를
바탕으로 출력되는것이다.

eth0      Link encap:Ethernet  HWaddr 00:50:FC:0C:ff:f1
          ...
          RX packets:894551 errors:0 dropped:0 overruns:0 frame:0
          TX packets:686533 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:100 
          Interrupt:5 Base address:0xac00 




private한 구조체를 관리하려면 당연히 먼저 메모리공간을 확보해야한다.
다음과 같이 한다.

dev->priv  = kmalloc(sizeof(struct snull_priv), GFP_KERNEL);
if( dev->priv == NULL )
    return -ENOMEM ;
memset( dev->priv, 0 sizeof(struct snull_priv));
spin_lock_init( &((struct snull_priv *) dev->priv)->lock ) ;

마지막에서 spin_lock_init()은 스핀락인 snull_priv의 lock을 초기화하는것이다.
일단 스핀락은 그냥 race condition을 방지하는것이라고만 이해하도록 하자.


모듈내리기 
^^^^^^^^^^^
모듈을 내리려면 다음과 같이 priv에 할당된 메모리를 해제하고 
unregister_netdev()로서 네트웍장치의 등록을 해제한다.


void snull_cleanup(void)
{   
    int i;
        
    for (i=0; i<2;  i++) {
        kfree(snull_devs[i].priv);
        unregister_netdev(snull_devs + i);
    }
    return;
}




net_device구조체에 대해서...
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
위에서 net_device에 대하여 조금 언급하였다.여기서는 조금더 깊이 들어가
보도록 하겠다.

이 구조체는 위에서 보았듯이 상당히 길이가 길다. 크게 두가지로 나뉘는데,
첫번째는 보이는(visible)필드과 숨겨진(hidden)필드이다.보이는 필드는 그냥
static구조체에 명시적으로 값을 할당할수있는 필드들로 구성된다.(the visible
part of the structure is made up of the fields that can be explicitly 
assigned in static net_device structure )
그냥 쉽게 말해 보이는필드는 우리가 신경써야되는부분이며, 숨겨진 필드는 
커널에서 내부적으로 사용되는것이라 생각하면 될것같다.

먼저 보이는필드를 살펴보자.


char name[IFNAMSIZ];
 이것은 장치의 이름을 지칭한다. 예를들어 이더넷장치로 eth0,eth1,eth2등을
 보았을것이다. 여기에 %d를 넣으면 0부터 장치이름이 지어진다.
 즉 "eth%d"로 한다면 맨 첫번째 장치는 eth0가되고 그담이 eth1...이런식이다.

unsigned long rmem_end ;
unsigned long rmem_start;
unsigned long mem_end ;
unsigned long mem_start ;
장치가 사용하는 메모리 정보이다. 만약 패킷을 받을때 사용되는 rx와 
패킷을 보낼때 사용하는 tx에서 다른영역이 할당되었다면 rmem_ 은 rx를 의미하고
mem_은 tx를 의미한다. mem_start, mem_end는 부팅할때 명령어 라인에서
지정할수있으며, 그값은 ifconfig로서 확인할수있다. end - start함으로서
사용되는 메모리를 확인할수있다.
ifconfig의 맨페이지에 다음과 같은 설명이 나온다.
    mem_start addr
        Set  the  start  address  for shared memory used by
        this device.  Only a few devices need this.
즉 mem_start를 이용해 ifconfig에서 값을 변경할수있다는것이다.


unsigned long base_addr ;

랜카드가 사용하는 I/O 주소이다.이것도 ifconfig로 확인가능하다.

unsigned char irq ;

할당된 인터럽트 번호이다. ifconfig로 확인가능하다.

unsigned char if_port ;
이것은 두개이상의 포트가 있는 랜카드의 경우 어느것이 사용되는가를 나타낸다.
예를들어 coaxial , twisted-pair의 두개가 있을경우이다.
(IF_PORT_10BASE2 , IF_PORT_10BASET )

unsigned char dma  ;
장치에 할당된 dma채널이다.이것은 isa의 경우에 쓰이며 pci는 안쓰인다.

unsigned long state ;
장치의 상태이다.이값은 보통 직접 접근하여 수정하지 않고, 이값을 관리하는
함수가 따로 존재한다.

struct net_device *next ;
앞에서 네트웍장치는 전역 리스트로 관리된다고 했다. 이것은 그 전역리스트에서
다음 장치를 지칭한다. 이것은 수정되어서는 안된다.

int (*init)(struct net_device *dev)
초기화 함수이다. (참고: rtl8139는 이걸 사용하지 않는다.)



이제 숨겨진 필드들을 살펴보도록 하자.
일반적으로 이 필드들은 드라이버를 초기화할때 할당된다.

unsigned mtu ;
maximum transfer unit(MTU)이다. 이것은 network layer에서 사용되는것이다.
즉, IP layer 에서의 패킷의 크기를 제한하는데 이더넷의경우 1500이다.
그렇다면 실제적으로 네트웍라인상에 보내지는 패킷의 최대크기는 몇일까?
mtu(1500) + ethernet header(14) + pad(4) = 1536이다.

unsigned long tx_queue_len ;
장치의 전송큐에 넣을수있는 최대한의  프레임수이다.이값은 이더넷의경우
100으로 할당되어있으나,변경할수도있다. ifconfig로 확인가능하다.

eth0      Link encap:Ethernet  HWaddr 00:50:FC:0C:ff:ff 
          ...
          collisions:0 txqueuelen:100 
          Interrupt:5 Base address:0xac00 



unsigned char addr_len ;
unsigned char broadcast[MAX_ADDR_LEN];
unsigned char dev_addr[MAX_ADDR_LEN];

addr_len은 주소의 길이로 이더넷의경우 6 옥텟(octets)이다.
broadcast는 6개의 옥텟이 0xff로 채워짐으로서 이뤄진다.
dev_addr 은 하드웨어 주소를 나타내는것으로 이값은 랜카드마다 고유하게 
갖고있게되며, 모듈초기화시 랜카드의 EEPROM에서 읽어오게된다.

<참고>---------------------------------------------------------
네트웍에서는 byte대신 octet이라는 용어가 사용된다.
그냥 똑같이 이해하면 별상관은 없을것이다.
---------------------------------------------------------------


unsigned short flags ;

이 플래그는 각각의 장치의 독특한 속성을 지정하게 된다.
앞에 IFF_라는 prefix가 있으며, <linux/if.h>에 모두 선언되어있다.
몇몇은 커널에의해 내부적으로 사용되고,몇몇은 초기화시 사용된다.
다음과 같은 값들이 가능하다.


----------------------------------------------------------------------
/* Standard interface flags. */
#define IFF_UP      0x1     /* interface is up      */
#define IFF_BROADCAST   0x2     /* broadcast address valid  */
#define IFF_DEBUG   0x4     /* turn on debugging        */
#define IFF_LOOPBACK    0x8     /* is a loopback net        */
#define IFF_POINTOPOINT 0x10        /* interface is has p-p link    */
#define IFF_NOTRAILERS  0x20        /* avoid use of trailers    */
#define IFF_RUNNING 0x40        /* resources allocated      */
#define IFF_NOARP   0x80        /* no ARP protocol      */
#define IFF_PROMISC 0x100       /* receive all packets      */
#define IFF_ALLMULTI    0x200       /* receive all multicast packets*/

#define IFF_MASTER  0x400       /* master of a load balancer    */
#define IFF_SLAVE   0x800       /* slave of a load balancer */

#define IFF_MULTICAST   0x1000      /* Supports multicast       */

#define IFF_VOLATILE    (IFF_LOOPBACK|IFF_POINTOPOINT|IFF_BROADCAST|IFF_MASTER|IFF_SLAVE|IFF_RUNNING)

#define IFF_PORTSEL 0x2000          /* can set media type       */
#define IFF_AUTOMEDIA   0x4000      /* auto media select active */
#define IFF_DYNAMIC 0x8000      /* dialup device with changing addresses*/
------------------------------------------------------------------------
다른값들은 참고만하고 IFF_PROMISC,IFF_ALLMULTI 플래그는 꼭 알아두자.
왜냐하면 우리가 분석할 8139too.c에서 사용되기 때문이다.

IFF_PROMISC  이것은 자기의 하드웨어 주소에 해당하는 패킷뿐아니라.
             랜에 돌아다니는 모든 패킷을 잡는 것이다. 
             tcpdump는 이플래그를 이용하여 패킷을 모두 잡는다.

IFF_MULTICAST 멀티캐스트 패킷을 보내는것을 가능하게한다. 이것은 디폴트로 
              "허용"이므로 만일 멀티캐스트패킷을 보내지 못하도록하려면,
             초기화시 이플래그를 꺼주어야한다.

IFF_ALLMULTI  모든 멀티캐스트 패킷을 받도록한다.커널은 해당 호스트가
              멀티캐스트 라우팅을 할때, IFF_MULTICAST가 켜져있는때에 한하여
              이 플래그를 세팅한다. 이값은 read-only이다.


이러한 플래그들이 수정되면 제일먼저 net_device의 set_multicase_list가
실행된다.그러므로 플래그가 변경되었을때 실행되야할 작업이 있다면
set_multicast_list에 넣도록한다.




* 이제 net_device필드들중에서 함수들을 살펴보도록하자.이미 몇가지는
  앞에서 언급했지만,다시한번 살펴보는 마음으로 살펴보도록하자.

블록이나 문자장치와 같이 네트웍장치도 장치를 다루는 여러가지 함수들이
존재한다. 문자나 블록장치는 file_operations구조체에 이값들을 세팅하나,
네트웍장치는 net_device구조체에 세팅한다. 이값들중 어떤값은 NULL로 세팅
할수도 있다. 그럼 하나하나 알아보도록하자.

int (*open)(struct net_device *dev);
  ifconfig eth0 up 과 같은 명령을 했을때 실행되는 함수이다. 여기서는 
  I/O ports , IRQ, DMA등의 리소스를 등록,usage count증가 등의 작업을 한다.

int (*stop)(struct net_device *dev);
  ifconfig eth0 down과 같은 명령을 했을때 실행되며, open에서 할당받은 리소스
  들을 반납하는 작업등을 한다.

int (*hard_start_xmit)(struct sk_buff *skb, struct net_device *dev)
  패킷을 외부로 전송하기위한 초기화 작업을 실행한다. 외부로 보내질 
  모든데이터는 sk_buff구조체에서 관리한다.


void (*tx_timeout)(struct net_device *dev);
  패킷을 전송하는것에 대한 timeout이다. 즉, 설정된 시간만큼 기다렸는데,
  패킷이 전송되지 못했다면, 재전송등, 어떠한 작업을 해주어야 한다.


struct net_device_stats *(*get_stats)(struct net_device *dev );

  장치에대한 정보를 요청할때 이함수가 실행된다. 예를들어...
  ifconfig, netstat -i 등이 되겠다.


int (*do_ioctl)(struct net_device *dev, struct ifreg *ifr, int cmd);
  ioctl명령어.사용하지 않을려면  NULL로 지정해도 상관없다.


void (*set_multicast_list)(struct net_device *dev);
  네트웍장치에대한 멀티캐스트 리스트(multicast list)가 변경되었거나 
  플래그가 변경되었을때 실행된다.



*이제 장치에대한 유용한 정보등을 포함한 잡다한 필드들을 알아보자.


unsigned long trans_start ;
unsigned long last_rx ;

  trans_start는 전송을 전송한 시간을 jiffies값으로 갖고있고, last_rx는 
  가장 최근에 패킷을 받은 시간을 jiffies값으로 갖고있다.


int watchdog_timeo ; 

  이값도 jiffies값으로 지정되며, 이 시간동안 전송되지 못했다면, tx_timeout
  함수가 실행된다.


void *priv ;
  장치의 private 데이터를 저장하는곳이다.


struct dev_mc_list *mc_list ;
int mc_count ;

  이두개의 필드는 멀티캐스트 전송을 관리하는데 사용된다.


여기서 설명하지 않은 net_device 필드들이 많이 있지만, 그것들은 네트웍 드라이
버에 사용되지 않는다. 또한 네트웍 드라이버에 사용된다 하더라도,
우리의 목표인 8139too.c에 사용되지 않는것은 배제하였다. 
필요한것은 필요한때... ^_^

그럼 다음시간에.... :)




          


『리눅스 학당-리눅스 강좌 / 연재 (go LINUX)』 737번
 제  목:[강좌]Network Device Driver만들기 [2]                       
 올린이:hetta   (이기천  )    01/10/21 17:42    읽음:109 관련자료 없음
 -----------------------------------------------------------------------------

########################################
#강좌: Network Device Driver만들기     #
#    (부재: 애인만들기)                #
########################################
1부: 네트워크 드라이버의 기본(2)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 이야기꾼:이기천(hetta@nownuri.net)


네트웍장치를 열기와 닫기
~~~~~~~~~~~~~~~~~~~~~~~~
이부분은 조금 중복되는내용도 있다. 정리하는기분으로 보면될것같다.
ifconfig eth0 111.111.111.111 up 이런식으로 했을때 다음과 같은 두가지 사건이
발생한다.
첫째로 ioctl(SIOCSIFADDR)로 인터페이스주소를 세팅한다. 이것은 드라
이버에서 구현하는것이 아니라 커널에서 하는작업이다. 
둘째로, ioctl(SIOCSIFFLAGS)를 사용하여 dev->flag를 IFF_UP으로 만든다.랜카드
를 쓸수있도록 활성화시키는것이다.  그다음 net_device에서 정의한 open함수를
실행한다.

open은 저번시간에도 설명했듯이, 장치가 필요로하는 각종 리소스들을 할당받는
다.그밖에도 여러가지 작업이 Open에서 실행된다.
첫째,하드웨어 주소를 dev->dev_addr 에 복사한다.

둘째, open에서는 인터페이스의 전송큐(interface's transmit queue)를 시작하기
위하여  netif_start_queue(struct net_device *dev); 를 호출하게 된다. 이작
업이 실행되어야 패킷을 외부로 전송할수가 있기때문이다.

ifconfig eth0 down 등의 명령을 할때 close함수가 실행되고  이때는,
netif_stop_queue()를 호출한다. 이것은 netif_start_queue()와 반대가 되는 
작업으로 패킷을 더이상 전송할수없도록 한다.


패킷전송하기
~~~~~~~~~~~~~
결국 네트웍 디바이스 드라이버는 무슨일을 할까? 
"잘받고", "잘보내고", 이러는것이 드라이버가 하는 일의 전부가 아닐까한다. 
나머지들은 이러한 작업들을 효율적으로 하도록 도와주는 역활을 하는것이다.
패킷을 받는것보다 보내는것이 더 쉽기때문에 보내는것을 먼저 알아보자.

패킷을 전송할필요가 있을때마다, hard_start_transmit 함수가 호출된다.이함수
가 하는일은 나가는큐(outgoing queue)에다가 데이터를 놓는것이다.

소켓버퍼(socket buffer)는 struct sk_buff 구조체로 만들어진다.
즉 하나의 패킷은 sk_buff구조체로 표현된다고 할수있다. 다음과 같이 생겼다
---------------------------------------------------------------------
struct sk_buff {
    /* These two members must be first. */
    struct sk_buff  * next;         /* Next buffer in list              */
    struct sk_buff  * prev;         /* Previous buffer in list          */

    struct sk_buff_head * list;     /* List we are on               */
    struct sock *sk;            /* Socket we are owned by           */
    struct timeval  stamp;          /* Time we arrived              */
    struct net_device   *dev;       /* Device we arrived on/are leaving by
*/

    /* Transport layer header */
    union
    {
        struct tcphdr   *th;
        struct udphdr   *uh;
        struct icmphdr  *icmph;
        struct igmphdr  *igmph;
        struct iphdr    *ipiph;
        struct spxhdr   *spxh;
        unsigned char   *raw;
    } h;

    /* Network layer header */
    union
    {
        struct iphdr    *iph;
        struct ipv6hdr  *ipv6h;
        struct arphdr   *arph;
        struct ipxhdr   *ipxh;
        unsigned char   *raw;
    } nh;

    /* Link layer header */
    union
    {
        struct ethhdr   *ethernet;
        unsigned char   *raw;
    } mac;

    struct  dst_entry *dst;

    /* 
     * This is the control buffer. It is free to use for every
     * layer. Please put your private variables there. If you
     * want to keep them across layers you have to do a skb_clone()
     * first. This is owned by whoever has the skb queued ATM.
     */
    char        cb[48];

    unsigned int    len;            /* Length of actual data            */
    unsigned int    data_len;
    unsigned int    csum;           /* Checksum                     */
    unsigned char   __unused,       /* Dead field, may be reused            */
            cloned,         /* head may be cloned (check refcnt to be sure).
*/
            pkt_type,       /* Packet class                 */
            ip_summed;      /* Driver fed us an IP checksum         */
    __u32       priority;       /* Packet queueing priority         */
    atomic_t    users;          /* User count - see datagram.c,tcp.c        */
    unsigned short  protocol;       /* Packet protocol from driver.         */
    unsigned short  security;       /* Security level of packet         */
    unsigned int    truesize;       /* Buffer size                  */

    unsigned char   *head;          /* Head of buffer               */
    unsigned char   *data;          /* Data head pointer                */
    unsigned char   *tail;          /* Tail pointer                 */
    unsigned char   *end;           /* End pointer                  */

    void        (*destructor)(struct sk_buff *);    /* Destruct function
*/
#ifdef CONFIG_NETFILTER
    /* Can be used for communication between hooks. */
        unsigned long   nfmark;
    /* Cache info */
    __u32       nfcache;
    /* Associated connection, if any */
    struct nf_ct_info *nfct;
#ifdef CONFIG_NETFILTER_DEBUG
        unsigned int nf_debug;
#endif
#endif /*CONFIG_NETFILTER*/

#if defined(CONFIG_HIPPI)
    union{
        __u32   ifield;
    } private;
#endif

#ifdef CONFIG_NET_SCHED
       __u32           tc_index;               /* traffic control index */
#endif
};

--------------------------------------------------------------------------

물론 이런 필드들을 다 알아야만 한다는것은 아니다. sk_buff구조체는 상당히 
복잡한 구조체이므로 이것을 다루는 다양한 함수들이 제공되며 뒤에서 설명할
것이다. struct sk_buff를 참조할때 skb를 사용하겠다.

hard_start_xmit이 호출될때 전달되는 socket buffer는 완벽한 패킷의 형태를
갖게된다.즉, 인터페이스(랜카드)에서 어떠한 데이터 수정도 필요가 없다.
그냥 비트의 연속이라고 생각하고 전송하면 된다. skb->data는 전송되어지는
패킷의 데이터를 가르킨다.skb->len은 길이를 나타내며 단위는 옥텟(바이트)이다.

snull에서는 snull_tx로 패킷전송을 처리한다.
snull에 대해 세부적으로 알려고 할필요는 없다고 본다. 그러나 전체적인 흐름은
실제 드라이버와 비슷하므로 여기서 보도록 하자.

저번시간에 나왔듯이 초기화시 snull_init에서 다음과 같은 세팅을 한다.

int snull_init(struct net_device *dev)
{
    ...
    dev->open            = snull_open;
    dev->stop            = snull_release;
    dev->set_config      = snull_config;
    dev->hard_start_xmit = snull_tx;
    dev->do_ioctl        = snull_ioctl;
    ...
}

여기서 dev->hard_start_xmit 이 snull_tx로 세팅되었음을 볼수있다.
그러므로 커널은 패킷을 전송해야할경우에 snull_tx를 호출하게 된다.
snull_tx의 구현을 살펴보도록 하자.

-----------------------------------------------------------------------
/*
 * Transmit a packet (called by the kernel)
 */
int snull_tx(struct sk_buff *skb, struct net_device *dev)
{
    int len;
    char *data;
    struct snull_priv *priv = (struct snull_priv *) dev->priv;

    len = skb->len < ETH_ZLEN ? ETH_ZLEN : skb->len;
    data = skb->data;
    dev->trans_start = jiffies; /* save the timestamp */

    /* 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에서는 가상장치이므로 하드웨어장치에 대한 실제적전송을 snull_hw_tx()로
표현하였다. 실제적 하드웨어 전송은 DMA등을 사용하여 전송한다.일단무시하자.



실제적 상황에서 발생할수있는 문제
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
hard_start_xmit함수는 net_device의 xmit_lock로 race condition문제를 해결함
을 저번시간에 보았다. 그러나 이전작업이 끝나면 곧바로 다시 호출된다.
메모리가 부족하거나, 다른 곳에서 그시간에 패킷이 전송중이라면 잠시 전송을
지연할필요가 있다. 이때 netif_stop_queue를 사용한다. 또한 전송이 가능해지면
netif_wake_queue를 호출한다.

void netif_wake_queue( struct net_devic *dev);

netif_wake_queue는 netif_start_queue와 비슷하나, netif_wake_queue는 다시
전송을 가능하게 해준다는것이 가능하다.
요즘나오는 하드웨어는 전송큐를 여러개 갖고있다. RTL8139에서도 4개의 전송큐
를 관리함을 확인할수있다.


전송 타임아웃
~~~~~~~~~~~~~~~
실제 네트웍상황은 예측불허이므로 여러가지 요인으로 패킷 전송을 확인하는 
ack가 안올수도 있다.
이럴경우, 만약 타임아웃값을 세팅해두지 않는다면 무한정으로 패킷전송 확인
패킷을 기다리게 된다. 
저번시간에도 나온내용이지만 리눅스는 타임아웃값을 세팅함으로 이것을 방지
한다. 
패킷이 dev->trans_start에 세팅한 값부터  dev->watchdog_timeo 값에 세팅한 
jiffies값만큼기다려도 패킷전송 확인이 안될경우 
dev->tx_timeout함수가 호출된다.

snull의 경우는 snull_tx_timeout함수가 호출된다.
다음과 같이 구현되었으며, 대충 흐름만을 알아보도록하자.

------------------------------------------------------------------
/*
 * Deal with a transmit timeout.
 */
void snull_tx_timeout (struct net_device *dev)
{
    struct snull_priv *priv = (struct snull_priv *) dev->priv;

    PDEBUG("Transmit timeout at %ld, latency %ld\n", jiffies,
                    jiffies - dev->trans_start);
    priv->status = SNULL_TX_INTR;
    snull_interrupt(0, dev, NULL);
    priv->stats.tx_errors++;
    netif_wake_queue(dev);
    return;
}   
------------------------------------------------------------------

타임아웃이 발생하면 놓친 인터럽트를 채우기위하여 snull_interrupt를 호출
한다. 그리고 다시한번 전송을 시도하게 된다.
( when a timeout happens in snull, the driver calls snull_nterrupt 
to fill in the "missing" interrupt and restarts the transmit queu 
with netif_wake_queue.)



패킷 받기
~~~~~~~~~~
이제 패킷을 어떻게 받을것인가를 알아보도록하자.패킷을 받는것은 전송하는것
보다 조금 복잡하다. 사실 실제 하드웨어에서 하는작업은 많이 틀리므로 
snull의 것을 보는것이 별도움은 안되겠지만, 흐름만을 알아보자.
먼저 랜카드에서 받은 패킷을 저장할 sk_buff를 할당해야한다.패킷을 받는것은
interrupt를 통해서이다. 즉, 랜카드에서 어느 일정량이상 데이터가 오면, 랜카드
가 인터럽느를 발생한다.그러면 드라이버에서 작업을 처리하는식이다. 어느 일정
량이라는것은 임으로 설정이 가능하다. 
물론 polling 방식으로 패킷받기를 처리하는것도 가능하나 효율성면에서
interrupt를 이용한 방식이 유리하다.대부분하드웨어가 interrupt를 사용한다.

-----------------------------------------------------------------
/*
 * Receive a packet: retrieve, encapsulate and pass over to upper levels
 */
void snull_rx(struct net_device *dev, int len, unsigned char *buf)
{
    struct sk_buff *skb;
    struct snull_priv *priv = (struct snull_priv *) dev->priv;
    
    /*              
     * The packet has been retrieved from the transmission
     * medium. Build an skb around it, so upper layers can handle it
     */
    skb = dev_alloc_skb(len+2);
    if (!skb) {
        printk("snull rx: low on mem - packet dropped\n");
        priv->stats.rx_dropped++;
        return;
    }
    skb_reserve(skb, 2); /* align IP on 16B boundary */
    memcpy(skb_put(skb, len), buf, len);

    /* 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 += len;
    netif_rx(skb);
    return;
}
-----------------------------------------------------------------
먼저 dev_alloc_skb를 사용하여 skb를 위한 공간을 마련한다. 여기서 +2는
16비트로 정렬하기 위하여 필요한 작업이다.왜냐하면 이더넷헤더는 14비트
이기때문이다. 그다음 skb_reserve로 앞에 2비트를 예비해둔다. 그다음 memcpy를
사용하여 buf의 데이터를 복사하게 된다. skb_put()은 skb의 뒤에 데이터를
붙이고자 할때 사용한다. 앞에서 2비트를 예비했으므로 그다음에 이더넷헤더
14바이트 등이 들어가서 16비트로 정렬(align)된다.
참고로, dev_alloc_skb()는 내부적으로 kmalloc를 atomic priority로 호출하므로
인터럽트 발생중일때 사용이 가능하다.

그다음 체크섬을 어떻게 처리할것인가를 세팅한다. snull은 가상장치라서 
CHECKSUM_UNNECESSARY을 세팅하나 신경쓰지 말기 바란다.
3가지가 존재하는데 다음과 같다.

CHECKSUM_HW        - 하드웨어로 체크섬을 관리한다.
CHECKSUM_NONE      - 소프트웨어로 체크섬을 관리한다. 디폴트 값이다. rtl8139
CHECK_UNNECESSARY  - 필요없음

그리고 protocol은 eth_type_trans로 간단히 세팅이 가능하다.
최종적으로 netif_rx()를 호출하여서 윗단계(ex: IP layer)로 패킷을 전송한다.


인터럽트 핸들러
~~~~~~~~~~~~~~~~
패킷을 처리할때 인터럽트를 이용한다고했다. 이제 인터럽트 핸들러를 살펴보자.
네트웍장치에서 인터럽트는 패킷이 왔을때와 패킷전송이 완료되었을때 발생되며,
각각의 구분은 랜카드의 status register를 읽음으로서 알수가 있다. 
snull은 가상장치이므로 priviate데이터 영역에 이상태정보를 저장하나,이것은 
실제 필드에서 쓰는방법이 아니다.! 그냥 무시하자.

-------------------------------------------------------------------
/*
 * The typical interrupt entry point
 */
void snull_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
    int statusword;
    struct snull_priv *priv;
    /*
     * As usual, check the "device" pointer for shared handlers.
     * Then assign "struct device *dev"
     */
    struct net_device *dev = (struct net_device *)dev_id;
    /* ... and check with hw if it's really ours */

    if (!dev /*paranoid*/ ) return;

    /* Lock the device */
    priv = (struct snull_priv *) dev->priv;
    spin_lock(&priv->lock);

    /* retrieve statusword: real netdevices use I/O instructions */
    statusword = priv->status;
    if (statusword & SNULL_RX_INTR) {
        /* send it to snull_rx for handling */
        snull_rx(dev, priv->rx_packetlen, priv->rx_packetdata);
    }
    if (statusword & SNULL_TX_INTR) {
        /* a transmission is over: free the skb */
        priv->stats.tx_packets++;
        priv->stats.tx_bytes += priv->tx_packetlen;
        dev_kfree_skb(priv->skb);
    }

    /* Unlock the device and we are done */
    spin_unlock(&priv->lock);
    return;
}

-------------------------------------------------------------------
만약 패킷을 받았다면 위에서 설명한 snull_rx를 호출해서 처리하고,
패킷전송완료 인터럽트라면 통계정보를 세팅한다음 dev_kfree_skb로 skb를 위해
할당된 메모리를 해제한다.즉, ack가 올때까지는 메모리에 패킷을 위한 메모리가
할당된채로 남아있는것이다.


링크의 상태를 변경하기
~~~~~~~~~~~~~~~~~~~~~~~
(RTL8139에서 사용되진 않는다. 참고만하자.)
인위적으로 랜상에 carrier가 존재하지 않는것처럼 할수있다는것이다.
즉, 랜선을 뽑았다면 carrier가 사라지고, 다시 꽂으면 carrier가 있게된다.
디폴트로 랜카드에 carrier가 존재한다고 가정되나,변경도 가능하다.

netif_carrier_on(strut net_device *dev);  // carrier on 으로!
netif_carrier_off(strut net_device *dev); //carrier off로!
netif_carrier_ok(strut net_device *dev);  //현재상태확인.

내부적으로 다음과 같이 세팅되어있을뿐이다. 그냥 참고만하자.
set_bit은 atomic하게 비트를 세팅하는 함수이다.

include/linux/netdevice.h -----------------------------------------
611 static inline void netif_carrier_off(struct net_device *dev)
612 {
613         set_bit(__LINK_STATE_NOCARRIER, &dev->state);
614 }
-------------------------------------------------------------------


소켓 버퍼(socket buffer)
~~~~~~~~~~~~~~~~~~~~~~~~~
소켓버퍼를 많이 사용하였으나 자세한설명은 지금까지 생략되었다.
sk_buff는 리눅스의 네트웍 코드에서 가장중심이 되는 구조체이다. 여러개의
필드들과 sk_buff 를 다루는 다양한 유틸리티 함수들을 알아보도록 하자.

 중요 필드
==========================

중요필드를 먼저 알아보자.

struct net_device *rx_dev ; //소켓 버퍼를 받는장치
struct net_device *dev ;    //소켓버퍼를 보내는 장치


     // transport layer 헤더  ex) tcp, ucp...
    union
    {
        struct tcphdr   *th;
        struct udphdr   *uh;
        struct icmphdr  *icmph;
        struct igmphdr  *igmph;
        struct iphdr    *ipiph;
        struct spxhdr   *spxh;
        unsigned char   *raw;
    } h;

    // Network layer 헤더  ex) ip ...
    union
    {
        struct iphdr    *iph;
        struct ipv6hdr  *ipv6h;
        struct arphdr   *arph;
        struct ipxhdr   *ipxh;
        unsigned char   *raw;
    } nh;

    // Link layer 헤더  ex) ethernet
    union
    {
        struct ethhdr   *ethernet;
        unsigned char   *raw;
    } mac;

위와같이 네트워크의 각 계층에 해당하는 헤더들이 정의되어있다.

<참고>---------------------------------------------------------------
예를들어서 source ip address , dest ip address 를 알고싶다고하자.
이때는 struct tcphdr을 얻어야하므로 그냥 skb->h.th 를 참조하면 된다.

struct tcphdr 은 include/linux/tcp.h 에서 다음과 같이 정의한다.


struct tcphdr {
    __u16   source;
    __u16   dest;
    __u32   seq;
    __u32   ack_seq;
#if defined(__LITTLE_ENDIAN_BITFIELD)
    __u16   res1:4,
        doff:4,
        fin:1,
        syn:1,
        rst:1,
        psh:1,
        ack:1,
        urg:1,
        ece:1,
        cwr:1;
#elif defined(__BIG_ENDIAN_BITFIELD)
    __u16   doff:4,
        res1:4,
        cwr:1,
        ece:1,
        urg:1,
        ack:1,
        psh:1,
        rst:1,
        syn:1,
        fin:1;
#else
#error  "Adjust your <asm/byteorder.h> defines"
#endif
    __u16   window;
    __u16   check;
    __u16   urg_ptr;
};


그러므로....
 source ip address를 알려면  skb->h.th.source 
 dest   ip address를 알려면  skb->h.th.dest 
로 하면 간단히 알수가 있다.
------------------------------------------------------------------------

그다음 조금 복잡한 변수들이 선언되어있다.

unsigned char *head ;
unsigned char *data ;
unsigned char *tail ;
unsigned char *end ;


필자도 좀 엇갈렸는데, 권수호님의 글에있는 그림을 보고 단번에 이해가 되었다.
여기에 그리기는 힘들므로 그림은 생략한다. 시간이 허락하시는분은 
꼭! 살펴보기를 바란다.  간단히 설명하면 다음과 같다.

위의 4개의 변수는 결국 버퍼의 주소를 저장하는 포인터 변수이다.
버퍼는  head 로부터 end 까지가 되며, 실제로 데이터가 있는곳은 data로부터
tail까지가 된다.간단히 그려보면 다음과 같다.

 +------------------------------------------------------------------+
 |        |                                         |               |
 |        |      실제 전송할 혹은 받은 데이터       |               |
 +------------------------------------------------------------------+
head     data                                      tail            end 

그러므로 사용가능한 버퍼크기는 skb->end - skb->head 가 되며,
현재사용되는 데이터의 크기는   skb->tail - skb->data 가된다.


unsigned long len ; 

이 값은 데이터의 길이로서 skb->tail - skb->data  가된다.


unsigned char ip_summed ;

체크섬을 표시하는 정책으로서 위에서 설명하였다.


unsigned char pkt_type ;
 
이것은 패킷타입으로서 PACKET_HOST , PACKET_BROADCAST , PACKET_MULTICAST,
PAKCET_OTHERHOST중의 하나를 세팅할책임이있다.그러나 실질적인 세팅은
다음과 같이 eth_type_trans()에서 세팅하므로 드라이버 개발자는 신경을 
쓰지 않아도 된다.

         skb->protocol = eth_type_trans (skb, dev);



소켓버퍼를 다루는 다양한 함수들
=================================
소켓버퍼를 다루는 다양한 함수들이 이미 정의되어있으므로 소켓버퍼를 쉽게 
사용할수가 있다.

struct sk_buff *alloc_skb(unsigned int len, int priority );
struct sk_buff *dev_alloc_skb(unsigned int len ) ;

dev_alloc_skb는 GFP_ATOMIC권한으로 alloc_skb를 실행시킨다.그리고 skb->data와
skb->head사이에 공간을 조금 예비해준다. 이 공간은 network layer에서 최적화에
사용되는것으로서 드라이버에서는 건들지 말아야한다. 보통 드라이버 개발에서는
dev_alloc_skb를 사용한다.


void kfree_skb( struct sk_buff *skb);
void dev_kfree_skb(struct sk_buff *skb);  <<==이걸 쓰자!

할당받은 공간을 해제한다.드라이버 개발자는 dev_kfree_skb를 사용해야한다.



unsigned char * skb_put(struct sk_buff *skb, int len );
unsigned char * __skb_put(struct sk_buff *skb, int len );

unsigned char * skb_push(struct sk_buff *skb, int len );
unsigned char * __skb_push(struct sk_buff *skb, int len );

skb_put은 버퍼의 tail 뒤에 붙이는것으로,변경되는값은 tail,len 이 된다.
tail은 당연히 늘겠고, len도 늘것이다.
이것은 버퍼를 늘리기전의 주소를 리턴하므로 snull에서도 이것을 사용하여
memcpy로 복사했다. 

skb_push는 버퍼의 앞에다가 붙이는것으로 변경되는것은 data , len이다.
data는 당연히 줄겠고, len은 늘것이다.
리턴값은 방금 만들어진 data값이 된다.

리턴값을 살펴보자면, memcpy로 복사하기 쉬운 값을 각각 리턴함을 알수있다.

__를 붙인것은 그냥 간단히 기능은 똑같은데 데이터를 버퍼에 복사할
때 공간이 맞는지를 체크하는 열할을 더 한다.


int skb_tailroom(struct sk_buff *skb);
int skb_headroom(strut sk_buff *skb);

지금 버퍼에 얼마나 공간이 남았나를 리턴하는것으로....
skb_tail은 뒷쪽공간을, skb_headroom은 앞쪽 남은 공간을 리턴한다.
하지만 실제적으로 쓸일은 없을것이다. 왜냐하면 skb_put ,skb_push를 호출할때
자동으로 체크해주기 때문이다.

void skb_reserve(struct sk_buff *skb , int len );
unsigned char *skb_pull(struct sk_buff *skb , int len );

skb_reserve는 앞쪽에 공간을 확보하므로 data,tail가 변한다. snull에서도 이것
을 사용하여 16비트정렬문제를 해결함을 위에서 보았다.

skb_pull은 패킷의 헤더로부터 데이터를 제거한다. 드라이버는 이작업이 필요가
없다. 그냥 참고만 하기 바란다. 당연히 skb->len,skb->data가 감소할것이다.
이것이 바로 ethernet header,ip header, tcp header, udp header 등이 
벗겨지는 원리이다.즉, 불필요한 메모리 복사가 필요없이 간단히 skb에서
몇몇 변수가 조정해주면 되는것이다.



이번강의를 마치며
~~~~~~~~~~~~~~~~~
이번시간까지 snull에 대해서 알아보았다. 
결국 네트웍 디바이스 드라이버란 잘받고,잘보내면 된다는것을 기억했으면 한다.
그러기 위하여, 인터럽트를 사용하며, 일정량이상의 데이터가 오면 랜카드가
인터럽트를 걸어서 "나좀 처리해줘.." 라고 말하며, 패킷 전송이 완료되어도
인터럽트가 발생하여서 할당된 버퍼를 해제하는등의 일을 함을 알아보았다.
또한 소켓 버퍼라는 struct skb_buff도 알아보았다.

실제 네트웍 디바이스 드라이버만드는 것은  이러한  이론보다는..
랜카드등의 하드웨어의 datasheet을 더많이 봐야할것이다.
하지만 여기서 설명한 내용들이 뼈대가 될것임은 분명하다. 

이글로 인해 독자들이 또 다른 재미(funny)를 만끽할수만 있다면 
더 바랄것이 없겠다.

그럼 담시간에 ^_^
반응형

'Network' 카테고리의 다른 글

디바이스 드라이버  (0) 2010.05.10
LDS2000의 네트워크 디바이스 드라이버 분석  (0) 2010.05.10
AAA와 RADIUS  (0) 2010.05.10
ns2 디버깅  (0) 2010.05.10
ubuntu 8.04에 ns2 2.29, PMIPv6 패치 설치하기  (1) 2010.05.02
Posted by pmj0403