图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 network
1098 * 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 Rx
1180 * This should not be set in
1181 * drivers, unless really needed,
1182 * because network stack (bonding)
1183 * use it if/when necessary, to
1184 * 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 are
1190 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_NS
1283 /* Network namespace this network device is inside */
1284 struct net *nd_net; //网络命名空间,container机制会用到
1285 #endif
1286
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 65535
1312 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(),下面以一张图来结束本章的内容。