⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 (ldd) ch14-网络驱动程序(下)(转载).txt

📁 献给ARM初学者
💻 TXT
📖 第 1 页 / 共 3 页
字号:
(LDD) Ch14-网络驱动程序(下)(转载)
       
       
      打开和关闭
       
      我们的驱动程序可以在模块加载和核心引导时探测接口。下一步是给接口赋一个地址,
      这样驱动程序就可以通过它交换数据了。打开和关闭一个接口由ifconfig命令完成。
       
      当使用ifconfig为一个接口赋地址时,它完成两项工作。第一,它通过
      ioctl(SIOCSIFADDR)(即Socket I/O Control Set InterFace ADDRess)来赋地址。接着
      它通过ioctl(SIOCSIFFLAGS)(即Socket I/O Control Set InterFace FLAGS) 对dev->fl
      ag中的IFF_UP置位来打开接口。
       
      至于设备,ioctl(SIOCSIFADDR)设置dev->pa_addr,dev->family,dev->pa_mask,dev-
      >pa_brdaddr,没有驱动程序函数被调用----这个任务是设备无关的,由核心来完成。不
      过,后一个命令ioctl(SIOCSIFFLAGS)为设备调用open方法。

      过,后一个命令ioctl(SIOCSIFFLAGS)为设备调用open方法。
       
      类似地,当一个接口关闭时,ifconfig使用ioctl(SIOCSIFFLAGS)来清除IFF_UP,并且调
      用stop方法。
       
      两个设备方法在成功时都返回0,发生错误时,通常返回一个负值。
       
      至于代码,驱动程序必须执行与字符和块设备同样的工作。open请求它所需要的所有的
      系统资源,并告诉接口启动;stop则关闭接口,并释放系统资源。
       
      如果驱动程序不准备使用共享中断(例如,它不打算与旧的核心兼容),还有最后一步
      需要做。核心引出一个irq2dev_map阵列,它由IRQ号寻址,持有空指针;驱动程序也许
      想用这个数组将中断号映射到指向device结构的指针。这是在不使用接口处理程序的情
      况下,在一个驱动程序里支持一个以上接口的唯一方法。
       
      另外,在接口可以和外界通信以前,硬件地址还必须从板上复制到dev->dev_addr。硬件
      地址可以按驱动程序的意愿在探测时或打开时被赋值。snull软件接口时从open里对其赋
      值;它用两个ASCII串伪造一个硬件号码。地址的第一个字节是个空字符(在后面的“地
      址解析”中解释)。
       
      结果得到的open代码如下所示:
       
      (代码319)

      (代码319)
       
      (代码320 #1)
       
      正如你所看到的,device结构中的几个域被修改了。start表明接口已准备好, tbusy断
      言发送者不忙(也就是说,核心可以发出一个包)。
       
      stop方法是open的操作的反转。由于这个原因,实现 stop的函数通常调用 close。
       
      (代码320 #2)
       
       
       
      包发送
       
      网络接口执行的最重要的工作是数据发送和接收。我准备从发送开始,因为它相对比较
      简单。
       
      当核心需要发送一个数据包时,它调用hard_start_transmit方法将数据放到一个输出队
      列。核心处理的每个包包含在一个套接字缓冲区结构(struct sk_buff)中,其定义见<
      linux/skbuff.h>。这个结构从Unix用来表示一个网络连接的抽象,即套接字得名。即使
      接口与套接字无关,每个网络包在较高的网络层中一定属于某个套接字,任何套接字的
      输入输出缓冲区都是sk_buff结构的列表。同样的sk_buff结构在整个Linux网络子系统中

      输入输出缓冲区都是sk_buff结构的列表。同样的sk_buff结构在整个Linux网络子系统中
      都被用来承载网络数据,但在考虑接口时,一个套接字缓冲区就是一个包。
       
      指向sk_buff的指针通常被称做skb,我将在示例和正文中都使用使用这个习惯。
       
      套接字缓冲区是一个复杂的结构,核心提供一组函数来对其操作。这些函数在后面的“
      套接字缓冲区”中描述----目前,知道sk_buff的一些基本事实已足以写出可工作的驱动
      程序。另外,我习惯于在扎入另人讨厌的细节之前先弄明白是如何工作的。
       
      传递给hard_start_xmit的套接字缓冲区含有物理包,它具有传输层的包头。接口不需要
      修改被发送的数据。skb->data指向被发送的包,skb->len是它的长度,以八元组为单位
       
      snull的包发送代码如下所示;物理发送机制被隔离在另一个函数中,因为每个接口驱动
      程序必须按照被驱动的特定硬件来实现它。
       
      (代码321)
       
      这样发送函数只进行一些清晰的对包的检查,并通过硬件相关的函数发送数据。在一个
      中断表明一个“发送结束”的条件时,dev->tbusy被清除。
       
       
       

       
      包接收
       
      从网络中接收数据比发送要复杂一些,因为必须分配一个sk_buff,并从一个中断处理程
      序中将其交递给高层----接收包的最好的办法是通过中断,除非接口是象snull一样是纯
      软件的,或是环回接口。尽管有可能写轮询的驱动程序,而且在正式的核心里也的确有
      几个,但中断驱动的要好的多,不管是在数据吞吐率还是计算需求上。由于绝大多数网
      络接口都是中断驱动的,我不打算谈论轮询实现,它只是利用了核心计时器。
       
      snull的实现是将硬件细节和设备无关的工作分离开的。这样,在硬件收到一个包后,sn
      ull_rx被调用,它已经在计算机的内存中了。snull_rx因此收到一个指向数据的指针和
      包的长度。。这个函数唯一的责任就是将包和一些额外信息发送到网络代码的高层。其
      代码与数据指针及长度获得的方法无关。
       
      (代码322)
       
      这个函数足够通用,可以作为任何网络驱动程序模版,但在你有信心重用这个代码段之
      前还需要一些解释。
       
      注意缓冲区分配函数需要知道数据长度。者避免了在调用kmalloc时浪费内存。dev_allo
      c_skb以原子优先级调用分配函数,因此它也可以在中断时安全地使用。核心还提供了套
      接字缓冲区分配的其它一些接口,但不值得在这里介绍;套接字缓冲区在本章后面的“
      套接字缓冲区”中详细介绍。

      套接字缓冲区”中详细介绍。
       
      一旦有了一个有效的skb指针,就可以通过调用memcpy将包数据复制到这个缓冲区。skb_
      put更新缓冲区中数据尾的指针,并返回一个指向新生成空间的指针。
       
      不幸的是,包头中没有足够的信息来正确处理网络层----在缓冲区向上层传递之前,dev
      和protocol域必须被赋值。接着我们需要指定如何执行校验和(snull不进行任何校验和
      )。skb->ip_summed可能的策略为:
       
      CHECKSUM_HW
       
      板子用硬件执行校验和。一个硬件校验和的离子是Sparc HME接口。
       
      CHECKSUM_NONE
       
      校验和完全有软件完成。对新分配的缓冲区,这是缺省的策略。
       
      CHECKSUM_UNNECESSARY
       
      不做任何校验和。这是snull和环回接口的策略。
       
      在1.2核心版本中没有校验和选项和ip_summed。
       

       
      最后,驱动程序更新它的统计计数器记录一个新包被收到了。统计结构有几个域组成,
      最重要的是rx_packets和tx_packets,它们包含收到的和发送的包的个数。所有的域在
      后面的“统计信息”中给出一个彻底的描述。
       
      包接收的最后一步由netif_rx完成,它将套接字缓冲区递交到上一层。
       
       
       
      中断驱动的操作
       
      大多数硬件接口以中断处理程序的方式控制。接口中断处理器表明两种事件中的一种:
      一个新包到达了或一个包发送完成了。这种一般化并不是总适用,但它基本上揭示了与
      异步包传送相关的问题。PLIP和PPP是不适用这种一般化的例子。它们处理同样的事件,
      但低级中断处理略有不同。
       
      一般的中断例程可以通过检查在硬件设备上的一个状态寄存器来分辨新包到达中断与完
      成发送的通知。snull接口工作方式类似,但其状态字在dev->priv中。网络接口的中断
      处理程序看起来如下:
       
      (代码324)
       
      处理程序的第一个任务是接收一个指向正确的device结构的指针。你可以用irq2dev_map

      处理程序的第一个任务是接收一个指向正确的device结构的指针。你可以用irq2dev_map
      [](假如你在打开是给它赋了一个值)或者接收到的dev_id指针作为一个参数。如果你
      希望驱动程序可以与新于1.3.70的核心工作,你必须使用irq2dev_map[],因为早期版本
      中没有dev_id。
       
      这个处理程序中有趣的部分是处理“发送完成”的部分。接口通过清除dev->tbusy并标
      志网络下半部例程来相应发送完成。如果net_bh的确运行了,它会试图发送所有等待的
      包。
       
      另一方面,包接收并不需要任何特殊的中断处理。所有需要做的就是调用snull_rx。
       
      实际上,当netif_rx被接收函数调用时,它所进行的实际操作只有标志net_bh。换句话
      说,核心在一个下半部处理程序中完成了所有网络相关的工作。因此,网络驱动程序应
      该总是宣称它的中断处理程序太慢,因为下半部将会更早地执行(见第九章中“下半部
      设计”)。
       
       
       
      套接字缓冲区
       
      我们已经讨论了于网络接口相关的多数内容。下面几节我们将更细地讨论sk_buff时如何
      设计的。这几节既介绍这个结构的主要域,也介绍在套接字缓冲区上操作的函数。
       

       
      尽管并没有理解sk_buff内部的严格需要,但是如果能理解它的内容将会有助于你解决问
      题和优化代码。例如,如果你看了loopback.c,你会发现一个基于sk_buff内部知识的优
      化。
       
      我不打算在这里描述整个结构,而只是那些可能被驱动程序用到的域。如果你想知道更
      多,你可以看<linux/skbuff.h>,结构的定义和函数的原形都在那里定义。至于这些域
      和函数如何使用的细节可以通过浏览核心源码得到。
       
      重要的域
       
      出于我们的目的,结构里重要的域是那些驱动程序的作者可能要用到的域。它们如下所
      示,无特别顺序。
       
      struct device *dev;
       
      设备接收或者发送这个缓冲区。
       
      __u32 saddr;
       
      __u32 daddr;
       
      __u32 raddr;

      __u32 raddr;
       
      源地址,目的地址,和路由器地址,由IP协议使用。raddr是包要到达其目的地的第一步
      。这些域在包被发送前被设置,收到之后就不必赋值了。到达hard_start_xmit方法的外
      出包已经有了一个合适的硬件包头设置反映了“第一步”信息。
       
      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开发时才实现。这是snull
      没有被移植在Linux1.2上编译的主要原因。
       
      unsigned long len;
       
      数据本身的长度(skb->tail-skb->head)。

      数据本身的长度(skb->tail-skb->head)。
       
      unsigned char ip_summed;
       
      这个域有驱动程序对进来包设置,由TCP/UDP校验和使用。它在前面的“包接收”中介绍
      过。
       
      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_tran
      s为以太网驱动程序负责这件事。非以太网驱动程序应设置skb->mac.raw指针,后面“非
      以太网包头”中将会提到。
       
       
       
      结构中其余的域并无特别兴趣。它们被用来维护缓冲区列表,解释占有缓冲区的套接字

      结构中其余的域并无特别兴趣。它们被用来维护缓冲区列表,解释占有缓冲区的套接字
      的内存,等等。
       
      在套接字缓冲区上操作的函数
       
      使用sock_buff的网络设备通过正式的接口函数在这个结构上操作。有很多在套接字缓冲
      区上操作的函数,下面是最有趣的一些:
       
      struct sk_buff *alloc_skb(unsigned int len, int priority);
       
      struct sk_buff *dev_alloc_skb(unsigned int len);
       
      分配一个缓冲区。alloc_skb分配一个缓冲区并初始化skb->data和skb->tail到skb->hea
      d。 dev_alloc_skb函数(在Linux1.2中没有)一个快捷方式,它用GFP_ATOMIC优先级调
      用alloc_skb,并反转skb->head和skb->data之间的16个字节。这个数据空间可以用来“
      推”硬件包头。
       

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -