📄 (ldd) ch14-网络驱动程序(下)(转载).txt
字号:
void kfree_skb(struct sk_buff *skb, int rw);
void dev_kfree)skb(struct sk_buff *skb, int rw);
释放一个缓冲区。kfree_skb被核心内部使用。驱动程序应该使用dev_kfree_skb,在拥
有缓冲区的套接字需要再次使用它的情况下,它可以正确地处理缓冲区加锁。两个函数
有缓冲区的套接字需要再次使用它的情况下,它可以正确地处理缓冲区加锁。两个函数
的rw参数是FREE_READ或FREE_WRITE。这个值用来跟踪套接字的内存。外出缓冲区应用
FREE_WRITE来释放,而进来的则使用FREE_READ。
unsigned char *skb_put(struct sk_buff *skb, int len);
这个线入函数更新结构sk_buff的tail和len域,它被用来在缓冲区尾加入数据。其返回
值是skb->tail以前的值(或者说,它指向刚生成的数据空间)。有些驱动程序通过调用
ins(ioaddr,skb_put(…))或memcpy(skb_put(…), data,len)来使用这个返回值。这个
函数及下面的一些在为Linux1.2构造模块是不存在。
unsigned char *skb_push(struct sk_buff *skb, int len);
这个函数减小skb->data,增加skb->len。类似于skb_put,除了数据是加在包开始而不
是结尾。返回值指向刚生成的空间。
int skb_tailroom(struct sk_buff *skb);
这个函数返回为在缓冲区中放置数据的可用空间量。如果驱动程序在缓冲区中放了多于
它能承载的数据,系统可能回崩溃。你也许会反对并认为,用printk指出这个错误已经
足够了,而内存崩溃对系统太有害了,开发者肯定要采取一些措施。但实际上,如果缓
冲区被正确分配了,你根本不必检查可用空间。因为驱动程序通常在分配缓冲区之前获
得包大小,只有有严重缺陷的驱动程序才可能在缓冲区内放太多的数据,崩溃可以认为
得包大小,只有有严重缺陷的驱动程序才可能在缓冲区内放太多的数据,崩溃可以认为
是应得的惩罚。
int skb_headroom(struct sk_buff *skb);
返回数据前面得可用空间量,也就是可以向缓冲区中“推”多少八元组。
void skb_reserve(struct sk_buff *skb, int len);
这个函数增加data和tail。它可以用来在填充缓冲区前预留空间。大多数以太网接口在
包前预留两个字节;这样IP头可以在一个4字节以太网头之后,在16字节边界对齐。snul
l完成得很好,尽管在“包接收”中并未提到这一点,那主要是为了避免彼时引入过多得
概念。
unsigned char *skb)pull(struct sk_buff *skb, int len);
从包头中删除数据。驱动程序并不用这个函数,但为了完整性也包含在这里。它减少skb
->len,增加skb->data;这是从进来包的开始剥出以太网包头的方法。
核心还定义了几个在套接字缓冲区上操作得别的函数,但它们主要应用于网络代码得高
层,驱动程序并不需要它们。
层,驱动程序并不需要它们。
地址解析
以太网通信最急迫得问题之一是硬件地址(接口得唯一标志符)与IP号码之间的关联。
大多数协议都有类似问题,但我只向重点讨论一下以太网类得情况。我力图给出一个全
面得描述,因此我将显示三种情况:ARP,没有ARP的以太网头(象plip),以及非以太网
包头。
在以太网上使用ARP
地址解析得一般方法是ARP,即地址解析协议。幸运的是,ARP由核心管理,以太网接口
不必为支持ARP做任何特殊工作。只要在打开时正确地设置了dev->addr和dev->addr_len
,驱动程序不需担心任何从IP号码到物理地址的转换;ether_setup将正确的设备方法赋
给dev->hard_header和dev->rebuild_header。
当一个包被构造时,以太网包头由dev->hard_header来布局,并由dev->rebuild_header
在后来填充,它使用ARP协议将未知的IP号码映射到地址上。驱动程序作者不必知道这个
过程的细节去写一个可工作得驱动程序。
越过ARP
越过ARP
简单得点到点网络接口如plip可以从以太网包头获益,但却要避免来回发送ARP包得开销
。snull中得示例代码就属于这一类网络设备。snull不能使用ARP,因为驱动程序修改被
发送得包得IP地址,而ARP包也交换IP地址。
如果你的设备想用一般的硬件包头,却不想运行ARP,你需要越过缺省的dev->rebuild_h
eader方法。这就是snull实现的方法,这个简单的函数有三条语句:
(代码329)
事实上,并没有指定eth->h_source和eth->h_dest内容的实际需要,因为这些值只被用
来进行包得物理传送,而一个点到点得连接保证能将包发送到它的目的地,而与硬件地
址无关。snull重构包头的原因是向你演示,当eth_rebuild_header不可用时,一个真实
的网络接口的重构函数是如何实现的,
当接口收到一个包时,硬件包头只被eth_type_trans使用。我们在snull_rx中已经见过
这个调用:
skb->protocol=eth_type_trans(skb,dev);
这个函数从以太网包头中抽取协议标志符(在这里是ETH_P_IP);它还要赋值skb->mac.
raw,从包数据中删去硬件包头,并设置skb->pkt_type。最后一项在skb分配时缺省为PA
raw,从包数据中删去硬件包头,并设置skb->pkt_type。最后一项在skb分配时缺省为PA
CKET_HOST(表明包被指向这个主机),当然它也可以改为符合以太网目的地址得其它值
。
如果你的接口是点到点连接,你将无法收到未想到的选播包。为避免这个,你必须记住
那些第一个八元组的最低位(LSB)是0的目的地址将被指向单个主机(也就是说,它是P
ACKET_HOST或PACKET_OTHERHOST)。plip驱动程序用0xfc作为它的硬件地址的第一个八
元组,而snull用0x00。这两个地址都导致一个可工作的以太网类的点到点连接。
非以太网包头
这节简要地介绍硬件包头是如何用来封装相关信息的。如果你希望了解细节,你可以从
核心源码或特别传输介质的技术文档中得到。我们刚才已经看到硬件包头除了含有目的
地址外,还有一些信息,其中最重要的是通信协议。
不过,并不是每个协议都要提供所有的信息。象plip或snull之类的点到点连接可以避免
传送整个以太网包头,同时不失去一般性。hard_header设备方法从核心接收传送信息--
--包括协议级和硬件地址。它也收到16位的协议号。例如IP由ETH_P_IP标志。驱动程序
应能正确地象接收主机传送包数据和协议号。点到点连接可以在硬件包头中省略地址,
只传送协议号,因为传送是有保证的,与源和目的地址无关。一个只有IP的连接甚至什
么硬件头都不传送。两种情况下,所有的工作都由hard_header完成,rebuild_header除
了返回0外什么都不做。
当包在连接的另一端被捡起时,接收函数将正确地设置skb->protocol,skb->pkt_type
,和skb->mac.raw。
skb->mac.raw是一个被网络高层代码实现的地址解析机制使用的字符指针(例如,net/i
pv4/arp.c)。它必须指向一个与dev->type匹配的机器地址。设备类型的可能值被定义
在<linux/if_arp.h>;以太网接口用ARPHRD_ETHER。例如,下面是eth_type_trans处理
收到包的以太网包头的方法:
skb->mac.raw=skb->data;
skb_pull(skb, dev->hard_header_len);
在最简单的情况下(无包头的点到点连接),skb->mac.raw可以指向一个含有这个接口
的硬件地址的静态缓冲区,protocol可以被置为ETH_P_IP,packet_type仍维持其缺省值
PACKET_HOST。
加载时配置
用户可以用几个标准的关键字来配置接口。任何新的网络模块都应遵循这个标准:
io=
为接口设置I/O端口的基地址。如果系统中安装了不只一个接口,那么可以用一个由逗号
分隔的列表来指定。
irq=
设置中断号。和上面一样,可以指定不止一个值。
换句话说,一个装了两个own_eth接口的Linux用户可能用下面的命令行来加载模块:
insmod own_eth.o io=0x300, 0x320 irq=5,7
如果指定0值,那么io=和irq=选项都要被探测。因此用户可以通过指定io=0来强制探测
。如果用户名优指定任何选项,多数驱动程序通常都探测一个接口,但有时,模块可能
被禁止探测。(见ne.c中关于NE2000设备的探测)。
设备驱动程序应该象刚才描述的这样工作。ISA设备的典型实现如下所示,假设驱动程序
设备驱动程序应该象刚才描述的这样工作。ISA设备的典型实现如下所示,假设驱动程序
最多可以支持四个接口:
(代码331)
这段代码缺省探测一个板子,并总是自动探测中断,但用户可以改变这种行为。例如,i
o=0,0,0将探测三块板子。
除了使用io和irq外,驱动程序的作者可以随意增加其它加载时配置参数。也没有已建立
的命名标准。
运行时配置
用户有时可能希望在运行时改变接口的配置。例如,当中断号无法探测时,想对它正确
配置的唯一办法就是“尝试—错误”技术。一个用户空间的程序可以获取设备的当前配
置,并通过在一个打开的套接字上调用ioctl来设置一个新的配置。例如,应用ifconfig
使用ioctl为接口设置I/O端口。
我们前面知道一个为网络接口定义的方法中的一个是set_config。这个方法被用来在运
我们前面知道一个为网络接口定义的方法中的一个是set_config。这个方法被用来在运
行时设置或改变一些接口特征。
当一个程序询问当前配置时,核心从结构device中抽取相关信息,而不通知驱动程序;
另一方面,当一个新的配置被传递给接口时,set_config被调用,这样驱动程序就可以
检查这些值并采取相应的动作。这个驱动程序方法对应下面的原形:
int (*set_config)(struct device *dev, struct ifmap *map);
map参数指向一个由用户程序传递的结构的拷贝;这个拷贝已经在核心空间,所以驱动程
序不需要调用memcpy_from _fs。
结构ifmap的域是:
unsigned long mem_start;
unsigned long mem_end;
unsigned short base_addr;
unsigned char irq;
unsigned char dma;
unsigned char dma;
这些域对应着结构device中的域。
Unsigned char prot;
这个域对应着dev中的if_port。map->port的含义是设备特定的。
当一个进程为设备发出ioctl(SIOCSIFMAP)(即Socket I/O Control Set InterFace
MAP)时,set_config设备方法被调用。这个进程在时强制使用新值之前,应该发出ioctl
(SIOCGIFMAP)(即Socket I/O Control Get InterFace MAP),这样驱动程序只需要查看d
ev和ifmap结构不匹配的地方。Map中任何不被驱动程序使用的域均可以略过。例如,一
个不使用DMA的网络设备可以忽略map->dma。
snull实现被设计成可以显示驱动程序是如何针对配置改变而动作的。对snull来说,没
有一个域由物理意义。但出于说明的目的,代码禁止改变I/O地址,允许改变IRQ号,并
忽略其它选项,从而显示这些改变是如何被响应、拒绝、或是忽略的。
(代码333 #1)
这个方法的返回值被作为发出的ioctl系统调用的返回值,对于没有实现set_config的驱
这个方法的返回值被作为发出的ioctl系统调用的返回值,对于没有实现set_config的驱
动程序则返回-EOPNOTSUPP。
如果你对接口配置如何从用户空间访问感到好奇,请看misc-progs/netifconfig.c,它
可以用来与set_config比较。下面是一个示例运行的输出:
(代码333 #2)
自定义ioctl命令
我们已经看到ioctl系统调用是为套接字实现的。SIOCSIFADDR和SIOCSIFMAP是“套接字i
octl”的例子现在让我们看看这个系统调用的第三个参数是如何被网络代码使用的。
当ioctl系统调用在套接字上被调用时,其命令号是在<linux/sockios.h>定义的符号之
一,并且函数sock_ioctl直接调用一个协议特定的函数(这里协议指使用的主要网络协
议,如IP或AppleTalk)。
任何协议层不认识的ioctl命令被传递给设备层。这些设备相关的ioctl命令从用户空间
接收第三个参数,即结构ifreq*;这个结构在<linux/if.h>中定义。SIOCSIFADDR和SIOC
接收第三个参数,即结构ifreq*;这个结构在<linux/if.h>中定义。SIOCSIFADDR和SIOC
SIFMAP命令实际上是工作在ifreq结构上。SIOCSIFMAP的额外参数,尽管定义为ifmap,
是ifreq的一个域。
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -