刚来实验室的时候主要看的就是数据报在协议栈的具体传输过程,当时有过记录,但是很凌乱,最近又回头看了看相关知识和内核源代码,算是理清了思路,特整理在此.本篇笔记写的是2.4中数据报的接收过程,从网卡到网络层的具体路线,2.4中大部分网卡采用的是中断的方式接收数据(好像是从2.5以后开始支持 NAPI的,不太确定),本篇笔记总结的是非NAPI,即采用中断接受数据的路线.ok,开始进入主题.
当网卡接收到一个数据报之后,产生一个中断通知内核,然后内核会调用相关的中断处理函数.一般,中断处理程序做如下工作:
1,把数据报拷贝到一个sk_buff中.
2,初始化sk_buff中的一些成员变量,为以后传输到上层用,特别是skb->protocol,标示了上层的具体协议,后面会调用相应的接收函数.
3,更新网卡的状态.
4,调用netif_rx将数据报送往上层(采用NAPI时,此处将调用netif_rx_schdule).
看netif_rx源代码之前首先要了解一下softnet_data结构.此结构是基于cpu的而不是device,即每个cpu对应一个softnet_data.
struct softnet_data
{
/*throttle用于拥塞控制,当拥塞时被设置,此后来的数据包都被丢弃*/
int throttle;
/*netif_rx返回的拥塞级别*/
int cng_level;
int avg_blog;
/*input_pkt_queue是skb的队列,接收到的skb全都进入到此队列等待后续处理*/
struct sk_buff_head input_pkt_queue;
/*poll_list是一个双向链表,链表的成员是有接收数据等待处理的device*/
struct list_head poll_list;
/*net_device链表,成员为有数据报要发送的device*/
struct net_device *output_queue;
/*完成发送的数据包等待释放的队列*/
struct sk_buff *completion_queue;
/*注意,backlog_dev不是一个指针,而是一个net_device实体,代表了调用net_rx_action时的device*/
struct net_device backlog_dev;
};
ok,了解了softnet_data结构体后接下来看netif_rx的源代码.
/*
*主要工作:
*1,初始化sk_buff的一些域值,比如数据报接收的时间截.
*2,把接收到的数据报入input_pkt_queue接收队列,并且通知内核然后触发相应的软中断,即NET_RX_SOFTIRQ.
* 2.1:当队列为空时,调用netif_rx_schedule,触发软中断.
* 2.2:当队列非空时,直接将数据报入队列,因为此时已经调用了软中断,所以无需再调用.
*3,更新相关状态信息.
*执行完后,流程来到net_rx_action
*/
int netif_rx(struct sk_buff *skb)
{
int this_cpu = smp_processor_id();
struct softnet_data *queue;
unsigned long flags;
//如果接收到的数据包时间截未设置,设置时间截
if (skb->stamp.tv_sec == 0)
do_gettimeofday(&skb->stamp);
queue = &softnet_data[this_cpu];
local_irq_save(flags); //disable irqs on local cpu
netdev_rx_stat[this_cpu].total++;
if (queue->input_pkt_queue.qlen = netdev_max_backlog) {
if (queue->input_pkt_queue.qlen) {
if (queue->throttle)
goto drop;
enqueue:
dev_hold(skb->dev); //即automic_inc(&(dev)->refcnt)累加设备引入计数器
__skb_queue_tail(&queue->input_pkt_queue,skb); //把skb入input_pkt_queue队列,此处只是指针的指向,而不是数据报的拷贝,目的是节省时间.
local_irq_restore(flags); //eable irqs on local cpu
#ifndef OFFLINE_SAMPLE
get_sample_stats(this_cpu);
#endif