LinuxTCP/IP协议栈笔记(一)网卡驱动和队列层中的数据包接收tcp/ip2008-05-1612:05:00阅读1评论0字号:大中小数据包的接收作者:kendo=14&extra=page%3D1Kernel:2.6.12一、从网卡说起这并非是一个网卡驱动分析的专门文档,只是对网卡处理数据包的流程进行一个重点的分析。这里以Intel的e100驱动为例进行分析。大多数网卡都是一个PCI设备,PCI设备都包含了一个标准的配置寄存器,寄存器中,包含了PCI设备的厂商ID、设备ID等等信息,驱动程序使用来描述这些寄存器的标识符。如下:[Copytoclipboard][-]CODE:structpci_device_id{__u32vendor,device;/*VendoranddeviceIDorPCI_ANY_ID*/__u32subvendor,subdevice;/*SubsystemID'sorPCI_ANY_ID*/__u32class,class_mask;/*(class,subclass,prog-if)triplet*/kernel_ulong_tdriver_data;/*Dataprivatetothedriver*/};这样,在驱动程序中,常常就可以看到定义一个structpci_device_id类型的数组,告诉内核支持不同类型的PCI设备的列表,以e100驱动为例:#defineINTEL_8255X_ETHERNET_DEVICE(device_id,ich){\PCI_VENDOR_ID_INTEL,device_id,PCI_ANY_ID,PCI_ANY_ID,\PCI_CLASS_NETWORK_ETHERNET8,0xFFFF00,ich}staticstructpci_device_ide100_id_table[]={INTEL_8255X_ETHERNET_DEVICE(0x1029,0),INTEL_8255X_ETHERNET_DEVICE(0x1030,0),INTEL_8255X_ETHERNET_DEVICE(0x1031,3),……/*略过一大堆支持的设备*/{0,}};在内核中,一个PCI设备,使用structpci_driver结构来描述,structpci_driver{structlist_headnode;char*name;structmodule*owner;conststructpci_device_id*id_table;/*mustbenon-NULLforprobetobecalled*/int(*probe)(structpci_dev*dev,conststructpci_device_id*id);/*Newdeviceinserted*/void(*remove)(structpci_dev*dev);/*Deviceremoved(NULLifnotahot-plugcapabledriver)*/int(*suspend)(structpci_dev*dev,pm_message_tstate);/*Devicesuspended*/int(*resume)(structpci_dev*dev);/*Devicewokenup*/int(*enable_wake)(structpci_dev*dev,pci_power_tstate,intenable);/*Enablewakeevent*/void(*shutdown)(structpci_dev*dev);structdevice_driverdriver;structpci_dynidsdynids;};因为在系统引导的时候,PCI设备已经被识别,当内核发现一个已经检测到的设备同驱动注册的id_table中的信息相匹配时,它就会触发驱动的probe函数,以e100为例:/**定义一个名为e100_driver的PCI设备*1、设备的探测函数为e100_probe;*2、设备的id_table表为e100_id_table*/staticstructpci_drivere100_driver={.name=DRV_NAME,.id_table=e100_id_table,.probe=e100_probe,.remove=__devexit_p(e100_remove),#ifdefCONFIG_PM.suspend=e100_suspend,.resume=e100_resume,#endif.driver={.shutdown=e100_shutdown,}};这样,如果系统检测到有与id_table中对应的设备时,就调用驱动的probe函数。驱动设备在init函数中,调用pci_module_init函数初始化PCI设备e100_driver:staticint__inite100_init_module(void){if(((1debug)-1)&NETIF_MSG_DRV){printk(KERN_INFOPFX%s,%s\n,DRV_DESCRIPTION,DRV_VERSION);printk(KERN_INFOPFX%s\n,DRV_COPYRIGHT);}returnpci_module_init(&e100_driver);}一切顺利的话,注册的e100_probe函数将被内核调用,这个函数完成两个重要的工作:1、分配/初始化/注册网络设备;2、完成PCI设备的I/O区域的分配和映射,以及完成硬件的其它初始化工作;网络设备使用structnet_device结构来描述,这个结构非常之大,许多重要的参考书籍对它都有较为深入的描述,可以参考《Linux设备驱动程序》中网卡驱动设计的相关章节。我会在后面的内容中,对其重要的成员进行注释;当probe函数被调用,证明已经发现了我们所支持的网卡,这样,就可以调用register_netdev函数向内核注册网络设备了,注册之前,一般会调用alloc_etherdev为以太网分析一个net_device,然后初始化它的重要成员。除了向内核注册网络设备之外,探测函数另一项重要的工作就是需要对硬件进行初始化,比如,要访问其I/O区域,需要为I/O区域分配内存区域,然后进行映射,这一步一般的流程是:1、request_mem_region()2、ioremap()对于一般的PCI设备而言,可以调用:1、pci_request_regions()2、ioremap()pci_request_regions函数对PCI的6个寄存器都会调用资源分配函数进行申请(需要判断是I/O端口还是I/O内存),例如:[Copytoclipboard][-]CODE:intpci_request_regions(structpci_dev*pdev,char*res_name){inti;for(i=0;i6;i++)if(pci_request_region(pdev,i,res_name))gotoerr_out;return0;[Copytoclipboard][-]CODE:intpci_request_region(structpci_dev*pdev,intbar,char*res_name){if(pci_resource_len(pdev,bar)==0)return0;if(pci_resource_flags(pdev,bar)&IORESOURCE_IO){if(!request_region(pci_resource_start(pdev,bar),pci_resource_len(pdev,bar),res_name))gotoerr_out;}elseif(pci_resource_flags(pdev,bar)&IORESOURCE_MEM){if(!request_mem_region(pci_resource_start(pdev,bar),pci_resource_len(pdev,bar),res_name))gotoerr_out;}return0;有了这些基础,我们来看设备的探测函数:staticint__devinite100_probe(structpci_dev*pdev,conststructpci_device_id*ent){structnet_device*netdev;structnic*nic;interr;/*分配网络设备*/if(!(netdev=alloc_etherdev(sizeof(structnic)))){if(((1debug)-1)&NETIF_MSG_PROBE)printk(KERN_ERRPFXEtherdevallocfailed,abort.\n);return-ENOMEM;}/*设置各成员指针函数*/netdev-open=e100_open;netdev-stop=e100_close;netdev-hard_start_xmit=e100_xmit_frame;netdev-get_stats=e100_get_stats;netdev-set_multicast_list=e100_set_multicast_list;netdev-set_mac_address=e100_set_mac_address;netdev-change_mtu=e100_change_mtu;netdev-do_ioctl=e100_do_ioctl;SET_ETHTOOL_OPS(netdev,&e100_ethtool_ops);netdev-tx_timeout=e100_tx_timeout;netdev-watchdog_timeo=E100_WATCHDOG_PERIOD;netdev-poll=e100_poll;netdev-weight=E100_NAPI_WEIGHT;#ifdefCONFIG_NET_POLL_CONTROLLERnetdev-poll_controller=e100_netpoll;#endif/*设置网络设备名称*/strcpy(netdev-name,pci_name(pdev));/*取得设备私有数据结构*/nic=netdev_priv(netdev);/*网络设备指针,指向自己*/nic-netdev=netdev;/*PCIy设备指针,指向自己*/nic-pdev=pdev;nic-msg_enable=(1debug)-1;/*将PCI设备的私有数据区指向网络设备*/pci_set_drvdata(pdev,netdev);/*激活PCI设备*/if((err=pci_enable_device(pdev))){DPRINTK(PROBE,ERR,CannotenablePCIdevice,aborting.\n);gotoerr_out_free_dev;}/*判断I/O区域是否是I/O内存,如果不是,则报错退出*/if(!(pci_resource_flags(pdev,0)&IORESOURCE_MEM)){DPRINTK(PROBE,ERR,CannotfindproperPCIdevicebaseaddress,aborting.\n);err=-ENODEV;gotoerr_out_disable_pdev;}/*分配I/O内存区域*/if((err=pci_request_regions(pdev,DRV_NAME))){DPRINTK(PROBE,ERR,CannotobtainPCIresources,aborting.\n);gotoerr_out_disable_pdev;}/**告之内核自己的DMA寻址能力,这里不是很明白,因为从0xFFFF