__netif_schedule
<h2>概述</h2>
<p>在发送网络包时,如果系统态 CPU 用尽了,则会调用 <code>__netif_schedule</code> 触发一个软中断。</p>
<h2>分析</h2>
<pre><code class="language-c">// file: net/core/dev.c
void __netif_schedule(struct Qdisc *q)
{
if (!test_and_set_bit(__QDISC_STATE_SCHED, &amp;q-&gt;state))
__netif_reschedule(q);
}
EXPORT_SYMBOL(__netif_schedule);
static inline void __netif_reschedule(struct Qdisc *q)
{
struct softnet_data *sd;
unsigned long flags;
local_irq_save(flags);
sd = &amp;__get_cpu_var(softnet_data); // 熟悉的 sd
q-&gt;next_sched = NULL;
*sd-&gt;output_queue_tailp = q;
sd-&gt;output_queue_tailp = &amp;q-&gt;next_sched;
raise_softirq_irqoff(NET_TX_SOFTIRQ); // 软中断
local_irq_restore(flags);
}</code></pre>
<p>前文已经分析,<code>NET_TX_SOFTIRQ</code> 类型的软中断,其响应函数为 <code>net_tx_action</code>。
需要注意:这以后发送数据占用 CPU 的时间为 si,不会消耗用户进程的系统时间。
继续分析:</p>
<pre><code class="language-c">// file: net/core/dev.c
static void net_tx_action(struct softirq_action *h)
{
struct softnet_data *sd = &amp;__get_cpu_var(softnet_data);
if (sd-&gt;completion_queue) {
struct sk_buff *clist;
local_irq_disable();
clist = sd-&gt;completion_queue;
sd-&gt;completion_queue = NULL;
local_irq_enable();
while (clist) {
struct sk_buff *skb = clist;
clist = clist-&gt;next;
WARN_ON(atomic_read(&amp;skb-&gt;users));
trace_kfree_skb(skb, net_tx_action);
__kfree_skb(skb);
}
}
// 如果 output_queue 上有 qdisc
// 疑问:与 output_queue_tailp 是什么关系
if (sd-&gt;output_queue) {
struct Qdisc *head;
local_irq_disable();
head = sd-&gt;output_queue; // 首个 qdisc
sd-&gt;output_queue = NULL;
sd-&gt;output_queue_tailp = &amp;sd-&gt;output_queue;
local_irq_enable();
// 遍历 qdisc 列表
while (head) {
struct Qdisc *q = head; // q
spinlock_t *root_lock;
head = head-&gt;next_sched; // 下一个
root_lock = qdisc_lock(q);
if (spin_trylock(root_lock)) {
smp_mb__before_clear_bit();
clear_bit(__QDISC_STATE_SCHED,
&amp;q-&gt;state);
qdisc_run(q); // 发送数据包
spin_unlock(root_lock);
} else {
if (!test_bit(__QDISC_STATE_DEACTIVATED,
&amp;q-&gt;state)) {
__netif_reschedule(q); // 同上
} else {
smp_mb__before_clear_bit();
clear_bit(__QDISC_STATE_SCHED,
&amp;q-&gt;state);
}
}
}
}
}
</code></pre>
<p>软中断中,会调用 <code>qdisc_run</code> 来发送数据包,它其实和进程用户态一样,也会调用 <code>__qdisc_run</code>,如下:</p>
<pre><code class="language-c">// file: include/net/pkt_sched.h
static inline void qdisc_run(struct Qdisc *q)
{
if (qdisc_run_begin(q))
__qdisc_run(q);
}</code></pre>