以snull为例分析linux网卡驱动的技术文档[转载]二

OPENING AND CLOSING

打开和关闭

       我们的驱动能够在模块再如或者核心引导的时候探测出接口。下一步是给接口指定一个地址,以便驱动能够通过他来交换数据。打开和关闭接口是由ifconfig命令完成的。

       ifconfig给端口指定一个地址的时候,它执行两个任务。首先它通过 ioctl(SIOCSIFADDR) (Socket I/O Control Set InterFace ADDRess). 然后再在 dev->flag 通过 ioctl(SIOCSIFLAGS) (Socket I/O Control Set InterFace FLAGS)设置IFF_UP位,来打开接口。

就驱动程序所涉及而言,ioctl(SIOCSIFADDR)  dev->pa_addr, dev->family, dev->pa_mask, and dev->independent, 但是没有驱动的函数被调用,该任务是独立于设备的, 由内核来执行它。后一个命令  ioctl(SIOCSIFLAGS) 为设备调用 open 方法。

       与此类似,当接口关闭时,ifconfig  ioctl(SIOCSIFLAGS) 来清 IFF_UP,并且调用 stop 方法。   

       这两个方法在成功时返回 0 ,出错时通常返回负值。

       至少就实际的代码来说,驱动必须执行与字符设备和块设备相同的任务。 open 请求它需要的任何系统资源并且让网络接口建立起来;stop 关闭掉网络接口并且释放系统资源。

       最后还有一件事情要做,如果驱动不使用共享中断的话(例如为了与旧的内核兼容)。内核输出一个 irq2dev_map   数组,该数组由中断请求号(IRQ number)来定地址,并且保存合法的指针;驱动可能需要该数组来映射中断号到一个指向 struct device 指针。这是在不使用中断句柄的 dev_id 参数的情况下,支持单驱动多接口的唯一途径。

       另外,网卡的硬件地址需要从板上拷贝到 dev->dev_addr 中,才能使接口与外界进行任何通讯联系。硬件地址根据驱动的意愿在探测或者打开的时候指定。 snull 软件接口在 open 的时候指定它,它仅仅指定两个ASCII字符串作为硬件号。第一个字节是空。      

       openclose的代码实现可以参看fops->openfops->close,代码如下:

      

       int snull_open(struct device *dev)

       {

              int i;

             

              /* request_region(), request_irq(), .... (like fops->open */

             

       #if 0

              /*

               * We have no irq line, otherwise this assignment can be used to

               * grab a non-shared interrupt. To share interrupt lines use

               * the dev_id argument of request_irq. Seel snull_interrupt below.

               */

              irq2dev_map[dev->irq] = dev;

       #endif

      

              /*

               * Assign the hardware address of the board; use "\0SNULx", where

               * x is 0 or 1. The first byte is '\0': a safe choice with regard

               * to multicast.

               */

              //给接口指定硬件地址,ETH_ALEN=6 octets

              for (i=0; i < ETH_ALEN; i++)

                     dev->dev_addr[i] = "\0SNUL0"[i];

                     //都先赋值为 "\0SNUL0"

              dev-dev_addr[ETH_ALEN-1] += (dev - snull_devs); /* the number */

              //再修改最后一个字节(octet),区分 "\0SNUL0" or "\0SNUL1"

             

              dev->start = 1;

              dev->tbusy = 0;

              MOD_INC_USE_COUNT;//模块引用计数+1

              return 0; 

       }

       正如所看到的,某些域在 device 就够中做了修改。start 指明接口已经准备好,tbusy 断言传送者不忙(也就是说,内核可以发出一个包)。

       stop 方法刚好是把 open 的错做反过来,出于这个原因,实现 stop 的函数通常叫做 close.

 

       int snull_release(struct device *dev)

       {

              /* release ports, irq and such--like fops->close */

             

              dev->start = 0;

              dev->tbusy = 1; /* can't transmit any more */

              MOD_DEC_USE_COUNT;

              /* if irq2dev_map was used, zero the entry here */

              return 0;

       }

 

PACKET TRANSMISSION

