第二章 主机到网络层(网卡)--基于Linux3.10

来源:互联网 发布:注册淘宝客收费标准 编辑:程序博客网 时间:2024/05/16 17:38

TCP/IP协议栈模型

网络协议栈常用OSI七层模型,实际上Linux网络协议栈使用的却是四层模型,图2.1展示了OSI七层和四层模型它们之间各层对应关系。图2.1的最左侧一列是数据在协议栈上各层的称谓。frame位于主机到网络层(Layer1),packet位于Layer2,segment位于Layer3,data或message位于Layer4;当然对于OSI七层模型而言,由下至上依次为Layer1~Layer7,即物理层为Layer1。


图2.1 TCP/IP协议栈的四层和七层模型

Layer1层对应到OSI七层模型是物理层,在四层模型中,这一层其实没有与之对应的层,这里偏重传输层和网络层,即tcp/ip协议栈。

网卡数据结构

和tcp/ip协议栈相比主机到网络层和硬件芯片打交道更多,因为这层的主要工作是转交给一个称为网卡(NIC)和PHY的硬件设备完成的,不同网卡性能还有区别,比如速率、offload、校验和、DMA、RSS、SG(scattergather)I/O等。内核将网卡应该具有的公共特性抽象成了一个称为net_device的结构体,因为各NIC(单网卡亦或集成与SOC的网卡)它们或多或少有些不同,现实应用中会将net_device实例、NIC特有特性、NIC特有功能封装到一个结构体内使用,使用register_netdev将net_device实例注册到内核。该结构体的定义如下:

