CXD Linux Engineer

Linux协议栈--数据链路层


数据链路层的数据接收过程

将数据包加入到CPU的输入队列

当设备驱动程序接受到数据时,产生一个中断然后在中断处理函数中调用数据链路层的netif_rx函数, 此函数的作用是把输入数据帧放入CPU的输入队列中(每个CPU都以一个独立的输入队列),随后标记软件中断,在软件中断中将数据帧上传给TCP/IP协议栈。 netif_rx函数的主要任务都是调用enqueue_to_backlog函数完成的。

static int enqueue_to_backlog(struct sk_buff *skb, int cpu,
			      unsigned int *qtail)
{
	struct softnet_data *sd;
	unsigned long flags;
	sd = &per_cpu(softnet_data, cpu);
	local_irq_save(flags);

	rps_lock(sd);
    /* 如果存放数据包的队列已满则直接丢掉数据包返回 NET_RX_DROP  */
	if (skb_queue_len(&sd->input_pkt_queue) <= netdev_max_backlog) {

        /* 如果存放数据包的队列不为空则将数据包放入队列后直接返回 NET_RX_SUCCESS */
        /* 当队列不为空时说明前面已经标记过接受软件中断,不需要在标记 */
		if (skb_queue_len(&sd->input_pkt_queue)) {
enqueue:
			__skb_queue_tail(&sd->input_pkt_queue, skb);
			input_queue_tail_incr_save(sd, qtail);
			rps_unlock(sd);
			local_irq_restore(flags);
			return NET_RX_SUCCESS;
		}

		/* 如果存放数据包的队列为空则先调用 ____napi_schedule 函数标记软件中断,然后再加入队列 */
        /* 如果队列为空时则说明是当前收到的第一个数据帧,应标记接受软件中断 */
		if (!__test_and_set_bit(NAPI_STATE_SCHED, &sd->backlog.state)) {
			if (!rps_ipi_queued(sd))
				____napi_schedule(sd, &sd->backlog);
		}
		goto enqueue;
	}

	sd->dropped++;
	rps_unlock(sd);
	local_irq_restore(flags);
	kfree_skb(skb);
	return NET_RX_DROP;
}

软件中断处理过程

软件中断的处理函数是net_rx_action函数,它在net_dev_init函数中被注册, 它的作用是展开poll_list队列链表,从队列中获取每一个struct napi_struct结构体的实例, 然后调用它们的poll函数指针指向的实例。

static void net_rx_action(struct softirq_action *h)
{
    /* 如果队列不为空 */
	while (!list_empty(&sd->poll_list)) {
		struct napi_struct *n;

        /* 取出队列中的第一个元素 */
		n = list_first_entry(&sd->poll_list, struct napi_struct, poll_list);

		if (test_bit(NAPI_STATE_SCHED, &n->state)) {
            /* 调用`poll`函数指针指向的实例 */
			work = n->poll(n, weight);
			trace_napi_poll(n);
		}
    }
}

struct napi_struct结构体中poll函数指针的实例是process_backlog函数,是在net_dev_init函数中初始化的。 process_backlog函数的作用就是从CPU输入队列中读取所有存放数据包的Socker Buffer, 然后调用__netif_receive_skb函数根据协议类型type = skb->protocol将数据包传递给上层对应的协议处理函数。

数据链路层的数据发送过程

内核提供了dev_queue_xmit函数,上层所有协议都调用此函数将数据帧放到网络设备的发送队列,随后流量控制系统 按照内核配置的队列管理策略,将网络设备发送队列中的数据帧依次发送给设备驱动程序的dev->netdev_ops->ndo_start_xmit方法。 dev_queue_xmit函数的任务是:如果此设备有队列则将数据帧放入队列中然后调用__dev_xmit_skb函数, 在此函数中根据一系列流量控制规则来决定要发送的数据包,最终调用dev_hard_start_xmit函数将数据帧发送给驱动程序。 如果此设备没有队列(如回环设备)则调用dev_hard_start_xmit函数,直接将数据帧发送给驱动程序。

__dev_xmit_skb函数的函数调用链如图,因为涉及到QoS部分,这部分的处理过程非常复杂,以后应该会专门分析这块内容,现在只能简单了解一下函数调用路径:

tc

参考

《嵌入式Linux网络体系结构设计与TCP/IP协议栈》


Comments

Content