包的传输

       网络接口最重要的任务就是数据的发送核接收。这里将从发送开始讲,因为它比接收稍微易懂一点。      

       每当内核需要传送一个数据包的时候,它调用 hard_start_transmit 方法把数据放到外发队列中。内核处理的每个包都容纳在一个套接字缓冲区结构(socket buffer structure)该结构为 (struct sk_buff),其定义在文件中。该结构的名字从 socket 得来,socket是用来表示一个网络连接的 Unix 抽象。即使接口与套接字(socket)没有什么关系,每个网络包在高层网络中也要属于一个套接字,任何套接字的输入/输出缓冲区也都是 struct sk_buff 结构的()表。相同的 sk_buff 结构也用来在整个 Linux 网络子系统中存放网络数据。而一个socket缓冲区仅仅是与接口相关的一个包而已。

       一个指向 sk_buff 的指针通常叫做 skb, 在下面的代码和解释中都将提到。socket buffer 是一个复杂的结构,内核提供了很多作用于它的函数。这些函数稍后在"The Socket Buffer"中描述现在只要知道关于 sk_buff 的一些基本情况我们就可以来写驱动了,并且在钻研繁琐的细节前应该先了解它是怎样工作的。      

       被传给 hard_start_xmit  socket buffer 包含物理包,拥有传输级的包头。接口不必改动被传输的数据。 skb->data 指向被传输的包,skb->len 是它的字节长度。

       snull 包的传输代码列在了下面,物理传输机在单独的一个函数中,因为每个接口驱动必须根据它们驱动的特定硬件来实现它。

      

       int snull_tx(struct sk_buff *skb, struct device *dev)

       {

              int len, retval=0;

              char *data;

             

              if (dev->tbusy) {    /* shouldn't happen */

                     retval = -EBUSY;

                     goto tx_done;

              }

       //下面的if语句对于2.2.x的内核已经没用了!主要是dev_tint()用不上了

              //if (skb == NULL) {

              //     PDEBUG("TINT FOR %P\N",dev);

              //     dev_tint(dev);        /* we are ready to transmit */

              //     return 0;

              //}

             

              dev->tbusy = 1;                   /* transmission is busy */

             

              len = ETH_ZLEN < skb->len ? skb->len : ETH_ZLEN; /*minimum len */

                    ~~~~~~~~定义在if_ether.h里面,值等于60

              data = skb->data;

              dev->trans_start = jiffies;      /* save the timestamp */

             

              /* actual deliver of data is device-specific, and not shown here */

              snull_hw_tx(data, len, dev);

//                       ~~~~前面定义一个字符指针data就是为了在这里传递参数。

//                       snull_hw_tx()函数却没有做讲解,还是要分析源代码。

              /* 为什么不讲snull_hw_tx()的实现?很显然,snull_hw_tx的实现是跟snull

               * 的设计相关的,不同于这里所讲解的其它部分,它们都是独立于特定设备的,

               * 有普遍性的和示例性的。不过snull_hw_tx()的实现还是很巧妙的,充分

               * 体现了snull的设计思想和编码上的技巧,值得好好学习。呵呵!

               */

             

       tx_done:

              dev_kfree_sdb(sdb);      //原来是像下面这么写,但是内核已经变了!

              //dev_kfree_sdb(sdb, FREE_WRITE);  /* release it */

              return retval;                 /* zero == done; nonzereo == fail */

       }

       传输函数就这样做一些包的"健全性"(sanity)检查,然后经由硬件相关的函数发送数据。当一个中断发出"传输完成"信号以后, dev->tbusy 被清除。

 

PACKET RECEPTION

