我们以NE2000兼容网卡为例,来具体介绍基于模块的网络驱动程序的设计过程。可以参考文件linux/drivers/net/ne.c和linux/drivers/net/8390.c。
1.模块加载和卸载
NE2000网卡的模块加载功能由init_module()函数完成。具体过程及解释如下:
int init_module(void)
{
int this_dev, found = 0;
//循环检测ne2000类型的网络设备接口
for (this_dev = 0; this_dev < MAX_NE_CARDS; this_dev++)
{
//获得网络接口对应的net-device结构指针
struct net_device *dev = &dev_ne[this_dev];
dev->irq = irq[this_dev]; //初始化该接口的中断请求号
dev->mem_end = bad[this_dev]; //初始化接收缓冲区的终点位置
dev->base_addr = io[this_dev]; //初始化网络接口的I/O基地址
dev->init = ne_probe; //初始化init为ne_probe,后面介绍此函数
//调用registre_netdevice()向系统登记网络接口,在这个函数中将分配给网络接口在系统中惟一
的名称。并且将该网络接口设备添加到系统管理的链表dev-base中进行管理。
if (register_netdev(dev) == 0) {
found++;
continue; }
… //省略
}
return 0;} |
模块卸载功能由cleanup_module()函数来实现。如下所示:
void cleanup_module(void)
{
int this_dev;
//遍历整个dev-ne数组
for (this_dev = 0; this_dev < MAX_NE_CARDS; this_dev++) {
//获得net-device结构指针
struct net_device *dev = &dev_ne[this_dev];
if (dev->priv != NULL) {
void *priv = dev->priv;
struct pci_dev *idev = (struct pci_dev *)ei_status.priv;
//调用函数指针 idev->deactive将已经激活的网卡关闭使用
if (idev) idev->deactivate(idev);
free_irq(dev->irq, dev);
//调用函数release_region()释放该网卡占用的I/O地址空间
release_region(dev->base_addr, NE_IO_EXTENT);
//调用unregister_netdev()注销 这个net_device()结构
unregister_netdev(dev);
kfree(priv); //释放priv空间
}
}
} |
2.网络接口初始化
实现此功能是由ne_probe()函数来完成的。前面已经提到过,在init_module()函数中用它来初始化init函数指针。它主要对网卡进行检测,并且初始化系统中网络设备信息,用于后面的网络数据的发送和接收。具体过程及解释如下:
int __init ne_probe(struct net_device *dev)
{
unsigned int base_addr = dev->base_addr;
//初始化dev-owner成员,因为使用模块类型驱动,会将dev-owner指向对象modules结构指针。
SET_MODULE_OWNER(dev);
//检测dev->base_addr是否合法,是则执行ne-probe1()函数检测过程。不是,则需要自动检测。
if (base_addr > 0x1ff)
return ne_probe1(dev, base_addr);
else if (base_addr != 0)
return -ENXIO;
//如果有ISAPnP设备,则调用ne_probe_isapnp()检测这种类型的网卡。
if (isapnp_present() && (ne_probe_isapnp(dev) == 0))
return 0;
…//省略
return -ENODEV;
} |
这其中两个函数ne_probe_isapnp()和ne_probe19()的区别在于检测中断号上。PCI方式只需指定I/O基地址就可以自动获得IRQ,是由BIOS自动分配的;而ISA方式需要获得空闲的中断资源才能分配。
3.网络接口设备打开和关闭
网络接口设备打开就是激活网络接口,使它能接收来自网络的数据并且传递到网络协议栈的上面,也可以将数据发送到网络上。设备关闭就是停止操作。
在NE2000网络驱动程序中,网络设备打开由dev_open()和ne_open()完成,设备关闭有dev_close()和ne_close()完成。它们相应调用底层函数ei_open()和ei_close()来完成。其实现过程相对简单,不再赘述。
4.数据包接收和发送
在驱动程序层次上的发送和接收数据都是通过低层对硬件的读写来完成的。当网络上的数据到来时,将触发硬件中断,根据注册的中断向量表确定处理函数,进入中断向量处理程序,将数据送到上层协议进行处理。
对NE2000网卡的数据接收过程是由ne_probe()函数中的中断处理函数ei_interrupt来完成的。在进入ei_interrupt()之后再通过ei_receive()从8309的接收缓冲区获得数据,并组合成sk_buff结构,再通过netif_rx()函数将接收到的数据存放在系统的饿接收队列之中。
Ei-interrupt()的函数原型:void ei_interrupt(int irq,void *dev_id, struct pt_regs *regs)。其中irq为中断号,dev-id是表示产生中断的网络接口设备对应的结构指针,regs表示当前的寄存器内容。数据发送是由dev_dev_start_xmit函数指针对应的ei_start_xmit函数它来完成数据包的发送。在函数ethdev_init()把net_device结构的hard_start_xmit指针初始化为ei_start_xmit。
设计Linux网络设备驱动程序有一定的模式。遵循这个模式,将会大大减轻我们设计程序的工作量。本文分析了网络驱动程序在内核中的工作机理,提出了设计网络驱动程序的一种模式,并给出了实例。这对设计复杂的网络驱动程序是很有帮助的。