實驗內容:
- 結合中斷背景關系切換和行程背景關系切換分析Linux內核一般執行程序
- 以fork和execve系統呼叫為例分析中斷背景關系的切換
- 分析execve系統呼叫中斷背景關系的特殊之處
- 分析fork子行程啟動執行時行程背景關系的特殊之處
- 以系統呼叫作為特殊的中斷,結合中斷背景關系切換和行程背景關系切換分析Linux系統的一般執行程序
實驗環境:
VMWare虛擬機下的Ubuntu18.04.4,實驗采用的內核版本為linux-5.4.34,
1 基礎概念
CPU作業狀態
CPU的作業狀態分為系統態(管態)和用戶態(目態),
引入這兩個作業狀態的原因是為了避免用戶程式錯誤地使用特權指令,保護作業系統不被用戶程式破壞,
當CPU處于用戶態時,不允許執行特權指令;當CPU處于系統態時,可執行包括特權指令在內的一切機器指令,
中斷與系統呼叫
-
系統呼叫
程式員或系統管理員通常并非直接和系統呼叫打交道,在實際應用中,程式員通過呼叫函式(或稱應用程式介面、API),管理員則使用更高層次的系統命令,
作業系統為每個系統呼叫在標準C函式庫中構造一個具有相同名字的封裝函式,由它來屏蔽下層的復雜性,負責把作業系統提供的服務介面(即系統呼叫)封裝成應用程式能夠直接呼叫的函式(庫函式)
-
中斷
所謂中斷是指CPU對系統發生的某個事件做出的一種反應,CPU暫停正在執行的程式,保留現場后自動地轉去執行相應的處理程式,處理完該事件后再回傳斷點繼續執行被“打斷”的程式,
中斷概念主要分為三類
- 外部中斷,如I/O中斷,時鐘中斷,控制臺中斷等,
- 例外,如CPU本身故障(電源電壓或頻率),程式故障(非法操作碼、地址越界、浮點溢位等),即CPU的內部事件或程式執行中的事件引起的程序,
- 陷入(陷阱),在程式中使用了請求系統服務的系統呼叫而引發的程序,
-
中斷與系統呼叫
外部中斷與例外通常都稱作中斷,它們的產生往往是無意、被動的,
陷入是有意和主動的,系統呼叫本身是一種特殊的中斷,
行程背景關系與中斷背景關系
-
行程背景關系
用戶空間的應用程式,通過系統呼叫進入內核空間,用戶空間的行程需要傳遞變數、引數的值給內核,在內核態運行時也要保存用戶行程的一些暫存器值、變數等,行程背景關系,可以看作是用戶行程傳遞給內核的這些引數以及內核要保存的那一整套的變數、暫存器值和當時的環境等,
相對于行程而言,就是行程執行時的環境,具體來說就是各個變數和資料,包括所有的暫存器變數、行程打開的檔案、記憶體資訊等,一個行程的背景關系可以分為三個部分:用戶級背景關系、暫存器背景關系以及系統級背景關系,
-
中斷背景關系
為了在 中斷執行時間盡可能短 和 中斷處理需完成大量作業 之間找到一個平衡點,Linux將中斷處理程式分解為兩個半部:頂半部和底半部,頂半部完成盡可能少的比較緊急的功能,它往往只是簡單地讀取暫存器中的中斷狀態并清除中斷標志后就進行“登記中斷”的作業,“登記中斷”意味著將底半部處理程式掛到該設備的底半部執行佇列中去,這樣,頂半部執行的速度就會很快,可以服務更多的中斷請求,
對于中斷而言,內核呼叫中斷處理程式,進入內核空間,這個程序中,硬體的一些變數和引數也要傳遞給內核,內核通過這些引數進行中斷處理,中斷背景關系就可以理解為硬體傳遞過來的這些引數和內核需要保存的一些環境,主要是被中斷的行程的環境,
2 fork系統呼叫
Linux中通過fork系統呼叫來處理行程創建的任務,
對于行程的創建,sys_clone, sys_vfork,以及sys_fork系統呼叫的內部都使用了do_fork函式,
在sys_clone,sys_vfork和sys_fork處打下斷點,運行系統,在sys_clone處停下:

