Linux TCP/IP协议栈笔记

本文深入探讨Linux网络驱动中TCP/IP协议栈的中断处理机制,从设备的open函数开始,讲解中断注册、网卡中断处理、数据接收流程,包括NAPI技术的应用,以及中断与轮询技术的差异。文章通过代码分析展示了如何从网卡接收数据,如何处理中断,以及如何将数据包交给上层协议栈。
摘要由CSDN通过智能技术生成
数据包的接收

作者:kendo

Kernel:2.6.12

一、从网卡说起

这并非是一个网卡驱动分析的专门文档,只是对网卡处理数据包的流程进行一个重点的分析。这里以Intel的e100驱动为例进行分析。
大多数网卡都是一个PCI设备,PCI设备都包含了一个标准的配置寄存器,寄存器中,包含了PCI设备的厂商ID、设备ID等等信息,驱动
程序使用来描述这些寄存器的标识符。如下:

            CODE:
struct pci_device_id {
        __u32 vendor, device;                /* Vendor and device ID or PCI_ANY_ID*/
        __u32 subvendor, subdevice;        /* Subsystem ID's or PCI_ANY_ID */
        __u32 class, class_mask;        /* (class,subclass,prog-if) triplet */
        kernel_ulong_t driver_data;        /* Data private to the driver */
};

这样,在驱动程序中,常常就可以看到定义一个struct pci_device_id 类型的数组,告诉内核支持不同类型的
PCI设备的列表,以e100驱动为例:

#define INTEL_8255X_ETHERNET_DEVICE(device_id, ich) {/
        PCI_VENDOR_ID_INTEL, device_id, PCI_ANY_ID, PCI_ANY_ID, /
        PCI_CLASS_NETWORK_ETHERNET << 8, 0xFFFF00, ich }
       
static struct pci_device_id e100_id_table[] = {
        INTEL_8255X_ETHERNET_DEVICE(0x1029, 0),
        INTEL_8255X_ETHERNET_DEVICE(0x1030, 0),
        INTEL_8255X_ETHERNET_DEVICE(0x1031, 3),
……/*略过一大堆支持的设备*/
        { 0, }
};

在内核中,一个PCI设备,使用struct pci_driver结构来描述,
struct pci_driver {
        struct list_head node;
        char *name;
        struct module *owner;
        const struct pci_device_id *id_table;        /* must be non-NULL for probe to be called */
        int  (*probe)  (struct pci_dev *dev, const struct pci_device_id *id);        /* New device inserted */
        void (*remove) (struct pci_dev *dev);        /* Device removed (NULL if not a hot-plug capable driver) */
        int  (*suspend) (struct pci_dev *dev, pm_message_t state);        /* Device suspended */
        int  (*resume) (struct pci_dev *dev);                        /* Device woken up */
        int  (*enable_wake) (struct pci_dev *dev, pci_power_t state, int enable);   /* Enable wake event */
        void (*shutdown) (struct pci_dev *dev);

        struct device_driver        driver;
        struct pci_dynids dynids;
};

因为在系统引导的时候,PCI设备已经被识别,当内核发现一个已经检测到的设备同驱动注册的id_table中的信息相匹配时,
它就会触发驱动的probe函数,以e100为例:
/*
  * 定义一个名为e100_driver的PCI设备
* 1、设备的探测函数为e100_probe;
  * 2、设备的id_table表为e100_id_table
  */
static struct pci_driver e100_driver = {
        .name =         DRV_NAME,
        .id_table =     e100_id_table,
        .probe =        e100_probe,
        .remove =       __devexit_p(e100_remove),
#ifdef CONFIG_PM
        .suspend =      e100_suspend,
        .resume =       e100_resume,
#endif

        .driver = {
                .shutdown = e100_shutdown,
        }

};

这样,如果系统检测到有与id_table中对应的设备时,就调用驱动的probe函数。

驱动设备在init函数中,调用pci_module_init函数初始化PCI设备e100_driver:

