首页 > 代码库 > OpenWRT数据发送过程【Linux内核-OpenWRT】

OpenWRT数据发送过程【Linux内核-OpenWRT】

之前一篇写的不完整,重新写一篇

OpenWRT数据发送过程 这里使用的是ath9k网卡驱动,硬件平台是TP-link TL-WR841N V7.1 路由器

1.  packet_sendmsg()
    Linux kernel发送数据的接口函数是packet_sendmsg,本质上对应了user space的sendmsg实现。上层通过调用sendmsg实现数据的发送。将待发送的数据放入kernel space中。
    在内核文件夹linux-3.3.8的子目录:/net/packet中,找到文件af_packet.c,这个文件里定义了如下一个结构:

static const struct proto_ops packet_ops = {
    …
	.sendmsg =      packet_sendmsg,
    .recvmsg =      packet_recvmsg,
	…
};
这个结构采用了C99标准的初始化方式,基本用法是“.成员=变量值”(已经预先定义了一个proto_ops结构,里面包含sendmsg函数指针)。这个结构中把上层的sendmsg函数和下层的packet_sendmsg函数对应了起来。上层通过调用sendmsg就将数据传递给了packet_sendmsg函数。下面我们就从内核态的packet_sendmsg出发来研究一下数据的发送过程。
下面来看看packet_sendmsg()的实现(位于linux-3.3.8/net/packet文件夹下的af_packet.c文件中)
static int packet_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg, size_t len)
{
    struct sock *sk = sock->sk;
    struct packet_sock *po = pkt_sk(sk);
    if (po->tx_ring.pg_vec)
        return tpacket_snd(po, msg);
    else
        return packet_snd(sock, msg, len);
}

2.  packet_snd()
调用packet_snd()(位于linux-3.3.8/net/packet文件夹下的af_packet.c文件中)。
static int packet_snd(struct socket *sock, struct msghdr *msg, size_t len)
{
    ...
    // 首先把数据从user space拷贝到kernel space
    err = memcpy_fromiovec((void *)&vnet_hdr, msg->msg_iov, vnet_hdr_len);
    ...
    /*
     *    Now send it
     */
    // 然后用dev_queue_xmit()来发送skb.
    err = dev_queue_xmit(skb);
    if (err > 0 && (err = net_xmit_errno(err)) != 0)
        goto out_unlock;
    ...
}

3.  dev_queue_xmit()
调用dev_queue_xmit()(位于linux-3.3.8/net/core文件夹下的dev.c文件中)。
int dev_queue_xmit(struct sk_buff *skb)
{
    struct net_device *dev = skb->dev;
    struct netdev_queue *txq;
    struct Qdisc *q;
    int rc = -ENOMEM;
    skb_reset_mac_header(skb);
    /* Disable soft irqs for various locks below. Also
     * stops preemption for RCU.
     */
    rcu_read_lock_bh();
    skb_update_prio(skb);
    txq = netdev_pick_tx(dev, skb);
    q = rcu_dereference_bh(txq->qdisc);
#ifdef CONFIG_NET_CLS_ACT
    skb->tc_verd = SET_TC_AT(skb->tc_verd, AT_EGRESS);
#endif
    trace_net_dev_queue(skb);
    if (q->enqueue) {
        rc = __dev_xmit_skb(skb, q, dev, txq);
        goto out;
    }
    ...
}

4.  __dev_xmit_skb()
调用__dev_xmit_skb()(位于linux-3.3.8/net/core文件夹下的dev.c文件中)。
static inline int __dev_xmit_skb(struct sk_buff *skb, struct Qdisc *q, struct net_device *dev, struct netdev_queue *txq)
{
    ...
    if (unlikely(test_bit(__QDISC_STATE_DEACTIVATED, &q->state))) {
        kfree_skb(skb);
        rc = NET_XMIT_DROP;
    } else if ((q->flags & TCQ_F_CAN_BYPASS) && !qdisc_qlen(q) &&
           qdisc_run_begin(q)) {
        /*
         * This is a work-conserving queue; there are no old skbs
         * waiting to be sent out; and the qdisc is not running -
         * xmit the skb directly.
         */
        if (!(dev->priv_flags & IFF_XMIT_DST_RELEASE))
            skb_dst_force(skb);
        qdisc_bstats_update(q, skb);
        // 注意这里
        if (sch_direct_xmit(skb, q, dev, txq, root_lock)) {
            if (unlikely(contended)) {
                spin_unlock(&q->busylock);
                contended = false;
            }
            __qdisc_run(q);
        } else
            qdisc_run_end(q);
        rc = NET_XMIT_SUCCESS;
    }
    ...
}

