中断された応答とサービス
IDTと割り込み応答キューの初期化が完了した後、割り込みに応答する方法を見てみましょう.
CPUは、割り込みコントローラから割り込みベクトルを取得し、IDTテーブルから対応するテーブル項目を探し出し、実際には割り込みゲートである.割り込みゲートのアドレス情報に基づいて,特定の割り込みのサービスエントリアドレスを見つけた.まず、この入り口の住所はどこに設置されているのでしょうか.IDTテーブルの初期化セクションに戻り、interrupt配列の初期化を次のようにレビューします.
パブリックジャンプ先common_interrupt:
重要なのはcommonですinterruptの実装:
スタックトップを~vectorに調整した後、SAVE_を実行するALLは、いわゆる「保存現場」です.
注意、ここでは、ユーザスタックの状況とフラグレジスタEFLAGSスタックをスタックしていません.なぜですか.CPUは割り込みサービスに入るとEFLAGSをリターンアドレス(すなわちユーザスタックSSとユーザスタックSP)とともにスタックしているので、オペレーティングシステムのメンテナンスは不要である.
これまで,割り込みサービスプログラムに入る際のシステムスタックを得たが,『シナリオ解析』P 211の図を参照できる.
do_を呼び出すことができますIRQ:
システムスタックに保存されているレジスタの状態が取得された後、次は割り込みコンテキストに入ります.
次に見なければならないのは、中断時間の操作を本格的に処理することです.
では、中断サービスの方法、すなわちhandle_を見てみましょう.irq_event()関数で完了した操作:
サービスプログラムを中断するプロセスが完了したようです.の
いいえ、まだです.正確には論理的な観点から割り込み要求のサービスが完了しただけである.Linuxカーネルはサービスプログラム全体を2つの部分に分割しているため、第1の部分は直ちに実行されなければならない.一般的には、オフブレークの条件下で実行され、要求のたびに個別に実行されなければならない.一方、一部であれば、後で割り込みを開く条件で実行することができ、複数の割り込みサービスの残りの部分を統合して実行することができ、この書き込み操作は時間がかかることが多いため、割り込みをオフにする条件で実行するのは適切ではなく、他の割り込みサービスに影響を与える.『情景分析』
後の操作の一部を見つけるためにdo_に戻りますIRQで、do_IRQの最後と、最初のirq_enter対応はirq_を呼び出しますexit、この関数が何をしたか見てみましょう.
commonに戻るinterruptでは,割り込みサービスプログラムが完了した後,すなわちdo_を呼び出す.IRQが終了すると、プログラムはret_にジャンプします.from_intrちょっと見てみましょう
(1)プリエンプトカーネルでなければ
その後、
(2)プリエンプトカーネルであれば
処理待ちは、処理対象の信号(do_signal呼び出し)を先に処理してから返される.もちろんこれはプロセス間通信の内容であり、後でお話しします.
処理する信号を処理した後、最終的にはretoreにジャンプを呼び出します.all、私たちはすでに前に細かく分析したので、余計なことは言いません.
これまで、やっと割り込みの処理が完了し、割り込まれる前の状態に戻り、割り込まれたプログラムが実行され続けました(再スケジュールされていなければ)!
CPUは、割り込みコントローラから割り込みベクトルを取得し、IDTテーブルから対応するテーブル項目を探し出し、実際には割り込みゲートである.割り込みゲートのアドレス情報に基づいて,特定の割り込みのサービスエントリアドレスを見つけた.まず、この入り口の住所はどこに設置されているのでしょうか.IDTテーブルの初期化セクションに戻り、interrupt配列の初期化を次のようにレビューします.
/*
* Build the entry stubs and pointer table with some assembler magic.
* We pack 7 stubs into a single 32-byte chunk, which will fit in a
* single cache line on all modern x86 implementations.
*/
.section .init.rodata,"a"
ENTRY(interrupt) //interrupt
.section .entry.text, "ax"
.p2align 5
.p2align CONFIG_X86_L1_CACHE_SHIFT
ENTRY(irq_entries_start)
RING0_INT_FRAME
vector=FIRST_EXTERNAL_VECTOR
.rept (NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7
.balign 32
.rept 7
.if vector < NR_VECTORS
.if vector <> FIRST_EXTERNAL_VECTOR
CFI_ADJUST_CFA_OFFSET -4
.endif
1: pushl_cfi $(~vector+0x80) /* Note: always in signed byte range*********************************** */
.if ((vector-FIRST_EXTERNAL_VECTOR)%7) <> 6
jmp 2f
.endif
.previous
.long 1b
.section .entry.text, "ax"
vector=vector+1
.endif
.endr
2: jmp common_interrupt //******************************************
.endr
END(irq_entries_start)
.previous
END(interrupt)
.previous
interrupt配列に格納されている内容は、1という符号のアドレスであり、外部割り込みごとにそのようなアドレスが対応し、割り込みゲートのアドレスフィールドに格納され、割り込みゲートのセグメント選択サブ格納は_KERNEL_CS.したがって、サービスプログラムを中断するエントリ、すなわち実行する最初の命令はpush_である.cfi$(~vector+0 x 80)、この後jmp common_interrupt,パブリックサービスプログラムcommon_interrupt. /*
* the CPU automatically disables interrupts when executing an IRQ vector,
* so IRQ-flags tracing has to follow that:
*/
.p2align CONFIG_X86_L1_CACHE_SHIFT
common_interrupt:
addl $-0x80,(%esp) /* Adjust vector into the [-256,-1] range */
SAVE_ALL
TRACE_IRQS_OFF
movl %esp,%eax // 。eax ,do_IRQ pt_regs eax 。
call do_IRQ
jmp ret_from_intr
ENDPROC(common_interrupt)
CFI_ENDPROC
この間何があったのか見てみましょう.1: pushl_cfi $(~vector+0x80) /* Note: always in signed byte range */
この行コードは、処理された割り込み呼び出し番号(~vector+0 x 80)をシステムスタックに押し込むパブリックジャンプ先common_interrupt:
addl $-0x80,(%esp) /* Adjust vector into the [-256,-1] range */
スタックトップに押し込む値に-0 x 80を加えると、スタックトップの値は~vector、すなわち最終的には~vectorをスタックトップに押し込むことになる.なぜvectorを直接スタックにしないのですか?システムスタックのこの位置は、システム呼び出しによってカーネルに入るときにシステム呼び出し番号を格納するために使用され、システム呼び出しは、割り込みサービスプログラムと一部のプログラムを共有するため、このような手段で区別することができる.重要なのはcommonですinterruptの実装:
スタックトップを~vectorに調整した後、SAVE_を実行するALLは、いわゆる「保存現場」です.
.macro SAVE_ALL
cld
PUSH_GS
pushl_cfi %fs
/*CFI_REL_OFFSET fs, 0;*/
pushl_cfi %es
/*CFI_REL_OFFSET es, 0;*/
pushl_cfi %ds
/*CFI_REL_OFFSET ds, 0;*/
pushl_cfi %eax
CFI_REL_OFFSET eax, 0
pushl_cfi %ebp
CFI_REL_OFFSET ebp, 0
pushl_cfi %edi
CFI_REL_OFFSET edi, 0
pushl_cfi %esi
CFI_REL_OFFSET esi, 0
pushl_cfi %edx
CFI_REL_OFFSET edx, 0
pushl_cfi %ecx
CFI_REL_OFFSET ecx, 0
pushl_cfi %ebx
CFI_REL_OFFSET ebx, 0
movl $(__USER_DS), %edx // 0
movl %edx, %ds
movl %edx, %es
movl $(__KERNEL_PERCPU), %edx
movl %edx, %fs
SET_KERNEL_GS %edx
.endm
PUSH_の場合GS: /*
* User gs save/restore
*
* %gs is used for userland TLS and kernel only uses it for stack
* canary which is required to be at %gs:20 by gcc. Read the comment
* at the top of stackprotector.h for more info.
*
* Local labels 98 and 99 are used.
*/
#ifdef CONFIG_X86_32_LAZY_GS
/* unfortunately push/pop can't be no-op */
.macro PUSH_GS
pushl_cfi $0
.endm
.macro POP_GS pop=0
addl $(4 + \pop), %esp
CFI_ADJUST_CFA_OFFSET -(4 + \pop)
.endm
.macro POP_GS_EX
.endm
/* all the rest are no-op */
.macro PTGS_TO_GS
.endm
.macro PTGS_TO_GS_EX
.endm
.macro GS_TO_REG reg
.endm
.macro REG_TO_PTGS reg
.endm
.macro SET_KERNEL_GS reg
.endm
#else /* CONFIG_X86_32_LAZY_GS */
.macro PUSH_GS
pushl_cfi %gs
/*CFI_REL_OFFSET gs, 0*/
.endm
.macro POP_GS pop=0
98: popl_cfi %gs
/*CFI_RESTORE gs*/
.if \pop <> 0
add $\pop, %esp
CFI_ADJUST_CFA_OFFSET -\pop
.endif
.endm
.macro POP_GS_EX
.pushsection .fixup, "ax"
99: movl $0, (%esp)
jmp 98b
.section __ex_table, "a"
.align 4
.long 98b, 99b
.popsection
.endm
.macro PTGS_TO_GS
98: mov PT_GS(%esp), %gs
.endm
.macro PTGS_TO_GS_EX
.pushsection .fixup, "ax"
99: movl $0, PT_GS(%esp)
jmp 98b
.section __ex_table, "a"
.align 4
.long 98b, 99b
.popsection
.endm
.macro GS_TO_REG reg
movl %gs, \reg
/*CFI_REGISTER gs, \reg*/
.endm
.macro REG_TO_PTGS reg
movl \reg, PT_GS(%esp)
/*CFI_REL_OFFSET gs, PT_GS*/
.endm
.macro SET_KERNEL_GS reg
movl $(__KERNEL_STACK_CANARY), \reg
movl \reg, %gs
.endm
#endif /* CONFIG_X86_32_LAZY_GS */
fsとgsレジスタについてstackprotector.hには、次のような注釈があります./*
* GCC stack protector support.
*
* Stack protector works by putting predefined pattern at the start of
* the stack frame and verifying that it hasn't been overwritten when
* returning from the function. The pattern is called stack canary
* and unfortunately gcc requires it to be at a fixed offset from %gs.
* On x86_64, the offset is 40 bytes and on x86_32 20 bytes. x86_64
* and x86_32 use segment registers differently and thus handles this
* requirement differently.
*
* On x86_64, %gs is shared by percpu area and stack canary. All
* percpu symbols are zero based and %gs points to the base of percpu
* area. The first occupant of the percpu area is always
* irq_stack_union which contains stack_canary at offset 40. Userland
* %gs is always saved and restored on kernel entry and exit using
* swapgs, so stack protector doesn't add any complexity there.
*
* On x86_32, it's slightly more complicated. As in x86_64, %gs is
* used for userland TLS. Unfortunately, some processors are much
* slower at loading segment registers with different value when
* entering and leaving the kernel, so the kernel uses %fs for percpu
* area and manages %gs lazily so that %gs is switched only when
* necessary, usually during task switch.
*
* As gcc requires the stack canary at %gs:20, %gs can't be managed
* lazily if stack protector is enabled, so the kernel saves and
* restores userland %gs on kernel entry and exit. This behavior is
* controlled by CONFIG_X86_32_LAZY_GS and accessors are defined in
* system.h to hide the details.
*/
fsとgsの2つのレジスタは2.4カーネルでスタック動作しない.注意、ここでは、ユーザスタックの状況とフラグレジスタEFLAGSスタックをスタックしていません.なぜですか.CPUは割り込みサービスに入るとEFLAGSをリターンアドレス(すなわちユーザスタックSSとユーザスタックSP)とともにスタックしているので、オペレーティングシステムのメンテナンスは不要である.
これまで,割り込みサービスプログラムに入る際のシステムスタックを得たが,『シナリオ解析』P 211の図を参照できる.
do_を呼び出すことができますIRQ:
/*
* do_IRQ handles all normal device IRQ's (the special
* SMP cross-CPU interrupts have their own specific
* handlers).
*/
unsigned int __irq_entry do_IRQ(struct pt_regs *regs) //regs do_IRQ mov eax 。
{
struct pt_regs *old_regs = set_irq_regs(regs); //
/* high bit used in ret_from_ code */
unsigned vector = ~regs->orig_ax; //
unsigned irq;
exit_idle(); // 32 , 。
irq_enter(); // 。( )
// , , per-cpu vector_irq
irq = __this_cpu_read(vector_irq[vector]);
if (!handle_irq(irq, regs)) { //
ack_APIC_irq();
if (printk_ratelimit())
pr_emerg("%s: %d.%d No irq handler for vector (irq %d)
",
__func__, smp_processor_id(), vector, irq);
}
irq_exit();
set_irq_regs(old_regs);
return 1;
}
まずdo_を見てみましょうIRQのパラメータのデータ構造のプロトタイプ:struct pt_regs {
unsigned long bx;
unsigned long cx;
unsigned long dx;
unsigned long si;
unsigned long di;
unsigned long bp;
unsigned long ax;
unsigned long ds;
unsigned long es;
unsigned long fs;
unsigned long gs;
unsigned long orig_ax;
unsigned long ip;
unsigned long cs;
unsigned long flags;
unsigned long sp;
unsigned long ss;
};
これまでのSAVE_と照らし合わせるとALL、彼らが一つ一つ対応していることに気づきます.CPUが割り込みに入る際に行うことは,実際にはdo_であることが分かる.IRQはdo_IRQでは、割り込み直前の各レジスタの内容を容易に知ることができ、実行終了後に「jmp ret_from_intr」に戻って割り込み戻りを実行することができる.『情景分析』P 213.システムスタックに保存されているレジスタの状態が取得された後、次は割り込みコンテキストに入ります.
/*
* Enter an interrupt context.
*/
void irq_enter(void)
{
int cpu = smp_processor_id();
rcu_irq_enter();
if (idle_cpu(cpu) && !in_interrupt()) {
/*
* Prevent raise_softirq from needlessly waking up ksoftirqd
* here, as softirq will be serviced on return from interrupt.
*/
local_bh_disable();
tick_check_idle(cpu);
_local_bh_enable();
}
__irq_enter();
}
/*
* It is safe to do non-atomic ops on ->hardirq_context,
* because NMI handlers may not preempt and the ops are
* always balanced, so the interrupted value of ->hardirq_context
* will always be restored.
*/
#define __irq_enter() \
do { \
account_system_vtime(current); \ // current
add_preempt_count(HARDIRQ_OFFSET); \ // preempt_count, 。
trace_hardirq_enter(); \
} while (0)
ではdo_に入りますIRQのコア操作handle_IRqちょっと見てみましょう.bool handle_irq(unsigned irq, struct pt_regs *regs)
{
struct irq_desc *desc;
int overflow;
overflow = check_stack_overflow(); //
desc = irq_to_desc(irq); // return (irq < NR_IRQS) ? irq_desc + irq : NULL;
if (unlikely(!desc))
return false;
if (!execute_on_irq_stack(overflow, desc, irq)) { //
if (unlikely(overflow)) //
print_stack_overflow();
desc->handle_irq(irq, desc);
}
return true;
}
関数の1つを見てみましょうexecute_on_irq_stack
割り込み要求スタックの概念に関連していますが、なぜこの概念があるのでしょうか.構成されたカーネルのthread_unionは4 Kサイズであり、カーネルスタックは実際には3つあり、そのうちの1つはハードウェア割り込み要求を処理するためのスタックである.(具体的にはULK-P 164またはWebサイトを参照http://blog.csdn.net/maray/article/details/5770889を選択します.しかし、多くの場合thread_unionは8 Kです.つまり、次のコードは直接0を返します.static inline int
execute_on_irq_stack(int overflow, struct irq_desc *desc, int irq)
{
union irq_ctx *curctx, *irqctx;
u32 *isp, arg1, arg2;
curctx = (union irq_ctx *) current_thread_info();
irqctx = __this_cpu_read(hardirq_ctx);
/*
* this is where we switch to the IRQ stack. However, if we are
* already using the IRQ stack (because we interrupted a hardirq
* handler) we can't do that and just have to keep using the
* current stack (which is the irq stack already after all)
*/
if (unlikely(curctx == irqctx)) // , , 。
return 0;
/* build the stack frame on the IRQ stack
, , , 。
*/
isp = (u32 *) ((char *)irqctx + sizeof(*irqctx));
irqctx->tinfo.task = curctx->tinfo.task;
irqctx->tinfo.previous_esp = current_stack_pointer;
/*
* Copy the softirq bits in preempt_count so that the
* softirq checks work in the hardirq context.
*/
irqctx->tinfo.preempt_count =
(irqctx->tinfo.preempt_count & ~SOFTIRQ_MASK) |
(curctx->tinfo.preempt_count & SOFTIRQ_MASK);
if (unlikely(overflow))
call_on_stack(print_stack_overflow, isp);
asm volatile("xchgl %%ebx,%%esp
"
"call *%%edi
"
"movl %%ebx,%%esp
"
: "=a" (arg1), "=d" (arg2), "=b" (isp)
: "0" (irq), "1" (desc), "2" (isp),
"D" (desc->handle_irq)
: "memory", "cc", "ecx");
return 1;
}
handleに戻るIRqで.次に見なければならないのは、中断時間の操作を本格的に処理することです.
desc->handle_irq(irq, desc);
割り込みキューの初期化で行ったことを忘れないでください. for (i = 0; i < legacy_pic->nr_legacy_irqs; i++)
irq_set_chip_and_handler_name(i, chip, handle_level_irq, name);
つまりhandle_IRqはhandle_に初期化されたlevel_IRq(レベルトリガ中断)です.もちろん、いくつかの共通通路はhandleです.edge_IRq(エッジトリガ割り込み)は(クロックに関するものなど)ですが、最終的にはhandle_を呼び出す必要があります.irq_event、これこそ私たちが関心を持っているポイントです./**
* handle_level_irq - Level type irq handler
* @irq: the interrupt number
* @desc: the interrupt description structure for this irq
*
* Level type interrupts are active as long as the hardware line has
* the active level. This may require to mask the interrupt and unmask
* it after the associated handler has acknowledged the device, so the
* interrupt line is back to inactive.
*/
void
handle_level_irq(unsigned int irq, struct irq_desc *desc)
{
raw_spin_lock(&desc->lock);
mask_ack_irq(desc);
if (unlikely(irqd_irq_inprogress(&desc->irq_data))) // , 。
if (!irq_check_poll(desc))
goto out_unlock;
desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING); // desc REPLAY
kstat_incr_irqs_this_cpu(irq, desc);
/*
* If its disabled or no action available
* keep it masked and get out of here
*/
if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data)))
goto out_unlock;
handle_irq_event(desc);
if (!irqd_irq_disabled(&desc->irq_data) && !(desc->istate & IRQS_ONESHOT))
unmask_irq(desc);
out_unlock:
raw_spin_unlock(&desc->lock);
}
まず、CPUが応答信号をどのように与えるか、すなわち操作mask_ack_IRq()、irq_dataのstate_アメリカ.accessorsをIRQDに設定IRQ_MASKEDは、次のように応答しました.static inline void mask_ack_irq(struct irq_desc *desc)
{
if (desc->irq_data.chip->irq_mask_ack)
desc->irq_data.chip->irq_mask_ack(&desc->irq_data);
else {
desc->irq_data.chip->irq_mask(&desc->irq_data);
if (desc->irq_data.chip->irq_ack)
desc->irq_data.chip->irq_ack(&desc->irq_data);
}
irq_state_set_masked(desc); // d->state_use_accessors |= IRQD_IRQ_MASKED;
}
ここでは、キューの初期化を中断したときにchipを追跡したことを覚えておいてください.結果chipはi 8259 Aです.chip. では、中断サービスの方法、すなわちhandle_を見てみましょう.irq_event()関数で完了した操作:
irqreturn_t handle_irq_event(struct irq_desc *desc)
{
struct irqaction *action = desc->action;
irqreturn_t ret;
desc->istate &= ~IRQS_PENDING; // istate PENDING 。 PENDING
// , , 《 》P215。
irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);
raw_spin_unlock(&desc->lock);
ret = handle_irq_event_percpu(desc, action);
raw_spin_lock(&desc->lock);
irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS); // INPROGRESS
return ret;
}
最終的にはhandleに来ましたirq_event_percpu()、つまり最終的にサービスプログラムを検索してサービスを行うための関数です.歩いてみると2.4よりずっと面倒です!irqreturn_t
handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
{
irqreturn_t retval = IRQ_NONE; //interrupt was not from this device.
unsigned int random = 0, irq = desc->irq_data.irq;
do {
irqreturn_t res;
trace_irq_handler_entry(irq, action);
res = action->handler(irq, action->dev_id); // , , 。
trace_irq_handler_exit(irq, action, res);
if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pF enabled interrupts
",
irq, action->handler))
local_irq_disable();
switch (res) {
case IRQ_WAKE_THREAD: /*handler requests to wake the handler thread*/
/*
* Catch drivers which return WAKE_THREAD but
* did not set up a thread function
*/
if (unlikely(!action->thread_fn)) {
warn_no_thread(irq, action);
break;
}
irq_wake_thread(desc, action);
/* Fall through to add to randomness */
case IRQ_HANDLED: /*interrupt was handled by this device*/
random |= action->flags;
break;
default:
break;
}
retval |= res;
action = action->next;
} while (action);
if (random & IRQF_SAMPLE_RANDOM) // , 。
add_interrupt_randomness(irq);
if (!noirqdebug)
note_interrupt(irq, desc, retval);
return retval;
}
この関数のトピックは、irqに対応するdescが指すactionサービスキューを巡回して対応するサービスプログラムを検索するループであることを示します.では、actionキューに複数のサービスがある場合、このチャネルからの割り込み要求があるたびに順番にキューを巡るのはもったいないのではないでしょうか.確かに、これは最も効率的な方法ではありませんが、まあまあです.まず、各具体的なサービスプログラムは、最初からそれぞれの割り込みソースをチェックする必要があります.一般的には、対応するデバイスの割り込みステータスレジスタを読み、デバイスからの割り込み要求があるかどうかを確認します.なければすぐに戻ります.このプロセスにはいくつかの命令が必要です.次に、各キューのサービスプログラムの数は一般的にあまり大きくありません.だから、顕著な影響はありません.『情景分析』P 218サービスプログラムを中断するプロセスが完了したようです.の
いいえ、まだです.正確には論理的な観点から割り込み要求のサービスが完了しただけである.Linuxカーネルはサービスプログラム全体を2つの部分に分割しているため、第1の部分は直ちに実行されなければならない.一般的には、オフブレークの条件下で実行され、要求のたびに個別に実行されなければならない.一方、一部であれば、後で割り込みを開く条件で実行することができ、複数の割り込みサービスの残りの部分を統合して実行することができ、この書き込み操作は時間がかかることが多いため、割り込みをオフにする条件で実行するのは適切ではなく、他の割り込みサービスに影響を与える.『情景分析』
後の操作の一部を見つけるためにdo_に戻りますIRQで、do_IRQの最後と、最初のirq_enter対応はirq_を呼び出しますexit、この関数が何をしたか見てみましょう.
/*
* Exit an interrupt context. Process softirqs if needed and possible:
*/
void irq_exit(void)
{
account_system_vtime(current); // vtime
trace_hardirq_exit();
sub_preempt_count(IRQ_EXIT_OFFSET); // preempt_count
if (!in_interrupt() && local_softirq_pending()) // softirq
invoke_softirq(); // softirq, !!!
rcu_irq_exit();
#ifdef CONFIG_NO_HZ
/* Make sure that timer wheel updates are propagated */
if (idle_cpu(smp_processor_id()) && !in_interrupt() && !need_resched())
tick_nohz_stop_sched_tick(0);
#endif
preempt_enable_no_resched();
}
サービス中断の第2部の実現については,別のテーマで紹介する.commonに戻るinterruptでは,割り込みサービスプログラムが完了した後,すなわちdo_を呼び出す.IRQが終了すると、プログラムはret_にジャンプします.from_intrちょっと見てみましょう
ret_from_exception: #
preempt_stop(CLBR_ANY)
ret_from_intr: #
GET_THREAD_INFO(%ebp)
check_userspace:
movl PT_EFLAGS(%esp), %eax # mix EFLAGS and CS
movb PT_CS(%esp), %al # CS CPL
andl $(X86_EFLAGS_VM | SEGMENT_RPL_MASK), %eax # VM CPL
cmpl $USER_RPL, %eax #
jb resume_kernel # not returning to v8086 or userspace , resume_kernel
ENTRY(resume_userspace) #
LOCKDEP_SYS_EXIT
DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don't miss an interrupt
# setting need_resched or sigpending
# between sampling and the iret
TRACE_IRQS_OFF
movl TI_flags(%ebp), %ecx
andl $_TIF_WORK_MASK, %ecx # is there any work to be done on
# int/exception return?
jne work_pending
jmp restore_all
END(ret_from_exception)
まずresumeを見てみましょうkernel、entry_32.Sには2つの場所が定義されています.(1)プリエンプトカーネルでなければ
#define resume_kernel restore_all
肝心なrestorを見てみましょうall: restore_all:
TRACE_IRQS_IRET
restore_all_notrace:
movl PT_EFLAGS(%esp), %eax # mix EFLAGS, SS and CS
# Warning: PT_OLDSS(%esp) contains the wrong/random values if we
# are returning to the kernel.
# See comments in process.c:copy_thread() for details.
movb PT_OLDSS(%esp), %ah
movb PT_CS(%esp), %al
andl $(X86_EFLAGS_VM | (SEGMENT_TI_MASK << 8) | SEGMENT_RPL_MASK), %eax
cmpl $((SEGMENT_LDT << 8) | USER_RPL), %eax # , LDT( )
CFI_REMEMBER_STATE
je ldt_ss # returning to user-space with LDT SS , VM ,LDT
restore_nocheck:
RESTORE_REGS 4 # skip orig_eax/error_code
irq_return:
INTERRUPT_RETURN
RESTORE_REGS展開:.macro RESTORE_INT_REGS
popl_cfi %ebx
CFI_RESTORE ebx
popl_cfi %ecx
CFI_RESTORE ecx
popl_cfi %edx
CFI_RESTORE edx
popl_cfi %esi
CFI_RESTORE esi
popl_cfi %edi
CFI_RESTORE edi
popl_cfi %ebp
CFI_RESTORE ebp
popl_cfi %eax
CFI_RESTORE eax
.endm
.macro RESTORE_REGS pop=0
RESTORE_INT_REGS
1: popl_cfi %ds
/*CFI_RESTORE ds;*/
2: popl_cfi %es
/*CFI_RESTORE es;*/
3: popl_cfi %fs
/*CFI_RESTORE fs;*/
POP_GS \pop # addl $(4 + \pop), %esp ~vector
.pushsection .fixup, "ax"
4: movl $0, (%esp)
jmp 1b
5: movl $0, (%esp)
jmp 2b
6: movl $0, (%esp)
jmp 3b
.section __ex_table, "a"
.align 4
.long 1b, 4b
.long 2b, 5b
.long 3b, 6b
.popsection
POP_GS_EX
.endm
このプログラムの役割は、割り込み前の各レジスタの状態を回復することである.その後、
INTERRUPT_RETURN
すなわちiret命令を実行し、そこに戻りますか?iretを実行すると、システムスタックは割り込みゲートに入ったばかりの状態に戻り、iretはCPUが割り込みから戻る.割り込み時に対応して、システム状態からユーザ状態に戻ると、現在のスタックがユーザスタックに切り替わります.(2)プリエンプトカーネルであれば
#ifdef CONFIG_PREEMPT
ENTRY(resume_kernel)
DISABLE_INTERRUPTS(CLBR_ANY)
cmpl $0,TI_preempt_count(%ebp) # non-zero preempt_count ? preempt==0 , 。
jnz restore_all # , restore_all, 。 , ???
# , , ???
need_resched:
movl TI_flags(%ebp), %ecx # need_resched set ?
testb $_TIF_NEED_RESCHED, %cl
jz restore_all # , 。
testl $X86_EFLAGS_IF,PT_EFLAGS(%esp) # interrupts off (exception path) ?
jz restore_all # ,
call preempt_schedule_irq #
jmp need_resched
END(resume_kernel)
#endif
preempt_schedule_IRqの実現は簡単です./*
* this is the entry point to schedule() from kernel preemption
* off of irq context.
* Note, that this is called and return with irqs disabled. This will
* protect us against recursive calling from irq.
*/
asmlinkage void __sched preempt_schedule_irq(void)
{
struct thread_info *ti = current_thread_info();
/* Catch callers which need to be fixed */
BUG_ON(ti->preempt_count || !irqs_disabled());
do {
add_preempt_count(PREEMPT_ACTIVE);
local_irq_enable();
schedule();
local_irq_disable();
sub_preempt_count(PREEMPT_ACTIVE);
/*
* Check again in case we missed a preemption opportunity
* between schedule and now.
*/
barrier();
} while (need_resched());
}
以上、カーネル太で割り込みが発生した場合について説明しましたが、割り込みがユーザー状態で発生した場合は、次のようにコードを実行します.ENTRY(resume_userspace) #
LOCKDEP_SYS_EXIT
DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don't miss an interrupt
# setting need_resched or sigpending
# between sampling and the iret
TRACE_IRQS_OFF
movl TI_flags(%ebp), %ecx
andl $_TIF_WORK_MASK, %ecx # is there any work to be done on
# int/exception return? , ?
jne work_pending # , work_pending。
jmp restore_all # , 。
END(ret_from_exception)
ワークを見てみましょうpending: work_pending:
testb $_TIF_NEED_RESCHED, %cl
jz work_notifysig # work_notifysig
work_resched:
call schedule
LOCKDEP_SYS_EXIT
DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don't miss an interrupt
# setting need_resched or sigpending
# between sampling and the iret
TRACE_IRQS_OFF
movl TI_flags(%ebp), %ecx
andl $_TIF_WORK_MASK, %ecx # is there any work to be done other
# than syscall tracing?
jz restore_all
testb $_TIF_NEED_RESCHED, %cl
jnz work_resched
work_notifysig: # deal with pending signals and
# notify-resume requests
#ifdef CONFIG_VM86 # VM86
testl $X86_EFLAGS_VM, PT_EFLAGS(%esp)
movl %esp, %eax
jne work_notifysig_v86 # returning to kernel-space or
# vm86-space
xorl %edx, %edx
call do_notify_resume
jmp resume_userspace_sig
ALIGN
work_notifysig_v86:
pushl_cfi %ecx # save ti_flags for do_notify_resume
call save_v86_state # %eax contains pt_regs pointer
popl_cfi %ecx
movl %eax, %esp
#else
movl %esp, %eax
#endif
xorl %edx, %edx
call do_notify_resume
jmp resume_userspace_sig
END(work_pending)
見てdo_notify_resume関数:/*
* notification of userspace execution resumption
* - triggered by the TIF_WORK_MASK flags
*/
void
do_notify_resume(struct pt_regs *regs, void *unused, __u32 thread_info_flags)
{
#ifdef CONFIG_X86_MCE
/* notify userspace of pending MCEs */
if (thread_info_flags & _TIF_MCE_NOTIFY)
mce_notify_process();
#endif /* CONFIG_X86_64 && CONFIG_X86_MCE */
/* deal with pending signal delivery */
if (thread_info_flags & _TIF_SIGPENDING)
do_signal(regs);
if (thread_info_flags & _TIF_NOTIFY_RESUME) {
clear_thread_flag(TIF_NOTIFY_RESUME);
tracehook_notify_resume(regs);
if (current->replacement_session_keyring)
key_replace_session_keyring();
}
if (thread_info_flags & _TIF_USER_RETURN_NOTIFY)
fire_user_return_notifiers();
#ifdef CONFIG_X86_32
clear_thread_flag(TIF_IRET);
#endif /* CONFIG_X86_32 */
}
ここでは,戻りを中断する前に,関連する信号を処理する.thread_info_flagsの_TIF_NOTIFY_RESUMEフィールドが設定されている場合、プロセスに「信号」があることを示します.処理待ちは、処理対象の信号(do_signal呼び出し)を先に処理してから返される.もちろんこれはプロセス間通信の内容であり、後でお話しします.
処理する信号を処理した後、最終的にはretoreにジャンプを呼び出します.all、私たちはすでに前に細かく分析したので、余計なことは言いません.
これまで、やっと割り込みの処理が完了し、割り込まれる前の状態に戻り、割り込まれたプログラムが実行され続けました(再スケジュールされていなければ)!