內核卡死有很多種可能,
- 驅動程序因爲邏輯問題,出現死循環
- 共享資源出現死鎖
- 系統跑飛等
一般情況下,系統跑飛內核會打印Oops信息。
有了Oops信息,我們就可以通過上一節的方法來推斷出出錯位置。
而死鎖或者驅動程序死循環並不會打印Oops信息。
這個時候需要我們自己打印出來,出錯位置的寄存器( r0 ~ r15等)信息來反推出出錯位置。
因爲這個時候卡住的位置基本已經是死掉了,所以只能採用別的方式來打印出內核的寄存器信息或者oops信息。
那這個時候什麼東西是還在正常運行的呢?
中斷!
因爲中斷是是突發的,不受到其它驅動和應用程序的影響。
而且,如果某個程序執行死循環時,中斷髮生後,也會把這個死循環的程序的寄存器進行存儲的。
如何有看過我之前,關於異常和中斷章節的文章應該知道。
https://blog.csdn.net/qq_16777851/article/details/82528933
https://blog.csdn.net/qq_16777851/article/details/82556519
https://blog.csdn.net/qq_16777851/article/details/82564302
無論是執行傳統的中斷函數
.L__vectors_start:
W(b) vector_rst
W(b) vector_und
W(ldr) pc, .L__vectors_start + 0x1000
W(b) vector_pabt
W(b) vector_dabt
W(b) vector_addrexcptn
W(b) vector_irq @----->>>>>>>>
W(b) vector_fiq
vector_stub irq, IRQ_MODE, 4
.long __irq_usr @ 0 (USR_26 / USR_32) ------->
.long __irq_invalid @ 1 (FIQ_26 / FIQ_32)
.long __irq_invalid @ 2 (IRQ_26 / IRQ_32)
.long __irq_svc @ 3 (SVC_26 / SVC_32) -------->
.long __irq_invalid @ 4
.long __irq_invalid @ 5
.long __irq_invalid @ 6
.long __irq_invalid @ 7
.long __irq_invalid @ 8
.long __irq_invalid @ 9
.long __irq_invalid @ a
.long __irq_invalid @ b
.long __irq_invalid @ c
.long __irq_invalid @ d
.long __irq_invalid @ e
.long __irq_invalid @ f
.align 5
__irq_svc:
svc_entry
irq_handler @-------->>>>>>>>>
#ifdef CONFIG_PREEMPT
ldr r8, [tsk, #TI_PREEMPT] @ get preempt count
ldr r0, [tsk, #TI_FLAGS] @ get flags
teq r8, #0 @ if preempt count != 0
movne r0, #0 @ force flags to 0
tst r0, #_TIF_NEED_RESCHED
blne svc_preempt
#endif
svc_exit r5, irq = 1 @ return from exception
UNWIND(.fnend )
ENDPROC(__irq_svc)
/*
* Interrupt handling.
*/
.macro irq_handler
#ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER
ldr r1, =handle_arch_irq @新的中斷方式,分發中斷
mov r0, sp
badr lr, 9997f
ldr pc, [r1]
#else
arch_irq_handler_default @老的傳統中斷方式
#endif
9997:
.endm
先看老的傳統中斷方式
/*
* Interrupt handling. Preserves r7, r8, r9
*/
.macro arch_irq_handler_default
get_irqnr_preamble r6, lr
1: get_irqnr_and_base r0, r2, r6, lr
movne r1, sp
@
@ routine called with r0 = irq number, r1 = struct pt_regs *
@
badrne lr, 1b
bne asm_do_IRQ
asmlinkage void asm_do_IRQ(struct pt_regs *regs)
{
irq_hw_number_t hwirq = get_intr_src();
handle_domain_irq(root_domain, hwirq, regs);
}
接下來看新的中斷處理
/*
* 定義了函數指針
*/
#ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER
void (*handle_arch_irq)(struct pt_regs *) __ro_after_init;
#endif
/*
* 運行期間可以更改這個指針,來改變中斷處理函數
*/
#ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER
int __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
{
if (handle_arch_irq)
return -EBUSY;
handle_arch_irq = handle_irq;
return 0;
}
#endif
下面是新的幾種中斷的處理當時,包括vic,gic等
/*
* Keep iterating over all registered VIC's until there are no pending
* interrupts.
*/
static void __exception_irq_entry vic_handle_irq(struct pt_regs *regs)
{
int i, handled;
do {
for (i = 0, handled = 0; i < vic_id; ++i)
handled |= handle_one_vic(&vic_devices[i], regs);
} while (handled);
}
static asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
u32 irqnr;
do {
irqnr = gic_read_iar();
if (likely(irqnr > 15 && irqnr < 1020) || irqnr >= 8192) {
int err;
if (static_branch_likely(&supports_deactivate_key))
gic_write_eoir(irqnr);
else
isb();
err = handle_domain_irq(gic_data.domain, irqnr, regs);
if (err) {
WARN_ONCE(true, "Unexpected interrupt received!\n");
if (static_branch_likely(&supports_deactivate_key)) {
if (irqnr < 8192)
gic_write_dir(irqnr);
} else {
gic_write_eoir(irqnr);
}
}
continue;
}
if (irqnr < 16) {
gic_write_eoir(irqnr);
if (static_branch_likely(&supports_deactivate_key))
gic_write_dir(irqnr);
#ifdef CONFIG_SMP
/*
* Unlike GICv2, we don't need an smp_rmb() here.
* The control dependency from gic_read_iar to
* the ISB in gic_write_eoir is enough to ensure
* that any shared data read by handle_IPI will
* be read after the ACK.
*/
handle_IPI(irqnr, regs);
#else
WARN_ONCE(true, "Unexpected SGI received!\n");
#endif
continue;
}
} while (irqnr != ICC_IAR1_EL1_SPURIOUS);
}
static asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
u32 irqnr;
do {
irqnr = gic_read_iar();
if (likely(irqnr > 15 && irqnr < 1020) || irqnr >= 8192) {
int err;
if (static_branch_likely(&supports_deactivate_key))
gic_write_eoir(irqnr);
else
isb();
err = handle_domain_irq(gic_data.domain, irqnr, regs);
if (err) {
WARN_ONCE(true, "Unexpected interrupt received!\n");
if (static_branch_likely(&supports_deactivate_key)) {
if (irqnr < 8192)
gic_write_dir(irqnr);
} else {
gic_write_eoir(irqnr);
}
}
continue;
}
if (irqnr < 16) {
gic_write_eoir(irqnr);
if (static_branch_likely(&supports_deactivate_key))
gic_write_dir(irqnr);
#ifdef CONFIG_SMP
/*
* Unlike GICv2, we don't need an smp_rmb() here.
* The control dependency from gic_read_iar to
* the ISB in gic_write_eoir is enough to ensure
* that any shared data read by handle_IPI will
* be read after the ACK.
*/
handle_IPI(irqnr, regs);
#else
WARN_ONCE(true, "Unexpected SGI received!\n");
#endif
continue;
}
} while (irqnr != ICC_IAR1_EL1_SPURIOUS);
}
上面我們看到一個共同點,就是這幾個c函數的參數是一樣的。
這裏我們看一下他的定義,可以看到18個long,存放的也是中斷調轉前的那個程序的18個寄存器。
struct pt_regs {
unsigned long uregs[18];
};
這裏我們看一下這十八個寄存分別是放的那個寄存器。
#define ARM_cpsr uregs[16]
#define ARM_pc uregs[15]
#define ARM_lr uregs[14]
#define ARM_sp uregs[13]
#define ARM_ip uregs[12]
#define ARM_fp uregs[11]
#define ARM_r10 uregs[10]
#define ARM_r9 uregs[9]
#define ARM_r8 uregs[8]
#define ARM_r7 uregs[7]
#define ARM_r6 uregs[6]
#define ARM_r5 uregs[5]
#define ARM_r4 uregs[4]
#define ARM_r3 uregs[3]
#define ARM_r2 uregs[2]
#define ARM_r1 uregs[1]
#define ARM_r0 uregs[0]
#define ARM_ORIG_r0 uregs[17]
如果我們向要看pc的值,我們只需要在內核中C中斷處理函數的入口添加打印即可。
比如vic
/*
* Keep iterating over all registered VIC's until there are no pending
* interrupts.
*/
static void __exception_irq_entry vic_handle_irq(struct pt_regs *regs)
{
int i, handled;
printk(KERN_ERR"vic_handle_irq entry pt_regs->pc = %x\n ",regs->ARM_pc);
do {
for (i = 0, handled = 0; i < vic_id; ++i)
handled |= handle_one_vic(&vic_devices[i], regs);
} while (handled);
}
上面這個是任何時候觸發中斷都會打印。一般不是我們想要的。
而且像內核心跳定時器是以ms級別都在觸發的,我們不可能讓不同的打印。
這個時候我們就要根據卡死時的現象來決定處理打印,中斷執行前的pc。
比如假設,5s時間當前進程都沒有進行切換,那就證明卡死,需要打印pc值(或者其他值)。
/*
* Keep iterating over all registered VIC's until there are no pending
* interrupts.
*/
static void __exception_irq_entry vic_handle_irq(struct pt_regs *regs)
{
int i, handled;
static pid_t old_pid = 0;
static unsigned int cnt = 0;
/* 進程不一樣,表示沒卡死 */
if(current->pid != old_pid )
{
old_pid = current->pid;
cnt = 0;
}
else
{
/* 進程一樣,且連續5s進程都一樣,表示卡死,這是打印出我們需要的進中斷前的寄存器信息 */
if(cnt >= 5* HZ)
{
printk(KERN_ERR"vic_handle_irq entry pt_regs->pc = %x\n ",regs->ARM_pc);
cnt = 0;
}
}
do {
for (i = 0, handled = 0; i < vic_id; ++i)
handled |= handle_one_vic(&vic_devices[i], regs);
} while (handled);
}
打印出來後,根據pc值,知道對應位置的代碼,進而找打卡死的原因。