文章详情

  • 游戏榜单
  • 软件榜单
关闭导航
热搜榜
热门下载
热门标签
php爱好者> php文档>linux协议栈之链路层上的数据传输之一

linux协议栈之链路层上的数据传输之一

时间:2010-03-11  来源:cao5170

一:预备知识          关于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
相关阅读 更多 +
排行榜 更多 +
弓箭手战士酷跑

弓箭手战士酷跑

飞行射击 下载
三角洲行动全面战场攀升A点进攻指南

三角洲行动全面战场攀升A点进攻指南

飞行射击 下载
僵尸射手世界大战

僵尸射手世界大战

飞行射击 下载