Linux中断处理流程
生活随笔
收集整理的這篇文章主要介紹了
Linux中断处理流程
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
看了一些網絡上關于
linux
中斷實現的文章,感覺有一些寫的非常好,在這里首先感謝他們的無私付出,然后也想再補充自己對一些問題的理解。先從函數注冊引出問題吧。
一、中斷注冊方法
在 linux 內核中用于申請中斷的函數是request_irq(),函數原型在Kernel/irq/manage.c中定義:
int request_irq(unsigned int irq, irq_handler_t handler,
? ?? ?? ?? ?? ?? ?? ?? ? unsigned long irqflags, const char *devname, void *dev_id)
irq是要申請的硬件中斷號。
handler是向系統注冊的中斷處理函數,是一個回調函數,中斷發生時,系統調用這個函數,dev_id參數將被傳遞給它。
irqflags是中斷處理的屬性,若設置了IRQF_DISABLED
(老版本中的SA_INTERRUPT,本版zhon已經不支持了),則表示中斷處理程序是快速處理程序,快速處理程序被調用時屏蔽所有中斷,慢速處理程
序不屏蔽;若設置了IRQF_SHARED
(老版本中的SA_SHIRQ),則表示多個設備共享中斷,若設置了IRQF_SAMPLE_RANDOM(老版本中的
SA_SAMPLE_RANDOM),表示對系統熵有貢獻,對系統獲取隨機數有好處。(這幾個flag是可以通過或的方式同時使用的)
dev_id在中斷共享時會用到,一般設置為這個設備的設備結構體或者NULL。
devname設置中斷名稱,在cat /proc/interrupts中可以看到此名稱。
request_irq()返回0表示成功,返回-INVAL表示中斷號無效或處理函數指針為NULL,返回-EBUSY表示中斷已經被占用且不能共享。
關于中斷注冊的例子,大家可在內核中搜索下request_irq。
在編寫驅動的過程中,比較容易產生疑惑的地方是:
1、中斷向量表在什么位置?是如何建立的?
? ?? ???2、從中斷開始,系統是怎樣執行到我自己注冊的函數的?
? ?? ???3、中斷號是如何確定的?對于硬件上有子中斷的中斷號如何確定?
? ?? ???4、中斷共享是怎么回事,dev_id的作用是?
本文以2.6.26內核和S3C2410處理器為例,為大家講解這幾個問題。
二、異常向量表的建立
在ARM V4及V4T以后的大部分處理器中,中斷向量表的位置可以有兩個位置:一個是0,另一個是0xffff0000。可以通過CP15協處理器c1寄存器中V位(bit[13])控制。V和中斷向量表的對應關系如下:
V=0? ?? ???~? ?? ???0x00000000~0x0000001C
? ?? ???V=1? ?? ???~? ?? ???0xffff0000~0xffff001C
arch/arm/mm/proc-arm920.S中
.section ".text.init", #alloc, #execinstr
? ?? ???__arm920_setup:
? ?? ???…… orr? ?? ? r0, r0, #0x2100? ?? ?? ?? ???@ ..1. ...1 ..11 ...1
//bit13=1 中斷向量表基址為0xFFFF0000。R0的值將被付給CP15的C1.
在 linux 中,向量表建立的函數為:
init/main.c->start_kernel()->trap_init()
void __init trap_init(void)
? ?? ???{?
? ?? ?? ?? ?? ???unsigned long vectors = CONFIG_VECTORS_BASE;
? ?? ?? ?? ?? ???……?
? ?? ?? ?? ?? ???memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
? ?? ?? ?? ?? ? memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
? ?? ?? ?? ?? ???....
? ?? ???}
在2.6.26內核中CONFIG_VECTORS_BASE最初是在各個平臺的配置文件中設定的,如:
arch/arm/configs/s3c2410_defconfig中
CONFIG_VECTORS_BASE=0xffff0000
__vectors_end 至 __vectors_start之間為異常向量表。
位于arch/arm/kernel/entry-armv.S
.globl __vectors_start
__vectors_start:
? ?? ???swi SYS_ERROR0:
? ?? ???b vector_und + stubs_offset //復位異常:
? ?? ???ldr pc, .LCvswi + stubs_offset? ?? ???//未定義指令異常:
? ?? ???b vector_pabt + stubs_offset? ?? ???//軟件中斷異常:
? ?? ???b vector_dabt + stubs_offset? ?? ???//數據異常:
? ?? ???b vector_addrexcptn + stubs_offset? ?? ???//保留:
? ?? ???b vector_irq + stubs_offset? ?? ???//普通中斷異常:
? ?? ???b vector_fiq + stubs_offset? ?? ???//快速中斷異常:
? ?? ???.globl __vectors_end:
__vectors_end:
__stubs_end 至 __stubs_start之間是異常處理的位置。也位于文件arch/arm/kernel/entry-armv.S中。vector_und、vector_pabt、vector_irq、vector_fiq都在它們中間。
stubs_offset值如下:
.equ stubs_offset, __vectors_start + 0x200 - __stubs_start
stubs_offset是如何確定的呢?(引用網絡上的一段比較詳細的解釋)
當匯編器看到B指令后會把要跳轉的標簽轉化為相對于當前PC的偏移量(±32M)寫入指令碼。從上面的代碼可以看到中斷向量表和stubs都發生了
代碼搬移,所以如果中斷向量表中仍然寫成b
vector_irq,那么實際執行的時候就無法跳轉到搬移后的vector_irq處,因為指令碼里寫的是原來的偏移量,所以需要把指令碼中的偏移量寫
成搬移后的。我們把搬移前的中斷向量表中的irq入口地址記irq_PC,它在中斷向量表的偏移量就是irq_PC-vectors_start,
vector_irq在stubs中的偏移量是vector_irq-stubs_start,這兩個偏移量在搬移前后是不變的。搬移后
vectors_start在0xffff0000處,而stubs_start在0xffff0200處,所以搬移后的vector_irq相對于中斷
向量中的中斷入口地址的偏移量就是,200+vector_irq在stubs中的偏移量再減去中斷入口在向量表中的偏移量,即200+
vector_irq-stubs_start-irq_PC+vectors_start = (vector_irq-irq_PC) +
vectors_start+200-stubs_start,對于括號內的值實際上就是中斷向量表中寫的vector_irq,減去irq_PC是由匯
編器完成的,而后面的
vectors_start+200-stubs_start就應該是stubs_offset,實際上在entry-armv.S中也是這樣定義的
三、中斷處理過程?
這一節將以S3C2410為例,描述 linux -2.6.26內核中,從中斷開始,中斷是如何一步一步執行到我們注冊函數的。?
3.1 中斷向量表 arch\arm\kernel\entry-armv.S
__vectors_start:
? ?? ?? ?? ? swi SYS_ERROR0
? ?? ?? ?? ? b? ? vector_und + stubs_offset
? ?? ?? ?? ? ldr pc, .LCvswi + stubs_offset
? ?? ?? ?? ? b? ? vector_pabt + stubs_offset
? ?? ?? ?? ? b? ? vector_dabt + stubs_offset
? ?? ?? ?? ? b? ? vector_addrexcptn + stubs_offset
? ?? ?? ?? ? b? ? vector_irq + stubs_offset?
? ?? ?? ?? ? b? ? vector_fiq + stubs_offset
? ?? ?? ?? ? .globl? ?__vectors_end
? ?? ???__vectors_end:
中斷發生后,跳轉到b vector_irq + stubs_offset的位置執行。注意現在的向量表的初始位置是0xffff0000。?
3.2 中斷跳轉的入口位置 arch\arm\kernel\entry-armv.S
? ?? ?.globl? ?__stubs_start
? ? __stubs_start:
? ???/*
? ???* Interrupt dispatcher
? ???*/
? ?? ?? ?? ? vector_stub? ? irq, IRQ_MODE, 4 @IRQ_MODE在include\asm\ptrace.h中定義:0x12
? ?? ?? ?? ? .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
上面代碼中vector_stub宏的定義為:?
.macro vector_stub, name, mode, correction=0
? ?? ?? ?? ? .align 5
? ?? ???vector_\name:
? ?? ?? ?? ? .if \correction
? ?? ?? ?? ? sub lr, lr, #\correction
? ?? ?? ?? ? .endif
? ?? ?? ?? ? @
? ?? ?? ?? ? @ Save r0, lr_ (parent PC) and spsr_
? ?? ?? ?? ? @ (parent CPSR)
? ?? ?? ?? ? @
? ?? ?? ?? ? stmia sp, {r0, lr} @ save r0, lr
? ?? ?? ?? ? mrs lr, spsr
? ?? ?? ?? ? str lr, [sp, #8] @ save spsr
? ?? ?? ?? ? @
? ?? ?? ?? ? @ Prepare for SVC32 mode. IRQs remain disabled.
? ?? ?? ?? ? @
? ?? ?? ?? ? mrs r0, cpsr
? ?? ?? ?? ? eor r0, r0, #(\mode ^ SVC_MODE)
? ?? ?? ?? ? msr spsr_cxsf, r0 @為后面進入svc模式做準備?
@
? ?? ?? ?? ? @ the branch table must immediately follow this code
? ?? ?? ?? ???@
? ?? ?? ?? ? and lr, lr, #0x0f @進入中斷前的mode的后4位?
? ?? ?? ?? ? @#define USR_MODE 0x00000010
? ?? ?? ?? ? @#define FIQ_MODE 0x00000011
? ?? ?? ?? ? @#define IRQ_MODE 0x00000012
? ?? ?? ?? ? @#define SVC_MODE 0x00000013
? ?? ?? ?? ? @#define ABT_MODE 0x00000017
? ?? ?? ?? ? @#define UND_MODE 0x0000001b
? ?? ?? ?? ? @#define SYSTEM_MODE 0x0000001f
? ?? ?? ?? ? mov r0, sp
? ?? ?? ?? ? ldr lr, [pc, lr, lsl #2] @如果進入中斷前是usr,則取出PC+4*0的內容,即__irq_usr @如果進入中斷前是svc,則取出PC+4*3的內容,即__irq_svc
? ?? ?? ?? ? movs pc, lr @ 當指令的目標寄存器是PC,且指令以S結束,則它會把@ spsr的值恢復給cpsr branch to handler in SVC mode
? ?? ?? ?? ? .endm
? ?? ?? ?? ? .globl __stubs_start
? ?? ? __stubs_start:
? ?? ? /*
? ?? ? * Interrupt dispatcher
? ?? ? */
? ?? ?? ?? ? 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)
用“irq, IRQ_MODE, 4”代替宏vector_stub中的“name, mode, correction”,找到了我們中斷處理的入口位置為vector_irq(宏里面的vector_\name)。?
從上面代碼中的注釋可以看出,根據進入中斷前的工作模式不同,程序下一步將跳轉到_irq_usr 、或__irq_svc等位置。我們先選擇__irq_usr作為下一步跟蹤的目標。?
3.3 __irq_usr的實現 arch\arm\kernel\entry-armv.S
__irq_usr:
? ?? ?? ?? ? usr_entry @后面有解釋?
? ?? ?? ?? ? kuser_cmpxchg_check
? ?? ? #ifdef CONFIG_TRACE_IRQFLAGS
? ?? ?? ?? ? bl trace_hardirqs_off
? ?? ? #endif
? ?? ?? ?? ? get_thread_info tsk @獲取當前進程的進程描述符中的成員變量thread_info的地址,并將該地址保存到寄存器tsk等于r9(在entry-header.S中定義)
? ?? ? #ifdef CONFIG_PREEMPT//如果定義了搶占,增加搶占數值?
? ?? ?? ?? ? ldr r8, [tsk, #TI_PREEMPT] @ get preempt count
? ?? ?? ?? ? add r7, r8, #1 @ increment it
? ?? ?? ?? ? str r7, [tsk, #TI_PREEMPT]
? ?? ? #endif
irq_handler @中斷處理,我們最關心的地方,3.4節有實現過程。?
? ?? ?#ifdef CONFIG_PREEMPT
? ?? ?? ?? ? ldr r0, [tsk, #TI_PREEMPT]
? ?? ?? ?? ? str r8, [tsk, #TI_PREEMPT]
? ?? ?? ?? ? teq r0, r7
? ?? ?? ?? ? strne r0, [r0, -r0]
? ?? ?#endif
? ?? ?#ifdef CONFIG_TRACE_IRQFLAGS
? ?? ?? ?? ? bl trace_hardirqs_on
? ?? ? #endif
mov why, #0
b ret_to_user @中斷處理完成,返回中斷產生的位置,3.7節有實現過程?
上面代碼中的usr_entry是一個宏,主要實現了將usr模式下的寄存器、中斷返回地址保存到堆棧中。
.macro usr_entry
? ?? ? sub sp, sp, #S_FRAME_SIZE @ S_FRAME_SIZE的值在arch\arm\kernel\asm-offsets.c
? ?? ? @ 中定義 DEFINE(S_FRAME_SIZE, sizeof(struct pt_regs));實際上等于72
? ???stmib sp, {r1 - r12}
? ?? ?? ?? ? ldmia r0, {r1 - r3}
? ?? ?? ?? ? add r0, sp, #S_PC @ here for interlock avoidance
? ?? ?? ?? ? mov r4, #-1 @ "" "" "" ""
? ?? ?? ?? ? str r1, [sp] @ save the "real" r0 copied
? ?? ?? ?? ? @ from the exception stack
? ???@
? ?? ?? ?? ? @ We are now ready to fill in the remaining blanks on the stack:
? ?? ?? ?? ? @
? ?? ?? ?? ? @ r2 - lr_, already fixed up for correct return/restart
? ?? ?? ?? ? @ r3 - spsr_
? ?? ?? ?? ? @ r4 - orig_r0 (see pt_regs definition in ptrace.h)
? ?? ?? ?? ? @
? ?? ?? ?? ? @ Also, separately save sp_usr and lr_usr
? ?? ?? ?? ? @
? ?? ?? ?? ? stmia r0, {r2 - r4}
? ?? ?? ?? ? stmdb r0, {sp, lr}^
? ?? ?@
? ?? ?? ?? ? @ Enable the alignment trap while in kernel mode
? ?? ?? ?? ? @
alignment_trap r0
? ???@
? ?? ?? ?? ? @ Clear FP to mark the first stack frame
? ?? ?? ?? ? @
? ?? ?? ?? ? zero_fp
? ?? ?? ?? ? .endm
上面的這段代碼主要在填充結構體pt_regs ,這里提到的struct pt_regs,在include/asm/ptrace.h中定義。此時sp指向struct pt_regs。?
? ?? ?struct pt_regs {
? ?? ?? ?? ?? ?? ?? ?? ???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]
3.4 irq_handler的實現過程,arch\arm\kernel\entry-armv.S
??.macro irq_handler
? ?? ?? ?? ???get_irqnr_preamble r5, lr
? ?? ?? ?? ???@在include/asm/arch-s3c2410/entry-macro.s中定義了宏get_irqnr_preamble為空操作,什么都不做
? ?? ?? ?? ???1: get_irqnr_and_base r0, r6, r5, lr @判斷中斷號,通過R0返回,3.5節有實現過程?
? ?? ?? ?? ???movne r1, sp
? ?? ?? ?? ???@
? ?? ?? ?? ???@ routine called with r0 = irq number, r1 = struct pt_regs *
? ?? ?? ?? ???@
? ?? ?? ?? ???adrne lr, 1b
? ?? ?? ?? ???bne asm_do_IRQ @進入中斷處理。?
? ?? ? ……?
? ?? ?? ?? ???.endm
3.5 get_irqnr_and_base中斷號判斷過程,include/asm/arch-s3c2410/entry-macro.s
.macro get_irqnr_and_base, irqnr, irqstat, base, tmp
? ?? ?? ?? ???mov \base, #S3C24XX_VA_IRQ
? ?? ?? ?? ???@@ try the interrupt offset register, since it is there
? ?? ?? ?? ???ldr \irqstat, [ \base, #INTPND ]
? ?? ?? ?? ???teq \irqstat, #0
? ?? ?? ?? ???beq 1002f
? ?? ?? ?? ???ldr \irqnr, [ \base, #INTOFFSET ] @通過判斷INTOFFSET寄存器得到中斷位置?
? ?? ?? ?? ???mov \tmp, #1
? ?? ?? ?? ???tst \irqstat, \tmp, lsl \irqnr
? ?? ?? ?? ???bne 1001f
? ?? ?? ?? ???@@ the number specified is not a valid irq, so try
? ?? ?? ?? ???@@ and work it out for ourselves
? ?? ?? ?? ???mov \irqnr, #0 @@ start here
? ?? ?? ?? ???@@ work out which irq (if any) we got
? ?? ?? ?? ???movs \tmp, \irqstat, lsl#16
? ?? ?? ?? ???addeq \irqnr, \irqnr, #16
? ?? ?? ?? ???moveq \irqstat, \irqstat, lsr#16
? ?? ?? ?? ???tst \irqstat, #0xff
? ?? ?? ?? ???addeq \irqnr, \irqnr, #8
? ?? ?? ?? ???moveq \irqstat, \irqstat, lsr#8
? ?? ?? ?? ???tst \irqstat, #0xf
? ?? ?? ?? ???addeq \irqnr, \irqnr, #4
? ?? ?? ?? ???moveq \irqstat, \irqstat, lsr#4
? ?? ?? ?? ???tst \irqstat, #0x3
? ?? ?? ?? ???addeq \irqnr, \irqnr, #2
? ?? ?? ?? ???moveq \irqstat, \irqstat, lsr#2
? ?? ?? ?? ???tst \irqstat, #0x1
? ?? ?? ?? ???addeq \irqnr, \irqnr, #1
? ?? ?? ?? ???@@ we have the value
? ?? ?1001:
? ?? ? adds \irqnr, \irqnr, #IRQ_EINT0
@加上中斷號的基準數值,得到最終的中斷號,注意:此時沒有考慮子中斷的具體情況,(子中斷的問題后面會有講解)。IRQ_EINT0在
include/asm/arch-s3c2410/irqs.h中定義.從這里可以看出,中斷號的具體值是有平臺相關的代碼決定的,和硬件中斷掛起寄存
器中的中斷號是不等的。?
1002:
? ?? ?? ?? ???@@ exit here, Z flag unset if IRQ
? ?? ?? ? .endm
3.6 asm_do_IRQ實現過程,arch/arm/kernel/irq.c?
asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
? ?? ???{
? ?? ?? ?? ???struct pt_regs *old_regs = set_irq_regs(regs);
? ?? ?? ?? ???struct irq_desc *desc = irq_desc + irq;//根據中斷號找到對應的irq_desc
? ?? ?? ?? ???/*
? ?? ?? ?? ???* Some hardware gives randomly wrong interrupts. Rather
? ?? ?? ?? ???* than crashing, do something sensible.
? ?? ?? ?? ???*/
? ?? ?? ?? ???if (irq >= NR_IRQS)
? ?? ?? ?? ???desc = &bad_irq_desc;
? ?? ?? ?? ???irq_enter();//沒做什么特別的工作,可以跳過不看?
? ?? ?? ?? ???desc_handle_irq(irq, desc);// 根據中斷號和desc進入中斷處理?
? ?? ?? ?? ???/* AT91 specific workaround */
? ?? ?? ?? ???irq_finish(irq);
? ?? ?? ?? ???irq_exit();
? ?? ?? ?? ???set_irq_regs(old_regs);
? ?? ? }
static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc)
? ?? ?? ?{
? ?? ?? ?? ?? ?? ?? ?desc->handle_irq(irq, desc);//中斷處理?
? ?? ?? ? }
上述asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)使用了asmlinkage標識。那么這個標識的含義如何理解呢??
該符號定義在kernel/include/ linux /linkage.h中,如下所示:?
#include //各個具體處理器在此文件中定義asmlinkage
? ?? ???#ifdef __cplusplus
? ?? ???#define CPP_ASMLINKAGE extern "C"
? ?? ???#else
? ?? ???#define CPP_ASMLINKAGE
? ?? ???#endif
#ifndef asmlinkage//如果以前沒有定義asmlinkage
? ?? ???#define asmlinkage CPP_ASMLINKAGE
? ?? ???#endif
對于ARM處理器的,沒有定義asmlinkage,所以沒有意義(不要以為參數是從堆棧傳遞的,對于ARM平臺來說還是符合ATPCS過程調用標準,通過寄存器傳遞的)。?
但對于X86處理器的中是這樣定義的:
#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))
表示函數的參數傳遞是通過堆棧完成的。
3.7 描述3.3節中的ret_to_user 中斷返回過程,/arch/arm/kernel/entry-common.S
ENTRY(ret_to_user)
? ?? ???ret_slow_syscall:
? ?? ?? ?? ???disable_irq @ disable interrupts
? ?? ?? ?? ???ldr r1, [tsk, #TI_FLAGS]
? ?? ?? ?? ???tst r1, #_TIF_WORK_MASK
? ?? ?? ?? ???bne work_pending
? ?? ???no_work_pending:
? ?? ?? ?? ???/* perform architecture specific actions before user return */
? ?? ?? ?? ???arch_ret_to_user r1, lr
? ?? ?@ slow_restore_user_regs
? ?? ?? ?? ???ldr r1, [sp, #S_PSR] @ get calling cpsr
? ?? ?? ?? ???ldr lr, [sp, #S_PC]! @ get pc
? ?? ?? ?? ???msr spsr_cxsf, r1 @ save in spsr_svc
? ?? ?? ?? ???ldmdb sp, {r0 - lr}^ @ get calling r0 - lr
? ?? ?? ?? ???mov r0, r0
? ?? ?? ?? ???add sp, sp, #S_FRAME_SIZE - S_PC
? ?? ?? ?? ???movs pc, lr @ return & move spsr_svc into cpsr
第三章主要跟蹤了從中斷發生到調用到對應中斷號的desc->handle_irq(irq, desc)中斷函數的過程。后面的章節還會繼續講解后面的內容。
一、中斷注冊方法
在 linux 內核中用于申請中斷的函數是request_irq(),函數原型在Kernel/irq/manage.c中定義:
int request_irq(unsigned int irq, irq_handler_t handler,
? ?? ?? ?? ?? ?? ?? ?? ? unsigned long irqflags, const char *devname, void *dev_id)
irq是要申請的硬件中斷號。
handler是向系統注冊的中斷處理函數,是一個回調函數,中斷發生時,系統調用這個函數,dev_id參數將被傳遞給它。
irqflags是中斷處理的屬性,若設置了IRQF_DISABLED
(老版本中的SA_INTERRUPT,本版zhon已經不支持了),則表示中斷處理程序是快速處理程序,快速處理程序被調用時屏蔽所有中斷,慢速處理程
序不屏蔽;若設置了IRQF_SHARED
(老版本中的SA_SHIRQ),則表示多個設備共享中斷,若設置了IRQF_SAMPLE_RANDOM(老版本中的
SA_SAMPLE_RANDOM),表示對系統熵有貢獻,對系統獲取隨機數有好處。(這幾個flag是可以通過或的方式同時使用的)
dev_id在中斷共享時會用到,一般設置為這個設備的設備結構體或者NULL。
devname設置中斷名稱,在cat /proc/interrupts中可以看到此名稱。
request_irq()返回0表示成功,返回-INVAL表示中斷號無效或處理函數指針為NULL,返回-EBUSY表示中斷已經被占用且不能共享。
關于中斷注冊的例子,大家可在內核中搜索下request_irq。
在編寫驅動的過程中,比較容易產生疑惑的地方是:
1、中斷向量表在什么位置?是如何建立的?
? ?? ???2、從中斷開始,系統是怎樣執行到我自己注冊的函數的?
? ?? ???3、中斷號是如何確定的?對于硬件上有子中斷的中斷號如何確定?
? ?? ???4、中斷共享是怎么回事,dev_id的作用是?
本文以2.6.26內核和S3C2410處理器為例,為大家講解這幾個問題。
二、異常向量表的建立
在ARM V4及V4T以后的大部分處理器中,中斷向量表的位置可以有兩個位置:一個是0,另一個是0xffff0000。可以通過CP15協處理器c1寄存器中V位(bit[13])控制。V和中斷向量表的對應關系如下:
V=0? ?? ???~? ?? ???0x00000000~0x0000001C
? ?? ???V=1? ?? ???~? ?? ???0xffff0000~0xffff001C
arch/arm/mm/proc-arm920.S中
.section ".text.init", #alloc, #execinstr
? ?? ???__arm920_setup:
? ?? ???…… orr? ?? ? r0, r0, #0x2100? ?? ?? ?? ???@ ..1. ...1 ..11 ...1
//bit13=1 中斷向量表基址為0xFFFF0000。R0的值將被付給CP15的C1.
在 linux 中,向量表建立的函數為:
init/main.c->start_kernel()->trap_init()
void __init trap_init(void)
? ?? ???{?
? ?? ?? ?? ?? ???unsigned long vectors = CONFIG_VECTORS_BASE;
? ?? ?? ?? ?? ???……?
? ?? ?? ?? ?? ???memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
? ?? ?? ?? ?? ? memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
? ?? ?? ?? ?? ???....
? ?? ???}
在2.6.26內核中CONFIG_VECTORS_BASE最初是在各個平臺的配置文件中設定的,如:
arch/arm/configs/s3c2410_defconfig中
CONFIG_VECTORS_BASE=0xffff0000
__vectors_end 至 __vectors_start之間為異常向量表。
位于arch/arm/kernel/entry-armv.S
.globl __vectors_start
__vectors_start:
? ?? ???swi SYS_ERROR0:
? ?? ???b vector_und + stubs_offset //復位異常:
? ?? ???ldr pc, .LCvswi + stubs_offset? ?? ???//未定義指令異常:
? ?? ???b vector_pabt + stubs_offset? ?? ???//軟件中斷異常:
? ?? ???b vector_dabt + stubs_offset? ?? ???//數據異常:
? ?? ???b vector_addrexcptn + stubs_offset? ?? ???//保留:
? ?? ???b vector_irq + stubs_offset? ?? ???//普通中斷異常:
? ?? ???b vector_fiq + stubs_offset? ?? ???//快速中斷異常:
? ?? ???.globl __vectors_end:
__vectors_end:
__stubs_end 至 __stubs_start之間是異常處理的位置。也位于文件arch/arm/kernel/entry-armv.S中。vector_und、vector_pabt、vector_irq、vector_fiq都在它們中間。
stubs_offset值如下:
.equ stubs_offset, __vectors_start + 0x200 - __stubs_start
stubs_offset是如何確定的呢?(引用網絡上的一段比較詳細的解釋)
當匯編器看到B指令后會把要跳轉的標簽轉化為相對于當前PC的偏移量(±32M)寫入指令碼。從上面的代碼可以看到中斷向量表和stubs都發生了
代碼搬移,所以如果中斷向量表中仍然寫成b
vector_irq,那么實際執行的時候就無法跳轉到搬移后的vector_irq處,因為指令碼里寫的是原來的偏移量,所以需要把指令碼中的偏移量寫
成搬移后的。我們把搬移前的中斷向量表中的irq入口地址記irq_PC,它在中斷向量表的偏移量就是irq_PC-vectors_start,
vector_irq在stubs中的偏移量是vector_irq-stubs_start,這兩個偏移量在搬移前后是不變的。搬移后
vectors_start在0xffff0000處,而stubs_start在0xffff0200處,所以搬移后的vector_irq相對于中斷
向量中的中斷入口地址的偏移量就是,200+vector_irq在stubs中的偏移量再減去中斷入口在向量表中的偏移量,即200+
vector_irq-stubs_start-irq_PC+vectors_start = (vector_irq-irq_PC) +
vectors_start+200-stubs_start,對于括號內的值實際上就是中斷向量表中寫的vector_irq,減去irq_PC是由匯
編器完成的,而后面的
vectors_start+200-stubs_start就應該是stubs_offset,實際上在entry-armv.S中也是這樣定義的
三、中斷處理過程?
這一節將以S3C2410為例,描述 linux -2.6.26內核中,從中斷開始,中斷是如何一步一步執行到我們注冊函數的。?
3.1 中斷向量表 arch\arm\kernel\entry-armv.S
__vectors_start:
? ?? ?? ?? ? swi SYS_ERROR0
? ?? ?? ?? ? b? ? vector_und + stubs_offset
? ?? ?? ?? ? ldr pc, .LCvswi + stubs_offset
? ?? ?? ?? ? b? ? vector_pabt + stubs_offset
? ?? ?? ?? ? b? ? vector_dabt + stubs_offset
? ?? ?? ?? ? b? ? vector_addrexcptn + stubs_offset
? ?? ?? ?? ? b? ? vector_irq + stubs_offset?
? ?? ?? ?? ? b? ? vector_fiq + stubs_offset
? ?? ?? ?? ? .globl? ?__vectors_end
? ?? ???__vectors_end:
中斷發生后,跳轉到b vector_irq + stubs_offset的位置執行。注意現在的向量表的初始位置是0xffff0000。?
3.2 中斷跳轉的入口位置 arch\arm\kernel\entry-armv.S
? ?? ?.globl? ?__stubs_start
? ? __stubs_start:
? ???/*
? ???* Interrupt dispatcher
? ???*/
? ?? ?? ?? ? vector_stub? ? irq, IRQ_MODE, 4 @IRQ_MODE在include\asm\ptrace.h中定義:0x12
? ?? ?? ?? ? .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
上面代碼中vector_stub宏的定義為:?
.macro vector_stub, name, mode, correction=0
? ?? ?? ?? ? .align 5
? ?? ???vector_\name:
? ?? ?? ?? ? .if \correction
? ?? ?? ?? ? sub lr, lr, #\correction
? ?? ?? ?? ? .endif
? ?? ?? ?? ? @
? ?? ?? ?? ? @ Save r0, lr_ (parent PC) and spsr_
? ?? ?? ?? ? @ (parent CPSR)
? ?? ?? ?? ? @
? ?? ?? ?? ? stmia sp, {r0, lr} @ save r0, lr
? ?? ?? ?? ? mrs lr, spsr
? ?? ?? ?? ? str lr, [sp, #8] @ save spsr
? ?? ?? ?? ? @
? ?? ?? ?? ? @ Prepare for SVC32 mode. IRQs remain disabled.
? ?? ?? ?? ? @
? ?? ?? ?? ? mrs r0, cpsr
? ?? ?? ?? ? eor r0, r0, #(\mode ^ SVC_MODE)
? ?? ?? ?? ? msr spsr_cxsf, r0 @為后面進入svc模式做準備?
@
? ?? ?? ?? ? @ the branch table must immediately follow this code
? ?? ?? ?? ???@
? ?? ?? ?? ? and lr, lr, #0x0f @進入中斷前的mode的后4位?
? ?? ?? ?? ? @#define USR_MODE 0x00000010
? ?? ?? ?? ? @#define FIQ_MODE 0x00000011
? ?? ?? ?? ? @#define IRQ_MODE 0x00000012
? ?? ?? ?? ? @#define SVC_MODE 0x00000013
? ?? ?? ?? ? @#define ABT_MODE 0x00000017
? ?? ?? ?? ? @#define UND_MODE 0x0000001b
? ?? ?? ?? ? @#define SYSTEM_MODE 0x0000001f
? ?? ?? ?? ? mov r0, sp
? ?? ?? ?? ? ldr lr, [pc, lr, lsl #2] @如果進入中斷前是usr,則取出PC+4*0的內容,即__irq_usr @如果進入中斷前是svc,則取出PC+4*3的內容,即__irq_svc
? ?? ?? ?? ? movs pc, lr @ 當指令的目標寄存器是PC,且指令以S結束,則它會把@ spsr的值恢復給cpsr branch to handler in SVC mode
? ?? ?? ?? ? .endm
? ?? ?? ?? ? .globl __stubs_start
? ?? ? __stubs_start:
? ?? ? /*
? ?? ? * Interrupt dispatcher
? ?? ? */
? ?? ?? ?? ? 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)
用“irq, IRQ_MODE, 4”代替宏vector_stub中的“name, mode, correction”,找到了我們中斷處理的入口位置為vector_irq(宏里面的vector_\name)。?
從上面代碼中的注釋可以看出,根據進入中斷前的工作模式不同,程序下一步將跳轉到_irq_usr 、或__irq_svc等位置。我們先選擇__irq_usr作為下一步跟蹤的目標。?
3.3 __irq_usr的實現 arch\arm\kernel\entry-armv.S
__irq_usr:
? ?? ?? ?? ? usr_entry @后面有解釋?
? ?? ?? ?? ? kuser_cmpxchg_check
? ?? ? #ifdef CONFIG_TRACE_IRQFLAGS
? ?? ?? ?? ? bl trace_hardirqs_off
? ?? ? #endif
? ?? ?? ?? ? get_thread_info tsk @獲取當前進程的進程描述符中的成員變量thread_info的地址,并將該地址保存到寄存器tsk等于r9(在entry-header.S中定義)
? ?? ? #ifdef CONFIG_PREEMPT//如果定義了搶占,增加搶占數值?
? ?? ?? ?? ? ldr r8, [tsk, #TI_PREEMPT] @ get preempt count
? ?? ?? ?? ? add r7, r8, #1 @ increment it
? ?? ?? ?? ? str r7, [tsk, #TI_PREEMPT]
? ?? ? #endif
irq_handler @中斷處理,我們最關心的地方,3.4節有實現過程。?
? ?? ?#ifdef CONFIG_PREEMPT
? ?? ?? ?? ? ldr r0, [tsk, #TI_PREEMPT]
? ?? ?? ?? ? str r8, [tsk, #TI_PREEMPT]
? ?? ?? ?? ? teq r0, r7
? ?? ?? ?? ? strne r0, [r0, -r0]
? ?? ?#endif
? ?? ?#ifdef CONFIG_TRACE_IRQFLAGS
? ?? ?? ?? ? bl trace_hardirqs_on
? ?? ? #endif
mov why, #0
b ret_to_user @中斷處理完成,返回中斷產生的位置,3.7節有實現過程?
上面代碼中的usr_entry是一個宏,主要實現了將usr模式下的寄存器、中斷返回地址保存到堆棧中。
.macro usr_entry
? ?? ? sub sp, sp, #S_FRAME_SIZE @ S_FRAME_SIZE的值在arch\arm\kernel\asm-offsets.c
? ?? ? @ 中定義 DEFINE(S_FRAME_SIZE, sizeof(struct pt_regs));實際上等于72
? ???stmib sp, {r1 - r12}
? ?? ?? ?? ? ldmia r0, {r1 - r3}
? ?? ?? ?? ? add r0, sp, #S_PC @ here for interlock avoidance
? ?? ?? ?? ? mov r4, #-1 @ "" "" "" ""
? ?? ?? ?? ? str r1, [sp] @ save the "real" r0 copied
? ?? ?? ?? ? @ from the exception stack
? ???@
? ?? ?? ?? ? @ We are now ready to fill in the remaining blanks on the stack:
? ?? ?? ?? ? @
? ?? ?? ?? ? @ r2 - lr_, already fixed up for correct return/restart
? ?? ?? ?? ? @ r3 - spsr_
? ?? ?? ?? ? @ r4 - orig_r0 (see pt_regs definition in ptrace.h)
? ?? ?? ?? ? @
? ?? ?? ?? ? @ Also, separately save sp_usr and lr_usr
? ?? ?? ?? ? @
? ?? ?? ?? ? stmia r0, {r2 - r4}
? ?? ?? ?? ? stmdb r0, {sp, lr}^
? ?? ?@
? ?? ?? ?? ? @ Enable the alignment trap while in kernel mode
? ?? ?? ?? ? @
alignment_trap r0
? ???@
? ?? ?? ?? ? @ Clear FP to mark the first stack frame
? ?? ?? ?? ? @
? ?? ?? ?? ? zero_fp
? ?? ?? ?? ? .endm
上面的這段代碼主要在填充結構體pt_regs ,這里提到的struct pt_regs,在include/asm/ptrace.h中定義。此時sp指向struct pt_regs。?
? ?? ?struct pt_regs {
? ?? ?? ?? ?? ?? ?? ?? ???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]
3.4 irq_handler的實現過程,arch\arm\kernel\entry-armv.S
??.macro irq_handler
? ?? ?? ?? ???get_irqnr_preamble r5, lr
? ?? ?? ?? ???@在include/asm/arch-s3c2410/entry-macro.s中定義了宏get_irqnr_preamble為空操作,什么都不做
? ?? ?? ?? ???1: get_irqnr_and_base r0, r6, r5, lr @判斷中斷號,通過R0返回,3.5節有實現過程?
? ?? ?? ?? ???movne r1, sp
? ?? ?? ?? ???@
? ?? ?? ?? ???@ routine called with r0 = irq number, r1 = struct pt_regs *
? ?? ?? ?? ???@
? ?? ?? ?? ???adrne lr, 1b
? ?? ?? ?? ???bne asm_do_IRQ @進入中斷處理。?
? ?? ? ……?
? ?? ?? ?? ???.endm
3.5 get_irqnr_and_base中斷號判斷過程,include/asm/arch-s3c2410/entry-macro.s
.macro get_irqnr_and_base, irqnr, irqstat, base, tmp
? ?? ?? ?? ???mov \base, #S3C24XX_VA_IRQ
? ?? ?? ?? ???@@ try the interrupt offset register, since it is there
? ?? ?? ?? ???ldr \irqstat, [ \base, #INTPND ]
? ?? ?? ?? ???teq \irqstat, #0
? ?? ?? ?? ???beq 1002f
? ?? ?? ?? ???ldr \irqnr, [ \base, #INTOFFSET ] @通過判斷INTOFFSET寄存器得到中斷位置?
? ?? ?? ?? ???mov \tmp, #1
? ?? ?? ?? ???tst \irqstat, \tmp, lsl \irqnr
? ?? ?? ?? ???bne 1001f
? ?? ?? ?? ???@@ the number specified is not a valid irq, so try
? ?? ?? ?? ???@@ and work it out for ourselves
? ?? ?? ?? ???mov \irqnr, #0 @@ start here
? ?? ?? ?? ???@@ work out which irq (if any) we got
? ?? ?? ?? ???movs \tmp, \irqstat, lsl#16
? ?? ?? ?? ???addeq \irqnr, \irqnr, #16
? ?? ?? ?? ???moveq \irqstat, \irqstat, lsr#16
? ?? ?? ?? ???tst \irqstat, #0xff
? ?? ?? ?? ???addeq \irqnr, \irqnr, #8
? ?? ?? ?? ???moveq \irqstat, \irqstat, lsr#8
? ?? ?? ?? ???tst \irqstat, #0xf
? ?? ?? ?? ???addeq \irqnr, \irqnr, #4
? ?? ?? ?? ???moveq \irqstat, \irqstat, lsr#4
? ?? ?? ?? ???tst \irqstat, #0x3
? ?? ?? ?? ???addeq \irqnr, \irqnr, #2
? ?? ?? ?? ???moveq \irqstat, \irqstat, lsr#2
? ?? ?? ?? ???tst \irqstat, #0x1
? ?? ?? ?? ???addeq \irqnr, \irqnr, #1
? ?? ?? ?? ???@@ we have the value
? ?? ?1001:
? ?? ? adds \irqnr, \irqnr, #IRQ_EINT0
@加上中斷號的基準數值,得到最終的中斷號,注意:此時沒有考慮子中斷的具體情況,(子中斷的問題后面會有講解)。IRQ_EINT0在
include/asm/arch-s3c2410/irqs.h中定義.從這里可以看出,中斷號的具體值是有平臺相關的代碼決定的,和硬件中斷掛起寄存
器中的中斷號是不等的。?
1002:
? ?? ?? ?? ???@@ exit here, Z flag unset if IRQ
? ?? ?? ? .endm
3.6 asm_do_IRQ實現過程,arch/arm/kernel/irq.c?
asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
? ?? ???{
? ?? ?? ?? ???struct pt_regs *old_regs = set_irq_regs(regs);
? ?? ?? ?? ???struct irq_desc *desc = irq_desc + irq;//根據中斷號找到對應的irq_desc
? ?? ?? ?? ???/*
? ?? ?? ?? ???* Some hardware gives randomly wrong interrupts. Rather
? ?? ?? ?? ???* than crashing, do something sensible.
? ?? ?? ?? ???*/
? ?? ?? ?? ???if (irq >= NR_IRQS)
? ?? ?? ?? ???desc = &bad_irq_desc;
? ?? ?? ?? ???irq_enter();//沒做什么特別的工作,可以跳過不看?
? ?? ?? ?? ???desc_handle_irq(irq, desc);// 根據中斷號和desc進入中斷處理?
? ?? ?? ?? ???/* AT91 specific workaround */
? ?? ?? ?? ???irq_finish(irq);
? ?? ?? ?? ???irq_exit();
? ?? ?? ?? ???set_irq_regs(old_regs);
? ?? ? }
static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc)
? ?? ?? ?{
? ?? ?? ?? ?? ?? ?? ?desc->handle_irq(irq, desc);//中斷處理?
? ?? ?? ? }
上述asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)使用了asmlinkage標識。那么這個標識的含義如何理解呢??
該符號定義在kernel/include/ linux /linkage.h中,如下所示:?
#include //各個具體處理器在此文件中定義asmlinkage
? ?? ???#ifdef __cplusplus
? ?? ???#define CPP_ASMLINKAGE extern "C"
? ?? ???#else
? ?? ???#define CPP_ASMLINKAGE
? ?? ???#endif
#ifndef asmlinkage//如果以前沒有定義asmlinkage
? ?? ???#define asmlinkage CPP_ASMLINKAGE
? ?? ???#endif
對于ARM處理器的,沒有定義asmlinkage,所以沒有意義(不要以為參數是從堆棧傳遞的,對于ARM平臺來說還是符合ATPCS過程調用標準,通過寄存器傳遞的)。?
但對于X86處理器的中是這樣定義的:
#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))
表示函數的參數傳遞是通過堆棧完成的。
3.7 描述3.3節中的ret_to_user 中斷返回過程,/arch/arm/kernel/entry-common.S
ENTRY(ret_to_user)
? ?? ???ret_slow_syscall:
? ?? ?? ?? ???disable_irq @ disable interrupts
? ?? ?? ?? ???ldr r1, [tsk, #TI_FLAGS]
? ?? ?? ?? ???tst r1, #_TIF_WORK_MASK
? ?? ?? ?? ???bne work_pending
? ?? ???no_work_pending:
? ?? ?? ?? ???/* perform architecture specific actions before user return */
? ?? ?? ?? ???arch_ret_to_user r1, lr
? ?? ?@ slow_restore_user_regs
? ?? ?? ?? ???ldr r1, [sp, #S_PSR] @ get calling cpsr
? ?? ?? ?? ???ldr lr, [sp, #S_PC]! @ get pc
? ?? ?? ?? ???msr spsr_cxsf, r1 @ save in spsr_svc
? ?? ?? ?? ???ldmdb sp, {r0 - lr}^ @ get calling r0 - lr
? ?? ?? ?? ???mov r0, r0
? ?? ?? ?? ???add sp, sp, #S_FRAME_SIZE - S_PC
? ?? ?? ?? ???movs pc, lr @ return & move spsr_svc into cpsr
第三章主要跟蹤了從中斷發生到調用到對應中斷號的desc->handle_irq(irq, desc)中斷函數的過程。后面的章節還會繼續講解后面的內容。
總結
以上是生活随笔為你收集整理的Linux中断处理流程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: S3C2440的LCD编程
- 下一篇: 整理了一下linux/ubuntu里面卸