linux网络报文发送前加包头,发送报文过程的调度 (linux网络子系统学习 第十二节 )...

在linux协议栈中,发送队列管理队列策略,而直接管理发送报文的是队列策略。所有发包软中断中调度的是队列策略,而不是发送队列。

一、softnet_date结构体中为报文的发送定义如下字段:struct softnet_data

{

//有报文需要发送的队列策略链表。

struct Qdisc *output_queue;

//调用 dev_kfree_skb_irq()函数延迟释放的skb链表。

struct sk_buff *completion_queue;

};

二、队列策略的调度:

1、队列策略的运行状态:

运行状态是由Qdisc->state字段来标记的。该字段使用的是位图,每一位定义如下enum qdisc_state_t

{

//队列策略处于运行状态

__QDISC_STATE_RUNNING,

//队列策略已经被调度了,既已经添加到了软中断管理的链表中了。

__QDISC_STATE_SCHED,

//队列策略被显示的禁用了。

__QDISC_STATE_DEACTIVATED,

};

2、发送队列策略的调度:void __netif_schedule(struct Qdisc *q)

{

/*如果队列策略没被调度,设置队列策略为调度状态*/

if (!test_and_set_bit(__QDISC_STATE_SCHED, &q->state))

{

/*调度设备队列策略*/

__netif_reschedule(q);

}

}static inline void __netif_reschedule(struct Qdisc *q)

{

struct softnet_data *sd;

unsigned long flags;

/*关闭硬中断*/

local_irq_save(flags);

/*取得当前CPU上的softnet_data结构*/

sd = &__get_cpu_var(softnet_data);

/*把设备的qdisc队列策略挂到softnet_data的 output_queue上*/

q->next_sched = sd->output_queue;

sd->output_queue = q;

/*调度发包软中断*/

raise_softirq_irqoff(NET_TX_SOFTIRQ);

/*恢复硬中断*/

local_irq_restore(flags);

}

三、启动/停止设备发送数据

网络设备的发送的控制是由发送队列来控制的。通过设置发送队列的运行状态来控制网络设备的发送。

1、发送队列的运行状态:

队列运行状态dev_queue->state:该字段是用位图来标记队列的运行状态。

enum netdev_queue_state_t

{

/*如果设置了该位,表示设备发送队列被关闭。

清除该位,表示开启发送队列。*/

__QUEUE_STATE_XOFF,

//如果设置了该位,表示设备发送队列已经僵死。

__QUEUE_STATE_FROZEN,

};

2、开启设备的发送:

void netif_tx_start_queue(struct netdev_queue *dev_queue)

{

clear_bit(__QUEUE_STATE_XOFF, &dev_queue->state);

}

void netif_start_queue(struct net_device *dev)

{

netif_tx_start_queue(netdev_get_tx_queue(dev, 0));

}

/*开启全部发送队列:*/

void netif_tx_start_all_queues(struct net_device *dev)

{

unsigned int i;

for (i = 0; i < dev->num_tx_queues; i++)

{

struct netdev_queue *txq = netdev_get_tx_queue(dev, i);

netif_tx_start_queue(txq);

}

}

设备有一个队列时调用netif_start_queue(),有多个发送队列时用netif_tx_start_all_queues()。

开启发送队列一般由dev->netdev_ops->ndo_open(dev)函数进行设置。

3、关闭设备的发送:

void netif_tx_stop_queue(struct netdev_queue *dev_queue)

{

set_bit(__QUEUE_STATE_XOFF, &dev_queue->state);

}

void netif_stop_queue(struct net_device *dev)

{

netif_tx_stop_queue(netdev_get_tx_queue(dev, 0));

}

/*关闭全部发送队列:*/

void netif_tx_stop_all_queues(struct net_device *dev)

{

unsigned int i;

for (i = 0; i < dev->num_tx_queues; i++)

{

struct netdev_queue *txq = netdev_get_tx_queue(dev, i);

netif_tx_stop_queue(txq);

}

}

设备有一个队列时调用netif_stop_queue(),有多个发送队列时调用netif_tx_stop_all_queues()。

关闭发送队列场景一般如下:

(1)、由dev->netdev_ops->ndo_stop(dev)函数进行设置。

(2)、当驱动程序意识到硬件没有足够的空间缓存新的发送报文时,驱动程序调用函数关闭发送队列,这样避免在将来的发送过程中浪费资源,因为内核知道发送会失败,就不会再发送新的报文给网络设备。

4、唤醒网络设备的发送队列:

唤醒网络设备发送队列,重新启动网络设备的发送功能。void netif_tx_wake_queue(struct netdev_queue *dev_queue)

{

/*开启发送队列,重新调度发送队列的队列策略*/

if (test_and_clear_bit(__QUEUE_STATE_XOFF, &dev_queue->state))

__netif_schedule(dev_queue->qdisc);

}

void netif_wake_queue(struct net_device *dev)

{

netif_tx_wake_queue(netdev_get_tx_queue(dev, 0));

}

void netif_tx_wake_all_queues(struct net_device *dev)

{

unsigned int i;

for (i = 0; i < dev->num_tx_queues; i++) {

struct netdev_queue *txq = netdev_get_tx_queue(dev, i);

netif_tx_wake_queue(txq);

}

}