包的接收

       从网络接收数据比传输要微妙一些,因为一个 sk_buff 必须在一个中断处理含数内被分配,并且传递给上面协议层。

       接收数据包的最好的方式就是通过中断处理,除非是像 snull 这样的纯软件接口,或者一个 loopback 接口。当然写一个查询的驱动是可能的,在正式的内核中也确实有一些是这样的,不过中断驱动操作确实要更好一些,不论在数据吞吐还是计算需求上,并且绝大多数的网络接口是采用的这种方式。   

       snull 的实现隔离了硬件的细节。函数 snull_rx 在硬件收到一个包且该包已经在内存中的时候被调用。 snull_rx因此得到一个指针指向包的数据和长度。这个函数的唯一功能就是把包和一些附加信息传给网络更高层的代码。那些代码与数据指针和数据长度的获得方式是无关的。

 

       void snull_rx(struct device *dev, int len, unsigned char *buf)

       {

              struct sk_buff *skb;

              struct snull_priv *privp = (struct snull_priv *)dev->priv;

              /*

               * The packet has been retriveved 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 mem\n");

                     return;

              }

              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*/

              netif_rx(skb);

              // net/core/dev.c

              //void netif_rx(struct sk_buff *skb)

              return;

       }

 

       /* IN skeleton.c:

        * If any worth-while packets have been received, dev_rint()

        * has done a mark_bh(NET_BH) for us and will work on them

        * when we get to the bottom-half routine.

        */

       这个函数十分通用,可以作为任何网络驱动的模版,但是在有把握的复用这些代码段之前,还有些必要的解释。   

       注意到缓冲区分配函数要知道数据长度。这样做避免了调用 kmalloc 浪费内存。 被调用的分配函数dev_alloc_skb具有原子优先级(atomic priority,原子操作是对于临界资源的,不会被中断打断), 在有中断时也可以安全的使用。内核也提供 socket-buffer 分配的调用接口,这些将在后面讲到。

       一旦得到了合法的 skb 指针,memcpy 调用就会把包的数据拷贝到缓冲区。skb_put 函数修改缓冲区中的数据结束指针    (end-of-data),并且返回一个指针指向新创建的空间。

       不足的是,没有足够的包头信息来正确处理网络层--dev  protocol 域在缓冲区传给上层之前必须指定。我们需要     指明包的校验和应该怎样计算。skb->ip_summed 域给出了可能的计算的方法。   

       CHECKSUM_HW

       网卡板上的硬件执行校验。一个例子就是 Sparc HME 接口。

       CHECKSUM_NONE

       校验和计算全部由软件完成。对于新分配的缓冲区,这是默认值。

       CHECKSUM_UNNECESSARY

       不做任何校验。这是 snull  loopback 的做法。

       校验和的选项和 ip_summed 1.2版本内核就没有了。

       最后,驱动修改它的静态计数器以纪录一个包被接收了。这里提及的静态结构是由几个域组成的,其中最重要的是       rx_packets  tx_packets, 它们用来纪录接收的和发送的包的数目。所有的域在"Statiistical Information"一节中有全面的讲述。

       接收的最后一步是由 netif_rx 执行的,它把 socket buffer 传给上面的层。

 

INTERRUPT-DRIVEN OPERATION

中断驱动操作

       大多数的硬件接口都是由中断处理函数来控制的。接口在两种可能的事件下给处理器发出中断信号:一个新的包到来和一个包传输的完成。这个普遍方法并不是总被应用,但是它的确适用于所有的非同步的包传输。PLIP  PPP 接口   就是步使用这个普遍方法的例子。他们也要处理同样的两种事件,但是低层(low-level)的中断处理就稍有不同了。

       一般的中断处理例程通过检查物理设备上的状态寄存器来区别中断是包到来中断还是通知包传输完成的中断。snull接口工作的与之类似,但是它的状态字在 dev->priv 中。一个网络接口的中断处理函数大概像下面这样:

 

       void snull_interrupt(int irq, void *dev_id, struct pt_regs *regs)

       {

              int statusword;

              struct snull_priv *privptr;

              /*

               * As usual, check the "device" pointer for shared handlers.

               * Then assign "struct device *dev"

               */

       #if 0

              /* This is the way to do things for non-shared handlers */

              struct device *dev = (struct device *)(irq2dev_map[irq]);

       #else

              /* Otherwise use this SA_SHIRQ-safe approach */

              struct device *dev = (struct device *)dev_id;

              /* ... and chick with hw if it's really ours */

       #endif

      

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

              dev->interrupt = 1; /* lock */

              /* retrieve statusword: real netdevices use inb() or inw() */

//                                               ~~~~~~~~~~~~~~~~~~~~~~~~记住它是干什么的!

              privptr = (struct snull_priv *)(dev->priv);

              statusword = privptr->status;

              if (statusword & SNULL_RX_INTR) {

                     /* send it to snull_rx for handling */

                     snull_rx(dev, privptr->packetlen, privptr->packetdata);

              }

              if (statusword * SNULL_TX_INTR) {

                     /* a transmission is over: tell we are no longer busy */

                     privptr->stats.tx_packets++;

                     dev->tbusy = 0;

                     mark_bh(NET_BH);      /* Inform upper layers. */

                     // NET_BH: include/linux/interrupt.h

                     //mark_bh:? extern inline void mark_bh(int nr)

              }

              dev->interrupt = 0; /* release lock */

              return;

       }

       处理函数的第一个任务是得到正确的指向 struct device 的指针。参数可以用 irq2dev_map[](假定你在打开设备时已经指定了值),或者得到的 dev_id指针。早于1.3.70的版本必须用前者,因为后者还没有提供。

       在处理"传输结束"情况时,接口首先清 dev->tbusy 确认传输已经完成,然后掩住网络底层例程(masks the network       bottom-half routine). net_bh 真正跑起来的时候,它总是图发送任何未完成的包。

       相反的,包的接收不需要任何的特殊处理,调用 snull_rx 就足够了。     

       在实际中,当接收函数调用 netif_rx 的时候,它唯一真正的操作就是标记 net_bh. 内核利用 bottom-half (记为bh)做了所有网络相关的工作。于是,一个网络驱动总是要将它的中断处理函数声明为慢速的,因为 bh 将要很快执行。

 