static int __init e100_init_module(void)
{
        if(((1 << debug) - 1) & NETIF_MSG_DRV) {
                printk(KERN_INFO PFX "%s, %s/n", DRV_DESCRIPTION, DRV_VERSION);
                printk(KERN_INFO PFX "%s/n", DRV_COPYRIGHT);
        }
        return pci_module_init(&e100_driver);
}

一切顺利的话,注册的e100_probe函数将被内核调用,这个函数完成两个重要的工作:
1、分配/初始化/注册网络设备;
2、完成PCI设备的I/O区域的分配和映射,以及完成硬件的其它初始化工作;

网络设备使用struct net_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内存),例如:

            CODE:
int pci_request_regions(struct pci_dev *pdev, char *res_name)
{
        int i;
       
        for (i = 0; i < 6; i++)
                if(pci_request_region(pdev, i, res_name))
                        goto err_out;
        return 0;



            CODE:
int pci_request_region(struct pci_dev *pdev, int bar, char *res_name)
{
        if (pci_resource_len(pdev, bar) == 0)
                return 0;
               
        if (pci_resource_flags(pdev, bar) & IORESOURCE_IO) {
                if (!request_region(pci_resource_start(pdev, bar),
                            pci_resource_len(pdev, bar), res_name))
                        goto err_out;
        }
        else if (pci_resource_flags(pdev, bar) & IORESOURCE_MEM) {
                if (!request_mem_region(pci_resource_start(pdev, bar),
                                        pci_resource_len(pdev, bar), res_name))
                        goto err_out;
        }
       
        return 0;

有了这些基础,我们来看设备的探测函数:
static int __devinit e100_probe(struct pci_dev *pdev,
        const struct pci_device_id *ent)
{
        struct net_device *netdev;
        struct nic *nic;
        int err;

        /*分配网络设备*/
        if(!(netdev = alloc_etherdev(sizeof(struct nic)))) {
                if(((1 << debug) - 1) & NETIF_MSG_PROBE)
                        printk(KERN_ERR PFX "Etherdev alloc failed, 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;
#ifdef CONFIG_NET_POLL_CONTROLLER
        netdev->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 = (1 << debug) - 1;
       
        /*将PCI设备的私有数据区指向网络设备*/
        pci_set_drvdata(pdev, netdev);

        /*激活PCI设备*/
        if((err = pci_enable_device(pdev))) {
                DPRINTK(PROBE, ERR, "Cannot enable PCI device, aborting./n");
                goto err_out_free_dev;
        }

        /*判断I/O区域是否是I/O内存,如果不是,则报错退出*/
        if(!(pci_resource_flags(pdev, 0) & IORESOURCE_MEM)) {
                DPRINTK(PROBE, ERR, "Cannot find proper PCI device "
                        "base address, aborting./n");
                err = -ENODEV;
                goto err_out_disable_pdev;
        }

        /*分配I/O内存区域*/
        if((err = pci_request_regions(pdev, DRV_NAME))) {
                DPRINTK(PROBE, ERR, "Cannot obtain PCI resources, aborting./n");
                goto err_out_disable_pdev;
        }

        /*
         * 告之内核自己的DMA寻址能力,这里不是很明白,因为从0xFFFFFFFF来看,本来就是内核默认的32了
         * 为什么还要调用pci_set_dma_mask来重复设置呢?可能是对ULL而非UL不是很了解吧。
         */
        if((err = pci_set_dma_mask(pdev, 0xFFFFFFFFULL))) {
                DPRINTK(PROBE, ERR, "No usable DMA configuration, aborting./n");
                goto err_out_free_res;
        }

        SET_MODULE_OWNER(netdev);
        SET_NETDEV_DEV(netdev, &pdev->dev);

        /*分配完成后,映射I/O内存*/
        nic->csr = ioremap(pci_resource_start(pdev, 0), sizeof(struct csr));
        if(!nic->csr) {
                DPRINTK(PROBE, ERR, "Cannot map device registers, aborting./n");
                err
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值