当发送队列已经被关闭,但网卡现在已经有资源可用了,就需要重新启动设备的发送过程,调用以上函数来唤醒发送队列。

四、发包软中断:

1、发包软中断的注册:

static int __init net_dev_init(void)

{

。。。。。。

open_softirq(NET_TX_SOFTIRQ, net_tx_action);

。。。。。。

}

2、发包软中断处理函数:

该函数做两件工作

(1)、回收调用dev_kfree_skb函数后延迟释放的报文。

(2)、调用qdisc_restart()来处理等待发送的队列策略上的报文。

static void net_tx_action(struct softirq_action *h)

{

/*取得本地cpu的softnet_data*/

struct softnet_data *sd = &__get_cpu_var(softnet_data);

/*如果completion_queue上有要释放的报文,就把上面所有的报文释放掉*/

if (sd->completion_queue)

{

/*取得要释放的报文链,把softnet_data的completion_queue置空*/

struct sk_buff *clist;

local_irq_disable();

clist = sd->completion_queue;

sd->completion_queue = NULL;

local_irq_enable();

/*循环释放所有报文*/

while (clist)

{

struct sk_buff *skb = clist;

clist = clist->next;

WARN_ON(atomic_read(&skb->users));

__kfree_skb(skb);

}

}

/*如果softnet_data上有等待发送报文的队列策略*/

if (sd->output_queue)

{

/*取得等待发送报文的队列策略*/

struct Qdisc *head;

local_irq_disable();

head = sd->output_queue;

sd->output_queue = NULL;

local_irq_enable();

/*循环处理每个等待发送报文的队列策略*/

while (head)

{

struct Qdisc *q = head;

spinlock_t *root_lock;

head = head->next_sched;

/*发送队列策略上的发送队列由qdisc->q.lock来保护*/

root_lock = qdisc_lock(q);

/*如果获得锁成功,就处理队列策略上的报文*/

if (spin_trylock(root_lock))

{

smp_mb__before_clear_bit();

/*清除队列策略的被调度状态*/

clear_bit(__QDISC_STATE_SCHED,

&q->state);

/*调用qdisc_run 来处理队列策略上要发送的报文*/

qdisc_run(q);

spin_unlock(root_lock);

}

else

{

/*如果获得锁失败,并且队列策略没有被禁止,

就重新调度队列策略*/

if (!test_bit(__QDISC_STATE_DEACTIVATED,

&q->state))

{

__netif_reschedule(q);

}

/*如果获得锁失败,并且队列策略已经被禁止,

就清除队列策略的调度状态*/

else

{

smp_mb__before_clear_bit();

clear_bit(__QDISC_STATE_SCHED,

&q->state);

}

}

}

}

}

3、处理队列策略上报文的函数qdisc_run:

static inline void qdisc_run(struct Qdisc *q)

{

/*设置队列策略为运行状态*/

if (!test_and_set_bit(__QDISC_STATE_RUNNING, &q->state))

{

__qdisc_run(q);

}

}void __qdisc_run(struct Qdisc *q)

{

unsigned long start_time = jiffies;

/*循环处理队列策略上的报文*/

while (qdisc_restart(q))

{

/*如果需要重新调度或运行时间已经超过了一个jiffies了,

就停止运行并重新调度该队列策略*/

if (need_resched() || jiffies != start_time)

{

__netif_schedule(q);

break;

}

}

/*处理完成,清除队列策略的运行状态*/

clear_bit(__QDISC_STATE_RUNNING, &q->state);

}

处理队列策略上的一个报文static inline int qdisc_restart(struct Qdisc *q)

{

struct netdev_queue *txq;

struct net_device *dev;

spinlock_t *root_lock;

struct sk_buff *skb;

/* 从队列策略上取一个报文 */

skb = dequeue_skb(q);

if (unlikely(!skb))

return 0;

root_lock = qdisc_lock(q);

dev = qdisc_dev(q);

/*根据skb找到所属设备的发送队列*/

txq = netdev_get_tx_queue(dev, skb_get_queue_mapping(skb));

/*直接发送出去,详见下一节*/

return sch_direct_xmit(skb, q, dev, txq, root_lock);

}

从队列策略所管理的报文队列上取的一个报文:static inline struct sk_buff *dequeue_skb(struct Qdisc *q)

{

struct sk_buff *skb = q->gso_skb;

/*如果q->gso_skb不为空,表示上次发送时有发送失败的报文,

这时要先发该报文,把该报文摘下来后返回*/

if (unlikely(skb))

{

struct net_device *dev = qdisc_dev(q);

struct netdev_queue *txq;

/* check the reason of requeuing without tx lock first */

txq = netdev_get_tx_queue(dev,

skb_get_queue_mapping(skb));

if (!netif_tx_queue_stopped(txq) &&

!netif_tx_queue_frozen(txq))

{

q->gso_skb = NULL;

q->q.qlen--;

}

else

{

skb = NULL;

}

}

/*q->gso_skb上没有报文,就调用队列策略自己的出队函数

取一个skb并返回*/

else

{

skb = q->dequeue(q);

}

return skb;

}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值