THE SOCKET BUFFERS

套接字缓冲区

       前面已经讲了根网络接口相关的大多数问题,下面几节会细致的讲解一下 sk_buff 结构的设计。既要介绍结构中主要的域,还有作用于 socket buffer的函数。

       尽管没有绝对的必要来理解 sk_buff 内部的必要,能够看看它的内容,对跟踪问题和优化代码还是很有帮助的。   例如,看看 loopback.c ,你就能发现基于了解 sk_buff 的内部而做的优化。    

       下面并不描述整个结构,只是对一个驱动可能用到的域。想了解更多可以看,那里定义了该结构和函数的原型,更系的东西可以在内核的源码中找到。

      

The Important Fields

重要的域

       该结构中的重要的域是写驱动程序可能需要的,下面列出的没有特别的顺序关系:

       struct device *dev;

       接收或者发送这个缓冲区的设备。

       __u32 saddr;

       __u32 daddr;

       __u32 raddr;

       源、目的和路由地址,利用IP协议。路由地址是包到达目的所必须经过的第一跳。这些域在包传输之前必须  置好,接收的时候不需要置。

       unsigned char *head;

       unsigned char *data;

       unsigned char *tail;

       unsigned char *end;

       这些指针用来定位包中的数据,head 指向已分配的空间的起始,data 指向合法的字节的起始(且通常比  head要大一点),tail 是合法的字节的结束,end 指向 tail 所能达到的最大的地址。另一种解释就是可用的缓冲区空间为(skb->end - skb->head),当前已经用的数据空间是(skb->tail - skb->data)。这种灵巧的方式在内核的1.3版本以后就采用了。

       源、目的和路由地址,利用IP协议。raddr(路由地址)是包到达目的所必须经过的第一跳这些域在包传输之前指定好,接收的时候不需要再指定。一个到达了 hard_start_xmit 方法的外发的包已经有一个合适的建立起来的硬件报头,那里反映了"第一跳"的信息。

       unsigned long len;

       数据本身的长度,即(skb->tail - skb->head).

       unsigned char ip_summed;

       在有到达包的时候驱动会置该域,它是用来计算TCP/UDP校验和的,并且在前面的"Packet Reception"包的接收中描述过。

       unsigned char pkt_type;

       该域是在内部使用的,用来传递到来得包。驱动程序负责把它设置为PACKET_HOST(说明这个包是我的),PACKET_BROADCAST, PACKET_MULTICAST, 或者PACKET_OTHERHOST(说明这个包不是我的)。以太网卡的驱动并不是明确的修改pkt_type ,因为有  eth_type_trans 替它来做。

       union { unsigned char *raw; [...]} mac;

        pkt_type 一样,该域用来处理到达的包,并且在接收的时候必须设置。对于以太网驱动来说,eth_type_trans 函数会照顾到这一点。而非以太网驱动应该设置skb->mac.raw 指针。

       该结构中其他的域我们不特别关心,它们是用来保持缓冲区的表,记录属于占有缓冲区的套接字的内存,等等。

      

