Chinaunix首页 | 论坛 | 博客
  • 博客访问: 505424
  • 博文数量: 157
  • 博客积分: 3010
  • 博客等级: 中校
  • 技术积分: 1608
  • 用 户 组: 普通用户
  • 注册时间: 2008-08-16 09:30
文章存档

2010年(155)

2008年(2)

我的朋友

分类: LINUX

2010-03-11 20:21:29

一:预备知识
         关于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)
返回缓存区中可用的空间大小
 
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/efan_linux/archive/2009/09/22/4578196.aspx
阅读(671) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~