netif_receive_skb函数是napi_struct实例的poll函数使用的,帮助函数处理输入数据帧。
netif_receive_skb主要流程:
Ø 给每个协议标签发送一个数据帧的拷贝
Ø 给skb->protocol中指定的网络层协议处理程序发送一个数据帧拷贝
Ø 执行需要在该层处理的功能特点。
如果上层没有skb->protocol指定的协议处理程序,也没有别的功能特点要在netif_receive_skb中执行,内核就会不知道怎么处理数据帧,因此会丢弃掉数据帧。
关键数据机构
struct softnet_data
struct softnet_data { struct net_device *output_queue; struct sk_buff_head input_pkt_queue; struct list_head poll_list; struct sk_buff *completion_queue;
struct napi_struct backlog; }; |
此处的queue->input_pkt_queue,当有数据到来时,分配一个skb来接收数据并将接收到的数据挂接在queue->input_pkt_queue队列中.在中断轮询的时候,软中断总函数do_softirq()直接到达网卡的接收软中断函数net_rx_action(),在此函数中调用queue->backlog_dev.poll=process_backlog;即process_backlog()函数,它将queue->input_pkt_queue队列中的数据向上层协议传输,比如网络层的ip协议等。
这边讲述个算法优化的问题:
关于ptype_all和pypte_base中的pt_prev的说明
一种结束:为了优化最common的一个case,就是只有一个handler match的话,省去了skb_clone的开销。
因为当有多个匹配的话,我们就要在deliver_skb函数总先增加引用计数,这要就要skb_clone了,增加了开销,但是如果只有一次match,我们直接就调用pt_prev->func功能,减少了一次clone。而且也减少了一次kfree_skb,就是少了一次原子读操作,所以这都是从效率方面进行了优化。
在上面的分析中,我们看到数据在继续往上层协议传送过程中,会调用pt_prev->func这个函数指针,这里主要用到的数据结构是struct packet_type。
该结构描述了网络层协议的标识符,接收处理函数与接收网络设备等地相互关系,每个在网络层的协议实例都有一个struct packrt_type类型的变量,来定义协议处理接收数据帧的实体。
struct packet_type { __be16 type; /* This is really htons(ether_type). */ struct net_device *dev; /* NULL is wildcarded here */ int (*func) (struct sk_buff *, struct net_device *, struct packet_type *, struct net_device *); struct sk_buff *(*gso_segment)(struct sk_buff *skb, int features); int (*gso_send_check)(struct sk_buff *skb); struct sk_buff **(*gro_receive)(struct sk_buff **head, struct sk_buff *skb); int (*gro_complete)(struct sk_buff *skb); void *af_packet_priv; struct list_head list; }; |
数据结构中各个数据域的具体含义:
² Type:协议标识符。协议标识符必须由网络设备驱动程序接收实例从数据帧中解析出来。
² Dev:指向接收网络数据帧的网络设备。指针不为空,表示只接收从指定网络设备上传过来的数据。如果空,则选择协议处理时,网络设备不起作用。
² Func:指向协议实例实现的接收处理程序的函数指针。
要实现L2和L3之间的接口实现,每个L3协议都得实例化一个struct packet_type 结构。
例如:IP协议在af_inet.c文件中进行了初始化。
/* * IP protocol layer initialiser */
static struct packet_type ip_packet_type __read_mostly = { .type = cpu_to_be16(ETH_P_IP), .func = ip_rcv, .gso_send_check = inet_gso_send_check, .gso_segment = inet_gso_segment, .gro_receive = inet_gro_receive, .gro_complete = inet_gro_complete, }; |
Linux内核中定义了两个哈希表,哈希表中每个元素都是一个struct list_head变量,指向网络层协议的struct packet_type数据结构实例。
有两种网络层协议类型需要区分
² 接口向量表
以上两种接口分别为ptype_all和ptype_base,两者都定义在Dev.c (net\core)中。
ptype_all中注册的接口主要用于网络工具和网络探测器接收数据,其目的是接收处理程序接收所有数据帧。
ptype_base[PTYPE_HASH_SIZE],其实例只接收与协议标识符相匹配的网络数据帧。
netif_receive_skb函数向网络层传递数据,查询ptype_base[PTYPE_HASH_SIZE],找到type数据域和skb->protocol相匹配的packet_type成员,将数据帧发送给对应的func函数。
² 向接口中增加/删除协议
内核实现了两个API:Dev.c (net\core):void dev_add_pack(struct packet_type *pt;和
void dev_remove_pack(struct packet_type *pt)。
分别将packet_type实例变量加入/移出ptype_all和ptype_base哈希表。
² 何时注册packet_type实例
协议需要定义自己的packet_type变量,并且为数据域赋值,随后在协议的初始化函数中调用dev_remove_pack函数向内核注册。
例如在inet_init函数中调用dev_add_pack(&ip_packet_type)将IP协议向内核注册。