Chinaunix首页 | 论坛 | 博客
  • 博客访问: 81809
  • 博文数量: 21
  • 博客积分: 1400
  • 博客等级: 上尉
  • 技术积分: 170
  • 用 户 组: 普通用户
  • 注册时间: 2010-07-29 10:03
文章分类
文章存档

2010年(21)

我的朋友

分类: LINUX

2010-08-01 21:13:23

一:预备知识

         关于I/O内存映射。

         设备通过控制总线,数据总线,状态总线与CPU相连。控制总数传送控制信号,例如,网卡的启用。数据总线控制数据传输,例如,网卡发送数据,状态总数一般都是读取设备的当前状态,例如读取网卡的MAC地址。

         在传统的操作中,都是通过读写设备寄存器的值来实现。但是这样耗费了CPU时钟。而且每取一次值都要读取设备寄存器,造成了效率的低下。在现代操作系统 中。引用了I/O内存映射。即把寄存器的值映身到主存。对设备寄存器的操作,转换为对主存的操作,这样极大的提高了效率。

         关于DMA

         这是关于设备数据处理的一种方式。传统的处理方法为:当设备接收到数据,向CPU报告中断。CPU处理中断,把数据放到内存。

         在现代操作系统中引入的DMA是指,设备接收到数据时,把数据放至DMA内存,再向CPU产生中断。这样节省了大量的CPU时间

         关于软中断与NAPI

         在现代操作系统中,对中断的处理速度要求越来越高。为了响应中断,将中断分为两部份,即上半部与下半部。上半部将数据推入处理队列,响应中断。然后再由下半部调度完成余下的任务。

         NAPI是2.6新引入的一个概念,它在发生中断的时候,禁用中断。然后处理数据。之后,每隔一定的时候,它会主动向设备询用是否有数据要处理。

二:从网卡驱动说起。

         以intel 100M 网卡驱动为例简要概述数据包的接收与发送流程。代码见(drivers/net/e100.c)

网卡是属于PCI设备,它的注册跟一般的PCI设备注册没什么两样。

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);

         }

         //注册PCI

        return pci_module_init(&e100_driver);

}

其中e100_driver对应为网卡的pci_driver.

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

}

当总数探测到PCI设备符合e100_id_table中的参数时,将会调用e100_probe,开始设备的初始化

在e100_probe中:

static int __devinit e100_probe(struct pci_dev *pdev,

         const struct pci_device_id *ent)

{

         struct net_device *netdev;

         struct nic *nic;

         int err;

         //分配net_device并为其赋值

         //alloc_etherdev为以太网接口的net_device分配函数。它是alloc_netdev的封装函数

         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中的函数指针赋初值

         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;

         //支持ethtool工具时有效

         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

         //获得net_device私有数据区,并对其赋值

         //私有数据大小是由alloc_etherdev()参数中指定的

         nic = netdev_priv(netdev);

         nic->netdev = netdev;

         nic->pdev = pdev;

         nic->msg_enable = (1 << debug) - 1;

         pci_set_drvdata(pdev, netdev);

         //启动网卡.为之后DMA,I/O内存映射做准备

//它实际上是对PCI的控制寄存器赋值来实现的

         if((err = pci_enable_device(pdev))) {

                   DPRINTK(PROBE, ERR, "Cannot enable PCI device, aborting.\n");

                   goto err_out_free_dev;

         }

         //获取该资源相关联的标志

         //如果该设备存在I/O内存,则置IORESOURCE_MEM

         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;

         }

         //对PCI的6个寄存器都会调用资源分配函数进行申请

         if((err = pci_request_regions(pdev, DRV_NAME))) {

                   DPRINTK(PROBE, ERR, "Cannot obtain PCI resources, aborting.\n");

                   goto err_out_disable_pdev;

         }

         //探制设备的DMA能力。如果设备支持DMA。pci_set_dma_mask返回0

         pci_set_master(pdev);

         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 = -ENOMEM;

                   goto err_out_free_res;

         }

         if(ent->driver_data)

                   nic->flags |= ich;

         else

                   nic->flags &= ~ich;

         spin_lock_init(&nic->cb_lock);

         spin_lock_init(&nic->cmd_lock);

         //设置定时器。

         init_timer(&nic->watchdog);

         nic->watchdog.function = e100_watchdog;

         nic->watchdog.data = (unsigned long)nic;

         init_timer(&nic->blink_timer);

         nic->blink_timer.function = e100_blink_led;

         nic->blink_timer.data = (unsigned long)nic;

         //为nic->mem建立线性DMA。只是在支持ethtool的时候才有用

if((err = e100_alloc(nic))) {

                   DPRINTK(PROBE, ERR, "Cannot alloc driver memory, aborting.\n");

                   goto err_out_iounmap;

         }

         //对nic成员赋初值

         e100_get_defaults(nic);

         e100_hw_reset(nic);

         e100_phy_init(nic);

         //读取网卡的EEPROM。其中存放着网卡的MAC地址。

         //对EEPROM是通过对I/O映射内存的操作实现的,即nic->csr

         if((err = e100_eeprom_load(nic)))

                   goto err_out_free;

         //设置netdev->dev_addr

         memcpy(netdev->dev_addr, nic->eeprom, ETH_ALEN);

         if(!is_valid_ether_addr(netdev->dev_addr)) {

                   DPRINTK(PROBE, ERR, "Invalid MAC address from "

                            "EEPROM, aborting.\n");

                   err = -EAGAIN;

                   goto err_out_free;

         }

         /* Wol magic packet can be enabled from eeprom */

         if((nic->mac >= mac_82558_D101_A4) &&

            (nic->eeprom[eeprom_id] & eeprom_id_wol))

                   nic->flags |= wol_magic;

         pci_enable_wake(pdev, 0, nic->flags & (wol_magic | e100_asf(nic)));

         //注册网络设备

         if((err = register_netdev(netdev))) {

                   DPRINTK(PROBE, ERR, "Cannot register net device, aborting.\n");

                   goto err_out_free;

         }

         DPRINTK(PROBE, INFO, "addr 0x%lx, irq %d, "

                   "MAC addr %02X:%02X:%02X:%02X:%02X:%02X\n",

                   pci_resource_start(pdev, 0), pdev->irq,

                   netdev->dev_addr[0], netdev->dev_addr[1], netdev->dev_addr[2],

                   netdev->dev_addr[3], netdev->dev_addr[4], netdev->dev_addr[5]);

         return 0;

err_out_free:

         e100_free(nic);

err_out_iounmap:

         iounmap(nic->csr);

err_out_free_res:

         pci_release_regions(pdev);

err_out_disable_pdev:

         pci_disable_device(pdev);

err_out_free_dev:

         pci_set_drvdata(pdev, NULL);

         free_netdev(netdev);

         return err;

}

它主要完成:对网应对应的net net_device赋初值。并向内核调用register_netdev完成网络设备的注册,网络设备注册我们在上一节中已经说过,这里不再赘述。

看一下net_device中几个关键的函数:

//在设备将打开的时候,调用此函数

netdev->open = e100_open;

//在设备停用的时候调用此函数

netdev->stop = e100_close;

//设备发送数据的时候调用此函数

netdev->hard_start_xmit = e100_xmit_frame;

到此时,网卡的初始化工作已经完成了。之后就可以操作网卡了。

那网卡应该怎么使用呢?必须首先唤起网卡,即使之UP,例如 ifconfig eth0 up

此时,内核会根据接口名字“eth0”找到对应的net_device.然后调用 net_device-> open.即:e100_open。

分析如下:

static int e100_open(struct net_device *netdev)

{

         struct nic *nic = netdev_priv(netdev);

         int err = 0;

         //网卡正在UP,关闭载波信号

         netif_carrier_off(netdev);

         if((err = e100_up(nic)))

                   DPRINTK(IFUP, ERR, "Cannot open interface, aborting.\n");

         return err;

}

我们关心的是e100_up。跟踪如下:

static int e100_up(struct nic *nic)

{

         int err;

         //分配收包队列

         if((err = e100_rx_alloc_list(nic)))

                   return err;

         //分配控制队列

         if((err = e100_alloc_cbs(nic)))

                   goto err_rx_clean_list;

         //硬件初始化

         if((err = e100_hw_init(nic)))

                   goto err_clean_cbs;

         //多播

         e100_set_multicast_list(nic->netdev);

         //开始接收数据

         e100_start_receiver(nic);

         mod_timer(&nic->watchdog, jiffies);

         //注册中断例程

         if((err = request_irq(nic->pdev->irq, e100_intr, SA_SHIRQ,

                   nic->netdev->name, nic->netdev)))

                   goto err_no_irq;

         //启用中断

         e100_enable_irq(nic);

         netif_wake_queue(nic->netdev);

         return 0;

err_no_irq:

         del_timer_sync(&nic->watchdog);

err_clean_cbs:

         e100_clean_cbs(nic);

err_rx_clean_list:

         e100_rx_clean_list(nic);

         return err;

}

在此函数中,我们可以看到,它主要完成了:接立接收环形DMA缓冲区。注册了中断处理函数

关于环形DMA缓冲区接立是由e100_rx_alloc_list(nic)完成的

static int e100_rx_alloc_list(struct nic *nic)

{

         struct rx *rx;

         // nic->params.rfds.count,接收缓存的总个数

         unsigned int i, count = nic->params.rfds.count;

         //rx_to_use:正在存在数据的位置

         //rx_to_clean:数据的初始为止。所以。数据的有限位置是从rx_to_use到rx_to_use

         nic->rx_to_use = nic->rx_to_clean = NULL;

         if(!(nic->rxs = kmalloc(sizeof(struct rx) * count, GFP_ATOMIC)))

                   return -ENOMEM;

         memset(nic->rxs, 0, sizeof(struct rx) * count);

         //遍历并建立循环链表

         for(rx = nic->rxs, i = 0; i < count; rx++, i++) {

                   rx->next = (i + 1 < count) ? rx + 1 : nic->rxs;

                   rx->prev = (i == 0) ? nic->rxs + count - 1 : rx - 1;

                   if(e100_rx_alloc_skb(nic, rx)) {

                            e100_rx_clean_list(nic);

                            return -ENOMEM;

                   }

         }

         //初始化起如位置为nic->rxs

         nic->rx_to_use = nic->rx_to_clean = nic->rxs;

         return 0;

}

为设备建立DMA映射的主函数为e100_rx_alloc_skb().分析如下:

static inline int e100_rx_alloc_skb(struct nic *nic, struct rx *rx)

{

         unsigned int rx_offset = 2; /* u32 align protocol headers */

         if(!(rx->skb = dev_alloc_skb(RFD_BUF_LEN + rx_offset)))

                   return -ENOMEM;

         /* Align, init, and map the RFD. */

         rx->skb->dev = nic->netdev;

         //在数据存储区之前空出offset空间

skb_reserve(rx->skb, rx_offset);

//skb->data前部置RFD

         memcpy(rx->skb->data, &nic->blank_rfd, sizeof(struct rfd));

         //DMA内存映射,映射至skb->data

         rx->dma_addr = pci_map_single(nic->pdev, rx->skb->data,

                   RFD_BUF_LEN, PCI_DMA_BIDIRECTIONAL);

         /* Link the RFD to end of RFA by linking previous RFD to

l        this one, and clearing EL bit of previous.  */

//初始化前一个skb中的控制信息

         if(rx->prev->skb) {

                   struct rfd *prev_rfd = (struct rfd *)rx->prev->skb->data;

                   put_unaligned(cpu_to_le32(rx->dma_addr),

                            (u32 *)&prev_rfd->link);

                   wmb();

                   prev_rfd->command &= ~cpu_to_le16(cb_el);

                   pci_dma_sync_single_for_device(nic->pdev, rx->prev->dma_addr,

                            sizeof(struct rfd), PCI_DMA_TODEVICE);

         }

         return 0;

}

在这个函数里,主要完成了:DMA环形链表的建立。在这里涉及到了一个重要的数据结构sk_buff.稍后再给出它的结构分析。在这里我们只要知道 在skb->data里储存的是接收数据就OK了。值得一提的是,Intel 100M 网卡对接收数据的处理,跟平时遇到的网卡不一样,接收数据时会由接收控制RU写入接收信息,由此判断接收是否完全等信息。也就是我们在代码里面看到的 rfd.所以,在skb->data对应的就是rfd+网络传过来的数据.

到这里,接收准备工作已经完成了。

sk_buff结构分析

sk_buff是我们遇到的第二个重要的结构,在内核中经常被缩写成skb.在linux 2.6.21它被定义成:

struct sk_buff {

         //指向下一个skb

struct sk_buff               *next;

//上一个skb

         struct sk_buff               *prev;

