📄 (ldd) ch14-网络驱动程序(上)(转载).txt
字号:
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;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -