其它
<h2>中断</h2>
<ul>
<li>
<p>同步中断,由 CPU 本身产生,又称为内部中断,也称异常</p>
<p>可屏蔽中断
不可屏蔽中断</p>
</li>
<li>
<p>异步中断,由外部硬件设备产生,可以在任何时候产生,又称为中断</p>
<p>故障
陷阱
终止</p>
</li>
</ul>
<p>中断描述表,IDT(它是一个数组,下标与向量值一一对应),它包含 3 种类型的描述符:</p>
<ul>
<li>任务门</li>
<li>中断门</li>
<li>陷阱门</li>
</ul>
<p>linux 利用中断门处理中断,利用陷阱门处理异常。</p>
<p>如果 CPL 与 DPL 不同,则需要切换栈,而且是先切换栈,再保存老的栈信息:ss 和 esp。然后再保存 eflags, cs, eip。然后跳转到中断处理程序那里。</p>
<p>Page Fault 发生在内核态 ?</p>
<p>中断处理程序可以抢占其它中断处理程序,也可以抢占异常处理程序;相反,异常处理程序从不抢占中断处理程序。在内核态能触发的唯一异常就是 Page Fault。</p>
<p>与异常相关的内核控制路径,可以在一个 CPU 上开始,由于进程切换,而迁移到另一个 CPU 上执行。</p>
<p>中断向量可以共享,比如向量 43 既分配给 USB 端口,也分配给声卡。那么共用同一个 IDT 函数吗,调用同一个 <code>do_IRQ()</code> 吗?并且 vector 还是相同的?那么怎么区分呢?</p>
<h2>中断向量</h2>
<p>物理 IRQ 可以分配给 32-238 范围内的任何向量(128 除外)。</p>
<p>> 说明:0-19 级非屏蔽中断和异常;20-31 保留</p>
<p>注意:在 <code>cat /proc/interrupts</code> 看到的,是 <code>irq_desc[]</code> 中的内容,并非 IDT 对应的内容。</p>
<h2>I/O APIC</h2>
<p>一共有 24 条 IRQ 线,分别对应 24 项的中断重定向表,表项中是一个 64 bit 的空间,其中低 8 bit 表示 vector。设备的 IRQ 线连接到 I/O APIC上。</p>
<p>中断亲和力,通过 I/O APIC 的中断重定向表来实现。</p>
<p><img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=dfe2b802ff7b49f51329d189f3c4fc0f" alt="" /></p>
<p>> 参考文档:<a href="https://zhuanlan.zhihu.com/p/393195942">https://zhuanlan.zhihu.com/p/393195942</a></p>
<h2>中断亲和性</h2>
<p>> 参考文档:<a href="https://blog.csdn.net/whut_gyx/article/details/8488768">https://blog.csdn.net/whut_gyx/article/details/8488768</a></p>
<h2>kirqd</h2>
<p>内核进程,纠正 CPU 对 IRQ 的自动分配。</p>
<h2>CPL、DPL、RPL</h2>
<p><a href="https://blog.csdn.net/better0332/article/details/3416749">https://blog.csdn.net/better0332/article/details/3416749</a></p>
<h2>irq_desc</h2>
<p>每个中断向量都有它自己的 irq_desc_t 描述符,所有这些描述符组织在一起形成 irq_desc 数组,如下图:</p>
<p><img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=48332265aae5d3181eb28bb8bb2ac1ee" alt="" /></p>
<p>结构中,action 是一个 list,这响应中断要调用的函数,如下图:</p>
<p><img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=90d0ec166771e1d8d919314e34e48123" alt="" /></p>
<p><img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=be4c94104616cbf06ee87c66e7d993f9" alt="" /></p>
<p>在 <code>request_irq()</code> 中,初始化一个 <code>irqaction</code>,然后调用 <code>setup_irq()</code> 将其放到 <code>irq_desc[irq] -&gt; action</code> 列表中,实现对同一个 irq 的共享。总的来看,主要是 <code>setup_irq()</code> 来负责注册 irq 的 action。</p>
<h2>vector_irq</h2>
<p><img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=7605e51c0770ff4244f927b34eb003fa" alt="" /></p>
<p>> 参考文档:<a href="https://blog.csdn.net/sinat_20184565/article/details/98095894">https://blog.csdn.net/sinat_20184565/article/details/98095894</a></p>
<h2>其它</h2>
<pre><code># 最多 256 个中断向量。本质上中断向量是 os 里面的统一的中断号
#define NR_VECTORS 256</code></pre>
<p>发送 IPI CPU 间中断,是直接往 I/O 端口或寄存器写命令实现的。</p>
<p>异常处理,是每个 vector 注册不同的处理函数;而中断处理,基本上每个 vector 都是相同的处理函数(调用 <code>do_IRQ()</code>),然后在这个统一的函数里,再根据 vector 进行不同的处理。</p>
<p>触发中断门时,CPU 会自动清 eflags 的 IF 位,来禁用中断。</p>
<p>init_IRQ() 初始化,从 0x20 号开始往后初始化,但避开了 0x80,因为之前已经初始化了,这里不能再覆盖:</p>
<p><img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=b7c325599dd82b334e723a777dad0ac1" alt="" /></p>
<h2>下半部</h2>
<h2>软中断及 tasklet</h2>
<ul>
<li>
<p>软中断 softirq(即使是同一种类型的软中断)可以并发地运行在多个 CPU 上,因此其是可重入函数。</p>
</li>
<li>而 tasklet 则不一样:同一种类型的是串行执行的。</li>
</ul>
<p>触发检查执行 softirq 的地方:</p>
<ol>
<li>
<p>local_bh_enable</p>
</li>
<li>
<p>do_IRQ() 退出时</p>
</li>
<li>
<p>时钟中断 ?</p>
</li>
<li>完成 CALL_FUNCTION_VERCTOR 处理器间中断时,</li>
</ol>
<pre><code class="language-c">asmlinkage void do_softirq(void)
{
__u32 pending;
unsigned long flags;
if (in_interrupt()) // 不能在中断中执行,包括软中断。在下面 __do_softirq 中,会通过禁用中断,来增加软中断计数
return;
local_irq_save(flags); // 注意:这里会保存 eflags,并且禁用 CPU 中断,对于 x86 是执行 cli 来实现禁用的
pending = local_softirq_pending();
if (pending)
__do_softirq();
local_irq_restore(flags);
}</code></pre>
<pre><code class="language-c">asmlinkage void __do_softirq(void)
{
struct softirq_action *h;
__u32 pending;
int max_restart = MAX_SOFTIRQ_RESTART;
int cpu;
pending = local_softirq_pending();
local_bh_disable(); // 在 preempt 中增加软中断计数。如果不这么做,那么在下面执行 action 的时候,可能会触发新的中断,在退出时又会调用 do_softirq
cpu = smp_processor_id();
restart:
/* Reset the pending bitmask before enabling irqs */
local_softirq_pending() = 0;
local_irq_enable(); // sti 允许 CPU 中断
h = softirq_vec;
do {
if (pending &amp; 1) {
h-&gt;action(h);
rcu_bh_qsctr_inc(cpu);
}
h++;
pending &gt;&gt;= 1;
} while (pending);
local_irq_disable(); // cli 禁止中断
pending = local_softirq_pending();
if (pending &amp;&amp; --max_restart) // 控制每次处理的次数
goto restart;
if (pending)
wakeup_softirqd();
__local_bh_enable();
}</code></pre>
<p>每个 CPU 都有自己的 ksoftirqd/n 内核线程,</p>
<pre><code class="language-c">static int ksoftirqd(void * __bind_cpu)
{
set_user_nice(current, 19);
current-&gt;flags |= PF_NOFREEZE;
set_current_state(TASK_INTERRUPTIBLE);
while (!kthread_should_stop()) {
if (!local_softirq_pending())
schedule();
__set_current_state(TASK_RUNNING); // 激活运行后,从这里开始
while (local_softirq_pending()) {
/* Preempt disable stops cpu going offline.
If already offline, we'll be on wrong CPU:
don't process */
preempt_disable(); // 防止抢占
if (cpu_is_offline((long)__bind_cpu))
goto wait_to_die;
do_softirq();
preempt_enable(); // 允许抢占
cond_resched();
}
set_current_state(TASK_INTERRUPTIBLE);
}
__set_current_state(TASK_RUNNING);
return 0;
wait_to_die:
preempt_enable();
/* Wait for kthread_stop */
set_current_state(TASK_INTERRUPTIBLE);
while (!kthread_should_stop()) {
schedule();
set_current_state(TASK_INTERRUPTIBLE);
}
__set_current_state(TASK_RUNNING);
return 0;
}</code></pre>