sys_clone原始碼:
SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
int __user *, parent_tidptr,
int, tls_val,
int __user *, child_tidptr)
#elif defined(CONFIG_CLONE_BACKWARDS2)
SYSCALL_DEFINE5(clone, unsigned long, newsp, unsigned long, clone_flags,
int __user *, parent_tidptr,
int __user *, child_tidptr,
int, tls_val)
#elif defined(CONFIG_CLONE_BACKWARDS3)
SYSCALL_DEFINE6(clone, unsigned long, clone_flags, unsigned long, newsp,
int, stack_size,
int __user *, parent_tidptr,
int __user *, child_tidptr,
int, tls_val)
#else
SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
int __user *, parent_tidptr,
int __user *, child_tidptr,
int, tls_val)
#endif
{
return do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr);
}
#endif
代碼最終呼叫do_fork函式,轉到do_fork執行,其他創建行程函式呼叫程序與此類似,do_fork函式如下:
long do_fork(unsigned long clone_flags,
unsigned long stack_start,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr)
{
struct task_struct *p;
int trace = 0;
long nr;
/*
* Determine whether and which event to report to ptracer. When
* called from kernel_thread or CLONE_UNTRACED is explicitly
* requested, no event is reported; otherwise, report if the event
* for the type of forking is enabled.
*/
if (!(clone_flags & CLONE_UNTRACED)) {
if (clone_flags & CLONE_VFORK)
trace = PTRACE_EVENT_VFORK;
else if ((clone_flags & CSIGNAL) != SIGCHLD)
trace = PTRACE_EVENT_CLONE;
else
trace = PTRACE_EVENT_FORK;
if (likely(!ptrace_event_enabled(current, trace)))
trace = 0;
}
p = copy_process(clone_flags, stack_start, stack_size,
child_tidptr, NULL, trace);
/*
* Do this prior waking up the new thread - the thread pointer
* might get invalid after that point, if the thread exits quickly.
*/
if (!IS_ERR(p)) {
struct completion vfork;
struct pid *pid;
trace_sched_process_fork(current, p);
pid = get_task_pid(p, PIDTYPE_PID);
nr = pid_vnr(pid);
if (clone_flags & CLONE_PARENT_SETTID)
put_user(nr, parent_tidptr);
if (clone_flags & CLONE_VFORK) {
p->vfork_done = &vfork;
init_completion(&vfork);
get_task_struct(p);
}
wake_up_new_task(p);
/* forking complete and child started to run, tell ptracer */
if (unlikely(trace))
ptrace_event_pid(trace, pid);
if (clone_flags & CLONE_VFORK) {
if (!wait_for_vfork_done(p, &vfork))
ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
}
put_pid(pid);
} else {
nr = PTR_ERR(p);
}
return nr;
}
do_fork會進行一些pcb的拷貝作業,
在呼叫copy_process函式時,會進行一些實際內容的拷貝:復制當前行程產生子行程,并且傳入關鍵引數為子行程設定回應行程背景關系,具體程序為:先通過呼叫 dup_task_struct 復制一份task_struct結構體,作為子行程的行程描述符,再初始化與調度有關的資料結構,呼叫sched_fork,將子行程的state設定為TASK_RUNNING,之后復制所有的行程資訊,包括fs、信號處理函式、信號、記憶體空間(包括寫時復制)等,最終呼叫copy_thread,設定子行程的堆疊資訊, 為子行程分配一個pid,
在呼叫wake_up_new_task函式時,主要任務是將子行程放入調度佇列中,從而使CPU有機會調度并得以運行,
3 execve系統呼叫
execve系統呼叫的作用是運行另外一個指定的程式,它會把新程式加載到當前行程的記憶體空間內,當前的行程會被丟棄,它的堆、堆疊和所有的段資料都會被新行程相應的部分代替,然后會從新程式的初始化代碼和 main 函式開始運行,同時,行程的 ID 將保持不變,
與fork系統呼叫不同,從一個行程中啟動另一個程式時,通常是先 fork 一個子行程,然后在子行程中使用 execve變為運行指定程式的行程, 例如,當用戶在 Shell 下輸入一條命令啟動指定程式時,Shell 就是先 fork了自身行程,然后在子行程中使用 execve來運行指定的程式,
execve系統呼叫的函式原型為:
int execve(const char *filename, char *const argv[], char *const envp[]);
filename 用于指定要運行的程式的檔案名,argv 和 envp 分別指定程式的運行引數和環境變數,除此之外,該系列函式還有很多變體(execl、execlp、execle、execv、execvp、execvpe),它們執行大體相同的功能,區別在于需要的引數不同,但都是通過execve系統呼叫進入內核,
execve系統呼叫的程序:首先,執行__x64_sys_execve系統呼叫,進入內核態后呼叫do_execve加載可執行檔案,之后再通過呼叫search_binary_handler覆寫當前行程的可執行程式,
static int exec_binprm(struct linux_binprm *bprm)
{
pid_t old_pid, old_vpid;
int ret;
/* Need to fetch pid before load_binary changes it */
old_pid = current->pid;
rcu_read_lock();
old_vpid = task_pid_nr_ns(current, task_active_pid_ns(current->parent));
rcu_read_unlock();
ret = search_binary_handler(bprm);
if (ret >= 0) {
audit_bprm(bprm);
trace_sched_process_exec(current, old_pid, bprm);
ptrace_event(PTRACE_EVENT_EXEC, old_vpid);
proc_exec_connector(current);
}
return ret;
}
最后將IP設定為新的行程的入口地址,然后回傳用戶態,繼續執行新行程,最終舊行程的背景關系被完全替換,但行程pid 不變,呼叫回傳新行程,
4 Linux系統的一般執行程序
當前linux系統中正在運行用戶態行程X,需要切換到用戶態行程Y的時候,會執行以下程序:
-
用戶態行程X正在運行
-
運行的程序當中,發生了中斷
-
中斷背景關系切換,swapgs指令保存現場后,再加載當前行程內核堆疊堆疊頂地址到RSP暫存器,由行程X的用戶態轉到行程X的內核態,
-
中斷處理程序中或中斷回傳前呼叫schedule函式,完成行程調度演算法,
-
switch_to呼叫__switch_to_asm匯編代碼,完成關鍵的行程背景關系切換,
-
中斷背景關系恢復,
-
繼續運行用戶態行程Y
Linux一般切換流程中有CPU的背景關系的切換和內核中的行程背景關系的切換,中斷和中斷回傳有中斷背景關系的切換,CPU和內核代碼中斷處理程式入口的匯編代碼結合起來完成中斷背景關系的切換,行程調度程序中有行程背景關系的切換,而行程背景關系的切換完全由內核完成,
幾種特殊情況
(1)通過中斷處理程序中的調度時機,用戶態行程與內核執行緒之間互相切換和內核執行緒之間互相切換,與最一般的情況非常類似,只是內核執行緒運行程序中發生中斷沒有行程用戶態和內核態的轉換,
(2)內核執行緒主動呼叫schedule(),只有行程背景關系的切換,沒有發生中斷背景關系的切換,與最一般的情況略簡略,
(3)創建子行程的系統呼叫在子行程中的執行起點及回傳用戶態,如fork,
(4)加載一個新的可執行程式后回傳到用戶態的情況,如execve,
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/54831.html
標籤:Linux
下一篇:單選題
