Linux中常见的SLAB分配器,你了解多少?
1、armv8中断、系统调用的入口在arch/arm64/kernel/entry.S
ENTRY(vectors)
ventry el1_sync_invalid // Synchronous EL1t
ventry el1_irq_invalid // IRQ EL1t
ventry el1_fiq_invalid // FIQ EL1t
ventry el1_error_invalid // Error EL1t
ventry el1_sync // Synchronous EL1h
ventry el1_irq // IRQ EL1h
ventry el1_fiq_invalid // FIQ EL1h
ventry el1_error_invalid // Error EL1h
ventry el0_sync // Synchronous 64-bit EL0
ventry el0_irq // IRQ 64-bit EL0
ventry el0_fiq_invalid // FIQ 64-bit EL0
ventry el0_error_invalid // Error 64-bit EL0
#ifdef CONFIG_COMPAT
ventry el0_sync_compat // Synchronous 32-bit EL0
ventry el0_irq_compat // IRQ 32-bit EL0
ventry el0_fiq_invalid_compat // FIQ 32-bit EL0
ventry el0_error_invalid_compat // Error 32-bit EL0
#else
ventry el0_sync_invalid // Synchronous 32-bit EL0
ventry el0_irq_invalid // IRQ 32-bit EL0
ventry el0_fiq_invalid // FIQ 32-bit EL0
ventry el0_error_invalid // Error 32-bit EL0
#endif
END(vectors)
实现的函数有el1_sync,el1_irq,el0_sync,el0_irq。其余都是invalid。
el1_sync:当前处于内核态时,发生了指令执行异常、缺页中断(跳转地址或者取地址)。
el1_irq:当前处于内核态时,发生硬件中断。
el0_sync:当前处于用户态时,发生了指令执行异常、缺页中断(跳转地址或者取地址)、系统调用。
el0_iqr:当前处于用户态时,发生了硬件中断。
Linux系统中常见的SLAB分配器,你了解多少?
2022-07-18 04:44·深度Linux
我们都知道Buddy分配器是按照页的单位分配的(Buddy系统分配器实现),如果我们需要分配几十个字节,几百个字节的时候,就需要用到SLAB分配器。
SLAB分配器专门是针对小内存分配而设计的,比如我们驱动中常见的Kmalloc分配器就是通过SLAB分配器分配的内存。
而SLAB分配器在linux系统中三种具体的实现:SLAB,SLUB,SLOB。目前内核代码中默认的SLAB分配器为SLUB算法。至于为啥不用SLAB大家可以网上看看资料,所以我们重点分析SLUB分配器的实现。
2、el1_sync,el1_irq,el0_sync,el0_irq在开始时会调用kernel_entry,在结束时会调用kernel_exit。
.macro kernel_entry, el, regsize = 64
sub sp, sp, #S_FRAME_SIZE - S_LR // room for LR, SP, SPSR, ELR
.if \regsize == 32
mov w0, w0 // zero upper 32 bits of x0
.endif
push x28, x29
push x26, x27
push x24, x25
push x22, x23
push x20, x21
push x18, x19
push x16, x17
push x14, x15
push x12, x13
push x10, x11
push x8, x9
push x6, x7
push x4, x5
push x2, x3
push x0, x1
.if \el == 0
mrs x21, sp_el0
get_thread_info tsk // Ensure MDSCR_EL1.SS is clear,
ldr x19, tsk, #TI_FLAGS // since we can unmask debug
disable_step_tsk x19, x20 // exceptions when scheduling.
.else
add x21, sp, #S_FRAME_SIZE
.endif
mrs x22, elr_el1
mrs x23, spsr_el1
stp lr, x21, sp, #S_LR
stp x22, x23, sp, #S_PC
/*
* Set syscallno to -1 by default (overridden later if real syscall).
*/
.if \el == 0
mvn x21, xzr
str x21, [sp, #S_SYSCALLNO]
.endif
/*
* Registers that may be useful after this macro is invoked:
*
* x21 - aborted SP
* x22 - aborted PC
* x23 - aborted PSTATE
*/
.endm .macro kernel_exit, el, ret = 0
ldp x21, x22, sp, #S_PC // load ELR, SPSR
.if \el == 0
ct_user_enter
ldr x23, sp, #S_SP // load return stack pointer
.endif
.if \ret
ldr x1, sp, #S_X1 // preserve x0 (syscall return)
add sp, sp, S_X2
.else
pop x0, x1
.endif
pop x2, x3 // load the rest of the registers
pop x4, x5
pop x6, x7
pop x8, x9
msr elr_el1, x21 // set up the return data
msr spsr_el1, x22
.if \el == 0
msr sp_el0, x23
.endif
pop x10, x11
pop x12, x13
pop x14, x15
pop x16, x17
pop x18, x19
pop x20, x21
pop x22, x23
pop x24, x25
pop x26, x27
pop x28, x29
ldr lr, sp, #S_FRAME_SIZE - S_LR // load LR and restore SP
eret // return to kernel
.endm
.macro get_thread_info, rd
mov \rd, sp
and \rd, \rd, #~(THREAD_SIZE - 1) // top of stack
.endm
el1_sync,el1_irq调用的是kernel_entry 1,kernel_exit 1,也就是上面宏el为1。
el0_sync,el0_irq调用的是kernel_entry 0,kernel_exit 0,也就是上面宏el为0。
我们可以看到 .macro kernel_exit, el, ret = 0,还有一个参数ret,只有在el0_sync处理系统调用时会被置成1。我们看下具体的代码。
el0_sync:
kernel_entry 0
mrs x25, esr_el1 // read the syndrome register
lsr x24, x25, #ESR_EL1_EC_SHIFT // exception class
cmp x24, #ESR_EL1_EC_SVC64 // SVC in 64-bit state
b.eq el0_svc
cmp x24, #ESR_EL1_EC_DABT_EL0 // data abort in EL0
b.eq el0_da
cmp x24, #ESR_EL1_EC_IABT_EL0 // instruction abort in EL0
b.eq el0_ia
cmp x24, #ESR_EL1_EC_FP_ASIMD // FP/ASIMD access
b.eq el0_fpsimd_acc
cmp x24, #ESR_EL1_EC_FP_EXC64 // FP/ASIMD exception
b.eq el0_fpsimd_exc
cmp x24, #ESR_EL1_EC_SYS64 // configurable trap
b.eq el0_undef
cmp x24, #ESR_EL1_EC_SP_ALIGN // stack alignment exception
b.eq el0_sp_pc
cmp x24, #ESR_EL1_EC_PC_ALIGN // pc alignment exception
b.eq el0_sp_pc
cmp x24, #ESR_EL1_EC_UNKNOWN // unknown exception in EL0
b.eq el0_undef
cmp x24, #ESR_EL1_EC_BREAKPT_EL0 // debug exception in EL0
b.ge el0_dbg
b el0_inv
el0_sync如果是发生了系统调用,调用el0_svc:
el0_svc:
adrp stbl, sys_call_table // load syscall table pointer
uxtw scno, w8 // syscall number in w8
mov sc_nr, #__NR_syscalls
el0_svc_naked: // compat entry point
stp x0, scno, [sp, #S_ORIG_X0] // save the original x0 and syscall number
enable_dbg_and_irq
ct_user_exit 1
ldr x16, [tsk, #TI_FLAGS] // check for syscall hooks
tst x16, #_TIF_SYSCALL_WORK
b.ne __sys_trace
cmp scno, sc_nr // check upper syscall limit
b.hs ni_sys
ldr x16, [stbl, scno, lsl #3] // address in the syscall table
blr x16 // call sys_* routine
b ret_fast_syscall
ni_sys:
mov x0, sp
bl do_ni_syscall
b ret_fast_syscall
ENDPROC(el0_svc)
ret_fast_syscall:
disable_irq // disable interrupts
ldr x1, [tsk, #TI_FLAGS]
and x2, x1, #_TIF_WORK_MASK
cbnz x2, fast_work_pending
enable_step_tsk x1, x2
kernel_exit 0, ret = 1 // 在系统调用时,ret==1
3、理解整个中断/系统调用流程的关键是kernel_entry和kernel_exit,也就是如何保存现场,并且恢复现场的。
我们先来看下armv8的寄存器,PLR(X30)无论是用户态还是内核态都用这个寄存器来存储程序的返回值。
sp_el0,sp_el1分别是有用户态和内核态的堆栈。
ELR_EL1用于存储,当在发生系统调用、异常、中断时,当前程序的pc值(无论是用户态还是内核态)。
SPSR_EL1用于存储,当在发生系统调用、异常、中断时,当前程序的PSTATE(无论是用户态还是内核态)。

4、当发生中断、异常、系统调用时,硬件会自动:
1)把当前程序的pc值放入ELR_EL1中
2)把当前状态PSTATE存入SPSR_EL1中
3)根据发生在内核态还是用户态,中断还是异常,会自动跳转到el1_sync,el1_irq,el0_sync,el0_irq
4)改变PSTATE,如果是用户态发生中断、异常、系统调用,此时已经进入内核态,堆栈是sp_el1。
5、kernel_entry

执行完kernel_entry的堆栈,ELR_EL1存放的是返回的PC值,SPSR_EL1存放的是返回的PSTATE。
如果是用户态发生的中断、异常、系统调用,则栈中保存都是用户态的寄存器信息。
如果是内核态发生的中断、异常,则栈中保存的内核态的寄存器信息。
6、kernel_exit
前面我们已经说过:
el1_sync,el1_irq调用的是kernel_entry 1,kernel_exit 1,也就是上面宏el为1。
el0_sync,el0_irq调用的是kernel_entry 0,kernel_exit 0,也就是上面宏el为0。
我们可以看到 .macro kernel_exit, el, ret = 0,还有一个参数ret,只有在el0_sync处理系统调用时会被置成1。
.macro kernel_exit, el, ret = 0
ldp x21, x22, [sp, #S_PC] // load ELR, SPSR
.if \el == 0
ct_user_enter
ldr x23, [sp, #S_SP] // 恢复用户态的堆栈sp_el0
.endif
.if \ret
ldr x1, [sp, #S_X1] // 如果处理系统调用x0存放的是系统的调用的返回值,所以不需要从堆栈中恢复
add sp, sp, S_X2 //sp_el1中可能存放了传递的参数,这里要把sp加上存放参数的大小
.else
pop x0, x1 //如果不是处理系统调用,直接恢复x0,x1
.endif
pop x2, x3 // load the rest of the registers
pop x4, x5
pop x6, x7
pop x8, x9
msr elr_el1, x21 // set up the return data
msr spsr_el1, x22
.if \el == 0
msr sp_el0, x23 // 恢复用户态的堆栈sp_el0
.endif
pop x10, x11
pop x12, x13
pop x14, x15
pop x16, x17
pop x18, x19
pop x20, x21
pop x22, x23
pop x24, x25
pop x26, x27
pop x28, x29
ldr lr, [sp], #S_FRAME_SIZE - S_LR // 恢复lr,恢复内核sp_el1
eret // 恢复elr_el1(pc),spsr_el1
.endm
.macro get_thread_info, rd
mov \rd, sp
and \rd, \rd, #~(THREAD_SIZE - 1) // top of stack
.endm
发生中断、异常、系统调用前是用户态,则返回用户态的寄存器(pc,lr,sp_el0,pstate)。注意还要把内核态的栈平衡了:ldr lr, [sp], #S_FRAME_SIZE - S_LR // 恢复lr,恢复内核sp_el1
发生中断、异常、系统调用前是内核态,则返回内核态的寄存器(pc,lr,sp_el1,pstate)。
如果处理系统调用x0存放的是系统的调用的返回值,所以不需要从堆栈中恢复。
.if \ret
ldr x1, [sp, #S_X1] // 如果处理系统调用x0存放的是系统的调用的返回值,所以不需要从堆栈中恢复
add sp, sp, S_X2 //sp_el1中可能存放了传递的参数,这里要把sp加上存放参数的大小
相关推荐
-
第18问:MySQL CPU 高了,怎么办?2025-02-24 10:27:18
-
mysql索引类型 normal, unique, full text
mysql索引类型 normal, unique, full text2025-02-24 10:05:05 -
uwsgi+django+nginx 搭建部分总结2025-02-24 10:03:33
-
使用Docker配置Nginx环境部署Nextcloud2025-02-24 10:02:03
-
Nginx安装和怎么使用2025-02-24 10:00:45