5.  sch_direct_xmit()
调用sch_direct_xmit()(位于linux-3.3.8/net/sched文件夹下的sch_generic.c文件中)。
/*
 * Transmit one skb, and handle the return status as required. Holding the
 * __QDISC_STATE_RUNNING bit guarantees that only one CPU can execute this
 * function.
 *
 * Returns to the caller:
 *                0  - queue is empty or throttled.
 *                >0 - queue is not empty.
 */
int sch_direct_xmit(struct sk_buff *skb, struct Qdisc *q,
            struct net_device *dev, struct netdev_queue *txq,
            spinlock_t *root_lock)
{
    ...
    if (!netif_xmit_frozen_or_stopped(txq))
        ret = dev_hard_start_xmit(skb, dev, txq);
    ...
}

6.  dev_hard_start_xmit()
调用dev_hard_start_xmit()(位于linux-3.3.8/net/core文件夹下的dev.c文件中)。
int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev, struct netdev_queue *txq)
{
    ...
    do {
        struct sk_buff *nskb = skb->next;
        skb->next = nskb->next;
        nskb->next = NULL;
        /*
         * If device doesn‘t need nskb->dst, release it right now while
         * its hot in this cpu cache
         */
        if (dev->priv_flags & IFF_XMIT_DST_RELEASE)
            skb_dst_drop(nskb);
        if (!list_empty(&ptype_all))
            dev_queue_xmit_nit(nskb, dev);
        skb_len = nskb->len;
        // 调用了ndo_start_xmit
        rc = ops->ndo_start_xmit(nskb, dev);
        trace_net_dev_xmit(nskb, rc, dev, skb_len);
        if (unlikely(rc != NETDEV_TX_OK)) {
            if (rc & ~NETDEV_TX_MASK)
                goto out_kfree_gso_skb;
            nskb->next = skb->next;
            skb->next = nskb;
            return rc;
        }
        txq_trans_update(txq);
        if (unlikely(netif_xmit_stopped(txq) && skb->next))
            return NETDEV_TX_BUSY;
    } while (skb->next);
    ...
}

7.  ops->ndo_start_xmit
这里调用了ops->ndo_start_xmit,那么ndo_start_xmit对应哪个函数?
为了探究ndo_start_xmit对应的函数,我们需要回头看看在驱动启动过程章节末尾提到的函数ieee80211_register_hw()。(位于OpenWRT内核文件夹子目录/net/mac80211,文件main.c中)。
7.1  ieee80211_register_hw()
int ieee80211_register_hw(struct ieee80211_hw *hw)
{
    ...
    /* add one default STA interface if supported */
    if (local->hw.wiphy->interface_modes &BIT(NL80211_IFTYPE_STATION)) {
        result = ieee80211_if_add(local, "wlan%d", NULL,
NL80211_IFTYPE_STATION, NULL);
        if (result)
            wiphy_warn(local->hw.wiphy, "Failed to add default virtual iface\n");
    }
    ...
}
EXPORT_SYMBOL(ieee80211_register_hw);

7.2  ieee80211_if_add()
调用函数ieee80211_if_add()(位于OpenWRT内核文件夹子目录/net/mac80211,文件iface.c中)。
int ieee80211_if_add(struct ieee80211_local *local, const char *name, struct wireless_dev **new_wdev, enum nl80211_iftype type, struct vif_params *params)
{
    struct net_device *ndev = NULL;
    struct ieee80211_sub_if_data *sdata = http://www.mamicode.com/NULL;>
7.3  ieee80211_setup_sdata()
继续跟进ieee80211_setup_sdata()(位于OpenWRT内核文件夹子目录/net/mac80211,文件iface.c中)。
static void ieee80211_setup_sdata(struct ieee80211_sub_if_data *sdata, enum nl80211_iftype type)
{
    ...
    /* only monitor/p2p-device differ */
    if (sdata->dev) {
        sdata->dev->netdev_ops = &ieee80211_dataif_ops;
        sdata->dev->type = ARPHRD_ETHER;
    }
    skb_queue_head_init(&sdata->skb_queue);
    INIT_WORK(&sdata->work, ieee80211_iface_work);
    INIT_WORK(&sdata->recalc_smps, ieee80211_recalc_smps_work);
    ...
}
从上述代码可以看出,ieee80211_dataif_ops这一组函数指针被注册到设备中。而ieee80211_dataif_ops的定义如下(位于OpenWRT内核文件夹子目录/net/mac80211,文件iface.c中):
static const struct net_device_ops ieee80211_dataif_ops = {
    .ndo_open        = ieee80211_open,
    .ndo_stop        = ieee80211_stop,
    .ndo_uninit        = ieee80211_uninit,
    .ndo_start_xmit        = ieee80211_subif_start_xmit,
    .ndo_set_rx_mode    = ieee80211_set_multicast_list,
    .ndo_change_mtu     = ieee80211_change_mtu,
    .ndo_set_mac_address     = ieee80211_change_mac,
    .ndo_select_queue    = ieee80211_netdev_select_queue,
};
显然,ndo_start_xmit对应的函数是ieee80211_subif_start_xmit()


8.  ieee80211_subif_start_xmit()
调用ieee80211_subif_start_xmit()(位于OpenWRT内核文件夹子目录/net/mac80211,文件tx.c中)。

netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
    ...
    ieee80211_xmit(sdata, skb, band);
    rcu_read_unlock();
    return NETDEV_TX_OK;
}