Functions Acting on Socket Buffers

作用于它的函数

       使用 sock_buff 结构的网络设备通过正式的接口函数作用于该结构。有许多对套接字缓冲区有操作的函数,最有趣的是下面这些:

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

       分配缓冲区。alloc_skb 函数分配一个缓冲区并且初始化 skb->data  skb->tail  skb->head.

       void kfree_skb(struct sk_buff *skb);

       void dev_kfree_skb(struct sk_buff *skb);

       释放一个缓冲区。kfree_skb 调用是在内核的内部使用的。驱动程序应该使用 dev_kfree_skb(当然,它就是实现kfree_skb的一个宏), 万一占有缓冲区的套接字需要再次使用这个缓冲区的时候,该函数可以正确的处理缓冲区得上锁问题。

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

       这个内联(inline)函数修改 sk_buff 结构的 tail  len 域,它是用来向缓冲区末尾追加数据的。函数的返回值是前一个 skb->tail 的值(换句话说,它指向新创建的数据空间)。有些驱动程序调用 ins(ioaddr, skb_put(...)) 或者 memcpy(skb_put(...), data, len) 来使用返回值。该函数和下面的一些在1.2版本以后就不提供了。

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

       void skb_reserve(struct skb_buff *skb, int len);  //存储skb?

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

内核也定义了其他一些作用于 socket buffer 的函数,但它们在更高的层中用到,驱动程序并不需要。

 

ADDRESS RESOLUTION

地质解析

       在以太网通信中最引人注意的问题就是联合硬件地址(接口的唯一标识号)和IP地址。下面讲述三种情况:ARP地址解析协议),无ARP的以太网头,非以太网头。

 

Using ARP with Ethernet

利用ARP的以太网

       通常的方法就是通过地址解析协议ARPARP由内核来管理,以太网接口不需要做对ARP的特别支持。只要在打开的时候正确的指定了 dev->addr dev->addr_len, 驱动程序就不必担心从IP值到物理地址的解析问题;ether_setupdev->headerdev->rebuild_header 指定正确的设备方法。  

       当一个包建立的时候,以太网报头放置在dev->hard_header, 之后填到dev->rebuild_header中,它用ARP协议来映射IP值到地址。驱动程序的编写不必关心该过程的细节。

 

Overriding ARP

不考虑ARP

       plip这样简单的点对点网络接口中,避免来回传送ARP包的开销是用以太网包头可能更加受益。下面的软件示例Snull就是这么做的:

 

       int snull_rebuild_header(void *buff, struct device *dev,

                            unsigned long dst, struct sk_buff *skb)

       {

              struct ethhdr *eth = (struct ethhdr *)buff;

             

              memcpy(eth->h_source, dev->dev_addr, dev->adr_len);

              memcpy(eth->h_dest, dev->dev_addr, dev->addr_len);

              eth->h_dest[ETH_ALEN-1]   ^= 0x01;  /* dest is us xor 1 */

              return 0; 

       }

当一个包在接口被接收的时候,硬件包头只被eth_type_trans使用,正如在前面snull_rx中看到的函数调用:

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

      

Non-Ethernet Headers

非以太网包头

当包在连接的另一头被取下时,接收函数应该正确的设置skb->protocol, skb->pkt_type, and skb->mac.raw

       skb->mac.raw=skb->data;

       skb_pull(skb,dev->hard_header_len);

 

LOAD-TIME CONFIGURATION

载入时的配置

有两个值需要用户来填写以配置接口,新的网络模块需要遵循以下两个标准:

io=

       为接口设置I/O端口的基地址。如果多于一个接口,用逗号分隔。

