struct task_struct *__switch_to(struct task_struct *prev,
struct task_struct *next)
{
struct task_struct *last;
fpsimd_thread_switch(next);--------------(1)
tls_thread_switch(next);----------------(2)
hw_breakpoint_thread_switch(next);--和硬件跟蹤相關(guān)
contextidr_thread_switch(next); --和硬件跟蹤相關(guān)
dsb(ish);
last = cpu_switch_to(prev, next); ------------(3)
(1)fp是float-point的意思,和浮點(diǎn)運(yùn)算相關(guān)。simd是Single Instruction Multiple Data的意思,和多媒體以及信號(hào)處理相關(guān)。fpsimd_thread_switch其實(shí)就是把當(dāng)前FPSIMD的狀態(tài)保存到了內(nèi)存中(task.thread.fpsimd_state),從要切入的next進(jìn)程描述符中獲取FPSIMD狀態(tài),并加載到CPU上。
(2)概念同上,不過(guò)是處理tls(thread local storage)的切換。這里硬件寄存器涉及tpidr_el0和tpidrro_el0,涉及的內(nèi)存是task.thread.tp_value。具體的應(yīng)用場(chǎng)景是和線程庫(kù)相關(guān),具體大家可以自行學(xué)習(xí)了。
(3)具體的切換發(fā)生在arch/arm64/kernel/entry.S文件中的cpu_switch_to,代碼如下:
ENTRY(cpu_switch_to) -------------------(1)
mov x10, #THREAD_CPU_CONTEXT ----------(2)
add x8, x0, x10 --------------------(3)
mov x9, sp
stp x19, x20, [x8], #16----------------(4)
stp x21, x22, [x8], #16
stp x23, x24, [x8], #16
stp x25, x26, [x8], #16
stp x27, x28, [x8], #16
stp x29, x9, [x8], #16
str lr, [x8] ---------A
add x8, x1, x10 -------------------(5)
ldp x19, x20, [x8], #16----------------(6)
ldp x21, x22, [x8], #16
ldp x23, x24, [x8], #16
ldp x25, x26, [x8], #16
ldp x27, x28, [x8], #16
ldp x29, x9, [x8], #16
ldr lr, [x8] -------B
mov sp, x9 -------C
ret -------------------------(7)
ENDPROC(cpu_switch_to)
(1)進(jìn)入cpu_switch_to函數(shù)之前,x0,x1用做參數(shù)傳遞,x0是prev task,就是那個(gè)要掛起的task,x1是next task,就是馬上要切入的task。cpu_switch_to和其他的普通函數(shù)沒(méi)有什么不同,盡管會(huì)走遍萬(wàn)水千山,但是**終還是會(huì)返回調(diào)用者函數(shù)__switch_to。
在進(jìn)入細(xì)節(jié)之前,先想一想這個(gè)問(wèn)題:cpu_switch_to要如何保存現(xiàn)場(chǎng)?要保存那些通用寄存器呢?其實(shí)上一小段描述已經(jīng)做了鋪陳:盡管有點(diǎn)怪異,本質(zhì)上cpu_switch_to仍然是一個(gè)普通函數(shù),需要符合ARM64標(biāo)準(zhǔn)過(guò)程調(diào)用文檔。在該文檔中規(guī)定,x19~x28是屬于callee-saved registers,也就是說(shuō),在__switch_to函數(shù)調(diào)用cpu_switch_to函數(shù)這個(gè)過(guò)程中,cpu_switch_to函數(shù)要保證x19~x28這些寄存器值是和調(diào)用cpu_switch_to函數(shù)之前一模一樣的。除此之外,pc、sp、fp當(dāng)然也是必須是屬于現(xiàn)場(chǎng)的一部分的。
(2)得到THREAD_CPU_CONTEXT的偏移,保存在x10中
(3)x0是pre task的進(jìn)程描述符,加上偏移之后就獲取了訪問(wèn)cpu context內(nèi)存的指針(x8寄存器)。所有context的切換的原理都是一樣的,就是把當(dāng)前cpu寄存器保存在內(nèi)存中,這里的內(nèi)存是在進(jìn)程描述符中的 thread.cpu_context中。
(4)一旦定位到保存cpu context(各種通用寄存器)的內(nèi)存,那么使用stp保存硬件現(xiàn)場(chǎng)。這里x29就是fp(frame pointer),x9保存了stack pointer,lr是返回的PC值。到A代碼處,完成了pre task cpu context的保存動(dòng)作。
(5)和步驟(3)類似,只不過(guò)是針對(duì)next task而言的。這時(shí)候x8指向了next task的cpu context。
(6)和步驟(4)類似,不同的是這里的操作是恢復(fù)next task的cpu context。執(zhí)行到代碼B處,所有的寄存器都已經(jīng)恢復(fù),除了PC和SP,其中PC保存在了lr(x30)中,而sp保存在了x9中。在代碼C出恢復(fù)了sp值,這時(shí)候萬(wàn)事俱備,只等PC操作了。
(7)ret指令其實(shí)就是把x30(lr)寄存器的值加載到PC,至此現(xiàn)場(chǎng)完全恢復(fù)到調(diào)用cpu_switch_to那一點(diǎn)上了。
參考文獻(xiàn):
1、ARM標(biāo)準(zhǔn)過(guò)程調(diào)用文檔(IHI0056C_beta_aaelf64.pdf)
2、linux 4.4.6內(nèi)核源代碼
相關(guān)推薦:
蘇州JAVA培訓(xùn) 蘇州JAVA培訓(xùn)班 蘇州JAVA培訓(xùn)機(jī)構(gòu)