9.  ieee80211_xmit()
调用ieee80211_xmit()(位于OpenWRT内核文件夹子目录/net/mac80211,文件tx.c中)。
void ieee80211_xmit(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb, enum ieee80211_band band)
{
    ...
    ieee80211_set_qos_hdr(sdata, skb);
    ieee80211_tx(sdata, skb, false, band);
}

10.  ieee80211_tx()
调用ieee80211_tx(),若数据成功被发送则返回true,没有被发送出去则返回false(位于OpenWRT内核文件夹子目录/net/mac80211,文件tx.c中)。
static bool ieee80211_tx(struct ieee80211_sub_if_data *sdata,
             struct sk_buff *skb, bool txpending,
             enum ieee80211_band band)
{
    ...
    if (!invoke_tx_handlers(&tx))
        result = __ieee80211_tx(local, &tx.skbs, led_len,
                    tx.sta, txpending);
    return result;
}

11.  __ieee80211_tx()
调用__ieee80211_tx()(位于OpenWRT内核文件夹子目录/net/mac80211,文件tx.c中)。
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 =http://www.mamicode.com/vif_to_sdata(info->control.vif);>

12.  ieee80211_tx_frags()
调用ieee80211_tx_frags()(位于OpenWRT内核文件夹子目录/net/mac80211,文件tx.c中)。
static bool ieee80211_tx_frags(struct ieee80211_local *local, struct ieee80211_vif *vif, struct ieee80211_sta *sta, struct sk_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) {
		struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
		int q = info->hw_queue;
		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)) {
				/*
				 * Drop off-channel frames if queues
				 * are stopped for any reason other
				 * than off-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 is stopped, queue up frames for
				 * later transmission from the tx-pending
				 * tasklet when the 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从待发送的队列中移除。然后进入下一轮循环处理下一个待发送的数据。
最后将数据发送出去的函数是drv_tx()。下面对其进行分析。


13.  drv_tx()
调用drv_tx()(位于OpenWRT内核文件夹子目录/net/mac80211,文件driver-ops.h中)

static inline void drv_tx(struct ieee80211_local *local, struct ieee80211_tx_control *control, struct sk_buff *skb)
{
    local->ops->tx(&local->hw, control, skb);
}
在ieee80211_alloc_hw()函数体中有这样的代码(位于文件OpenWRT内核文件夹子目录/net/mac80211,文件main.c中):
local->ops = ops;
而这个ops又是从ieee80211_alloc_hw()的参数传进来的(系统启动进行初始化时执行ath_pci_probe函数调用ieee80211_alloc_hw()进行传递,ath_pci_probe函数定义于OpenWRT内核文件夹子目录/drivers/net/wireless/ath/ath9k/,文件pci.c中),也就是ath9k_ops(定义于OpenWRT内核文件夹子目录/drivers/net/wireless/ath/ath9k/,文件main.c中)。我们在驱动启动过程中提到过。
结构ath9k_ops如下所示:
struct ieee80211_ops ath9k_ops = {
	.tx  = ath9k_tx,
	…
}
所以local->ops-tx()实际上就触发了ath9k_tx()


14.  ath9k_tx()
调用ath9k_tx()(定义于OpenWRT内核文件夹子目录/drivers/net/wireless/ath/ath9k/,文件main.c中)

static void ath9k_tx(struct ieee80211_hw *hw, struct ieee80211_tx_control *control, struct sk_buff *skb)
{
…
	if (ath_tx_start(hw, skb, &txctl) != 0) {
		ath_dbg(common, XMIT, "TX failed\n");
		TX_STAT_INC(txctl.txq->axq_qnum, txfailed);
		goto exit;
	}
	return;
exit:
	ieee80211_free_txskb(hw, skb);
}

15.  ath_tx_start()
调用ath_tx_start()(定义于OpenWRT内核文件夹子目录/drivers/net/wireless/ath/ath9k/,文件xmit.c中)
int ath_tx_start(struct ieee80211_hw *hw, struct sk_buff *skb, struct ath_tx_control *txctl)
{
…
	bf = ath_tx_setup_buffer(sc, txq, tid, skb);
	if (!bf) {
		ath_txq_skb_done(sc, txq, skb);
		if (txctl->paprd)
			dev_kfree_skb_any(skb);
		else
			ieee80211_free_txskb(sc->hw, skb);
		goto out;
	}
…
}


经过这些函数调用步骤之后,信息被传送出去。

OpenWRT数据发送过程【Linux内核-OpenWRT】