irq=

       设置中断号。多于一个时也用逗号分割。

       用户有两个以太网接口 two_eth interfaces,可以用下面的命令来载入模块:

       insmod own_eth.o io=0x300,0x320 irq=5,7 

       如果被定为0值的话,io=  irq= 选项都会探测接口。这样用会就可以强迫探测。多数的驱动一般只探测一个接口如果用户不再指出任何选项的话,但有时对模块的探测是不允许的(参看ne.c中关于探测NE2000设备的注释)。

       设备驱动程序中应该做上面描述的工作,在ISA设备中典型的实现可以参看下面,我们假设驱动可以支持到四个接口:

      

       static unsigned int io[] = {0, ~0, ~0 ~0}; /* probe the first */

       static unsigned int irq[] = {0, 0, 0, 0}; /* probe if unknown*/

      

       int init_module(void)

       {

        i, found = 0;

            

             /* too high I/O addresses are not valid ones */

             for (i=0; io[i]<0x1000 /* 4KB */ && i<4; i++) {

                    /*the device-specific function below is boolean */

                    foun += own_eth_register(io[i], irq[i]);

             }

             return found ? 0 : -ENODEV;

       }

 

       /* device-specific registration function */

       int own_eth_register(unsigned int iobase, unsinged int irq)

       {

              if (!iobase) /* if iobase is 0, then probe for a device */

                     iobase = own_eth_probe_iobase();

              if (!iobase)

                     return 0; /* not found */

              if (!irq) /* if irq is 0, probe for it */

                     irq = own_eth_probe_irq(iobase)

              return own_eth_init(iobase, irq);

       }

       默认情况下,这段代码探测一个板卡,并且总试图自动发现中断,不过用户可以改变这种行为。例如,io=0,0,0探测三个板卡。另外对于使用 io irq, 编写驱动可以加入其它的载入时配置变量。没有已建立的命名标准。

 

RUN-TIME CONFIGURATION

运行时配置

       用户可能偶尔想在运行的时候改变接口的配置。举例来说,如果中断号(IRQ)不能被探测,唯一正确配置它的方法就是通过反复试验 (trial-and-error)的技术。一个拥护空间的程序可以获得设备当前的配置,或者对一个打开的套接字(open socket)通过调用 ioctl 设一个新的配置。例如 ifconfig 应用程序就可以用 ioctl 为接口设置I/O 端口。

       早先我们曾经看到过一个为网络接口定义的方法 set_config, 这个方法就是用来在运行的时候设置或者改变接口特征的。

       当一个程序询问当前配置的时候,内核并不通知驱动就从 struct device 中摘录信息;另一方面,当一个新的配置   传送到接口时,set_config 方法被调用,因此驱动可以检查传来的值,并采取合适的动作。

该设备方法有下面的原型:

       int (*set_config)(struct device *dev, struct ifmap *map);

       map 参数指向从用户程序传递来的一个该结构的拷贝,这份拷贝已经在内核空间了,所以驱动不需要调memcpy_form_fs.

Ifmap结构包含的域是:

              unsigned long mem_start;

       unsigned long mem_end;

       unsigned short base-addr;

       unsigned char irq;

       unsigned char dma;

       这些域跟 struct device 的域相对应。

       unsigned char port;

       这个域对应 if_port,  dev 结构中, map->prot 的含义是设备确定(device-specific)

       set_config 设备方法在一个进程对设备发出 ioctl(SIOCSIFMAP) (Socket I/O Control Set InterFace MAP) 命令时被调用。改进程应该在试图加上新值之前首先发出 ioctl(SIOCGIFMAP) (Socket I/O Control Get InterFace MAP),这样驱动程序仅需要查看 struct dev  struct ifmap 之间不相匹配的部分。map 中不被驱动所用到的任何域都可以跳过。例如,不用DMA的网络设备就可以忽略 map->dma.  

       snull 的实现是为了表明驱动程序对于配置的改变应该怎样做。snull 驱动中的任何域都没有物理感知,但是作为示例,代码禁止I/O地址的改变,允许IRQ改变,并且忽略其它选项来显示改变是怎样被认可、拒绝和忽略的。

      

       int snull_cofig(struct deivce *dev, struct ifmap *map)

       {

              if (dev->flags & IFF_UP) /* can't act on a running interface */

                     return -EBUSY;

                    

              /* Don't allow changing the I/O address */

              if (map->base_addr != dev->base_addr) {

                     printk(KERN_WARNING "snull: Can't change I/O address\n");

                     return -EOPNOPSUPP;

              }

             

              /* Allow changing the IRQ */

              if (map->irq != dev->irq) {

                     dev->irq = map->irq;

                     /* request_irq() is delayed to open-time */

              }

             

              /* ignore other fields */

              return 0;

       }

       该方法的返回值也是作为典型的 ioctl 系统调用的返回值,如果驱动不实现 set_config 的话,会返回-EOPNOTSUPP

       至于接口的配置是怎样通过用户空间被访问的,可以看misc-progs/netficonfig.c,它可以被set_config使用,输出样例可以用如下命令得到:

       # ./netifconfig sn0

       # ./netifconfig sn0 irq=4

       # ifconfig sn0 down

       # ./netifconfig sn0 irq=4 tell

       # ./netifconfig eth0

       # ./netifconfig sn0 io=0x400

 

