最近在研究Linux内核的网络子系统,一边分析一边总结,顺便记录在博客里面方便自己查找,也希望引来一些讨论和批评。刚开始写技术博客,而且对程序的理解不深刻,里面有些地方写的不清楚或者有错误希望能有大神给我指出来,学渣先在此谢过。
IEEE802.11协议在Linux内核中实现时,数据发送模块的执行依靠的是函数ieee80211_xmit():
voidieee80211_xmit(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb,enumieee80211_band band) { ... ieee80211_set_qos_hdr(sdata, skb); ieee80211_tx(sdata,skb, false, band); }
函数ieee80211_xmit()调用ieee80211_tx(),若数据成功被发送则返回true,如果仅仅被加入了发送队列而没有被发送出去则返回false:
static boolieee80211_tx(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb, booltxpending, enum ieee80211_band band) { ... if (!invoke_tx_handlers(&tx)) result =__ieee80211_tx(local, &tx.skbs, led_len,tx.sta, txpending); return result; }
接下来就是关键的__ieee80211_tx函数了,该函数将bool型结果返回给ieee80211_tx函数(即若数据成功被发送则返回true,如果仅仅被加入了发送队列而没有被发送出去则返回false):
static bool__ieee80211_tx(struct ieee80211_local *local, struct sk_buff_head *skbs, intled_len, struct sta_info *sta, bool txpending) { struct ieee80211_tx_info *info; struct ieee80211_sub_if_data *sdata; struct ieee80211_vif *vif; struct ieee80211_sta *pubsta; struct sk_buff *skb; bool result = true; __le16 fc; /*这里首先检查待发送的队列是否为空,如果是空队列,则直接返回,不做其他任何操作*/ if (WARN_ON(skb_queue_empty(skbs))) return true; /*把队列中的第一个元素取出来作为待发送的元素*/ skb = skb_peek(skbs); fc = ((struct ieee80211_hdr*)skb->data)->frame_control; info = IEEE80211_SKB_CB(skb); sdata =vif_to_sdata(info->control.vif); if (sta && !sta->uploaded) /*确定目标sta是否被正确的上载到驱动中,如果没有,则把目标站点置为NULL*/ sta = NULL; if (sta) pubsta = &sta->sta; else pubsta = NULL; ... result = ieee80211_tx_frags(local, vif,pubsta, skbs,txpending);/*调用函数ieee80211_tx_frags向目标站点传送信息,返回标记传送成功或失败的bool型变量*/ ieee80211_tpt_led_trig_tx(local, fc,led_len); WARN_ON_ONCE(!skb_queue_empty(skbs)); return result; }
下面再来看看函数ieee80211_tx_frags():
static bool ieee80211_tx_frags(struct ieee80211_local*local, struct ieee80211_vif *vif, struct ieee80211_sta *sta, structsk_buff_head *skbs, bool txpending) { struct ieee80211_tx_control control; struct sk_buff *skb, *tmp; unsigned long flags; skb_queue_walk_safe(skbs, skb, tmp) { structieee80211_tx_info *info = IEEE80211_SKB_CB(skb); int q =info->hw_queue; #ifdef CONFIG_MAC80211_VERBOSE_DEBUG if (WARN_ON_ONCE(q >= local->hw.queues)){ __skb_unlink(skb,skbs); ieee80211_free_txskb(&local->hw,skb); continue; } #endif spin_lock_irqsave(&local->queue_stop_reason_lock,flags); if (local->queue_stop_reasons[q] || (!txpending&& !skb_queue_empty(&local->pending[q]))) { if (unlikely(info->flags&IEEE80211_TX_INTFL_OFFCHAN_TX_OK)) { if(local->queue_stop_reasons[q] & ~BIT(IEEE80211_QUEUE_STOP_REASON_OFFCHANNEL)) { /* * Dropoff-channel frames if queues * are stopped for any reason other * thanoff-channel operation. Never * queue them. */ spin_unlock_irqrestore(&local->queue_stop_reason_lock,flags); ieee80211_purge_tx_queue(&local->hw,skbs); return true; } } else { /* * Since queue isstopped, queue up frames for * latertransmission from the tx-pending * tasklet whenthe queue is woken again. */ if (txpending) skb_queue_splice_init(skbs,&local->pending[q]); else skb_queue_splice_tail_init(skbs,&local->pending[q]); spin_unlock_irqrestore(&local->queue_stop_reason_lock,flags); return false; } } spin_unlock_irqrestore(&local->queue_stop_reason_lock,flags); info->control.vif = vif; control.sta = sta; __skb_unlink(skb, skbs); drv_tx(local, &control, skb); } return true; }
为了清晰的了解整个发送过程,现在对函数做一个详细的分析:
skb_queue_walk_safe不是函数,而是内核中定义的一个宏,将其展开来是如下这种形式:
#define skb_queue_walk_safe(queue, skb, tmp) \ for (skb = (queue)->next, tmp = skb->next; \ skb != (struct sk_buff *)(queue); \ skb = tmp, tmp = skb->next)
用在这里的含义就是当队列不为空时,对队列中的元素进行操作。
接着使用spin_lock_irqsave将自旋锁上锁,也就是将中断关闭,其中spin_lock指的是自旋锁,在操作系统层面来看,自旋锁与信号量有一些相似,但二者并不相同,这里就不多谈了。将中断关闭以后,系统无法采用中断的方式发送信息,这有助于对当前的发送状态进行判决,然后再解锁进行信息的发送。
接下来就是对发送状态的判断的部分了,首先判断队列是否因为某个因素停止了或延迟队列中有待发送的数据,如果是,则判断对方站点是否已经脱离了信道,如果已经脱离了信道并且还有可发送的信息则将信息推送出去(这一特性由IEEE80211_TX_INTFL_OFFCHAN_TX_OK标记,该标记表示在发送队列停止、且系统进行脱离信道的操作时,当前数据是可以被发送的(说起来有点拗口,程序注释中的原句是Used to indicate that a frame can betransmitted while the queues are stopped for off-channel operation))。推送数据时需要先用spin_unlock_irqrestore对自旋锁进行解锁(置为1),将中断开启,以便让系统利用中断向目标站点发送数据。发送完成后,通过函数ieee80211_purge_tx_queue将队列中的已发送数据清除掉,后面的数据顶到前面来。
如果队列停止了并且队列中的元素不能被发送,那么就将待发送的元素存入延迟发送队列,然后将队首元素丢弃(根据程序来看,其实这个数据还是被发送出去了,只是发送出去以后没有站点可以对其进行接收罢了)。
如果队列没有停止并且延迟队列中没有待发送的数据,则直接将自旋锁解锁,将数据发送出去,发送成功后调用_skb_unlink(skb,skbs)将skb从待发送的队列中移除。然后进入下一轮循环处理下一个待发送的数据。