在http://blog.chinaunix.net/u2/64681/showart_1404746.html 内核中的TCP的追踪分析-7-TCP(IPV4)的socket接收连接,那节中,我们在文章最后出现了一点错误,误将newsock当做客户端也就是发送连接请求方的socket来理解了,并且newsk也并非是从客户端即发送方载运过来的sock结构,它也是在服务器端生成的,具体生成过程我们本节将会看到,这也是我们弥补整个连接请求到连接接收的关键过程。在开始这节之前我们先将那里的newsock和newsk的错误分析纠正过来,请朋友们再回去看一下,因为本文系个人独立完成,所以错误在所难免。我们来看这关键的底层驱动相关的过程。还是象上节一样我们来看cs8900和dm9000的驱动中的代码,在cs8900的驱动程序中,我们上一节看到了他的初始化函数cs89x0_probe()进一步调用了cs89x0_probe1()函数
static int __init cs89x0_probe1(struct net_device *dev, int ioaddr, int modular) { 。。。。。。 dev->open = net_open; 。。。。。。 retval = register_netdev(dev); 。。。。。。 }
|
在这个初始化过程中将网卡设备的open钩子函数挂入了驱动程序中的net_open()函数后向内核注册登记了cs8900的设备结构dev。那么什么时候会执行这个net_open()钩子函数呢我们看一下上一节http://blog.chinaunix.net/u2/64681/showart_1421758.html 在那篇文章中我们提到了使用ifconfig eth0 up启动网卡时会执行系统调用sys_ioctl(),那里只是简要的描述了一个大致的过程,将来有以后的对内核的分析文章中我们会再把ioctl的系统调用过程详细的描述,在那篇文章中我们看到执行了dev_open()
int dev_open(struct net_device *dev) { 。。。。。。 ret = dev->open(dev); 。。。。。。 }
|
所以会执行钩子函数net_open(),我们来看这个函数
static int net_open(struct net_device *dev) { 。。。。。。 if (request_irq(i, net_interrupt, 0, dev->name, dev) == 0) 。。。。。。 }
|
很明显这里为我们的cs8900网卡向内核注册登记了一个中断,这是在一个for循环中执行的i变量是确定cs8900要申请的中断数量。
如果朋友们在你的终端中查看中断输入以下命令
[root@/wumingxiaozu]#cat /proc/interrupts CPU0 30: 8122 s3c S3C2410 Timer Tick 32: 0 s3c s3c2410-lcd 34: 0 s3c I2SSDI 35: 0 s3c I2SSDO 42: 0 s3c ohci_hcd:usb1 43: 0 s3c s3c2440-i2c 53: 3884 s3c-ext eth0 70: 36 s3c-uart0 s3c2440-uart 71: 62 s3c-uart0 s3c2440-uart 83: 0 - s3c2410-wdt Err: 0
|
很明显我们的网卡名称是etho他申请到了53号中断线,并且看到他已经产生的中断次数是3884次,这里我们不详细讲述中断线的申请过程了,以后在内核相关的分析文章中分详细描述2.6.26内核的中断流程。我们还是围绕最重要的,这里不了解的朋友请参阅相关的资料,假设我们的中断线申请成功后,每次网卡接收到数据时都会产生一次中断,我们看到在调用request_irq()函数时注册的中断处理函数是net_interrupt(),因为我们的分析重点不是驱动程序本身所以这里只集中看与我们内核至关重要的部分
static irqreturn_t net_interrupt(int irq, void *dev_id) { 。。。。。。 while ((status = readword(dev->base_addr, ISQ_PORT))) { if (net_debug > 4)printk("%s: event=%04x\n", dev->name, status); handled = 1; switch(status & ISQ_EVENT_MASK) { case ISQ_RECEIVER_EVENT: /* Got a packet(s). */ net_rx(dev); break; 。。。。。。 }
|
查看中断产生的原因如果是由于接收到数据产生的中断则会调用net_rx()进行处理。
static void net_rx(struct net_device *dev) { struct net_local *lp = netdev_priv(dev); struct sk_buff *skb; int status, length;
int ioaddr = dev->base_addr; status = readword(ioaddr, RX_FRAME_PORT); length = readword(ioaddr, RX_FRAME_PORT);
if ((status & RX_OK) == 0) { count_rx_errors(status, lp); return; }
/* Malloc up new buffer. */ skb = dev_alloc_skb(length + 2); if (skb == NULL) { #if 0 /* Again, this seems a cruel thing to do */ printk(KERN_WARNING "%s: Memory squeeze, dropping packet.\n", dev->name); #endif lp->stats.rx_dropped++; return; } skb_reserve(skb, 2); /* longword align L3 header */
readwords(ioaddr, RX_FRAME_PORT, skb_put(skb, length), length >> 1); if (length & 1) skb->data[length-1] = readword(ioaddr, RX_FRAME_PORT);
if (net_debug > 3) { printk( "%s: received %d byte packet of type %x\n", dev->name, length, (skb->data[ETH_ALEN+ETH_ALEN] << 8) | skb->data[ETH_ALEN+ETH_ALEN+1]); }
skb->protocol=eth_type_trans(skb,dev); netif_rx(skb); dev->last_rx = jiffies; lp->stats.rx_packets++; lp->stats.rx_bytes += length; }
|
Dm9000的代码类同,我们这里只拿cs8900的驱动代码来举例分析,我们可以从上述代码中看到cs8900网卡驱动程序分配一个数据包专用结构变量skb,然后初始化设置后就将网卡的数据读入到这个结构中,调用netif_rx()函数向内核通知已经有数据接收到了。函数的其余代码我们不分析了,与具体硬件电路相关。我们看这个netif_rx()
int netif_rx(struct sk_buff *skb) { struct softnet_data *queue; unsigned long flags;
/* if netpoll wants it, pretend we never saw it */ if (netpoll_rx(skb)) return NET_RX_DROP;
if (!skb->tstamp.tv64) net_timestamp(skb);
/* * The code is rearranged so that the path is the most * short when CPU is congested, but is still operating. */ local_irq_save(flags); queue = &__get_cpu_var(softnet_data);
__get_cpu_var(netdev_rx_stat).total++; if (queue->input_pkt_queue.qlen <= netdev_max_backlog) { if (queue->input_pkt_queue.qlen) { enqueue: dev_hold(skb->dev); __skb_queue_tail(&queue->input_pkt_queue, skb); local_irq_restore(flags); return NET_RX_SUCCESS; }
napi_schedule(&queue->backlog); goto enqueue; }
__get_cpu_var(netdev_rx_stat).dropped++; local_irq_restore(flags);
kfree_skb(skb); return NET_RX_DROP; }
|
这个函数位于/net/core/dev.c中的1790行处,函数中出现了一个我们还未曾接触过的数据结构
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; #ifdef CONFIG_NET_DMA struct dma_chan *net_dma; #endif };
|
从名称上看是“软中断数据”这个结构其实是专门用于中断概念中的“下半部”,也就是关键的中断是我们的CPU在关闭外部中断防止其他干扰情况下处理的“上半部”,但是了解中断朋友们知道中断不能关闭的时间太长,但是我们的数据还需要继续处理就需要放在“下半部”中断过程中来完成,这些概念我们将在以后的中断章节描述,我们在上面的函数中看到首先声明了这么一个“软中断”变量queue,接着我们看到
queue = &__get_cpu_var(softnet_data);
|
从每个cpu的全局结构中取得这个结构,这是什么时候初始化的呢?我们在/include/linux/netdevice.h头文件中看到
DECLARE_PER_CPU(struct softnet_data,softnet_data);
|
我们看到引用的宏DECLARE_PER_CPU在/include/linux/percpu.h中11行处
#ifdef CONFIG_SMP #define DEFINE_PER_CPU(type, name) \ __attribute__((__section__(".data.percpu"))) \ PER_CPU_ATTRIBUTES __typeof__(type) per_cpu__##name
|
这个宏在使用smp多cpu的时候有效,这里有一个名词per-CPU 变量,它是一个有趣的 2.6 内核特性,使用smp架构时创建一个per-CPU变量后,smp系统中每个处理器都会获得该变量的副本。它的优点就是对per-CPU变量的访问(几乎)不需要加锁,因为每个处理器都使用自己的副本。per-CPU 变量也可存在于它们各自的处理器缓存中,这就在频繁更新时带来了更好性能。关于smp的话题有很多资料,我们也会在将来分析和探讨。
我们既然看到这里要取得这个per_cpu的变量softnet_data,他肯定是系统初始化时进行了相关的初始化操作。首先请朋友看一下http://blog.chinaunix.net/u2/64681/showart_1385994.html 那节中我们看到了如何使系统执行net_dev_init()网络的初始化函数,softnet_data就是在那里初始化的,我们看一下相关的代码
static int __init net_dev_init(void) { 。。。。。。 for_each_possible_cpu(i) { struct softnet_data *queue;
queue = &per_cpu(softnet_data, i); skb_queue_head_init(&queue->input_pkt_queue); queue->completion_queue = NULL; INIT_LIST_HEAD(&queue->poll_list);
queue->backlog.poll = process_backlog; queue->backlog.weight = weight_p; } 。。。。。。 }
|
这里对每个cpu的softnet_data这样的per_cpu变量都进行了初始化,包括接收数据包队列input_pkt_queue和轮询队列poll_list,尤其最重要是设置了轮询的函数process_backlog我们在下边的分析中用到这个函数。
回到我们上边的netif_rx()函数中,这里我们看们到具体的cpu取得这个结构,这个结构中保存着网络数据包的队列头,我们可以netif_rx函数中将cs8900的数据包调用__skb_queue_tail()挂入到这个结构input_pkt_queue队列的尾部。然后调用napi_schedule()函数。
static inline void napi_schedule(struct napi_struct *n) { if (napi_schedule_prep(n)) __napi_schedule(n); } void __napi_schedule(struct napi_struct *n) { unsigned long flags;
local_irq_save(flags); list_add_tail(&n->poll_list, &__get_cpu_var(softnet_data).poll_list); __raise_softirq_irqoff(NET_RX_SOFTIRQ); local_irq_restore(flags); }
|
这里注意struct napi_struct是用于网卡在中断中的轮询结构。用于后半部中断使用。
struct napi_struct { /* The poll_list must only be managed by the entity which * changes the state of the NAPI_STATE_SCHED bit. This means * whoever atomically sets that bit can add this napi_struct * to the per-cpu poll_list, and whoever clears that bit * can remove from the list right before clearing the bit. */ struct list_head poll_list;
unsigned long state; int weight; int (*poll)(struct napi_struct *, int); #ifdef CONFIG_NETPOLL spinlock_t poll_lock; int poll_owner; struct net_device *dev; struct list_head dev_list; #endif };
|
我们看到这个结构从上面调用napi_schedule()函数时传递下来的是&queue->backlog,也就是全局的softnet_data结构中的backlog,我们看到上面代码中通过
list_add_tail(&n->poll_list, &__get_cpu_var(softnet_data).poll_list);
|
将softnet_data的中的这个backlog链头挂入到了它的poll_list队列中。然后调用__raise_softirq_irqoff()触发软中断函数net_rx_action()这个过程我们放在中断章节中讲解软中断,这样内核在适当的时机一般是空闲时会执行统一的do_softirq()软中断处理函数来执行这里已经触发的net_rx_action()。触发了软中断以后上半部就执行完成了,驱动程序恢复中断后,cs8900网卡继续接收下一个数据包了,难道这样就完了?下半部也就是在处理软件中断过程中会对我们刚才链入到softnet_data中的数据包进行处理。但是上面netif_rx()函数中我们应该注意那里只有在
queue->input_pkt_queue.qlen <= netdev_max_backlog
|
也就是规定接收包的长度内才会将数据包挂入并触发软中断。我们跳过软中断总函数do_softirq()直接到达网卡的接收软中断函数net_rx_action()继续往下看,关于软中断的概念也暂时放在以后,朋友们可以自行先查看一下介绍。这个函数在/net/core/dev.c中的2188行处
static void net_rx_action(struct softirq_action *h) { struct list_head *list = &__get_cpu_var(softnet_data).poll_list; unsigned long start_time = jiffies; int budget = netdev_budget; void *have;
local_irq_disable();
while (!list_empty(list)) { struct napi_struct *n; int work, weight;
/* If softirq window is exhuasted then punt. * * Note that this is a slight policy change from the * previous NAPI code, which would allow up to 2 * jiffies to pass before breaking out. The test * used to be "jiffies - start_time > 1". */ if (unlikely(budget <= 0 || jiffies != start_time)) goto softnet_break;
local_irq_enable();
/* Even though interrupts have been re-enabled, this * access is safe because interrupts can only add new * entries to the tail of this list, and only ->poll() * calls can remove this head entry from the list. */ n = list_entry(list->next, struct napi_struct, poll_list);
have = netpoll_poll_lock(n);
weight = n->weight;
/* This NAPI_STATE_SCHED test is for avoiding a race * with netpoll's poll_napi(). Only the entity which * obtains the lock and sees NAPI_STATE_SCHED set will * actually make the ->poll() call. Therefore we avoid * accidently calling ->poll() when NAPI is not scheduled. */ work = 0; if (test_bit(NAPI_STATE_SCHED, &n->state)) work = n->poll(n, weight);
WARN_ON_ONCE(work > weight);
budget -= work;
local_irq_disable();
/* Drivers must not modify the NAPI state if they * consume the entire weight. In such cases this code * still "owns" the NAPI instance and therefore can * move the instance around on the list at-will. */ if (unlikely(work == weight)) { if (unlikely(napi_disable_pending(n))) __napi_complete(n); else list_move_tail(&n->poll_list, list); }
netpoll_poll_unlock(have); } out: local_irq_enable();
#ifdef CONFIG_NET_DMA /* * There may not be any more sk_buffs coming right now, so push * any pending DMA copies to hardware */ if (!cpus_empty(net_dma.channel_mask)) { int chan_idx; for_each_cpu_mask(chan_idx, net_dma.channel_mask) { struct dma_chan *chan = net_dma.channels[chan_idx]; if (chan) dma_async_memcpy_issue_pending(chan); } } #endif
return;
softnet_break: __get_cpu_var(netdev_rx_stat).time_squeeze++; __raise_softirq_irqoff(NET_RX_SOFTIRQ); goto out; }
|
函数很大但是也有大量的注释,我们从头来看,
list_head *list = &__get_cpu_var(softnet_data).poll_list;
|
首先是取得softnet_data的队列头,然后进入一个wile循环中检查所有轮询结构,也就是取得我们在上半部中插入到softnet_data中的struct napi_struct,这里是根据
n = list_entry(list->next, struct napi_struct, poll_list);
|
关于如何取得宿主结构的函数list_entry()我们不介绍了,相关资料很多。
接着我们看到函数中调用
work = n->poll(n, weight);
|
也就是通过轮询结构中的poll钩子函数来执行轮询,这里我们在上边的net_dev_init()函数中看到了被设置成了process_backlog()函数。追踪进入这个函数看一下
static int process_backlog(struct napi_struct *napi, int quota) { int work = 0; struct softnet_data *queue = &__get_cpu_var(softnet_data); unsigned long start_time = jiffies;
napi->weight = weight_p; do { struct sk_buff *skb; struct net_device *dev;
local_irq_disable(); skb = __skb_dequeue(&queue->input_pkt_queue); if (!skb) { __napi_complete(napi); local_irq_enable(); break; }
local_irq_enable();
dev = skb->dev;
netif_receive_skb(skb);
dev_put(dev); } while (++work < quota && jiffies == start_time);
return work; }
|
这段函数的代码并不难理解,在一个do_while循环中依次取出在softnet_data中input_pkt_queue队列上的数据包,调用netif_receive_skb()函数往上传递,也就是我们在http://blog.chinaunix.net/u2/64681/showart_1404746.html 最后讲到的与服务器端对接的目标。
int netif_receive_skb(struct sk_buff *skb) { struct packet_type *ptype, *pt_prev; struct net_device *orig_dev; int ret = NET_RX_DROP; __be16 type;
/* if we've gotten here through NAPI, check netpoll */ if (netpoll_receive_skb(skb)) return NET_RX_DROP;
if (!skb->tstamp.tv64) net_timestamp(skb);
if (!skb->iif) skb->iif = skb->dev->ifindex;
orig_dev = skb_bond(skb);
if (!orig_dev) return NET_RX_DROP;
__get_cpu_var(netdev_rx_stat).total++;
skb_reset_network_header(skb); skb_reset_transport_header(skb); skb->mac_len = skb->network_header - skb->mac_header;
pt_prev = NULL;
rcu_read_lock();
/* Don't receive packets in an exiting network namespace */ if (!net_alive(dev_net(skb->dev))) goto out;
#ifdef CONFIG_NET_CLS_ACT if (skb->tc_verd & TC_NCLS) { skb->tc_verd = CLR_TC_NCLS(skb->tc_verd); goto ncls; } #endif
list_for_each_entry_rcu(ptype, &ptype_all, list) { if (!ptype->dev || ptype->dev == skb->dev) { if (pt_prev) ret = deliver_skb(skb, pt_prev, orig_dev); pt_prev = ptype; } }
#ifdef CONFIG_NET_CLS_ACT skb = handle_ing(skb, &pt_prev, &ret, orig_dev); if (!skb) goto out; ncls: #endif
skb = handle_bridge(skb, &pt_prev, &ret, orig_dev); if (!skb) goto out; skb = handle_macvlan(skb, &pt_prev, &ret, orig_dev); if (!skb) goto out;
type = skb->protocol; list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) { if (ptype->type == type && (!ptype->dev || ptype->dev == skb->dev)) { if (pt_prev) ret = deliver_skb(skb, pt_prev, orig_dev); pt_prev = ptype; } }
if (pt_prev) { ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev); } else { kfree_skb(skb); /* Jamal, now you will not able to escape explaining * me how you were going to use this. :-) */ ret = NET_RX_DROP; }
out: rcu_read_unlock(); return ret; }
|
我们在这个函数中看到一个新的数据结构struct packet_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); void *af_packet_priv; struct list_head list; };
|
这个结构从名称上看是用于不同协议的数据包类型使用的,我们不管其内部的复杂性,还是坚持“读到所以理解”方法。列在这里是为了让大家查询方便,我们看到在上边的代码中还出现了一个全局的变量ptype_all和数组ptype_base[]。我们看到关于其的轮询操作,这二个全局变量结构在系统初始化过程被设置过了。结合http://blog.chinaunix.net/u2/64681/showart_1358880.html 那节对socket的初始化过程函数inet_init()为什么会执行这个函数的过程那篇文章讲的已经很详细了,我们看到这个函数
static int __init inet_init(void) { 。。。。。。 dev_add_pack(&ip_packet_type); 。。。。。。 }
|
这里的ip_packet_type是ip数据包类型结构
static struct packet_type ip_packet_type = { .type = __constant_htons(ETH_P_IP), .func = ip_rcv, .gso_send_check = inet_gso_send_check, .gso_segment = inet_gso_segment, };
|
而dev_add_pack()则将其与ptype_all联系起来
void dev_add_pack(struct packet_type *pt) { int hash;
spin_lock_bh(&ptype_lock); if (pt->type == htons(ETH_P_ALL)) list_add_rcu(&pt->list, &ptype_all); else { hash = ntohs(pt->type) & PTYPE_HASH_MASK; list_add_rcu(&pt->list, &ptype_base[hash]); } spin_unlock_bh(&ptype_lock); }
|
很显然将ip_packet_type的链头挂入到了ptype_bash杂凑队列中了。我们来看netif_receive_skb()函数的代码,这里就要通过与这个结构中的数据包类型的比对来调用deliver_skb()函数
static inline int deliver_skb(struct sk_buff *skb, struct packet_type *pt_prev, struct net_device *orig_dev) { atomic_inc(&skb->users); return pt_prev->func(skb, skb->dev, pt_prev, orig_dev); }
|
这个函数转而调用了数据包类型结构中的func()函数,我们看到上面是注册登记的我们的ip的数据包类型结构,它的结构我们在上面列出了,其钩子函数.func = ip_rcv,所以转到了ip_rcv()函数去进一步完成接收。篇幅限制不得分解到下一篇