1040 struct net_device{1041 1047     char            name[IFNAMSIZ];   //这个是网卡所呈现的接口的名称,如eth0、eth1等1048 1049     /* device name hash chain, please keep it close to name[] */1050     struct hlist_node   name_hlist;1051 /*ifconfig命令配置别名时使用,在单网卡多接口用以区分网段时会使用到,在IPROUTE工具中,提供了新方法。*/1053     char            *ifalias; /*网卡使用的内存、IO和中断信息*/1059     unsigned long       mem_end;    /* shared mem end   */1060     unsigned long       mem_start;  /* shared mem start */1061     unsigned long       base_addr;  /* device I/O address   */1062     unsigned int        irq;        /* device IRQ number    */1071     struct list_head    dev_list; //设备链表1072     struct list_head    napi_list; //napi链表1073     struct list_head    unreg_list;1074     struct list_head    upper_dev_list; /* List of upper devices */ /*这些特性指,如分散聚集IO,UFO,校验和等等,见include/linux/netdev_features.h的17~71行。*/1078     netdev_features_t   features; 1079     /* user-changeable features */1080     netdev_features_t   hw_features;1081     /* user-requested features */1082     netdev_features_t   wanted_features;1092     /* Interface index. Unique device identifier    */1093     int         ifindex;  //接口索引,每个设备对应一个唯一的索引值1094     int         iflink;1095 /*该网卡相关的统计信息,如接收、发送的数据包总数相关信息等。netstat -i或者ifconfig看到的关于数据包统计信息源于此处。这些命令行根据在下篇有提及*/1096     struct net_device_stats stats; 1097     atomic_long_t       rx_dropped; /* dropped packets by core network1098                          * Do not use this in drivers.1099                          *//*这里的ops非常重要,网卡发送接收数据的函数就在这里,不同的设备不同,这也是网络驱动编写者需要完成的工作。*/1109     const struct net_device_ops *netdev_ops; 1110     const struct ethtool_ops *ethtool_ops;  //ethtool工具的操作方法的集合。1112     /* Hardware header description */1113     const struct header_ops *header_ops; //1114 1115     unsigned int        flags;  /* interface flags (a la BSD)   */1116     unsigned int        priv_flags; /* Like 'flags' but invisible to userspace.1117                          * See if.h for definitions. */1118     unsigned short      gflags;1119     unsigned short      padded; /* How much padding added by alloc_netdev() */1120 1121     unsigned char       operstate; /* RFC2863 operstate */1122     unsigned char       link_mode; /* mapping policy to operstate */1123 1124     unsigned char       if_port;    /* Selectable AUI, TP,..*/1125     unsigned char       dma;        /* DMA channel      */1126 1127     unsigned int        mtu;    /* interface MTU value    其会影响到分片操作  */1128     unsigned short      type;   /* interface hardware type  见if_arp.h文件*/1129     unsigned short      hard_header_len;    /* hardware hdr length  */1138     /* Interface address info. */1139     unsigned char       perm_addr[MAX_ADDR_LEN]; /* permanent hw address */1140     unsigned char       addr_assign_type; /* hw address assignment type */1141     unsigned char       addr_len;   /* hardware address length  */1142     unsigned char       neigh_priv_len;1143     unsigned short          dev_id;     /* for shared network cards *///混杂模式标志,即数据包目的地址非本机也会被收集到,wireshark、tcpdump会将网卡置于该工作模式1156     unsigned int        promiscuity; 1157     unsigned int        allmulti;1169     struct in_device __rcu  *ip_ptr;    /* IPv4 specific data   */1176 /*1177  * Cache lines mostly used on receive path (including eth_type_trans())1178  */1179     unsigned long       last_rx;    /* Time of last Rx1180                          * This should not be set in1181                          * drivers, unless really needed,1182                          * because network stack (bonding)1183                          * use it if/when necessary, to1184                          * avoid dirtying this cache line.1185                          */1186 1187     /* Interface address info used in eth_type_trans() */1188     unsigned char       *dev_addr;  /* hw address, (before bcast    MAC地址1189                            because most packets are1190                            unicast) *//发送队列的记录信息*/1214     struct netdev_queue *_tx ____cacheline_aligned_in_smp;1215 1216     /* Number of TX queues allocated at alloc_netdev_mq() time  */1217     unsigned int        num_tx_queues;1218 1219     /* Number of TX queues currently active in device  */1220     unsigned int        real_num_tx_queues;1221 1222     /* root qdisc from userspace point of view */1223     struct Qdisc        *qdisc; //queue discipline,会影响数据包的收发。1224 1225     unsigned long       tx_queue_len;   /* Max frames per queue allowed */1226     spinlock_t      tx_global_lock;/*register_netdevice注册该网卡,ifconfig up/down等,会修改网卡的设备的状态,所有状态均在此。*/1259     /* register/unregister state machine */1260     enum { NETREG_UNINITIALIZED=0,1261            NETREG_REGISTERED,   /* completed register_netdevice */1262            NETREG_UNREGISTERING,    /* called unregister_netdevice */1263            NETREG_UNREGISTERED, /* completed unregister todo */1264            NETREG_RELEASED,     /* called free_netdev */1265            NETREG_DUMMY,        /* dummy device for NAPI poll */1266     } reg_state:8;   1267 1268     bool dismantle; /* device is going do be freed */1269 1270     enum {1271         RTNL_LINK_INITIALIZED,1272         RTNL_LINK_INITIALIZING,1273     } rtnl_link_state:16; //rtnl,是route Netlink的缩写,描述其状态。1274 1282 #ifdef CONFIG_NET_NS1283     /* Network namespace this network device is inside */1284     struct net      *nd_net; //网络命名空间,container机制会用到1285 #endif1286 1287     /* mid-layer private */1288     union {1289         void                *ml_priv; /*注意这里的__percpu,这就意味着这些成员再SMP情况下,每个CPU核都有一个副本以提高效率*/1290         struct pcpu_lstats __percpu *lstats; /* loopback stats */   1291         struct pcpu_tstats __percpu *tstats; /* tunnel stats */1292         struct pcpu_dstats __percpu *dstats; /* dummy stats */1293         struct pcpu_vstats __percpu *vstats; /* veth stats */1294     };1300     /* class/net/name entry */1301     struct device       dev; // 通用的设备模型结构体,该结构体描述的是设备都需要的成员1305     /* rtnetlink link ops */1306     const struct rtnl_link_ops *rtnl_link_ops; //ip和tc工具的内核支持机制Netlink的操作集1307 1308     /* for setting kernel sock attribute on TCP connection setup *//*GSO 是generic segment offload,是TSO(TCP segment offload)的升级,segment数据原本在TCP层,但是为了效率,现在网卡自己支持分片操作,所以有些情况下会将分片操作推迟到网卡去完成。这些网络多队列等新特性在下部中会提及*/1309 #define GSO_MAX_SIZE        65536                   1310     unsigned int        gso_max_size;                    1311 #define GSO_MAX_SEGS        655351312     u16         gso_max_segs;/*拥塞控制相关*/1318     u8 num_tc;1319     struct netdev_tc_txq tc_to_txq[TC_MAX_QUEUE];1320     u8 prio_tc_map[TC_BITMASK + 1]; 1321 1329     /* phy device may attach itself for hardware timestamping *///PHY内容,参看十六章。1330     struct phy_device *phydev; 1338 };

该数据结构占298行,是Linux内核中比较大的数据结构了,该结构主要就是描述网卡的状态、能力、操作集、用户空间的接口支持等。网卡的函数操作集如下:

 906 struct net_device_ops {//当网卡注册时仅会调用一次,该函数用于进一步的完成设备特定的初始化工作。 907     int         (*ndo_init)(struct net_device *dev);                 908     void            (*ndo_uninit)(struct net_device *dev);         //网卡注销或者注册失败时调用。 //打开网卡时会调用,ifconfig,ip等命令会触发,当将网卡转到up时调用。 909     int         (*ndo_open)(struct net_device *dev);             910     int         (*ndo_stop)(struct net_device *dev);               //当将网卡转到down时调用。 911     netdev_tx_t     (*ndo_start_xmit) (struct sk_buff *skb,  912                            struct net_device *dev);                  //图2.1中的frame就是通过该函数发送到RJ45线上(实际上是配置网卡内部发送寄存器,启动硬件发送) 918     int         (*ndo_set_mac_address)(struct net_device *dev, 919                                void *addr); 920     int         (*ndo_validate_addr)(struct net_device *dev); 921     int         (*ndo_do_ioctl)(struct net_device *dev, 922                             struct ifreq *ifr, int cmd);                 //ifconfig使用的ioctl方法,会调用该接口将命令发送到网卡,但是ip和tc工具使用Netlink方法和此不同。 923     int         (*ndo_set_config)(struct net_device *dev, 924                               struct ifmap *map); ... }

header_ops用于协议的头部信息处理,处理方式如下:

 265 struct header_ops { 266     int (*create) (struct sk_buff *skb, struct net_device *dev, 267                unsigned short type, const void *daddr, 268                const void *saddr, unsigned int len);                   //创建一个协议头 269     int (*parse)(const struct sk_buff *skb, unsigned char *haddr);  //获得packet包对应的硬件地址,集MAC地址。 270     int (*rebuild)(struct sk_buff *skb); 271     int (*cache)(const struct neighbour *neigh, struct hh_cache *hh, __be16 type); 272     void    (*cache_update)(struct hh_cache *hh, 273                 const struct net_device *dev, 274                 const unsigned char *haddr); 275 };

网卡注册流程

上面的网卡数据结构在网卡驱动架构中是处于核心的地位,在注册网卡时就使用到了,TCP/IP网络协议栈的Layer1还是比较重要的,毕竟所有的数据均通过这里,下面是网卡的注册流程,绝大多数情况下网卡的注册实际上就是MAC控制器的注册,通常会外接PHY芯片,补充一下这里注册实例:

1、这里给的例子基于SOC芯片上的MAC控制器注册。其不挂载在PCI总线上。

2、通常意义上常说的网络驱动程序编写也指的是下面所写的内容,由于是设备驱动代码了,所以代码不再net目录下,而在drivers目录下了。

drivers/net/ethernet/目录下的代码,该目录下每个厂商会对应一个子目录。

网卡注册流程:

static struct platform_driver XXYY_driver = {.probe  = XXYY_drv_probe,.remove  = XXYY_drv_remove,   .driver = {   .name = "XXYY-eth",   .owner  = THIS_MODULE,   .of_match_table= XXYY_eth_dt_ids,   },};module_platform_driver(XXYY_driver); 

module_platform_driver在注册设备驱动时,会调用回调函数probe完成特定硬件设备需要的一些工作,代码框架如下:

static int XXYY_drv_probe(struct platform_device *pdev) {/* device_node 设备树相关*/struct device_node *np = pdev->dev.of_node;struct net_device *ndev;struct XXYY_info *lp;struct resource *res;const char *macaddr;int ret_val = 0;/*XXYY_info是对net_device结构体的封装*/ndev = alloc_etherdev(sizeof(struct XXYY_info)); //lp = netdev_priv(ndev);res = platform_get_resource(pdev, IORESOURCE_MEM, 0);lp->regbase = devm_ioremap(&pdev->dev, res->start, resource_size(res));ndev->irq = platform_get_irq(pdev, 0);    //如果中断使能,那么在接收和发送数据包时会用到该中断号。lp->ndev = ndev;lp->mii_bus.read = &XXYY_mdio_read,   //PHY设备读写,参考《PHY Linux 驱动》lp->mii_bus.write = &XXYY_mdio_write,lp->mii_bus.reset = &XXYY_mdio_reset,ret_val = of_mdiobus_register(&lp->new_bus, pdev->dev.of_node);lp->phydev = phy_find_first(&lp->new_bus);ether_setup(ndev);    //该函数用于初始化以太网设备通用的字段,见后面讲述。//这里再一次看见了struct net_device_ops 结构体,这是需要驱动程序编写者根据芯片手册完成的。ndev->netdev_ops = &ambeth_netdev_ops;   netif_napi_add(ndev, &lp->napi, XXYY_napi, XXYY_NAPI_WEIGHT); //NAPI主机到网络层再来看这里的意义。ret_val = register_netdev(ndev);  //前文所述的将网络设备注册到Linux核心的函数。platform_set_drvdata(pdev, ndev);  //platform 总线上的信息记录return 0;}

ether_setup(ndev),该函数用于初始化以太网设备通用的字段,函数的如下:

void ether_setup(struct net_device *dev) {dev->header_ops= ð_header_ops;dev->type  = ARPHRD_ETHER;dev->hard_header_len = ETH_HLEN;dev->mtu  = ETH_DATA_LEN;dev->addr_len  = ETH_ALEN;dev->tx_queue_len= 1000; /* Ethernet wants good queues */dev->flags  = IFF_BROADCAST|IFF_MULTICAST;dev->priv_flags|= IFF_TX_SKB_SHARING;memset(dev->broadcast, 0xFF, ETH_ALEN);}

函数的操作集如下:

static const struct net_device_ops XXYY _netdev_ops = {.ndo_open  = XXYY_open,.ndo_stop  = XXYY_stop,.ndo_start_xmit= XXYY_hard_start_xmit,.ndo_set_rx_mode= XXYY_set_multicast_list,.ndo_set_mac_address  = XXYY_set_mac_address,.ndo_validate_addr= eth_validate_addr,.ndo_do_ioctl  = XXYY_ioctl,.ndo_change_mtu= eth_change_mtu,.ndo_tx_timeout= XXYY_timeout,.ndo_get_stats= XXYY_get_stats,};

netif_napi_add添加一个napi服务函数。

void netif_napi_add(struct net_device *dev, struct napi_struct *napi,   int (*poll)(struct napi_struct *, int), int weight){INIT_LIST_HEAD(&napi->poll_list);napi->gro_count = 0;napi->gro_list = NULL;napi->skb = NULL;napi->poll = poll;napi->weight = weight;list_add(&napi->dev_list, &dev->napi_list);napi->dev = dev;set_bit(NAPI_STATE_SCHED, &napi->state);}

注册完毕后,PHY设备通过自协商选择合适的网络速率,MAC层这时也注册到Linux核心了,网卡和数据以及PHY的关联,见图1.2。

图1.2 网卡结构体关键字段组织

该图中的sk_buff,在Linux内核代码中通常将sk_buff注释成SKB,后面沿用此注释法。SKB存放数据,发送和接收维护各自的队列,虽然通常一个网卡的接收和发送使用同一套DAM控制器,但是其接收和发送数据包的缓存是区分开的。XXYY_netdev_ops结构体中并没有接收数据包的方法,其实由于数据包到达时刻的不确定性而采用了中断的方式接收数据,为了节约中断资源,在XXYY_open中才注册中断服务函数,通常其它类型的设备驱动程序都是这么做的,在close设备时,将中断号释放掉,以便其它设备可以使用该中断号。open函数的原型如下:

static int XXYY_open(struct net_device *ndev){int ret_val = 0;struct XXYY_info *lp;lp = (struct XXYY_info *)netdev_priv(ndev);ret_val = XXYY_start_hw(ndev);  //特定于SOC的函数,配置相关寄存器使能该网卡设备ret_val = request_irq(ndev->irq, XXYY_interrupt,//注册中断服务函数,发送和接收共享该中断,服务函数中判断是接收还是发送中断。IRQF_SHARED | IRQF_TRIGGER_HIGH, ndev->name, ndev);   napi_enable(&lp->napi);   //NAPI接口netif_start_queue(ndev);   //发送使能netif_carrier_off(ndev);  ret_val = XXYY_phy_start(lp); //复位一下PHY设备return ret_val;}

接收中断调度NAPI,发送中断则执行发送端代码,napi主体思想如下:

l  常规流程在接收数据包时,一个数据包到达网卡时,网卡产生中断,通知CPU数据到来,CPU响应该次接收,当下一个数据包到来时,再次重复上述过程,这个过程的特点是一个中断对应一个数据包,为了省去中断带来的资源开销,能不能一个中断多收几个数据包呢?轮询就可以实现一个中断接收多个数据包,流程变为当数据包到来产生中断时, CPU关中断,不停的轮询网卡是否有新数据到来,接收数据包个数的上限值(和网卡接收缓存大小相关,常取buffer的2/3~1/3之间)或者超时可以作为退出条件。

l  发送也是这么概念,一次发送缓冲区大小的1/3~2/3,然后再使能发送中断,网卡发送完数据后会产生发送中断。

static inline void XXYY_interrupt_rx(struct XXYY_info *lp, u32 irq_status){napi_schedule(&lp->napi);}

napi所完成的工作就是实质将数据向上层发送

static inline void XXYY_napi_rx(struct ambeth_info *lp, u32 status, u32 entry){   struct sk_buff *skb;    skb = lp->rx.rng_rx[entry].skb;    netif_receive_skb(skb);  //该函数将数据从主机到网络发送到网络层}

netif_receive_skb(skb); 该函数将数据从主机到网络层发送到网络层,即IP层;其调用__netif_receive_skb将NAPI(napi_struct)方法添加到轮询表,并设置软中断NET_RX_SOFTIRQ。软中断服务函数net_rx_action将会执行poll函数,关闭中断并轮询网卡,超时或者接收数据包数量超限时会退出。

IP层发送数据到主机到网络层使用的接口函数是:

intdev_queue_xmit(struct sk_buff *skb),还会经过流控环节,路由也会咨询一些流控信息。这个接口是IP和主机到网络层的接口,后面还会遇到。和IP层连通的函数是ip_rcv()和ip_out(),下面以一张图来结束本章的内容。





图1.3 网卡注册流程






0 0