         struct sk_buf0f_head   *list;

         //对应的sock。这也是个重要的结构,在传输层的时候我们再来分析

         struct sock          *sk;

         //接收或者发送时间戳

         struct timeval               stamp;

         //接收或者发送时对应的net_device

         struct net_device *dev;

         //接收的net_device

         struct net_device *input_dev;

         //数据包对应的真实net_device.关于虚拟设备可以在之后的网桥模式分析中讨论

         struct net_device *real_dev;

         //ip层的相关信息

         union {

                   struct tcphdr       *th;

                   struct udphdr      *uh;

                   struct icmphdr    *icmph;

                   struct igmphdr    *igmph;

                   struct iphdr         *ipiph;

                   struct ipv6hdr     *ipv6h;

                   unsigned char      *raw;

         } h;

         //协议层的相关信息

         union {

                   struct iphdr         *iph;

                   struct ipv6hdr     *ipv6h;

                   struct arphdr       *arph;

                   unsigned char      *raw;

         } nh;

         //链路层的相关信息

         union {

                unsigned char     *raw;

         } mac;

         //在路由子系统中再来分析这一结构

         struct  dst_entry        *dst;

         struct         sec_path    *sp;

         /*

          * This is the control buffer. It is free to use for every

          * layer. Please put your private variables there. If you

          * want to keep them across layers you have to do a skb_clone()

          * first. This is owned by whoever has the skb queued ATM.

          */

         char                     cb[40];

         //各层的数据长度

         unsigned int                  len,

                                     data_len,

                                     mac_len,

                                     csum;

         unsigned char                local_df,

                                     cloned,

                                     pkt_type,

                                     ip_summed;

         __u32                           priority;

         unsigned short              protocol,

                                     security;

         void                     (*destructor)(struct sk_buff *skb);

#ifdef CONFIG_NETFILTER

        unsigned long                  nfmark;

         __u32                           nfcache;

         __u32                           nfctinfo;

         struct nf_conntrack      *nfct;

#ifdef CONFIG_NETFILTER_DEBUG

        unsigned int           nf_debug;

#endif

#ifdef CONFIG_BRIDGE_NETFILTER

         struct nf_bridge_info    *nf_bridge;

#endif

#endif /* CONFIG_NETFILTER */

#if defined(CONFIG_HIPPI)

         union {

                   __u32                  ifield;

         } private;

#endif

#ifdef CONFIG_NET_SCHED

       __u32                       tc_index;        /* traffic control index */

#ifdef CONFIG_NET_CLS_ACT

         __u32           tc_verd;               /* traffic control verdict */

         __u32           tc_classid;            /* traffic control classid */

#endif

#endif

         /* These elements must be at the end, see alloc_skb() for details.  */

         unsigned int                  truesize;

         //引用计数

         atomic_t              users;

         //存储空间的起始地址

         unsigned char      *head,

         //网络数据的起始起址

                                     *data,

         //存放网络数据的结束地址

                                     *tail,

         //存储空间的结束地址

                                     *end;

}

对应我们上面的网卡驱动分析。接收到的数据是存放在data至tail之间的区域。

Skb通常还有常用的几个函数,一一列举分析如下:

struct sk_buff *alloc_skb(unsigned int size,int gfp_mask)

分配存储空间为sixe的skb,内存分配级别为gfp_mask.注意这里的存储空间的含义,即为skb->data至skb->tail的区域

struct sk_buff *skb_clone(struct sk_buff *skb, int priority)

克隆出的skb指向同一个结构,同时会增加skb的引用计数

struct sk_buff *skb_copy(const struct sk_buff *skb, int priority)

复制一个全新的skb

void kfree_skb(struct sk_buff *skb)

当skb的引用计数为1的时候,释放此skb

unsigned char *skb_put(struct sk_buff *skb, unsigned int len)

使skb的存储空间扩大len.即使tail指针下移

unsigned char *skb_push(struct sk_buff *skb, unsigned int len)

push,即推出一段数据,使data指针下层。

void skb_reserve(struct sk_buff *skb, unsigned int len)

该操作使data指针跟tail指针同时下移,即扩大存储区域之前的空间

int skb_headroom(const struct sk_buff *skb)

返回data之前可用的空间数量

int skb_tailroom(const struct sk_buff *skb)

返回缓存区中可用的空间大小

阅读(1197) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~