- 통합: Exception -5- (Extable) | 문c
<kernel v5.4>
Exception Table(extable)에는 프로세스 주소 공간(process address space)을 access하는 커널 API를 사용한 코드의 주소와 해당 API의 예외 처리 코드의 주소가 함께 담겨 있다.
프로세스가 특정 주소를 access 하다가 exception이 발생하는 여러 fault(ARM32에서는 fsr_info[], ARM64에서는 fault_info[])별 처리 핸들러 함수를 실행시킬 때 extable을 검사하여 에러 진원지와 동일한 엔트리를 찾은 경우 해당 fixup 코드를 실행시킨다.
다음 함수들에서 .fixup 섹션과 __ex_table 섹션을 사용한다.
Main Exception Table이 정렬되어 있지 않은 경우 정렬한다.
kernel/extable.c
/* Sort the kernel's built-in exception table */ void __init sort_main_extable(void) { if (main_extable_sort_needed && __stop___ex_table > __start___ex_table) { pr_notice("Sorting __ex_table...\n"); sort_extable(__start___ex_table, __stop___ex_table); } }
Main 커널에서 사용하는 Exception table을 정렬되어 있지 않은 경우 정렬한다.
kernel/extable.c
/* Cleared by build time tools if the table is already sorted. */ u32 __initdata __visible main_extable_sort_needed = 1;
kernel/extable.c
void sort_extable(struct exception_table_entry *start, struct exception_table_entry *finish) { sort(start, finish - start, sizeof(struct exception_table_entry), cmp_ex_sort, swap_ex); }
Exception table의 시작 부터 끝 까지 정렬한다.
lib/extable.c
/* * The exception table needs to be sorted so that the binary * search that we use to find entries in it works properly. * This is used both for the kernel exception table and for * the exception tables of modules that get loaded. */
static int cmp_ex(const void *a, const void *b) { const struct exception_table_entry *x = a, *y = b; /* avoid overflow */ if (ex_to_insn(x) > ex_to_insn(y)) return 1; if (ex_to_insn(x) < ex_to_insn(y)) return -1; return 0; }
a와 b를 비교한다.
lib/extable.c
static void swap_ex(void *a, void *b, int size) { struct exception_table_entry *x = a, *y = b, tmp; int delta = b - a; tmp = *x; x->insn = y->insn + delta; y->insn = tmp.insn - delta; #ifdef swap_ex_entry_fixup swap_ex_entry_fixup(x, y, tmp, delta); #else x->fixup = y->fixup + delta; y->fixup = tmp.fixup - delta; #endif }
a와 b를 치환한다.
arch/arm/kernel/vmlinux.lds.S
#ifdef CONFIG_DEBUG_RODATA . = ALIGN(1<<SECTION_SHIFT); #endif RO_DATA(PAGE_SIZE) . = ALIGN(4); __ex_table : AT(ADDR(__ex_table) - LOAD_OFFSET) { __start___ex_table = .; #ifdef CONFIG_MMU *(__ex_table) #endif __stop___ex_table = .; }
__ex_table은 .rodata 섹션 뒤에 위치한다.
arch/arm64/kernel/vmlinux.lds.S
.text : { /* Real text segment */ _stext = .; /* Text and read-only data */ __exception_text_start = .; *(.exception.text) __exception_text_end = .; IRQENTRY_TEXT SOFTIRQENTRY_TEXT ENTRY_TEXT TEXT_TEXT SCHED_TEXT CPUIDLE_TEXT LOCK_TEXT KPROBES_TEXT HYPERVISOR_TEXT IDMAP_TEXT HIBERNATE_TEXT TRAMP_TEXT *(.fixup) *(.gnu.warning) . = ALIGN(16); *(.got) /* Global offset table */ } . = ALIGN(SEGMENT_ALIGN); _etext = .; /* End of text section */ RO_DATA(PAGE_SIZE) /* everything from this point to */ EXCEPTION_TABLE(8) /* __init_begin will be marked RO NX */ NOTES
__ex_table은 EXCEPTION_TABLE() 매크로로 정의되고, .rodata 섹션 뒤에 위치한다.
include/asm-generic/vmlinux.lds.h
/* * Exception table */
define EXCEPTION_TABLE(align) \ . = ALIGN(align); \ __ex_table : AT(ADDR(__ex_table) - LOAD_OFFSET) { \ __start___ex_table = .; \ KEEP(*(__ex_table)) \ __stop___ex_table = .; \ }
다음 그림은 커널 코드가 process space에 있는 한 페이지에 접근하다 exception이 발생한 경우를 보여준다.
kernel/extable.c
/* Given an address, look for it in the exception tables. */ const struct exception_table_entry *search_exception_tables(unsigned long addr) { const struct exception_table_entry *e; e = search_kernel_exception_table(addr); if (!e) e = search_module_extables(addr); return e; }
Main exception table과 전체 모듈에 있는 exception table의 insn 가상주소가 인수 addr와 같은 경우 해당 엔트리를 반환하고 검색되지 않는 경우 null을 리턴한다.
lib/extable.c
/* Given an address, look for it in the kernel exception table */ const struct exception_table_entry *search_kernel_exception_table(unsigned long addr) { return search_extable(__start___ex_table, __stop___ex_table - __start___ex_table, addr); }
메인 커널에 위치한 exception table을 대상으로 가상 주소 @addr가 검색되는 경우 해당 엔트리를 반환한다. 그 외의 경우 null을 반환한다.
lib/extable.c
/* * Search one exception table for an entry corresponding to the * given instruction address, and return the address of the entry, * or NULL if none is found. * We use a binary search, and thus we assume that the table is * already sorted. */
const struct exception_table_entry * search_extable(const struct exception_table_entry *base, const size_t num, unsigned long value) { return bsearch(&value, base, num, sizeof(struct exception_table_entry), cmp_ex_search); }
@base에 위치한 exception 테이블에서 가상 주소 @value를 검색하되 최대 @num 엔트리 수만큼 검색한다.
lib/bsearch.c
/* * bsearch - binary search an array of elements * @key: pointer to item being searched for * @base: pointer to first element to search * @num: number of elements * @size: size of each element * @cmp: pointer to comparison function * * This function does a binary search on the given array. The * contents of the array should already be in ascending sorted order * under the provided comparison function. * * Note that the key need not have the same type as the elements in * the array, e.g. key could be a string and the comparison function * could compare the string with the struct's name field. However, if * the key and elements in the array are of the same type, you can use * the same comparison function for both sort() and bsearch(). */
void *bsearch(const void *key, const void *base, size_t num, size_t size, int (*cmp)(const void *key, const void *elt)) { const char *pivot; int result; while (num > 0) { pivot = base + (num >> 1) * size; result = cmp(key, pivot); if (result == 0) return (void *)pivot; if (result > 0) { base = pivot + size; num--; } num >>= 1; } return NULL; } EXPORT_SYMBOL(bsearch); NOKPROBE_SYMBOL(bsearch);
바이너리 검색을 수행한다.
kernel/module.c
/* Given an address, look for it in the module exception tables. */ const struct exception_table_entry *search_module_extables(unsigned long addr) { const struct exception_table_entry *e = NULL; struct module *mod; preempt_disable(); mod = __module_address(addr); if (!mod) goto out; if (!mod->num_exentries) goto out; e = search_extable(mod->extable, mod->num_exentries, addr); out: preempt_enable(); /* * Now, if we found one, we are running inside it now, hence * we cannot unload the module, hence no refcnt needed. */ return e; }
전체 모듈을 루프를 돌며 각각의 모듈에 있는 exception table의 insn 가상주소가 인수 addr와 같은 경우 해당 엔트리를 반환하고 검색되지 않는 경우 null을 리턴한다.
include/asm-generic/extable.h
/* * The exception table consists of pairs of addresses: the first is the * address of an instruction that is allowed to fault, and the second is * the address at which the program should continue. No registers are * modified, so it is entirely up to the continuation code to figure out * what to do. * * All the routines below use bits of fixup code that are out of line * with the main instruction path. This means when everything is well, * we don't even have to jump over them. Further, they do not intrude * on our cache or tlb entries. */ struct exception_table_entry { unsigned long insn, fixup; };
arch/arm64/include/asm/extable.h
struct exception_table_entry { int insn, fixup; };
ARM64의 경우 generic 코드와 다르게 unsigned long 대신 int를 사용하는 것을 알 수 있다.
arch/arm/mm/fsr-2level.c
static struct fsr_info fsr_info[] = { /* * The following are the standard ARMv3 and ARMv4 aborts. ARMv5 * defines these to be "precise" aborts. */ { do_bad, SIGSEGV, 0, "vector exception" }, { do_bad, SIGBUS, BUS_ADRALN, "alignment exception" }, { do_bad, SIGKILL, 0, "terminal exception" }, { do_bad, SIGBUS, BUS_ADRALN, "alignment exception" }, { do_bad, SIGBUS, 0, "external abort on linefetch" }, { do_translation_fault, SIGSEGV, SEGV_MAPERR, "section translation fault" }, { do_bad, SIGBUS, 0, "external abort on linefetch" }, { do_page_fault, SIGSEGV, SEGV_MAPERR, "page translation fault" }, { do_bad, SIGBUS, 0, "external abort on non-linefetch" }, { do_bad, SIGSEGV, SEGV_ACCERR, "section domain fault" }, { do_bad, SIGBUS, 0, "external abort on non-linefetch" }, { do_bad, SIGSEGV, SEGV_ACCERR, "page domain fault" }, { do_bad, SIGBUS, 0, "external abort on translation" }, { do_sect_fault, SIGSEGV, SEGV_ACCERR, "section permission fault" }, { do_bad, SIGBUS, 0, "external abort on translation" }, { do_page_fault, SIGSEGV, SEGV_ACCERR, "page permission fault" }, /* * The following are "imprecise" aborts, which are signalled by bit * 10 of the FSR, and may not be recoverable. These are only * supported if the CPU abort handler supports bit 10. */ { do_bad, SIGBUS, 0, "unknown 16" }, { do_bad, SIGBUS, 0, "unknown 17" }, { do_bad, SIGBUS, 0, "unknown 18" }, { do_bad, SIGBUS, 0, "unknown 19" }, { do_bad, SIGBUS, 0, "lock abort" }, /* xscale */ { do_bad, SIGBUS, 0, "unknown 21" }, { do_bad, SIGBUS, BUS_OBJERR, "imprecise external abort" }, /* xscale */ { do_bad, SIGBUS, 0, "unknown 23" }, { do_bad, SIGBUS, 0, "dcache parity error" }, /* xscale */ { do_bad, SIGBUS, 0, "unknown 25" }, { do_bad, SIGBUS, 0, "unknown 26" }, { do_bad, SIGBUS, 0, "unknown 27" }, { do_bad, SIGBUS, 0, "unknown 28" }, { do_bad, SIGBUS, 0, "unknown 29" }, { do_bad, SIGBUS, 0, "unknown 30" }, { do_bad, SIGBUS, 0, "unknown 31" }, };
exception이 발생했을 때 fault가 32개로 구분되며 해당 fault 핸들러 함수가 등록된다.
arch/arm64/mm/fault.c
static const struct fault_info fault_info[] = { { do_bad, SIGKILL, SI_KERNEL, "ttbr address size fault" }, { do_bad, SIGKILL, SI_KERNEL, "level 1 address size fault" }, { do_bad, SIGKILL, SI_KERNEL, "level 2 address size fault" }, { do_bad, SIGKILL, SI_KERNEL, "level 3 address size fault" }, { do_translation_fault, SIGSEGV, SEGV_MAPERR, "level 0 translation fault" }, { do_translation_fault, SIGSEGV, SEGV_MAPERR, "level 1 translation fault" }, { do_translation_fault, SIGSEGV, SEGV_MAPERR, "level 2 translation fault" }, { do_translation_fault, SIGSEGV, SEGV_MAPERR, "level 3 translation fault" }, { do_bad, SIGKILL, SI_KERNEL, "unknown 8" }, { do_page_fault, SIGSEGV, SEGV_ACCERR, "level 1 access flag fault" }, { do_page_fault, SIGSEGV, SEGV_ACCERR, "level 2 access flag fault" }, { do_page_fault, SIGSEGV, SEGV_ACCERR, "level 3 access flag fault" }, { do_bad, SIGKILL, SI_KERNEL, "unknown 12" }, { do_page_fault, SIGSEGV, SEGV_ACCERR, "level 1 permission fault" }, { do_page_fault, SIGSEGV, SEGV_ACCERR, "level 2 permission fault" }, { do_page_fault, SIGSEGV, SEGV_ACCERR, "level 3 permission fault" }, { do_sea, SIGBUS, BUS_OBJERR, "synchronous external abort" }, { do_bad, SIGKILL, SI_KERNEL, "unknown 17" }, { do_bad, SIGKILL, SI_KERNEL, "unknown 18" }, { do_bad, SIGKILL, SI_KERNEL, "unknown 19" }, { do_sea, SIGKILL, SI_KERNEL, "level 0 (translation table walk)" }, { do_sea, SIGKILL, SI_KERNEL, "level 1 (translation table walk)" }, { do_sea, SIGKILL, SI_KERNEL, "level 2 (translation table walk)" }, { do_sea, SIGKILL, SI_KERNEL, "level 3 (translation table walk)" }, { do_sea, SIGBUS, BUS_OBJERR, "synchronous parity or ECC error" }, // RR eserved when RAS is implemented { do_bad, SIGKILL, SI_KERNEL, "unknown 25" }, { do_bad, SIGKILL, SI_KERNEL, "unknown 26" }, { do_bad, SIGKILL, SI_KERNEL, "unknown 27" }, { do_sea, SIGKILL, SI_KERNEL, "level 0 synchronous parity error (translatii on table walk)" }, // Reserved when RAS is implemented { do_sea, SIGKILL, SI_KERNEL, "level 1 synchronous parity error (translatii on table walk)" }, // Reserved when RAS is implemented { do_sea, SIGKILL, SI_KERNEL, "level 2 synchronous parity error (translatii on table walk)" }, // Reserved when RAS is implemented { do_sea, SIGKILL, SI_KERNEL, "level 3 synchronous parity error (translatii on table walk)" }, // Reserved when RAS is implemented { do_bad, SIGKILL, SI_KERNEL, "unknown 32" }, { do_alignment_fault, SIGBUS, BUS_ADRALN, "alignment fault" }, { do_bad, SIGKILL, SI_KERNEL, "unknown 34" }, { do_bad, SIGKILL, SI_KERNEL, "unknown 35" }, { do_bad, SIGKILL, SI_KERNEL, "unknown 36" }, { do_bad, SIGKILL, SI_KERNEL, "unknown 37" }, { do_bad, SIGKILL, SI_KERNEL, "unknown 38" }, { do_bad, SIGKILL, SI_KERNEL, "unknown 39" }, { do_bad, SIGKILL, SI_KERNEL, "unknown 40" }, { do_bad, SIGKILL, SI_KERNEL, "unknown 41" }, { do_bad, SIGKILL, SI_KERNEL, "unknown 42" }, { do_bad, SIGKILL, SI_KERNEL, "unknown 43" }, { do_bad, SIGKILL, SI_KERNEL, "unknown 44" }, { do_bad, SIGKILL, SI_KERNEL, "unknown 45" }, { do_bad, SIGKILL, SI_KERNEL, "unknown 46" }, { do_bad, SIGKILL, SI_KERNEL, "unknown 47" }, { do_bad, SIGKILL, SI_KERNEL, "TLB conflict abort" }, { do_bad, SIGKILL, SI_KERNEL, "Unsupported atomic hardware update fault" }, { do_bad, SIGKILL, SI_KERNEL, "unknown 50" }, { do_bad, SIGKILL, SI_KERNEL, "unknown 51" }, { do_bad, SIGKILL, SI_KERNEL, "implementation fault (lockdown abort)" }, { do_bad, SIGBUS, BUS_OBJERR, "implementation fault (unsupported exclusivee )" }, { do_bad, SIGKILL, SI_KERNEL, "unknown 54" }, { do_bad, SIGKILL, SI_KERNEL, "unknown 55" }, { do_bad, SIGKILL, SI_KERNEL, "unknown 56" }, { do_bad, SIGKILL, SI_KERNEL, "unknown 57" }, { do_bad, SIGKILL, SI_KERNEL, "unknown 58" }, { do_bad, SIGKILL, SI_KERNEL, "unknown 59" }, { do_bad, SIGKILL, SI_KERNEL, "unknown 60" }, { do_bad, SIGKILL, SI_KERNEL, "section domain fault" }, { do_bad, SIGKILL, SI_KERNEL, "page domain fault" }, { do_bad, SIGKILL, SI_KERNEL, "unknown 63" }, };
<kernel v5.4>
non-secure PL1에서 동작하는 리눅스 커널 위주로 벡터테이블을 설명한다.
다음 그림은 각 모드별 ARM Exception Vector 테이블을 보여준다.
벡터 테이블의 주소는 arm 모드 및 Security Extension 사용 유무에 따라 각각 다르다.
ARM에서 Exception이 발생하면 CPU는 지정된 벡터테이블(low/high)로 강제 jump되고 해당 엔트리에는 각 exception을 처리할 수 있는 코드로의 branch 코드가 수행된다.
아래 그림은 8개의 exception vector entry 중 5개 엔트리의 경우 각각 16개의 모드 테이블을 갖고 있고 exception 되기 전의 모드를 인덱스로 하여 해당 루틴으로 이동하는 것을 보여준다.
arch/arm/kernel/entry-armv.S
.section .vectors, "ax", %progbits .L__vectors_start: W(b) vector_rst W(b) vector_und W(ldr) pc, __vectors_start + 0x1000 W(b) vector_pabt W(b) vector_dabt W(b) vector_addrexcptn W(b) vector_irq W(b) vector_fiq
arch/arm/kernel/vmlinux.lds.S
#ifdef CONFIG_STRICT_KERNEL_RWX . = ALIGN(1<<SECTION_SHIFT); #else . = ALIGN(PAGE_SIZE); #endif __init_begin = .; ARM_VECTORS INIT_TEXT_SECTION(8) .exit.text : { ARM_EXIT_KEEP(EXIT_TEXT) } .init.proc.info : { ARM_CPU_DISCARD(PROC_INFO) } .init.arch.info : { __arch_info_begin = .; *(.arch.info.init) __arch_info_end = .; } .init.tagtable : { __tagtable_begin = .; *(.taglist.init) __tagtable_end = .; } #ifdef CONFIG_SMP_ON_UP .init.smpalt : { __smpalt_begin = .; *(.alt.smp.init) __smpalt_end = .; } #endif .init.pv_table : { __pv_table_begin = .; *(.pv_table) __pv_table_end = .; } INIT_DATA_SECTION(16)
arch/arm/kernel/vmlinux.lds.h
/* * The vectors and stubs are relocatable code, and the * only thing that matters is their relative offsets */
#define ARM_VECTORS \ __vectors_start = .; \ .vectors 0xffff0000 : AT(__vectors_start) { \ *(.vectors) \ } \ . = __vectors_start + SIZEOF(.vectors); \ __vectors_end = .; \ \ __stubs_start = .; \ .stubs ADDR(.vectors) + 0x1000 : AT(__stubs_start) { \ *(.stubs) \ } \ . = __stubs_start + SIZEOF(.stubs); \ __stubs_end = .; \ \ PROVIDE(vector_fiq_offset = vector_fiq - ADDR(.vectors));
exception 벡터 및 stub 코드들은 아래의 루틴에서 설치된다.
arch/arm/kernel/entry-armv.S
/* * Vector stubs. * * This code is copied to 0xffff1000 so we can use branches in the * vectors, rather than ldr's. Note that this code must not exceed * a page size. * * Common stub entry macro: * Enter in IRQ mode, spsr = SVC/USR CPSR, lr = SVC/USR PC * * SP points to a minimal amount of processor-private memory, the address * of which is copied into r0 for the mode specific abort handler. */
.macro vector_stub, name, mode, correction=0 .align 5 vector_\name: .if \correction sub lr, lr, #\correction .endif @ @ Save r0, lr_<exception> (parent PC) and spsr_<exception> @ (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 | PSR_ISETSTATE) msr spsr_cxsf, r0 @ @ the branch table must immediately follow this code @ and lr, lr, #0x0f THUMB( adr r0, 1f ) THUMB( ldr lr, [r0, lr, lsl #2] ) mov r0, sp ARM( ldr lr, [pc, lr, lsl #2] ) movs pc, lr @ branch to handler in SVC mode ENDPROC(vector_\name) .align 2 @ handler addresses follow this label 1: .endm
Exception이 발생되면 8개의 exception vector 엔트리 중 해당하는 exception 위치로 jump 되는데 이 중 5개의 exception 엔트리에서 사용되는 공통 매크로 루틴에서는 exception 벡터로 점프되기 직전의 최대 16개 모드별 테이블에 해당하는 루틴을 찾아 수행한다. 수행 전에 svc 모드로 변환한다.
arch/arm/kernel/entry-armv.S
.section .stubs, "ax", %progbits __stubs_start: @ This must be the first word .word vector_swi
stub 영역은 시스템 콜이 호출되는 vector_swi 부터 시작하여 다음과 같은 순으로 위치한다.
arch/arm/kernel/entry-armv.S
vector_rst: ARM( swi SYS_ERROR0 ) THUMB( svc #0 ) THUMB( nop ) b vector_und
SYS_ERROR0 소프트웨어 인터럽트를 발생시켜 시스템 콜을 호출하게 한다.
arch/arm/kernel/entry-armv.S
/* * 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) .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
arch/arm/kernel/entry-armv.S
/* * Data abort dispatcher * Enter in ABT mode, spsr = USR CPSR, lr = USR PC */
vector_stub dabt, ABT_MODE, 8 .long __dabt_usr @ 0 (USR_26 / USR_32) .long __dabt_invalid @ 1 (FIQ_26 / FIQ_32) .long __dabt_invalid @ 2 (IRQ_26 / IRQ_32) .long __dabt_svc @ 3 (SVC_26 / SVC_32) .long __dabt_invalid @ 4 .long __dabt_invalid @ 5 .long __dabt_invalid @ 6 .long __dabt_invalid @ 7 .long __dabt_invalid @ 8 .long __dabt_invalid @ 9 .long __dabt_invalid @ a .long __dabt_invalid @ b .long __dabt_invalid @ c .long __dabt_invalid @ d .long __dabt_invalid @ e .long __dabt_invalid @ f
arch/arm/kernel/entry-armv.S
/* * Prefetch abort dispatcher * Enter in ABT mode, spsr = USR CPSR, lr = USR PC */
vector_stub pabt, ABT_MODE, 4 .long __pabt_usr @ 0 (USR_26 / USR_32) .long __pabt_invalid @ 1 (FIQ_26 / FIQ_32) .long __pabt_invalid @ 2 (IRQ_26 / IRQ_32) .long __pabt_svc @ 3 (SVC_26 / SVC_32) .long __pabt_invalid @ 4 .long __pabt_invalid @ 5 .long __pabt_invalid @ 6 .long __pabt_invalid @ 7 .long __pabt_invalid @ 8 .long __pabt_invalid @ 9 .long __pabt_invalid @ a .long __pabt_invalid @ b .long __pabt_invalid @ c .long __pabt_invalid @ d .long __pabt_invalid @ e .long __pabt_invalid @ f
arch/arm/kernel/entry-armv.S
/* * Undef instr entry dispatcher * Enter in UND mode, spsr = SVC/USR CPSR, lr = SVC/USR PC */
vector_stub und, UND_MODE .long __und_usr @ 0 (USR_26 / USR_32) .long __und_invalid @ 1 (FIQ_26 / FIQ_32) .long __und_invalid @ 2 (IRQ_26 / IRQ_32) .long __und_svc @ 3 (SVC_26 / SVC_32) .long __und_invalid @ 4 .long __und_invalid @ 5 .long __und_invalid @ 6 .long __und_invalid @ 7 .long __und_invalid @ 8 .long __und_invalid @ 9 .long __und_invalid @ a .long __und_invalid @ b .long __und_invalid @ c .long __und_invalid @ d .long __und_invalid @ e .long __und_invalid @ f .align 5
arch/arm/kernel/entry-armv.S
/*============================================================================= * Address exception handler *----------------------------------------------------------------------------- * These aren't too critical. * (they're not supposed to happen, and won't happen in 32-bit data mode). */
vector_addrexcptn: b vector_addrexcptn
arch/arm/kernel/entry-armv.S
/*============================================================================= * FIQ "NMI" handler *----------------------------------------------------------------------------- * Handle a FIQ using the SVC stack allowing FIQ act like NMI on x86 * systems. */
vector_stub fiq, FIQ_MODE, 4 .long __fiq_usr @ 0 (USR_26 / USR_32) .long __fiq_svc @ 1 (FIQ_26 / FIQ_32) .long __fiq_svc @ 2 (IRQ_26 / IRQ_32) .long __fiq_svc @ 3 (SVC_26 / SVC_32) .long __fiq_svc @ 4 .long __fiq_svc @ 5 .long __fiq_svc @ 6 .long __fiq_abt @ 7 .long __fiq_svc @ 8 .long __fiq_svc @ 9 .long __fiq_svc @ a .long __fiq_svc @ b .long __fiq_svc @ c .long __fiq_svc @ d .long __fiq_svc @ e .long __fiq_svc @ f .globl vector_fiq_offset .equ vector_fiq_offset, vector_fiq
arch/arm/include/asm/tlbflush.h
static inline void local_flush_tlb_kernel_page(unsigned long kaddr) { const unsigned int __tlb_flag = __cpu_tlb_flags; kaddr &= PAGE_MASK; if (tlb_flag(TLB_WB)) dsb(nshst); __local_flush_tlb_kernel_page(kaddr); tlb_op(TLB_V7_UIS_PAGE, "c8, c7, 1", kaddr); if (tlb_flag(TLB_BARRIER)) { dsb(nsh); isb(); } }
arch/arm/include/asm/tlbflush.h
#define tlb_flag(f) ((always_tlb_flags & (f)) || (__tlb_flag & possible_tlb_flags & (f)))
arch/arm/include/asm/tlbflush.h
#define tlb_op(f, regs, arg) __tlb_op(f, “p15, 0, %0, ” regs, arg)
arch/arm/include/asm/tlbflush.h
#define __tlb_op(f, insnarg, arg) \ do { \ if (always_tlb_flags & (f)) \ asm("mcr " insnarg \ : : "r" (arg) : "cc"); \ else if (possible_tlb_flags & (f)) \ asm("tst %1, %2\n\t" \ "mcrne " insnarg \ : : "r" (arg), "r" (__tlb_flag), "Ir" (f) \ : "cc"); \ } while (0)
instruction을 실행시키되 다음의 조건을 만족해야 한다.
#define always_tlb_flags (v4_always_flags & \ v4wbi_always_flags & \ fr_always_flags & \ v4wb_always_flags & \ fa_always_flags & \ v6wbi_always_flags & \ v7wbi_always_flags)
arm의 모든 아키텍처가 지원하는 명령어
#define possible_tlb_flags (v4_possible_flags | \ v4wbi_possible_flags | \ fr_possible_flags | \ v4wb_possible_flags | \ fa_possible_flags | \ v6wbi_possible_flags | \ v7wbi_possible_flags)
arm 아키텍처 중 현재 아키텍처가 지원하는 명령어
현재 cpu가 지원하는 명령어 플래그
arch/arm/include/asm/tlbflush.h
static inline void __local_flush_tlb_kernel_page(unsigned long kaddr) { const int zero = 0; const unsigned int __tlb_flag = __cpu_tlb_flags; tlb_op(TLB_V4_U_PAGE, "c8, c7, 1", kaddr); tlb_op(TLB_V4_D_PAGE, "c8, c6, 1", kaddr); tlb_op(TLB_V4_I_PAGE, "c8, c5, 1", kaddr); if (!tlb_flag(TLB_V4_I_PAGE) && tlb_flag(TLB_V4_I_FULL)) asm("mcr p15, 0, %0, c8, c5, 0" : : "r" (zero) : "cc"); tlb_op(TLB_V6_U_PAGE, "c8, c7, 1", kaddr); tlb_op(TLB_V6_D_PAGE, "c8, c6, 1", kaddr); tlb_op(TLB_V6_I_PAGE, "c8, c5, 1", kaddr); }
arch/arm/include/asm/tlbflush.h
static inline void clean_pmd_entry(void *pmd) { const unsigned int __tlb_flag = __cpu_tlb_flags; tlb_op(TLB_DCLEAN, "c7, c10, 1 @ flush_pmd", pmd); tlb_l2_op(TLB_L2CLEAN_FR, "c15, c9, 1 @ L2 flush_pmd", pmd); }
TLB 엔트리 하나를 clean 한다.
struct cpu_tlb_fns { void (*flush_user_range)(unsigned long, unsigned long, struct vm_area_struct *); void (*flush_kern_range)(unsigned long, unsigned long); unsigned long tlb_flags; };
<kernel v5.0>
fs/dcache.c
void __init vfs_caches_init_early(void) { int i; for (i = 0; i < ARRAY_SIZE(in_lookup_hashtable); i++) INIT_HLIST_BL_HEAD(&in_lookup_hashtable[i]); dcache_init_early(); inode_init_early(); }
vfs(virtual file system)에서 사용되는 각종 해시 테이블들을 초기화한다.
fs/dcache.c
static void __init dcache_init_early(void) { /* If hashes are distributed across NUMA nodes, defer * hash allocation until vmalloc space is available. */ if (hashdist) return; dentry_hashtable = alloc_large_system_hash("Dentry cache", sizeof(struct hlist_bl_head), dhash_entries, 13, HASH_EARLY | HASH_ZERO, &d_hash_shift, NULL, 0, 0); d_hash_shift = 32 - d_hash_shift; }
Dentry 캐시 해시 리스트를 memblock에 early 할당 받고 초기화한다.
fs/inode.c
/* * Initialize the waitqueues and inode hash table. */
void __init inode_init_early(void) { /* If hashes are distributed across NUMA nodes, defer * hash allocation until vmalloc space is available. */ if (hashdist) return; inode_hashtable = alloc_large_system_hash("Inode-cache", sizeof(struct hlist_head), ihash_entries, 14, HASH_EARLY | HASH_ZERO, &i_hash_shift, &i_hash_mask, 0, 0); }
Inode 캐시 해시 리스트를 memblock에 early 할당 받고 초기화한다.