第二章 主机到网络层(网卡)--基于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 网卡注册流程
- 第二章 主机到网络层(网卡)--基于Linux3.10
- 第五章 传输层(tcp)到网络层(ip)--基于Linux3.10
- (OK) 第五章 传输层(tcp)到网络层(ip)--基于Linux3.10
- 第六章 应用层(网络)--基于Linux3.10
- 第四章 网络层接收数据包流程--基于Linux3.10
- 第七章 tcp发送(传输层)--基于Linux3.10
- 第十五章 提升网络性能技术--基于Linux3.10
- 第十三章 网络命名空间(内核源码实现)--基于Linux3.10
- 第一章网络子系统初始化--基于Linux3.10
- 第十章 网络工具--基于Linux3.10
- 第十二章 trie路由--基于Linux3.10
- 第十四章 netlink机制--基于Linux3.10
- 第十六章PHY -基于Linux3.10
- 第三章 套接字相关数据结构--基于Linux3.10
- 第九章 tcp拥塞控制--基于Linux3.10
- 第十一章 Linux包过滤防火墙-netfilter--基于Linux3.10
- 双网卡主机,网络连接超时(卡)的解决方法。
- 编写i2c驱动-基于Linux3.10
- OpenGL ES编程入门资源集合
- 众愚成智
- 判断1000~2000年之间的闰年
- OUI-10058: The OUI Inventory on this system does not&nbs
- Scala 点滴:String & String Interpolation
- 第二章 主机到网络层(网卡)--基于Linux3.10
- poj2187 Beauty Contest 平面最远点对(凸包)
- 带有filter-depend的BADI的用法
- 验证码倒计时发送
- cocos2dx 菜单制作+弹性弹出特效
- vim生成连续数字列
- Android上学习ARM指令集之开篇
- Krypton Factor
- URAL 1612. Tram Forum(字符串啊 )