Linux中断与进程切换,结合中断上下文切换和进程上下文切换分析Linux内核的一般执行过程...
@
實驗環(huán)境
OS
Linux cj-virtual-machine 5.3.0-51-generic
虛擬機
QEMU
內(nèi)核版本
5.3.4
調(diào)式方法
GDB
PS:調(diào)試環(huán)境安裝請看上一篇博客匯編級理解Linux系統(tǒng)調(diào)用
fork系統(tǒng)調(diào)用過程
和普通系統(tǒng)系統(tǒng)調(diào)用對比
正常的?個系統(tǒng)調(diào)?都是陷?內(nèi)核態(tài),再返回到?戶態(tài),然后繼續(xù)執(zhí)?系統(tǒng)調(diào)?后的下?條指令。
fork和其他系統(tǒng)調(diào)?不同之處是它在陷?內(nèi)核態(tài)之后有兩次返回,第?次返回到原來的?進程的位置繼續(xù)向下執(zhí)?,這和其他的系統(tǒng)調(diào)?是?樣的。
在?進程中fork也返回了?次,會返回到?個特 定的點——ret_from_fork,通過內(nèi)核構(gòu)造的堆棧環(huán)境,它可以正常系統(tǒng)調(diào)?返回到?戶態(tài)
_do_fork系統(tǒng)調(diào)用流程概述
源碼在/linux/kernel/fork.c目錄下,由于代碼太多,只是大概了解
long _do_fork(struct kernel_clone_args *args) {
.....
//復制進程描述符和執(zhí)?時所需的其他數(shù)據(jù)結(jié)構(gòu)
p = copy_process(NULL, trace, NUMA_NO_NODE, args);
......
wake_up_new_task(p);//將?進程添加到就緒隊列
.......
return nr;//返回?進程pid(?進程中fork返回值為?進程的pid)
}
_do_fork
copy_process 復制進程描述符和執(zhí)?時所需的其他數(shù)據(jù)結(jié)構(gòu)
dup_task_struct 復制進程描述符task_struct、創(chuàng)建內(nèi)核堆棧等
copy_thread_tls 初始化?進程內(nèi)核棧和thread
wake_up_new_task 將?進程添加到就緒隊列
系統(tǒng)調(diào)用返回
總的來說,進程的創(chuàng)建過程?致是?進程通過fork系統(tǒng)調(diào)?進?內(nèi)核_do_fork函數(shù),如圖所示復制進程描述符及相關進程 資源(采?寫時復制技術)、分配?進程的內(nèi)核堆棧并對內(nèi)核堆棧和thread等進程關鍵上下?進?初始化,最后將?進程 放?就緒隊列,fork系統(tǒng)調(diào)?返回;??進程則在被調(diào)度執(zhí)?時根據(jù)設置的內(nèi)核堆棧和thread等進程關鍵上下?開始執(zhí)?。
普通系統(tǒng)調(diào)用和fork子進程內(nèi)核堆棧對比
fork系統(tǒng)調(diào)用子進程的內(nèi)核堆棧和普通系統(tǒng)調(diào)用堆棧相比多了一個,inactive_task_frame,該結(jié)構(gòu)主要用于進程切換過程。
fork系統(tǒng)調(diào)用實驗
編寫程序,使用fork() 函數(shù)
#include
#include
#include
#include
#include
int main(int argc, char* argv[])
{
int pid;
pid = fork();
if(pid<0)
{
//error
fprintf(stderr,"For Failed");
exit(-1);
}
else if(pid==0)
{
//child
printf("this is child process \n");
}
else
{
//parent
printf("this is Parent process \n");
wait(NULL);
printf("child complete \n");
}
return 0;
}
編譯后執(zhí)行
gcc -o fork fork.c -static
./fork
反匯編objdump -S fork -o fork.s,查看fock.s中使用的系統(tǒng)調(diào)用為56號,查/linux-5.4.34/arch/x86/entry/syscalls/syscall_64.tbl表得到內(nèi)核函數(shù)__x64_sys_clone
在 /linux/kernel/fork.c中,發(fā)現(xiàn),__x64_sys_clone是調(diào)用了內(nèi)核中的_do_fork函數(shù)。
開啟虛擬機,在__x64_sys_clone ,_do_fork,cpoy_process,dup_task_struct,copy_thread_tls下斷點,shell下運行fork可執(zhí)行文件,查看此時函數(shù)棧
結(jié)果
execve系統(tǒng)調(diào)用
圖示
和普通系統(tǒng)系統(tǒng)調(diào)用對比
當前的可執(zhí)?程序在執(zhí)?,執(zhí)?到execve系統(tǒng)調(diào)?時陷?內(nèi)核態(tài),在內(nèi)核???do_execve加載可執(zhí)??件,把當前進程的可執(zhí)?程序給覆蓋掉。當execve系統(tǒng)調(diào)?返回 時,返回的已經(jīng)不是原來的那個可執(zhí)?程序了,?是新的可執(zhí)?程序。
execve返回的是新的可執(zhí)?程序執(zhí)?的起點,靜態(tài)鏈接的可執(zhí)??件也就是main函數(shù)的?致位置,動態(tài)鏈接的可執(zhí)??件還需 要ld鏈接好動態(tài)鏈接庫再從main函數(shù)開始執(zhí)?。
Linux系統(tǒng)?般會提供了execl、execlp、execle、execv、execvp和execve等6個?以加載執(zhí)? ?個可執(zhí)??件的庫函數(shù),這些庫函數(shù)統(tǒng)稱為exec函數(shù),差異在于對命令?參數(shù)和環(huán)境變量參數(shù) 的傳遞?式不同。
exec函數(shù)都是通過execve系統(tǒng)調(diào)?進?內(nèi)核,對應的系統(tǒng)調(diào)?內(nèi)核處理函數(shù)為sys_execve或__x64_sys_execve,它們都是通過調(diào)?do_execve來具體執(zhí)?加載可執(zhí)??件的 ?作。
整體的調(diào)?的遞進關系為:
sys_execve()或__x64_sys_execve -> // 內(nèi)核處理函數(shù)
do_execve() –> // 系統(tǒng)調(diào)用函數(shù)
do_execveat_common() -> // 系統(tǒng)調(diào)用函數(shù)
__do_execve_?le ->
exec_binprm()-> // 根據(jù)讀入文件頭部,尋找該文件的處理函數(shù)
search_binary_handler() ->
load_elf_binary() -> // 加載elf文件到內(nèi)存中
start_thread() // 開始新進程
進程切換
進程切換時機
?戶進程上下?中主動調(diào)?特定的系統(tǒng)調(diào)?進?中斷上下?,系統(tǒng)調(diào)?返回 ?戶態(tài)之前進?進程調(diào)度。
內(nèi)核線程或可中斷的中斷處理程序,執(zhí)?過程中發(fā)?中斷進?中斷上下?, 在中斷返回前進?進程調(diào)度。
內(nèi)核線程主動調(diào)?schedule函數(shù)進?進程調(diào)度
進程上下?
?戶地址空間:包括程序代碼、數(shù)據(jù)、?戶堆棧等。 (CR3寄存器代表進程??錄表,即地址空間、數(shù)據(jù))
控制信息:進程描述符(thread)、內(nèi)核堆棧(sp寄存器)等。
進程的CPU上下?,相關寄存器的值(指令指針寄存器ip代表進程的CPU上下?)。
進程切換過過程
切換?全局?錄(CR3)以安裝?個新的地址空間,這樣不同進程的虛擬地 址如0x8048400(32位x86)就會經(jīng)過不同的?表轉(zhuǎn)換為不同的物理地址。
切換內(nèi)核態(tài)堆棧和進程的CPU上下?,因為進程的CPU上下?提供了內(nèi)核執(zhí) ?新進程所需要的所有信息,包含所有CPU寄存器狀態(tài)。
核心代碼
((last) = __switch_to_asm((prev), (next)));
ENTRY(__switch_to_asm)
pushq %rbp
pushq %rbx
pushq %r12
pushq %r13
pushq %r14
pushq %r15
/* switch stack */
movq %rsp, TASK_threadsp(%rdi)
movq TASK_threadsp(%rsi), %rsp
popq %r15
popq %r14
popq %r13
popq %r12
popq %rbx
popq %rbp
jmp __switch_to END(__switch_to)
__switch_to_asm是在C代碼中調(diào)?的,也就是使?call指令,?這段匯編的結(jié)尾是jmp __switch_to, __switch_to函數(shù)是C代碼最后有個return,也就是ret指令。將__switch_to_asm和__switch_to結(jié)合起來,正好是call指令和ret指令的配對出現(xiàn)。
call指令壓棧RIP寄存器到進程切換前的prev進程內(nèi)核堆棧;?ret指令出棧存?RIP 寄存器的是進程切換之后的next進程的內(nèi)核堆棧棧頂數(shù)據(jù)。
由此完成了進程的切換。
中斷上下文和進程上下文對比
中斷上下文的切換
中斷是由CPU實現(xiàn)的,所以中斷上下?切換過程中最關鍵的棧頂寄存器sp和指令指針寄存器 ip 是由CPU協(xié)助完成的。
進程上下文的切換
進程切換是由內(nèi)核實現(xiàn)的(且一般情況下,進程上下文切換嵌套在中斷中),所以進程上下?切換過程最關鍵的棧頂寄存器sp切換是通過進程描述符的thread.sp實現(xiàn)的,指令指針 寄存器ip的切換是在內(nèi)核堆棧切換的基礎上巧妙利?call/ret指令實現(xiàn)的。
Linux系統(tǒng)的一般執(zhí)行過程(含中斷與進程切換)
一般函數(shù)調(diào)用框架
(1)正在運?的?戶態(tài)進程X。
(2)發(fā)?中斷(包括異常、系統(tǒng)調(diào)?等),CPU完成load cs:rip(entry of a speci?c ISR),即跳轉(zhuǎn)到中斷處理程序??。
(3)中斷上下?切換,具體包括如下?點:
swapgs指令保存現(xiàn)場,可以理解CPU通過swapgs指令給當前CPU寄存器狀態(tài)做了?個快照。
rsp point to kernel stack,加載當前進程內(nèi)核堆棧棧頂?shù)刂返絉SP寄存器。快速系統(tǒng)調(diào)?是由系統(tǒng)調(diào)???處的匯編代碼實現(xiàn)?戶堆棧和內(nèi)核堆棧的切換。
save cs:rip/ss:rsp/r?ags:將當前CPU關鍵上下?壓?進程X的內(nèi)核堆棧,快速系統(tǒng)調(diào)?是由系統(tǒng)調(diào)???處的匯編代碼實現(xiàn)的。
此時完成了中斷上下?切換,即從進程X的?戶態(tài)到進程X的內(nèi)核態(tài)。
(4)中斷處理過程中或中斷返回前調(diào)?了schedule函數(shù),其中完成了進程調(diào)度算法選擇next進程、進程地址空間切換、以及switch_to關鍵的進程上下?切換等。
(5)switch_to調(diào)?了__switch_to_asm匯編代碼做了關鍵的進程上下?切換。將當前進程X的內(nèi)核堆棧切換到進程調(diào)度算法選出來的next進程(本例假定為進程Y)的內(nèi)核堆棧,并完成了進程上下?所需的指令指針寄存器狀態(tài)切換。之后開始運?進程Y(這?進程Y曾經(jīng)通過以上步驟被切換出去,因此可以從switch_to下??代碼繼續(xù)執(zhí)?)。
(6)中斷上下?恢復,與(3)中斷上下?切換相對應。注意這?是進程Y的中斷處理過程中,?(3)中斷上下?切換是在進程X的中斷處理過程中,因為內(nèi)核堆棧從進程X 切換到進程Y了。
(7)為了對應起?,中斷上下?恢復的最后?步單獨拿出來(6的最后?步即是7)iret - pop cs:rip/ss:rsp/r?ags,從Y進程的內(nèi)核堆棧中彈出(3)中對應的壓棧內(nèi)容。此時完 成了中斷上下?的切換,即從進程Y的內(nèi)核態(tài)返回到進程Y的?戶態(tài)。注意快速系統(tǒng)調(diào)?返回sysret與iret的處理略有不同。
(8)繼續(xù)運??戶態(tài)進程Y。
總結(jié)
以上是生活随笔為你收集整理的Linux中断与进程切换,结合中断上下文切换和进程上下文切换分析Linux内核的一般执行过程...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 蜻蜓旺卡是什么
- 下一篇: printf linux 头文件,Lin