Linux内核分析---进程的创建,执行与切换
學號:210
“原創作品轉載請注明出處 +?https://github.com/mengning/linuxkernel/?”
一.實驗要求
從整理上理解進程創建、可執行文件的加載和進程執行進程切換,重點理解分析fork、execve和進程切換。
二.實驗內容
1.理解task_struct數據結構
為了管理進程,內核必須對每個進程進行清晰的描述。每個進程在內核中都有一個進程控制塊(PCB)來維護進程相關的信息,Linux內核的進程控制塊是task_struct結構體。進程描述符提供了內核所需了解的進程信息:基本信息,管理信息,控制信息等。
task_struct是Linux內核的一種數據結構,每個進程都把它的信息放在 task_struct 這個數據結構體,task_struct 包含了這些內容:
(1)標示符 : 描述本進程的唯一標識符,用來區別其他進程。?
(2)狀態 :任務狀態,退出代碼,退出信號等。?
(3)優先級 :相對于其他進程的優先級。?
(4)程序計數器:程序中即將被執行的下一條指令的地址。?
(5)內存指針:包括程序代碼和進程相關數據的指針,還有和其他進程共享的內存塊的指針。
(6)上下文數據:進程執行時處理器的寄存器中的數據。?
(7) I/O狀態信息:包括顯示的I/O請求,分配給進程的I/O設備和被進程使用的文件列表。?
(8) 記賬信息:可能包括處理器時間總和,使用的時鐘數總和,時間限制,記賬號等。
2.分析fork函數對應的內核處理過程do_fork,理解創建一個新進程如何創建和修改task_struct數據結構
Linux系統中,除第一個進程是被捏造出來的,其他進程都是通過do_fork()復制出來的。
函數原型:int do_fork(unsigned long clone_flags, unsigned long stack_start,struct pt_regs *regs, unsigned long stack_size)
1.創建子進程task_struct結構體
首先需要申請進程最基本的單位task_struct結構,然后需要將父進程task_struct結構中的各種參數復制到子進程task_struct中。
2.獲取一個空閑的pid。
3.復制各種資源:copy_files(clone_flags, p)父進程中可能打開了一系列文件,因此要復制給子進程。copy_fs(clone_flags, p),復制父進程當前目錄環境,如當前文件系統。復制父進程的用戶空間。copy_thread()等。
3.使用gdb跟蹤分析一個fork系統調用內核處理函數do_fork
1.啟動Menu OS
cd LinuxKernel rm menu -rf git clone https://github.com/mengning/menu.git cd menu mv test_fork.c test.cmake rootfs
2.進入gdb調試模式
gdb
file linux-3.18.6/vmlinux
在這幾個地方設置斷點
b sys_clone
b do_fork
b dup_task_struct
b copy_process
b copy_thread
b ret_from_for
首先在sys_clone處
do_fork處
copy_process處
進入copy_thread
查看p值
4.理解編譯鏈接的過程和ELF可執行文件格式
1.鏈接過程預處理:主要是做一些代碼文本的替換工作。(該替換是一個遞歸逐層展開的過程。)
(1)將所有的#define刪除,并展開所有的宏定義(2)處理所有的條件預編譯指令,如:#if ?#ifdef #elif #else #endif
(3)處理#include預編譯指令,將被包含的文件插進到該指令的位置,這個過程是遞歸的
(4)刪除所有的注釋//與/* */
(5)添加行號與文件名標識,以便產生調試用的行號信息以及編譯錯誤或警告時能夠顯示行號
(6)保留所有的#pragma編譯器指令,因為編譯器需要使用它們
編譯:把預處理完的文件進行一系列詞法分析、語法分析、語義分析及優化后生成匯編代碼,這個過程是程序構建的核心部分。?
匯編:將匯編代碼轉成機器指令。
鏈接:此時的鏈接,嚴格說應該叫靜態鏈接。將多個目標文件、庫拼合成最終的可執行文件。
5.使用exec*庫函數加載一個可執行文件
exec()族函數功能是將當前的進程替換成一個新的進程,執行到exec()函數時當前進程就會結束新進程則開始執行。
process.c代碼
?
運行結果:
?
?
6.理解Linux系統中進程調度的時機
1.中斷處理過程(包括時鐘中斷、I/O中斷、系統調用和異常)中,直接調用schedule(),或者返回用戶態時根據need_resched標記調用schedule();
2.內核線程可以直接調用schedule()進行進程切換,也可以在中斷處理過程中進行調度,也就是說內核線程作為一類的特殊的進程可以主動調度,也可以被動調度;
3.用戶態進程無法實現主動調度,僅能通過陷入內核態后的某個時機點進行調度,即在中斷處理過程中進行調度。
7.特別關注并仔細分析switch_to中的匯編代碼,理解進程上下文的切換機制,以及與中斷上下文切換的關系
1.關鍵函數的調用關系:
schedule() --> context_switch() --> switch_to --> __switch_to()
2.代碼分析
asm volatile("pushfl\n\t" /* 保存當前進程的標志位 */
"pushl %%ebp\n\t" /* 保存當前進程的堆棧基址EBP */
"movl %%esp,%[prev_sp]\n\t" /* 保存當前棧頂ESP */
"movl %[next_sp],%%esp\n\t" /* 把下一個進程的棧頂放到esp寄存器中,完成了內核堆棧的切換,從此往下壓棧都是在next進程的內核堆棧中。 */
"movl $1f,%[prev_ip]\n\t" /* 保存當前進程的EIP */
"pushl %[next_ip]\n\t" /* 把下一個進程的起點EIP壓入堆棧 */
__switch_canary
"jmp __switch_to\n" /* 因為是函數所以是jmp,通過寄存器傳遞參數,寄存器是prev-a,next-d,當函數執行結束ret時因為沒有壓棧當前eip,所以需要使用之前壓棧的eip,就是pop出next_ip。 */
"1:\t" /* 認為next進程開始執行。 */
"popl %%ebp\n\t" /* restore EBP */
"popfl\n" /* restore flags */
/* output parameters 因為處于中斷上下文,在內核中
prev_sp是內核堆棧棧頂
prev_ip是當前進程的eip */
: [prev_sp] "=m" (prev->thread.sp),
[prev_ip] "=m" (prev->thread.ip), //[prev_ip]是標號
"=a" (last),
/* clobbered output registers: */
"=b" (ebx), "=c" (ecx), "=d" (edx),
"=S" (esi), "=D" (edi)
__switch_canary_oparam
/* input parameters:
next_sp下一個進程的內核堆棧的棧頂
next_ip下一個進程執行的起點,一般是$1f,對于新創建的子進程是ret_from_fork*/
: [next_sp] "m" (next->thread.sp),
[next_ip] "m" (next->thread.ip),
/* regparm parameters for __switch_to(): */
[prev] "a" (prev),
[next] "d" (next)
__switch_canary_iparam
: /* reloaded segment registers */
"memory");
} while (0)
內核在switch_to中執行如下操作:
1.進程切換, 即esp的切換, 由于從esp可以找到進程的描述符
2.硬件上下文切換, 設置ip寄存器的值, 并jmp到__switch_to函數
3.堆棧的切換, 即ebp的切換, ebp是棧底指針, 它確定了當前用戶空間屬于哪個進程
通過系統調用,用戶空間的應用程序就會進入內核空間,由內核代表該進程運行于內核空間,這就涉及到上下文的切換,用戶空間和內核空間具有不同的地址映射,通用或專用的寄存器組,而用戶空間的進程要傳遞很多變量、參數給內核,內核也要保存用戶進程的一些寄存器、變量等,以便系統調用結束后回到用戶空間繼續執行,所謂的進程上下文,就是一個進程在執行的時候,CPU的所有寄存器中的值、進程的狀態以及堆棧中的內容,當內核需要切換到另一個進程時,它需要保存當前進程的所有狀態,即保存當前進程的進程上下文,以便再次執行該進程時,能夠恢復切換時的狀態,繼續執行。
?
轉載于:https://www.cnblogs.com/lizhenhuaxxx/p/10603897.html
總結
以上是生活随笔為你收集整理的Linux内核分析---进程的创建,执行与切换的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JS第三方中间件的延伸
- 下一篇: 20_集合_第20天(Map、可变参数、