CUSTOM ioctl COMMANDS

用户 ioctl 命令

       我们已经看到了 ioctl 系统调用对套接字的实现;SIOCSIFADDR  SIOCSIFMAP "socket ioctls"的例子。 现在我们看看这个系统调用在网络代码中的第三个参数。

       当一个套接字调用 ioctl 的时候,命令号是在中定义的一个符号,函数 sock_ioctl 直接调用一个协议相关的函数。(协议是值应用的主要的网络协议,如IP或者AppleTalk。)

       任何不被协议层辨认的 ioctl 命令就被传递到设备层。这些设备相关的 ioctl 命令从用户空间接收第三个参数,一个 struct ifreq * 指针;该结构定义在. SIOCSIFADDR  SIOCSIFMAP 命令实际是工作于ifreq结构之上。对于 SIOCSIFMAP 其域的参数,尽管定义在 ifmap , 也只是 ifreq 的一个域。

       除了用标准的调用之外,每个几口可以定义自己的 ioctl 命令。例如 plip 接口,允许接口经由 ioctl 修改它的内部超时间隔。套接字的 ioctl 的实现能够辨认16个私有于该接口的命令:SIOCDEVPRIVATE  SIOCDEVPRIVATE+15.

       当其中一个命令被认出时,dev->do_ioctl 就在相关的接口驱动中被调用。该函数接收到与通用的 ioctl 函数相同的struct ifreq * 指针:

 

       int (*do_ioctl)(struct device *dev, struct ifreq *ifr, int cmd);

       ifr 指针指向一个核心空间地址,那里将保存一个从用户传来的该结构的一个拷贝。当 do_ioctl 返回时,这个结构又被拷回到用户空间,这样的话驱动程序就可以对接收的和返回的数据都使用私有命令了。

       这里不需要说 do_ioctl 的实现,但是根据本章的信息和内核的示例,已经可以写出自己需要的驱动了。注意,plip的实现对 ifr_data 的使用不正确,不能用为 ioctl 实现的示例。

STATISTICAL INFORMATION

静态信息

       驱动需要的最后一个方法是 get_stats. 这个方法返回一个指针指向设备的静态信息,它的实现也颇为简单:

 

       struct enet_statustics *snull_stats(struct device *dev)

       {

              struct snull_priv *priv = (struct snull_priv *)dev->priv;

              retun &rpiv->stats;

       }

       返回有意义的静态信息所真正需要的工作分散在整个驱动之中,因为各个域都在更新。下面列出了

       struct enet_statistics 结构中最有趣的域:

       int rx_packets;

       int tx_packets;

       int rx_errors;

       int tx_errors;

int rx_dropped;

       int tx_dropped;

       该结构还有一些其它的其它的域,可以用来详述在发送和接收过程中出现的错误。可以参看.

 

QUICK REFERENCE

快速参阅

 

       #include

       void netif_rx(struct sk_buff *skb);

       #include

              ETH_ALEN

       ETH_P_IP

              struct ethhdr;

       struct enet_statistics;

       #include

       #include

       void ether_setup(struct device *dev);

       unsigned short eth_type_trans(struct sk_buff *skb, struct device *dev);

       #include

       SIOCDEVPRIVATE

 

另外,所谓多播就是指一个主机(确切的说应该是接口)发出的包,被子网中的多个接口而又不是全部接口所接收。Linux的内核有相应的支持,我们这里就不详述了。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值