-
以
snull
为例分析
linux
网卡驱动的技术文档
网络
设备,即网络接口,
在操作系统核心级上处理包的发送和接收。
与块设备一样,
网
络接口也在特定的数据结构之中注册自己,<
/p>
以利于在跟外界进行包交换的时候被调用;
但是
< br>它不象块设备一样存在于文件系统当中。
二者最主要的区别在于:
块设备是收到要求,
才向
内核发送一个块缓冲区的内容
;网络接口是主动向内核推入从接口进入的包。
Linux
核心
的
网络子系统,在设计的时候是完全独立于协议的,对网络协议(如
IP
对
IPX
或其它)和硬
件协议
(以太网对令牌环等等)
都是一样的。
一个网络接口的驱动和内核的交互是一次处理
一个网络包,
p>
这样就可以让协议问题巧妙的隐藏在驱动后面,
也可以让物理上的传
输隐藏在
协议后面。
在下面的讲述中
,我们将以一个基于内存的(即纯软件的)模块化网络接口,
SNULL
,
来作为示例。为了简化讨论,我们让
snull
p>
使用以太网协议并传输
IP
包。
How snull Is
Designed
SNULL
的设计
snull
模块有两个接口,并且不同于
loopback
接口,它看起来就像两个外部的连接,但
实
际上是依赖于一个计算机本身。
我们把
snull
指定上
IP
地址,
这样并不
影响通用性的编码,
只是在示例的时候比较方便。两个接口
sn
0
和
sn1
对应两个
< br>C
类网络
snullnet0
和
snullnet1
,
local0<
/p>
和
local1
是对
sn0
和
sn1
接口指定的
IP
地址,而
remote0
和
remote1
分别是
sn
ullnet0
和
snullnet1
网络中的两个主机。我们在
/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
并使用下面的命令建立路由信息:
# ifconfig sn0 local0
# route add -net snullnet0 netmask
255.255.255.0 sn0
#
ifconfig sn1 local1
# route
add -net snullnet1 netmask 255.255.255.0 sn1
由于
Linux
的内核是不会把一个包从一个接口
直接传送到本机另一个接口的,
因此在实
现上采用了一些技巧,
就是在传输数据的过程中修改源和目的地址。
换句话说就是让发
出的
包被本地的另一个接口所接收,但是接收接口由不被认为是本地的。具体的做法就是
修改
IP
地址,把目标地址的第三个字节置反,那么发往
remote0(192.168.0.99)
的包就变成了去往
p>
local1(192.168.1.99)
的包,回到了本机的另
一个接口。这样我们就可以让
remote
端的接口变
得可达,也显示了怎样让通过
snull
到达<
/p>
remote0
和
remote1.
The Physical Transprot of
Packets
包的物理传输
就数
据的传输过程来说,
snull
接口应该算作以太网一类的,示
例代码也使用了内核对
以太网的支持。使用以太网模型建立
sn
ull
首先是因为以太网设备太为通用了。其次
snull
p>
也可以在接口上运行
tcpdump
,当然
,运行
tcpdump
的话,接口就要叫做
ethx
,而不是
snx
。
snull
模块已经准备好将自己声明为
et
hx.
如果在
insmod
命令中指
定
eth=1
,
则该功能就被选
中。如果忘了给
snull
起一个
eth
的名字,
tcpdump
< br>就会拒绝转储接口,并返回一个
unknown
physical layer type
的错误。
在实际中,
snull
的代码还会对包进行侦听甚至修改它,因为这是要求代码做的。
p>
snull
的代码会修改每个包的
IP
p>
包头中的源、目的和校验和,但不检查这个包是否真正的包含
IP<
/p>
信息。这种修改方式捣毁非
IP
包。
p>
Connecting to the
Kernel
连接内核
我们通过剖
析
snull
的源程序来看看网络驱动的结构。同时参看一些驱
动程序的源码,
有助于下面的讨论。内核的驱动程序,由易到难,可以参看
loopback.c, plip.c, 3c509.c;
可以
作为示例代码还有
skeleton.c
,<
/p>
尽管它并不能真正运行。
3c59x.c
和
tulip.c
是
pci
的并运用
DMA
的例子。
Module Loading
模块加载
当一个驱动模块载入运行的
内核中的时候,
它需要申请资源并向核心提供调用接口。
申
p>
请资源并没有什么特别的,
驱动要探测它的设备和硬件的位置
(
I/O
端口和
IR
Q
中断请求号)
,
但是并不进行登记。
一个网络驱动的登记是通过它的
init_module
函数完成的,
这一点不同
于字符设备和块设备的驱动程序
,
不同于取得一个设备描述符或者文件描述符,对于网络设
备,
< br>有一张全局的网络设备列表,
驱动程序对每一个新探测到的接口都会在该表中插入
一个
数据结构。
每个接口由一个
struct
device
项来描述。两个
snull
接口
sn0
和
sn1
的结构声明如下:
char snull_names[16]; /* 2
8-byte buffers */
struct device
snull_devs[2] = {
{
snull_name,
/* name -- set at load time */
0, 0, 0, 0, /*
shmem addresses */
0x000,
/* ioport
*/
0,
/* irq line */
0, 0, 0,
/*
various flags, init to 0 */
NULL,
/* next ptr */
snull_init,
/
*
init function, fill other fields with NULLs */
},
{
snull_name+8,
/* name -- set at load time */
0, 0, 0, 0, /* shmem
addresses */
0x000,
/* ioport
*/
0,
/* irq line */
0, 0, 0,
/*
various flags, init to 0 */
NULL,
/* next ptr */
snull_init,
/
*
init function, fill other fields with NULLs */
},
}
注意第一个域
name
,指向一个在打开时填充的静态缓冲区。在这种方式下,接口的名
字可以晚一
些
来
选择,万一你在该结构中使用了
一个明确的缓冲区,比如
那么
代
码就不能可靠地工作。
因为编译会崩溃于重
复串:
以单缓冲结束,
却有两个指针来指向。
< br>此外,
编译器可能甚至选择在只读内存中保存常量串,
而不是所预想的。
前面的代码端已经已经明确的利用了
struct device
的
name
域和
init
域。
name
域
,
保
存接
口的名字(名字时标示接口的串)
,
eth0,eth1...
安数字升序。驱动可以为接口写入一个
名字,
< br>或者允许动态指定
(就是从
0
开
始按数字升序)
。
如果在载入时指定
eth=1
,
init_module
就会使用动态指定。默认的名字都是由
init_module
指定的:
if (!snull_eth)
{ /* call them
memcpy(snull_devs[0].name,
memcpy(snull_devs[1].name,
} else { /* use automatic
assignment */
snull_devs[0].name[0] =
snull_devs[1].name[0] = '';
}
init
域是一个函数指针。无论
在何时登记一个设备,内核都会让驱动自己进行初始化。
初始化意味着它测物理接口,以
正确的值填写
device
结构。
如果初始化失败,该结构就不
被连接到全局的网络设备表中。
这
种特有的建立方法在系统引导的时候最为有用,
每个驱动
都试图
登记自己的设备,
但是只有确实存在的设备才连接到全局设备表。
由于实际的初始化
是在别处执行的,所以
init_module
只需要一个语句做就够了:
for (i=0; i<2;
i++)
if (
(result = register_netdev(snull_devs + i)) )
printk(
result,
snull_devs[i], name);
else device_present++;
Initalizing Each Device
初始化每个设备
初始化函数
init
也经常叫做
probe
。
snull_init
的核心代码是这样的:
ether_setup(dev); /* assign
some of the fields */
dev->open
= snull_open;
dev->stop
=
snull_release;
dev->set_config
= snull_config;
dev->had_start_xmit
=
snull_tx;
dev->do_ioctl
= snull_ioctl;
dev->get_stats
=
snull_stats;
dev->rebuild_header
=
snull_rebuild_header;
/*
keep the default flags, just add NOARP */
/* NOTE: every real
Ethernet interface is ARP-aware and won't set this
flag!! */
/*
plip interface (an Ethernet
like) can work without ARP support, like snull */
dev->flags
|= IFF_NOARP
/* xxx_probe is usually a big function
with more 200 lines of code */
snull
模块声明一个
snull_priv
数据结构给
priv
使用
(priv
是
*dev
中的一个域
)
,其实包括
struct
enet_statistics
结构,那是存放接口静态信息的标准位
置。下面的是
snull_init
分配
dev->priv
的代码:
dev->priv =
kmalloc(sizeof(struct snull_priv), GFP_KERNEL);
//
~~~Get Free Page!
if (DEV->PRIV == null)
return -ENOMEM;
memset(dev->priv, 0,
sizeof(struct snull_priv));
Module Unloading
模块卸载
函数
cleanup_module
的工作首先释放分配给设备
私有的内存,然后在全局网络设备表
上注销自己的设备:
void
cleanup_module(void)
{
int i;
for (i=0; i<2; i++) {
kfree(snull_devs[i].priv);
unregister_netdev(snull_devs + i);
}
return;
}
Modularized
and Non-Modularized Drivers
模块化和非模块化驱动
模块化驱动是指驱动程序作为模块,
可以在运行的系统中插入和删除。
p>
非模块化就是指
设备驱动内建入系统的内核,作为内核的一部分。对
于字符和块设备驱动
,
这点上没有显著
区别
,
但是网络设备驱动则不同。
一个
网络设备驱动内建入内核时,
它并不声明自己的
device<
/p>
结构,而由在
drivers/net/Space.c
中声明的结构来代替它。
Space.c
声明一个所有网络设备
的链表,既有驱动特定的结构又有通用的以太网设备结构。<
/p>
以太网驱动根本不关心它们的
device
结构,而使用通用的结构。这种通用的以太网设
备结构将声明
ethif_probe
作为它们的初始化函数。在内核中插
入一个新的以太网接口只需
要在
ethif_probe
中加入一个对驱动的初始化函数的调
用。对于非以太网
(non-
eth)
的驱动,
需要在
Space.c
中加入它们的
device
结构。在这两种情况下,如果要讲一个驱动正确
连接
到内核的话,都是只修改
Space.c
就可以了。
系统引导的时候,
网络初始化代码会
轮巡
(loops through)
所有的
device
结构,
并且传
递一个指向设备本身的指针,来调用探
测函数
(dev->init)
。如
果探测成功,
Space.c
就初始
化
device
结构。建立驱动的方式允许将设备以升序命名为
eht0,eth1,
等等,而不改变每个
设备的<
/p>
name
域。
当一个模块化设备驱动载入的时候,它声明自己的
device
结构,即便它所控制的接口
是一个以太网接口。
研读
Space.c and
net_init.c
,
对于驱动建立的介绍,
目的在于强调初始化设备的方法
,
如
果驱动模块包含一个预先填好的
device
结构,
那么在
struct device
结构中
加入新的域时,
它就不能配合内核的初始化技术,也不是向前兼容的。
THE DEVICE STRRUCTURE IN
DETAIL
device
结构详解
device
结构是网络设备驱动的核心
device
结构分为可见和不可见两部分。前者由明确指定的静态结构组成,正如
前面在
snull
中出现的两个条目
。后者(余下的部分)
是
在内部使用
的,其中有些由驱动访问(例
如在初始化的时候分配的条目)
,
还有些并不触及。
The
Visible Head
可见部分
char *name;
如果第一各字符是
0(
即表示空
NULL)
或者一个空格,
register_netdev
就把它指定为
ethn
,给一个合适的
n
值。
unsigned long rmem_end;
unsigned long rmem_start;
unsigned long mem_end;
unsigned long mem_start;
这几个域记录由设备占用的共享内存的起始和结束地址。
p>
如果接收和发送内存是分开
的话,那么
me
m
表示发送内存,
rmem
表示接收内
存。
mem_start
和
mem_end
在系统引导
的时候可以从核心的命令行
确定,它们的值由
ifconfig
命令获得。
rmem
域从不在
driver
以外被引用。按照习惯
,
置
end
域就可以由
(end
-
start
)得到可用板上
(on-board)
内存的
大小。
unsigned long base_addr;
基本输入
/
输出基址。
与前面的一样,
该域是在设备探测时指定的。
用
ifconfig
命令可以
显示或者修改当前值。
base_addr
可以在系统引导或者(模块)载入时明确指定。
unsigned char irq;
< br>指定的中断号,
dev->irq
的值可由
ifconfig
列出,在接口列出以后。该值一般可以在系
统引导或者(模块)载入时置,也可以后来用
ifconfig
修改。
unsigned char start;
unsigned char interrupt;
p>
这两个域时二进制标志。
start
在一般
在设备打开始置,在关闭时清除,
当接口准备好
可以操作时是非零值。
interrupt
用来告诉
高层的代码接口上有中断到来,并且正在中断服务
中。
unsigned long tbusy;
传输忙。只要驱动不能再接收一个要传输的新的包的时候(例
如所有输出缓冲区满)
,
该域就应置为非零值。
用
long
而不用
char
< br>类型,因为原子位操作有时用来避免出现竞争
(原子操作不会被中断)
。
struct device *next;
用来保持全局设备表,与驱动无关。
int (*init)(struct device *dev)
初始化函数。该域一般是在
device
结构中最后一个明确列出的。
*init
是一个指针,
指向一个函
数,
该函数的返回值是
int
p>
类型。
如果没有扩号,
则
< br>
init
成为函数名返回类型是
int
类型的指针。
The Hidden Fields
隐藏域
device
结构包括一些另外的域,一般再设备初始化时指
定。其中某些域传达
(convey)
接口信息,有些仅仅为了有益于驱动程序本身(也就是说并不为
kernel
所用)
,还有其它的
域,主要是设备方法
p>
(device
methods)
,它作为
核心驱动的接口。
下面将分为三组介绍,与实际顺序(这一点也不重要)无关。
Interface information
接口信息
大多数的接口信息都由
ether_setup
函数正确建立。以太网卡的多数域都
可以依靠
这个通用型函数,唯有
flags
和
dev_addr
是依赖于设备的,
必须在初始化的时候明确指定。
某些非以太网接口卡可以用类似于
ether_setup
的公用函数
driver/net/net_init.c
,
export
s tr_setup (token ring) and fddi_setup.
若你的设备不包含在这些类型中,你需要手工指
定所有这些域。
unsigned
short hard_header_len;
传输的包
的
IP
包头之前的字节数,以太网的
hard_header_len
值是
14
。
unsigned
short
mtu;
最大传输单元,该玉在包
传输过程中被网络层使用。以太网
的
mtu
是
1500
< br>字节。
__u32 tx_queue_len;
设备传输队列可以挂的最大帧数。
ether_setup
把它设为
1
00
,
但是你可以改变。例如,
plip
就用
10
< br>以避免系统内存的浪费(
plip
比实际的以太网接口的
吞吐量小)
。
unsigned short
type;
接口的硬件类型。
ARP
用该域来知
道接口所支持的硬件地址类型。对于以太网接口,
ether_setup
会把它设置为
ARPHRD_ETHER.
unsigned char addr_len;
unsigned char broadcast[MAX_ADDR_LEN];
unsigned char
dev_addr[MAX_ADDR_LEN];
以太网
的
MAC
地址长度是
6
个字节,广播地址是由
6
个
0
xff
字节组成的,
ether_setup
会把这些值设置正确。另一
方面,设
备地址必须由设备特定的方式从接口板上读取,并且
驱动程序要把它拷贝到
dev_addr.
硬件地址用来在包送到驱动
程序做传输之前,
产生正确的
以太网的包头。
< br>snull
设备没有用到物理接口,它会自造一个硬件地址。
unsigned
?
hort
family;
接口的地址族最常用的是
AF_INET.
接口一般不需要看这个域或者为它指定值。
unsigned short
pa_alen;
协议地址长度
Protocol Address Length.
对
AF_INET
设为
4
字节。接口不需要修改。
unsigned long pa_addr;
unsigned long pa_brdaddr;
unsigned long pa_mask;
刻画接口的三个地址:
接口地址、<
/p>
广播地址和网络掩码,它们的值是协议特定的
(也就
是说它们是协议地址)
如果
dev->family
是
AF_INET
,
它们就是
IP
地址。这些域是由
ifconfig
指定的,对驱动来说是只读的。
unsigned long pa_dstaddr;
点对点的接口,
像
plip
和
ppp
用该域来纪录连接的另一端的
IP
值,
该域也是只读的。
unsigned short flags;
接口标志。标志域
flags
包括下面的位值。
IFF_
前缀表示
Flags
。一些标志
由内核管理,其它
的
在接口
初始化的时候置,用来断言不同的接口功能(或者没有功能)
。
合法的标志是:
IFF_UP
内核置该标志表示接口是活跃的,该标志对驱动是只读的。
IFF_BROADCAST
声明接口的广播地址是合法的。以太网卡支持广播。
IFF_DEBUG
调试模式,
Debug Mode,
用来控制
printk
调用或者其它调试目的输出的冗长
程度。
尽管目前
没有正式的驱动程序用到,但是它可以被用户程序通过
ioctl
来置位或者清
位,并且你
的驱动也可以用它。
misc-
progs/netifdebug
程序可以用来置该标志和清该标志。
IFF_LOOPBACK
只能在轮讯接口
(loopback
interface)
中置该标志
.
内核检查
IFF_LOOPBACK
以
代替
lo
的名字作为特殊接口。
IFF_POINTOPOINT
点对点接口的初始化函数要设该标
志,
例如
plip
。
ifconfig
也可以来设置和清
除。
当
IFF_POINTOPOINT
设置以后,
p>
dev->pa_dstaddr
就参指连接的另一端。
IFF_NOARP
常规的网络接口可以传递
ARP
p>
包,如果接口不能的话就要设置该标志。
IFF_PROMISC
设置该位以进行不区别的操作。默
认情况下,以太网接口用硬件过滤来保证
他们只接收广播包和直接送到它的硬件地址上的
包。而
tcpdump
这样的包的
吸食者就在接
口上设置不区别模式,以获
取经过接口传输介质上的所有包。
IFF_MULTICAST
兼容多播传送的接口设置该位。
ether_setup
p>
默认设置
IFF_MULTICAST
,<
/p>
如果
你的驱动不支持多播,
就有必要在初始化的时候清除它。
IFF_ALLMULTI
告诉接口接收所有的多目包。除非
IFF_MULTICAST
已经设置,内核才在该
主机执行多播路由的时候设置它。
IFF_ALLMULTI
对接口是只读的。
IFF_MASTER
IFF_SLA
VE
由负载均横化代码使用,接口驱动不必知道。
IFF_NOTRAILERS
IFF_RUNNING
这两个
标志在
Linux
中没有,但是对
BS
D
兼容系统是存在的。
当一个程序改变
IFF_UP,
open
和
close
方法就被调用。当
IFF_UP
或者其它任何
表示被修改,
s
et_multicast_list
被调用。
如果是因为标志的修改,
需要驱动做一些动作的话
,
那么必须在
set_multicast_list
来做。例如,
IFF_PROMIS
被设置或清除,板上的硬件过滤
器就要收到通知。
The device methods
设备方法
每个网络设备都声明作用于自己的函数。
可以在网络设备上执行的操作在下面列出。
p>
有
些操作可以空着,有些一般用不上,因为
ether_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
devcie *dev);
硬件开始传输。
该方法要求传输一个包。
包包含在一个套接字缓冲区
(sk_buff)
结构中。
int (*rebuild_header) (void
*buf, struct device *dev,unsigned long
raddr,struct sk_buff *skb);
在包传输前重建硬件包头。以太网设备默认的函数使用
ARP
来填写落下的信息。
ebuild_header
的参数是:指向硬件包头的指针,设备,
router
address
(包的初始目标)
,和
传输的缓冲区。
int
(*hard_header) (struct sk_buff *skb, struct device
*dev, unsigned short type,
void *daddr,
void *saddr, unsigned len);
硬件包头。
该函数从此前获得的源和目的硬件地址来建立硬件包头,
< br>它的工作就是把作
为参数传递给它的信息组织起来。
et
h_header
是默认的以太网类接口所用的函数,
因此它
来
指定这个域。
struct enet_statistics*
(*get_stats)(struct device *dev);
任何时候一个应用程序需要接口的静态信息的时候,
这个方法被调用。<
/p>
例如,
ifconfig
或
netstat -i
跑的时候。后面有
snull
实现的示例。
int (*set_config)(struct
device *dev, struct ifmap *map);
改变接口配置。这个方法是配置驱动程序的入口点。设备的
I/O
地址和中断号可以在
运行时由
s
et_config
修改。当接口不能被侦测的时候,系统管理员可以使用该功能。
剩下的设备操作被视为可选。在传输过程中传递给他们的参数
从内核的
1.2
到
2.0
版本
之间修改了几次。
如果开发驱动的话只需要实现对
1.2
版本以后的操作。
int
(*do_ioctl) (struct device *dev, struct ifreq
*ifr, int cmd);
执行接口特定的
ioctl
命令。这些命令的实现方法在后面的
ioctl
Commands
讲到。这里所示的原型是通用的,如果接口不需要任何特定的命令的话,在
device
结构中
该域可以空着。
void
(*set_multicast_list)(struct device *dev);
当设备的多播表以及当多播标志改变的时候调用该方法。
int
(*set_mac_address)(struct device *dev, void
*addr);
如果端口支持修改硬件地址的能力的话就可
以实现这个函数。
多数接口或者不支持此功
能,或者用默认的<
/p>
eth_mac_addr
的实现。
Utility fields
效用域
device
结构中剩下的数据域是接口用来保存有用的状态信息的。有些被
ifconfig
和
netstat
使用,来为用户当前的配置信息。因此,接口
应该给这些域指定值。
unsigned long
trans_start;
unsigned long last_rx;
这两个域都是用来保存瞬时的值,
它
们目前还没用上,
但是内核在将来可能用到这
些时间线索。
p>
当
传送开始和当一个包被接收到的时候,
驱动有责任修改这些值。驱动还可
以用
trans_start
域来发现一个锁定。当等待
传送完成
中断的时候,
驱动可以由
trans_start
检查超时。
void *priv;
等于
filp->private_data.
驱动占有这个指针
并且在愿意的时候适用它。一般的,私
有数据结构包含一个
enet_statistics
条目。该域在早先的
初始化每个设备
中就
使用了。
unsigned char if_port;
用来纪录哪一个硬件端口正在被接口使用(例如,
BNC,AUI,TP
)
。
if_port
是给驱
动用的,可以根据
需
要给它赋上算术值。
unsigned char dma;
设备使用的
DMA
< br>通道。被
SIOCGIFMAP ioctl
命令使用。
struct dev_mc_list
*mc_list;
int
mc_count;
这两个域用来处理多播传输。
mc_count
是
mc_list
例的条目的计数值
还有其他的域,但是不被驱动使用。
OPENING AND
CLOSING
打开和关闭
p>
我们的驱动能够在模块再如或者核心引导的时候探测出接口。
下一步
是给接口指定一个
地址,以便驱动能够通过他来交换数据。打开和关闭接口是由
ifconfig
命令完成的。
当
ifconfig
给
端
口
指
定
一
个
地
址
的
时
候
,<
/p>
它
执
行
两
个
任
务
。
首
先
它
通
过
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,
但是没有驱动的函数被调用,该任务是独立于设备的,
由内核来执
行它。后一个命令
i
octl(SIOCSIFLAGS)
为设备调用
open
方法。
与此类似,当接口关闭时,
ifconfig
用
ioctl(SIOCSIFLAGS)
来清
IFF_UP
,并且调用
stop
方法。
这两个方法在成功时返回
0
,出错时通常返回负值。
至少就实际的代码来说,
驱动必须执行与字符设备和块设备相同的任务。
open
请求它
< br>需要的任何系统资源并且让网络接口建立起来;
stop
关闭掉网络接口并且释放系统资源。
最后还有一件事情要做,如果驱动不使用共享中断的话(例如为了与旧的内核兼容)
p>
。
内核输出一个
irq2dev_map
数组,
该数组由中断请求号
(IRQ
number)
来定地址,
并且保存合
法的指针;驱动可能需要该数组来映射中断号到一个指向
struct
device
指针。这是在不使
用中断句柄的
dev_id
参数的情况下,支持单驱动多接口的唯一途径。
另外,网卡的硬件地址需要从板上拷贝到
dev->dev_addr
中,才能使接口与外界进行
p>
任何通讯联系。硬件地址根据驱动的意愿在探测或者打开的时候指定。
snull
软件接口在
open
的时候指定它,它仅仅指定两个
ASCII
字符串作为硬件号。第一个字节是空。
open
和
close
的代码实现可以参看
fo
ps->open
和
fops->close
< br>,代码如下:
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
* x
is 0 or 1. The first byte is '0': a safe choice
with regard
* to multicast.
*/
//
给接口指定硬件地址
,ETH_A
LEN=6 octets
for (i=0; i < ETH_ALEN; i++)
dev->dev_addr[i] =
//
都先赋值为
dev-
dev_addr[ETH_ALEN-1] += (dev - snull_devs); /* the
number */
//
再修改最后一个字节
(octet),
区分<
/p>
dev->start = 1;
dev->tbusy = 0;
MOD_INC_USE_COUNT;//
模块引用计数
p>
+1
return 0;
}
正如所看到的,
某些域在
device
就够中做了修改。
start
指明接口已经准备好,
tbusy
断
言传送者不忙(也就是说,内核可以发出一个包)
。
stop
方法刚好是把
open
的错做反过来,出于这个原因,实现
stop
的函数通常叫做
close.
int snull_release(struct device *dev)
{
/* release ports, irq and such--like
fops->close */
-
-
-
-
-
-
-
-
-
上一篇:铁路术语专用英语知识讲解
下一篇:昴宿星人Alaje-光冥想文字稿(英文全)