|
|
|
|
|
|

目录页 | 上一页 | 下一页

(LDD) Ch14-网络驱动程序(上)(转载)

发信人: Altmayer (alt), 信区: GNULinux
标  题: (LDD) Ch14-网络驱动程序(上)(转载)
发信站: 饮水思源 (2001年12月13日08:57:57 星期四), 站内信件
 
【 以下文字转载自 UNIXpost 讨论区 】
【 原文由 altmayer.bbs@bbs.nju.edu.cn, 所发表 】
 
【 以下文字转载自 altmayer 的信箱 】
 
第十四章    网络驱动程序
 
 
 
我们已经讨论了字符设备和块设备驱动程序,接着要讨论的是迷人的网络世界。网络接
口是Linux设备中的第三标准类,这一章就是讲述它们是如何与核心的其余部分交互的。
 
网络接口并不象字符和块设备那样存在于文件系统。相反,它在核心层处理包的发送和
接收,并不与进程中的某个打开的文件绑定在一起。
 
网络接口在文件系统中的角色就象被安装的块设备。一个块设备在blk_dev数组和其它核
心结构中注册它的特征,接着按照要求通过它的request_fn函数“发送”和“接收”块
。类似地,一个网络接口必须在特定的数据结构中注册自己,从而在与外部世界交换包
时可以被调用。

时可以被调用。
 
安装的磁盘与包发送接口有几个重要的不同。首先,磁盘以一个结点的形式存在于/dev
目录,而网络接口并不在文件系统中出现。不过两者之间最大的不同在于:磁盘是被请
求向核心发送一个缓冲区,而网络接口则是请求向核心推送进来的包。
 
Linux的网络子系统被设计成完全协议无关的。这对网络协议(IP vs. IPX 或其它协议
)和硬件协议(以太网vs.令牌环等)都是如此。网络驱动程序和核心之间的交互一次处
理一个网络包;这允许协议可以干净地对驱动程序隐`藏起来,而物理传输则可以对协议
隐藏起来。
 
本章描述网络接口如何与核心的其它部分紧密合作,并给出一个基于内存的模块化的网
络接口,称之为(你可能已经猜到了)snull。为简化讨论,这个接口使用以太网硬件协
议并传送IP包。通过snull获得的知识可以很好地应用于IP以外的协议,从以太网移到其
它硬件协议只要求你对使用的物理协议有所了解。
 
snull的另一个限制是它不能在Linux1.2中编译。再说一遍,这样做只是为了保持代码简
单,并避免在snull中加入一些另人厌倦的条件。不过,本章将会提到与网络驱动程序相
关的可移植性问题。
 
本章并不介绍IP的编号原则,网络协议,以及其它普通的网络概念。这个主题与驱动程
序作者无关,而且以不到几百页的篇幅想对网络技术有一个令人满意的概述是不可能的
。感兴趣的读者可以参考一些讲述网络问题的书。

。感兴趣的读者可以参考一些讲述网络问题的书。
 
在讨论网络设备之前,我想提醒你网络事务中的原子数据项被称做一个八元组(octet)
,由八个数据位组成。在本章中我都这样使用。网络文档从不使用术语“字节”。
 
 
 
Snull如何设计
 
 
 
本节讨论与snull网络接口有关的一些设计概念。尽管这些信息可能显得用处并不大,但
如果不理解它则可能在研究示例代码时遇到一些困难。
 
第一位的设计决定(也是最重要的)是示例接口不应绑定于任何实际硬件。实际接口不
依赖于所传送的协议,snull的这个限制并不影响本章给出的示例代码,因为它是协议无
关的。IP限制的唯一影响是地址分配----我们将位示例接口分配IP地址。
 
分配IP号码
 
snull模块生成两个接口。这样的接口与简单的环回(loopback)并不一样,这里你从一
个接口传送的包总是环回到另一个接口,而不是它自己。它看起来好象是你有两个外部
链接,但实际上你的计算机只是应答自己。

链接,但实际上你的计算机只是应答自己。
 
不幸的是,这个效果并不能仅仅通过IP号码分配来达到,因为核心不会从接口A发送一个
指向它自己接口B的包。相反,这时它会使用环回通道,从而根本不通过snull。为了能
建立一个通过snull接口的通信,源和目的地址必须在数据传送的时候修改一下。换句话
说,从一个接口发出的包应能被另一个接口接收,但外出包的接收者不能被认为是本机
。这也适用于收到包的源地址。
 
为了收到这种“隐藏环回”的效果,snull接口反转一下源和目的地址的第三个八元组的
最低位。其效果就是发向网络A(连在接口sn0上)的包在sn1接口上好象是属于网络B。
 
为了避免和太多的号码打交道,我们给用到的IP号码分配一些符号名:
 
l         snullnet0是一个连接在sn0接口上的一个C类网络。类似地,snullnet1是连
在sn1上的网络。这两个网络的地址仅在第三个八元组的最低位不同。
 
l         local0是分配给接口sn0的IP地址;它属于snullnet0。与sn1相关联的地址是
local1。local0和local1的第三和第四个八元组必须都不相同。
 
l         remote0是snullnet0中的一个主机,它的第四个八元组与local1相同。所有
发向remote0的包在其C类地址被接口代码修改后将到达local1。主机remote1属于snulln
et1,并且它的第四个八元组与local0相同。
 

 
snull接口的操作见图14-1,图中与接口相关联的主机名印在接口名旁边。
 
下面是几个可能的网络号码。一旦你把这几行写到/etc/networks,你就可以用名字来称
呼这些网络。这些值是从保留私用的号码范围中选取的。
 
                      snullnet0      192.168.0.0
 
                      snullnet1      192.168.1.0
 
下面是写入/etc/hosts的可能的主机号码:
 
192.168.0.88    local0
 
192.168.0.99            remote0
 
192.168.1.99            local1
 
192.168.1.88    remote1
 
 
 
(图14-1 Page304)

(图14-1 Page304)
 
不过如果你的计算机已经连到了一个网络上,那么一定要注意。你选择的号码有可能是
实际的Internet或intranet的号码,把它们分配给你的接口可能会妨碍与真正主机的通
信。而且,尽管我给出的这些号码不是实际的Internet号,但也有可能被你的私用网所
适用,如果它处于防火墙之后的话。
 
不论你选择什么号码,你可以通过发出下面的命令来正确地设置接口:
 
(代码304 #1)
 
到此为止,接口的“远”端已经可以到达了。下面的屏幕快照显示了我的主机是如何通
过snull到达remote0和remote1的。
 
(代码304 #2)
 
注意你不可能达到属于这两个网络的其它主机,因为在包的地址被改变并被接收到后,
你的计算机会把它丢弃。
 
包的物理传送
 
至于数据传送,snull属于以太网一类。示例代码使用了核心的以太网支持。这使我们不
必去实现网络设备一些令人厌倦的细节。

必去实现网络设备一些令人厌倦的细节。
 
我选择以太网是因为现存网络的主体----至少与工作站相连的这一部分-----都是基于以
太网技术,不论是10base2,10baseT,还是100baseT。另外,核心还提供了对以太网设
备的一般化的支持,因此没有理由拒绝使用。以太网设备的优势如此明显,连plip接口
(一类使用打印机端口的接口)都自称是以太网设备。
 
在snull中使用以太网设置的最后一个优势是你可以在接口上运行tcpdump。不过,如果
你想这样做,你需要把接口称做ethx,而不是snx。snull模块已经准备好将自己声明为e
thx。如果在insmode命令行中指定eth=1,你就选择了这种行为。如果你忘了为snull请
求eth命名,tcpdump会拒绝倾倒这个接口,而是返回一个“未知的物理层类型”错。
 
snull接口的另一个设计决定是只处理IP协议,本章的讨论也仅限于IP。不过要注意,接
口驱动程序本身并不依赖于它所处理的底层协议;网络驱动程序根本不查看它所传送的
包。关于多协议传送将在后面的“非以太网包头”中详细介绍。
 
不过说实话,snull还是查看包内容的,甚至还要修改它们,因为这是为保证代码工作要
求的。代码修改每个IP包头的源,目的,以及校验和,但不检查它是否真地携带了IP信
息。这种快而脏的数据修改会破坏非IP包。如果你想让snull处理其它协议,你必须修改
这个模块的源码。不过,这种需求不太可能增长,因为每个拥有Linux盒的人都运行IP,
而其它协议则是可选的。
 
 

 
 
与核心相连
 
我们将通过拆解snull源码来看看网络驱动程序的结构。保证有几个驱动程序的源码在手
边会很有助于你理解我们的讨论。我个人推荐loopback.c,plip.c,以及3c509.c,以逐
渐增加的复杂性排序。有skeleton.c在手边也很有帮助,尽管这个示例驱动程序并不能
真正运行。所有这些文件都居于核心源码树的drivers/net下。
 
模块加载
 
当一个模块被加载到运行的核心时,它要请求一些资源,并提供一些方便的功能;这已
不再新鲜。另外请求资源的方式也不新鲜。驱动程序要探测它的设备及硬件位置(I/O端
口和IRQ线)----但并不注册它们----就象在第九章中断处理中“安装一个中断处理程序
”一节中介绍的一样。网络驱动程序通过它的函数init_module进行注册的方法与字符或
块设备驱动程序不一样。与请求一个主设备号不同,驱动程序为每个新检测到的接口在
一个网络设备的全局列表中插入一个数据结构。
 
每个接口用一个device结构描述。sn0和sn1这两个snull接口的结构如下所示:
 
(代码 306)
 
注意第一个域,既名字域指向一个静态缓冲区,它在加载时将被填充。通过这个方法,

注意第一个域,既名字域指向一个静态缓冲区,它在加载时将被填充。通过这个方法,
可以晚点儿选择接口名,下面会给出解释。如果你想在这个结构中使用一个显式缓冲区
,如“01234567”,我要警告你那样可能导致代码不能可靠地工作。这是因为编译器会
将两个重复的串折叠;因此你得到的会是一个缓冲区和两个指向它的缓冲区。而且,编
译器有可能将常量串存在只读内存中,这显然不是你想要的。
 
在下节之前我不想完整描述结构device,因为它是一个庞大的结构,太早地肢解它没有
什么好处。我想在驱动程序中使用这个结构,并在每个域被使用时再解释它。
 
前面的代码显式地使用了device结构中的name和init域。name是第一个域,含有接口名
(识别接口的字符串)。驱动程序可以将接口名硬写在程序中,也允许动态赋值,其工
作方式如下:如果名字的第一个字符是个空或者空格,那么设备注册项就使用第一个可
用的ethn名。这样第一个以太网接口就被称做eth0,其它的按序号类推。snull接口则被
缺省地称为sn0和sn1。不过,如果在加载时指定eth=1,那么init_module将使用动态赋
值。缺省名由init_module给出:
 
(代码307 #1)
 
init域是个函数指针。任何时候当你注册一个设备时,核心要求驱动程序初始化自己。
初始化就是指探测物理接口,用正确的数值填充device结构,下一节将给予描述。如果
初始化失败,这个结构就不能被链入网络设备的全局列表。这种特别的设置的方法在系
统引导时特别有效;每个驱动程序都试图注册它自己的设备,但只有确实存在的设备才
被链入列表。这与字符和块设备驱动程序不同,它们被组织成一个两级树,由主设备号

被链入列表。这与字符和块设备驱动程序不同,它们被组织成一个两级树,由主设备号
和次设备号索引。
 
由于真正的初始化在别的地方完成,init_module要做的工作非常少,只需一句如下:
 
(代码307 #2)
 
初始化每个设备
 
设备的探测在接口的init函数里完成,它通常被称做“探测”函数。init收到的唯一的
参数是一个指向正被初始化的设备的指针,其返回值是0或者一个负的错误代码----通常
是-ENODEV。
 
对snull接口并没有进行实际的探测,因为它未绑定到任何硬件上。当你为一个实际的接
口写实际的驱动程序时,探测字符设备的原则仍然适用:在使用I/O端口之前先检测它们
,在检测期间不要向它写。另外,你还要避免在此时注册I/O端口和中断线。真正的注册
应该推迟到设备打开时;这个非常重要,特别是当中断线被其它设备共享时。每次当别
的设备触发中断线时,你的接口当然不希望被调用,而应简单地回答:不,它不是我的

 
实际上,在加载时进行设备探测对ISA设备并不鼓励,因为这有可能很危险----ISA体系
结构在容错方面名声不佳。由于这个原因,大多数网络驱动程序在以模块的方式加载时
拒绝为其硬件探测,核心也只探测第一个网络接口,在一个网络设备检测出来后不再进

拒绝为其硬件探测,核心也只探测第一个网络接口,在一个网络设备检测出来后不再进
行任何硬件测试。通常dev->base_addr----当前设备的I/O基地址----决定了要做什么:
 
l         如果dev->base_addr是一个有效的设备I/O地址,将不再探测其它I/O位置,
而是使用这个值。如果这个值在加载时被赋值,这种情况就会发生。
 
l         如果dev->base_addr是0,那么探测设备是可以接受的。拥护可以通过在加载
时置这个I/O地址为0来请求探测。
 
l         其它情况下,不进行探测。核心使用0xffe0来阻止探测,但其实任何无效值
都行。这需要依赖于驱动程序来无声地拒绝base_addr中的一个无效地址。一个模块应该
缺省地置这个地址为无效值来防止不期望的探测。注意查看PCI设备总是安全的,因为它
并不牵扯任何探测(见第15章,外围总线概述)
 
正如你可能已经注意到的,用一个加载时的设置来控制探测与我们在skull中使用的技术
是一样的。
 
当从dev->init中退出时,dev结构应该用正确的值填充。初始化例程的主要工作就是填
充这个结构。幸运的是,核心通过函数ether_setup填充结构device负责了一些以太网的
缺省设置。
 
snull_init的核心是:
 

 
(代码308)
 
                        /* keep the default flags, just add NOARP */
 
                       dev->flags                      |=IFF_NOARP;
 
这段代码唯一不寻常的特征是在标志中设置IFF_NOARP。这指明接口不能使用ARP,即“
地址解析协议”。ARP是一个低级的以太网协议;每个真实的以太网接口都懂得ARP,因
此不需要设置这个标志。有趣的是注意到一个接口在没有ARP时仍能工作。例如,plip接
口就是没有ARP支持的以太网类接口,与snull相似。这个主题将在后面的“地址解析”
中详细讨论,device结构将在下一节肢解。
 
现在我想介绍结构device的另一个域priv。它的作用类似与我们在字符设备驱动程序中
用过的private_data指针。与fops->private_data不同的是,priv指针是在初始化时分
配,而不是在打开时,因为priv所指向的数据项包含有接口的统计信息。有一点很重要
,就是统计信息要保证总是可用的,即使在接口宕掉时,因为用户可能在任何时候通过
调用ifconfig来显式统计信息。在初始化时而不是打开时分配priv浪费的内存是无关紧
要的,因为多数被探测到的接口保持一直在系统中运行。snull模块为priv声明了一个数
据结构snull_priv。这个结构包含了结构enet_statistics,它是存放接口信息的标准地
方。
 
下面这几条snull_init中的语句分配dev->priv:

下面这几条snull_init中的语句分配dev->priv:
 
(代码309 #1)
 
模块卸载
 
当模块被卸载时没有什么特殊的事情发生。函数cleanup_module在释放了与私有结构相
关的内存后,只需将接口从列表中取消即可。
 
(代码309 #2)
 
模块化的和非模块化的驱动程序
 
尽管在对字符设备和块设备来说,模块化和非模块化的驱动程序并没有什么引人注意的
区别,但对网络驱动程序来说,情况并非如此。
 
如果一个驱动程序做为主流Linux核心的一部分发行的话,它并不声明自己的device结构
,而是使用在drivers/net/Space.c中声明的结构。Space.c声明了所有网络设备的链表
,即包括plip1一类驱动程序特定的结构,也包括通用目的的eth设备。以太网根本不关
心它们的device结构,因为它们使用通用目的的结构。这种通用的eth设备结构声明ethi
f_probe为它们的init函数。程序员要想在主流核心中插入一个新的以太网接口只需要在
ethif_probe中加入一个对驱动程序初始化函数的调用。另一方面,非eth驱动程序的作
者需要在Space.c中插入它们的device结构。在两种情况下,如果驱动程序必须被链到核

者需要在Space.c中插入它们的device结构。在两种情况下,如果驱动程序必须被链到核
心,只需要修改源文件Space.c。
 
在系统引导时,网络初始化代码循环遍历所有的device结构,调用它们的探测函数(dev
->init),向它们传递一个指向设备本身的指针。如果探测函数成功了,Space.c初始化
device结构。这种设置驱动程序的方式允许渐增地将设备赋予名字eth0,eth1,依次类
推,而不需要改变每个设备的name域。
 
另一方面,当加载一个模块化的驱动程序时,它声明它自己的device结构(入我们在本
章中已经看到的那样),即使它控制的接口是以太网接口。
 
好奇的读者可以查看Space.c和net_init.c来得到更多关于接口初始化的信息。这里对驱
动程序设置的介绍只是为了强调init设备方法的重要性。如果一个驱动程序模块包含了
预填好的设备结构,那么它将不适合主流核心的初始化技术,并且如果结构device中引
入新的域,会使它变的不能向前兼容。
 
 
 
设备结构的细节
 
device结构居于网络驱动程序的真正核心,值得完全的描述。第一次阅读本书的读者可
以跳过本节,因为开始时不需要对这个结构有详细的理解。下面的列表描述所有的域,
但主要目的是提供一个参考而不是要被记住。本章的其余部分在一个域被示例代码用到

但主要目的是提供一个参考而不是要被记住。本章的其余部分在一个域被示例代码用到
的时候会简单地描述一下,所以你不必不停地回头来参考本节。
 
结构device在结构上可以分为两个部分:“可见的”和“不可见的”。可见部分由那些
在静态device结构中显式赋值的域组成,象前面给出的在snull中出现的两项。其余的域
内部使用。有些被驱动程序访问(例如在初始化时被赋值的那些),而有些不能动。本
章在版本2.0.30前都是完全的。
 
可见的头
 
结构device的第一部分由下列域组成,按序为:
 
char *name;
 
设备名。如果第一个字符是0(NULL字符)或空格,register_netdev给它分配名字ethn
,n取合适的值。
 
unsigned long rmem_end;
 
unsigned long rmem_start;
 
unsigned long mem_end;
 

 
unsigned long mem_start;
 
这些域存有设备使用的共享内存的开始和结束地址。如果设备有不同的发送和接收内存
,那么mem域就用做发送内存,而rmem用做接收内存。mem_end和mem_start可以在系统引
导时在核心命令行指定,它们的值由ifconfig获取。Rmem域在驱动程序以外不会被引用
。一般地,end域被设置成使得end-start为板上可用内存量。
 
unsigned long base_addr;
 
I/O基地址。这个域,和前面的一样,在设备检测时被赋值。ifconfig可以用来显示和修
改当前值。base_addr可以在系统引导或加载时在核心命令行显式赋值。
 
unsigned char irq;
 
被赋予的中断号。当接口被列出时dev->irq由ifconfig打印出来。这个值通常在引导或
加载时被设置,以后可以用ifconfig修改。
 
unsigned char start;
 
unsigned char interrupt;
 
这些域是二进制标志。start通常在设备打开时设置,在关闭时清楚。在接口准备号运行

这些域是二进制标志。start通常在设备打开时设置,在关闭时清楚。在接口准备号运行
时它是非零。interrupt是用来告诉代码的高层一个中断到达接口,并正在处理中。
 
unsigned long tbusy;
 
这个域表明“传送忙”。当驱动程序不能再接收新的包发送时(既所有的输出缓冲区都
满了),它应该为非零。使用long类型而不是char是因为有时要使用原子的位操作以避
免竞争条件。注意在核心1.2,tbusy的确是个八位的域,向后可移植的驱动程序应该注
意这一点。原子的位操作在第九章的“使用锁变量”一节中介绍过。
 
struct device *next;
 
用来维护链表;任何驱动程序都不能动这个域。
 
int (*init)(struct device *dev);
 
初始化函数。这个域通常是device结构中显式列出的最后一个域。
 
隐藏的域
 
device结构包含几个额外的域,通常在设备初始化时被赋值。这些域中的一些携带了接
口的信息,一些存在只是为了方便驱动程序(也就是说,核心并不使用它们);还有一
些域,最引人注意的是一些设备方法,它们是核心和驱动程序的接口。

些域,最引人注意的是一些设备方法,它们是核心和驱动程序的接口。
 
我想分别列为三组,与域的实际顺序无关,那并不重要。
 
接口信息
 
多数接口信息都由函数ether_setup来正确设置。以太网卡在大部分域都可以依赖这个通
用目的的函数,但flags和dev_addr域是设备特定的,必须在初始化时显式地赋值。
 
一些非以太网的接口可以使用类似于ether_setup的助手函数。driver/net/net_init.c
引出tr_setup(令牌环)和fddi_setup。如果你的设备不属于这些类中的一种,你需要自
己为所有的域赋值。
 
unsigned short hard_header_len;
 
“硬件包头长”。发送包头中IP头(或其它协议信息)之前那部分的八元组个数。对以
太网接口来说,这个值是14。
 
unsigned short mtu;
 
“最大传送单元”。在包传输时,这个域由网络层使用。以太网的MTU为1500个八元组。
 
__u32 tx_queue_len;

__u32 tx_queue_len;
 
在设备传送队列中可以排队的最大祯数。ether_setup将这个值设为100,不过你可以改
变它。例如,plip使用10以避免浪费系统内存(plip比实际的以太网接口吞吐率要低)

 
unsigned short type;
 
接口的硬件类型。这个域被ARP使用以判断接口支持的硬件地址类型。以太网接口把它设
为ARPHRD_ETHER----ether_setup为你做这件事。
 
unsigned char addr_len;
 
unsigned char broadcast[MAX_ADDR_LEN];
 
unsigned char dev_addr[MAX_ADDR_LEN];
 
以太网地址长为六个八元组(我们是指接口板的硬件标志),播送地址由六个0xff八元
组组成;ether_setup负责这些值的正确设置。另一方面,设备地址必须以设备特定的方
式从接口板中读出,驱动程序应把它复制到dev_addr。这个硬件地址用来在把包交给驱
动程序传送前产生正确的以太网包头。snull并不使用物理接口,它生成一个它自己的物
理地址。
 

 
unsigned short family;
 
接口的地址族,通常为AF_INET。接口并不常查看这个域或者向其赋值。
 
unsigned short pa_alen;
 
协议地址长。对AF_INET来说为四个八元组。接口不需要修改这个数。
 
unsigned long pa_addr;
 
unsigned long pa_brdaddr;
 
unsigned long pa_mask;
 
刻划接口的三个地址:接口地址,播送地址,及网络掩码。这些值是协议特定的(既它
们是“协议地址”);如果dev->family是INET,则它们为IP地址。这些域由ifconfig赋
值,对驱动程序是只读的。
 
unsigned long pa_dstaddr;
 
plip和ppp一类点到点协议使用这个域记录连接另一侧的IP号码。和前面的域一样,它也
是只读的。

是只读的。
 
unsigned short flags;
 
接口标志。这个域含有下列位值。前缀IFF意为接口标志(InterFace Flags)。有些标
志由核心管理,有些则是在初始化时由接口设置,以确认接口的能力。有效的标志是:
 
IFF_UP
 
当接口是活跃的时,核心置上该标志。这个标志对驱动程序是只读的。
 
IFF_BROADCAST
 
这个标志表明接口的播送地址是有效的。以太网卡支持播送。
 
IFF_DEBUG
 
查错模式。这标志控制printk调用的唠叨,还用在其它一些查错目的。尽管目前没有官
方驱动程序使用它,用户程序可以通过ioctl来对其置位或者清除,你的驱动程序可以使
用它。misc-progs/netifdebug程序可以用来将这个标志打开或关闭。
 
IFF_LOOPBACK
 

 
这个标志在环回接口中要被置位。核心检测这个标志而不是将名字lo作为特殊接口硬写
入程序。
 
IFF_POINTOPOINT
 
点到点的初始化函数应置位这个标志。例如,plip对它置位。ifconfig工具也可以对其
置位和清除。当它被置位时,dev->pa_dstaddr应该指向连接的另一端。
 
IFF_NOARP
 
常规网络接口可以传送ARP包。如果接口不能进行ARP,它必须置这个标志。例如,点到
点接口并不需要运行ARP,它只能增加额外的通信,却不能获取任何有用的信息。snull
不具有ARP能力,因此它要对其置位。
 
IFF_PROMISC
 
这个标志被置位以获得杂类操作。在缺省情况下,以太网接口使用硬件过滤器以保证它
只收到播送包和指向其硬件地址的包。而象tcpdump一类包监视器则在接口上设置杂类模
式,以获取经过接口传输介质的所有包。
 
IFF_MULTICAST
 

 
能进行选播传送的接口要置这个标志。ether_setup在缺省情况下对其置位。所以如果你
的驱动程序不支持选播,它必须在初始化时清除这个标志。
 
IFF_ALLMULTI
 
这个标志告诉接口接收所有的选播包。只有当IFF_MULTICAST被置位,而主机由进行选播
路由时,核心对其置位。它对接口时只读的。IFF_MULTICAST和IFF_ALLMULTI早在1.2版
就已经定义了,但那时并未使用。在后面“选播”一节我们将看到它是如何使用的。
 
IFF_MASTER
 
IFF_SLAVE
 
这些标志被加载均衡代码使用。接口驱动程序不需要知道它们。
 
IFF_NOTRAILERS
 
IFF_RUNNING
 
这些标志在Linux中不使用,只是为了和BSD兼容而存在。
 
当一个程序改变IFF_UP,open和close方法会被调用。当IFF_UP或其它标志被修改时,se

当一个程序改变IFF_UP,open和close方法会被调用。当IFF_UP或其它标志被修改时,se
t_multicast_list方法被调用。如果驱动程序因为标志的修改而要执行一些动作,那么
必须在set_multicast_list中进行。例如,当IFF_PROMIS被置位或清除时,板上硬件过
滤器必须被通知。这个设备方法的责任将在后面的“选播”一节简单介绍。
 
 
 
设备方法
 
与字符设备和块设备的情况一样,每个网络设备要声明在其上操作的函数。可以在网络
接口上进行的操作列在下面。一些操作可以留为NULL,还有一些通常不去动它们,因为e
ther_setup给它们分配合适的方法。
 
一个网络接口的设备方法可以分为两类:基本的和可选的。基本的包括那些为访问接口
所需要的;可选的方法实现一些并不严格要求的高级功能。下面是基本方法:
 
int (*open)(struct device *dev);
 
打开接口。只要ifconfig激活一个接口,它就被打开了。open方法要注册它需要的所有
资源(I/O端口,IRQ,DMA,等),打开硬件,增加模块的使用计数。
 
int (*stop)(struct device *dev);
 

 
终止接口。接口在关闭时就终止了;在打开时进行的操作应被保留。
 
int (*hard_start_xmit)(struct sk_buff *skb, struct device *dev);
 
硬件开始传送。这个方法请求一个包的传送。这个包含在一个套接字缓冲区结构(sk_bu
ff)中。套接字缓冲区在下面介绍。
 
int (*rebuild_header)(void *buf, struct device *dev, unsigned long raddr,
struct sk_buffer *skb);
 
这个函数用来在一个包传送之前重构硬件包头。这个以太网设备使用的缺省包头用ARP向
包中填入缺少的信息。snull驱动程序实现了它自己的这个方法,因为ARP并不在sn接口
上运行。(在本章的后面会介绍ARP。)这个方法的参数是一些指针,分别指向硬件包头
,设备,“路由器地址”(包的初始目的地),以及被传送的缓冲区。
 
int (*hard_header)(struct sk_buffer *skb, struct device *dev, unsigned
short type,
 
                               void *daddr, void *saddr, unsigned len);
 
硬件包头。这个函数用以前获取的源和目的地址构造包头;它的任务是组织那些以参数
的形式传给它的信息。eth_header是以太网类接口的缺省函数, ether_setup相应地对

的形式传给它的信息。eth_header是以太网类接口的缺省函数, ether_setup相应地对
这个域赋值。给出的参数顺序适用于核心2.0或更高版本,但与1.2有所不同。这个改变
对以太网驱动程序是透明的,因为它继承了 eth_header的实现;其它驱动程序可能要处
理一下这个不同,如果它们想保持向后兼容的话。
 
struct enet_statistics * (*get_stats)(struct device *dev);
 
当应用希望获得接口的统计信息时需要调用这个方法,例如,当运行ifconfig或netstat
 –i时。在snull中的一个示例实现将在后面“统计信息”中介绍。
 
int (*set_config)(struct device *dev, struct ifmap *map);
 
改变接口的配置。这个方法是配置驱动程序的入口点。设备的I/O地址和中断号可以在运
行时用set_config改变。在接口不能探测到时,系统管理员可以适用这个能力。这个方
法在后面的“运行时配置”中介绍。
 
 
 
其余的设备方法是被我称为可选的那些。传递给其中一些的参数在Linux1.2到Linux2.0
的转变中改了好几次。如果你想写一个可以在两个版本核心都工作的驱动程序,你可以
只为从2.0开始的版本实现这些操作。
 
int (*do_ioctl)(struct devices *dev, struct ifreg *ifr, int cmd);

int (*do_ioctl)(struct devices *dev, struct ifreg *ifr, int cmd);
 
执行接口特定的ioctl命令。这些命令的实现在后面的“自定义ioctl命令”中描述。这
里给出的原形在1.2以上的核心都能工作。如果接口不需要任何接口特定的命令,那么结
构device中相应的域可以留为NULL。
 
void (*set_multicast_list)(struct device *dev);
 
当设备的选播列表改变和标志改变时,将调用这个方法。这里的参数传递与1.2版本不同
。更多的细节和一个示例实现见“选播”一节。
 
int (*set_mac_address)(struct device *dev, void *addr);
 
如果接口支持改变硬件地址的能力,可实现这个函数。多数接口要么不支持这个能力,
要么使用缺省的eth_mac_addr实现。这个原形与1.2版也不同。
 
#define HAVE_HEADER_CACHE
 
void (*header_cache_bind)(struct hh_cache **hhp, struct device *dev,
unsigned short htype, __u32 daddr);
 
void (*header_cache_update)(struct hh_cache *hh, struct device *dev,
unsigned char *haddr);

unsigned char *haddr);
 
这些函数和宏在Linux1.2中没有。以太网驱动程序不必关心header_cache的问题,因为e
th_setup会安排使用缺省的方法。
 
#define HAVE_CACHE_MTU
 
int (*change_mtu)(struct device *dev, int new_mtu);
 
如果接口的MTU(最大传送单元)发生了改变,这个函数负责采取动作。这个函数和宏在
Linux1.2中都没有。当MTU改变时,如果驱动程序要做一些特殊的事情,它应该声明它自
己的函数,不然将由缺省函数来完成。如果你感兴趣,snull有一个这个函数的模版。
 
 
 
工具域
 
其余的结构device中的域被接口用来保存一些有用的状态信息。其中一些被ifconfig和n
etstat用来向用户提供当前配置的信息。因此,接口应该对这些域赋值。
 
unsigned long trans_start;
 
unsigned long last_rx;

unsigned long last_rx;
 
这两个域用来保存一些瞬间值。它们目前不用,但核心有可能将来使用这些计时提示。
驱动程序负责在传送开始时和收到包时更新这些值。trans_start域还可以被驱动程序用
来检测锁定。驱动程序可以在等待一个“传送完成”的中断时用trans_start来检查超时

 
void *priv
 
等价于filp->private_data。驱动程序拥有这个指针,可以随意使用。通常这个私有数
据结构含有一个enet_statistics结构项。这个域在以前的“初始化每个设备”中用过。
 
 
unsigned char if_prot;
 
这个域用来记录哪个硬件端口被接口使用(例如,BNC,AUI,TP)。任何数值都可以按
需要赋给它。
 
unsigned char dma;
 
被接口使用的DMA通道。这个域被ioctl的SIOCGIFMAP命令使用。
 
struct dev_mc_list *mc_list;

 
unsigned char dma;
 
被接口使用的DMA通道。这个域被ioctl的SIOCGIFMAP命令使用。
 
struct dev_mc_list *mc_list;
 
int mc_count
 
这两个域被用来处理选播传送。Mc_count是mc_list中项的个数。更多的细节见“选播”
结构device中还有一些别的域,但驱动程序没有使用它们
--
※ 来源:.华南网木棉站 bbs.gznet.edu.cn.[FROM: 202.38.196.234]
--
※ 转寄:.华南网木棉站 bbs.gznet.edu.cn.[FROM: 211.80.41.106]
--
※ 转寄:.华南网木棉站 bbs.gznet.edu.cn.[FROM: 211.80.41.106]
--
※ 转载:.南京大学小百合站 bbs.nju.edu.cn.[FROM: 211.80.41.106]
--
※ 转载:·饮水思源 bbs.sjtu.edu.cn·[FROM: 211.80.41.106]

目录页 | 上一页 | 下一页

|
|
|
|
|
|
Copyright©Edward Wang 2000 - 2001
NewB工作室,北京