主頁 > 作業系統 > 結合中斷背景關系切換和行程背景關系切換分析Linux內核的一般執行程序

結合中斷背景關系切換和行程背景關系切換分析Linux內核的一般執行程序

2020-09-16 07:11:04 作業系統

實驗內容:

  • 結合中斷背景關系切換和行程背景關系切換分析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的時候,會執行以下程序:

  1. 用戶態行程X正在運行

  2. 運行的程序當中,發生了中斷

  3. 中斷背景關系切換,swapgs指令保存現場后,再加載當前行程內核堆疊堆疊頂地址到RSP暫存器,由行程X的用戶態轉到行程X的內核態,

  4. 中斷處理程序中或中斷回傳前呼叫schedule函式,完成行程調度演算法,

  5. switch_to呼叫__switch_to_asm匯編代碼,完成關鍵的行程背景關系切換,

  6. 中斷背景關系恢復,

  7. 繼續運行用戶態行程Y

Linux一般切換流程中有CPU的背景關系的切換和內核中的行程背景關系的切換,中斷和中斷回傳有中斷背景關系的切換,CPU和內核代碼中斷處理程式入口的匯編代碼結合起來完成中斷背景關系的切換,行程調度程序中有行程背景關系的切換,而行程背景關系的切換完全由內核完成,

幾種特殊情況

(1)通過中斷處理程序中的調度時機,用戶態行程與內核執行緒之間互相切換和內核執行緒之間互相切換,與最一般的情況非常類似,只是內核執行緒運行程序中發生中斷沒有行程用戶態和內核態的轉換,

(2)內核執行緒主動呼叫schedule(),只有行程背景關系的切換,沒有發生中斷背景關系的切換,與最一般的情況略簡略,

(3)創建子行程的系統呼叫在子行程中的執行起點及回傳用戶態,如fork,

(4)加載一個新的可執行程式后回傳到用戶態的情況,如execve,

轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/54831.html

標籤:Linux

上一篇:Centos終端命令提示符顏色修改

下一篇:單選題

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • CA和證書

    1、在 CentOS7 中使用 gpg 創建 RSA 非對稱密鑰對 gpg --gen-key #Centos上生成公鑰/密鑰對(存放在家目錄.gnupg/) 2、將 CentOS7 匯出的公鑰,拷貝到 CentOS8 中,在 CentOS8 中使用 CentOS7 的公鑰加密一個檔案 gpg -a ......

    uj5u.com 2020-09-10 00:09:53 more
  • Kubernetes K8S之資源控制器Job和CronJob詳解

    Kubernetes的資源控制器Job和CronJob詳解與示例 ......

    uj5u.com 2020-09-10 00:10:45 more
  • VMware下安裝CentOS

    VMware下安裝CentOS 一、軟硬體準備 1 Centos鏡像準備 1.1 CentOS鏡像下載地址 下載地址 1.2 CentOS鏡像下載程序 點擊下載地址進入如下圖的網站,選擇需要下載的版本,這里選擇的是Centos8,點擊如圖所示。 決定選擇Centos8后,選擇想要的鏡像源進行下載,此 ......

    uj5u.com 2020-09-10 00:12:10 more
  • 如何使用Grep命令查找多個字串

    如何使用Grep 命令查找多個字串 大家好,我是良許! 今天向大家介紹一個非常有用的技巧,那就是使用 grep 命令查找多個字串。 簡單介紹一下,grep 命令可以理解為是一個功能強大的命令列工具,可以用它在一個或多個輸入檔案中搜索與正則運算式相匹配的文本,然后再將每個匹配的文本用標準輸出的格式 ......

    uj5u.com 2020-09-10 00:12:28 more
  • git配置http代理

    git配置http代理 經常遇到克隆 github 慢的問題,這里記錄一下幾種配置 git 代理的方法,解決 clone github 過慢。 目錄 git配置代理 git單獨配置github代理 git配置全域代理 配置終端環境變數 git配置代理 主要使用 git config 命令 git單獨 ......

    uj5u.com 2020-09-10 00:12:33 more
  • Linux npm install 裝包時提示Error EACCES permission denied解

    npm install 裝包時提示Error EACCES permission denied解決辦法 ......

    uj5u.com 2020-09-10 00:12:53 more
  • Centos 7下安裝nginx,使用yum install nginx,提示沒有可用的軟體包

    Centos 7下安裝nginx,使用yum install nginx,提示沒有可用的軟體包。 18 (flaskApi) [root@67 flaskDemo]# yum -y install nginx 19 已加載插件:fastestmirror, langpacks 20 Loading ......

    uj5u.com 2020-09-10 00:13:13 more
  • Linux查看服務器暴力破解ssh IP

    在公網的服務器上經常遇到別人爆破你服務器的22埠,用來挖礦或者干其他嘿嘿嘿的事情~ 這種情況下正確的做法是: 修改默認ssh的22埠 使用設定密鑰登錄或者白名單ip登錄 建議服務器密碼為復雜密碼 創建普通用戶登錄服務器(root權限過大) 建立堡壘機,實作統一管理服務器 統計爆破IP [root ......

    uj5u.com 2020-09-10 00:13:17 more
  • CentOS 7系統常見快捷鍵操作方式

    Linux系統中一些常見的快捷方式,可有效提高操作效率,在某些時刻也能避免操作失誤帶來的問題。 ......

    uj5u.com 2020-09-10 00:13:31 more
  • CentOS 7作業系統目錄結構介紹

    作業系統存在著大量的資料檔案資訊,相應檔案資訊會存在于系統相應目錄中,為了更好的管理資料資訊,會將系統進行一些目錄規劃,不同目錄存放不同的資源。 ......

    uj5u.com 2020-09-10 00:13:35 more
最新发布
  • vim的常用命令

    Vim的6種基本模式 1. 普通模式在普通模式中,用的編輯器命令,比如移動游標,洗掉文本等等。這也是Vim啟動后的默認模式。這正好和許多新用戶期待的操作方式相反(大多數編輯器默認模式為插入模式)。 2. 插入模式在這個模式中,大多數按鍵都會向文本緩沖中插入文本。大多數新用戶希望文本編輯器編輯程序中一 ......

    uj5u.com 2023-04-20 08:43:21 more
  • vim的常用命令

    Vim的6種基本模式 1. 普通模式在普通模式中,用的編輯器命令,比如移動游標,洗掉文本等等。這也是Vim啟動后的默認模式。這正好和許多新用戶期待的操作方式相反(大多數編輯器默認模式為插入模式)。 2. 插入模式在這個模式中,大多數按鍵都會向文本緩沖中插入文本。大多數新用戶希望文本編輯器編輯程序中一 ......

    uj5u.com 2023-04-20 08:42:36 more
  • docker學習

    ###Docker概述 真實專案部署環境可能非常復雜,傳統發布專案一個只需要一個jar包,運行環境需要單獨部署。而通過Docker可將jar包和相關環境(如jdk,redis,Hadoop...)等打包到docker鏡像里,將鏡像發布到Docker倉庫,部署時下載發布的鏡像,直接運行發布的鏡像即可。 ......

    uj5u.com 2023-04-19 09:26:53 more
  • 設定Windows主機的瀏覽器為wls2的默認瀏覽器

    這里以Chrome為例。 1. 準備作業 wsl是可以使用Windows主機上安裝的exe程式,出于安全考慮,默認情況下改功能是無法使用。要使用的話,終端需要以管理員權限啟動。 我這里以Windows Terminal為例,介紹如何默認使用管理員權限打開終端,具體操作如下圖所示: 2. 操作 wsl ......

    uj5u.com 2023-04-19 09:25:49 more
  • docker學習

    ###Docker概述 真實專案部署環境可能非常復雜,傳統發布專案一個只需要一個jar包,運行環境需要單獨部署。而通過Docker可將jar包和相關環境(如jdk,redis,Hadoop...)等打包到docker鏡像里,將鏡像發布到Docker倉庫,部署時下載發布的鏡像,直接運行發布的鏡像即可。 ......

    uj5u.com 2023-04-19 09:19:04 more
  • Linux學習筆記

    IP地址和主機名 IP地址 ifconfig可以用來查詢本機的IP地址,如果不能使用,可以通過install net-tools安裝。 Centos系統下ens33表示主網卡;inet后表示IP地址;lo表示本地回環網卡; 127.0.0.1表示代指本機;0.0.0.0可以用于代指本機,同時在放行設 ......

    uj5u.com 2023-04-18 06:52:01 more
  • 解決linux系統的kdump服務無法啟動的問題

    問題:專案麒麟系統服務器的kdump服務無法啟動,沒有相關日志無法定位問題。 1、查看服務狀態是關閉的,重啟系統也無法啟動 systemctl status kdump 2、修改grub引數,修改“crashkernel”為“512M(有的機器數值太大太小都會導致報錯,建議從128M開始試,或者加個 ......

    uj5u.com 2023-04-12 09:59:50 more
  • 解決linux系統的kdump服務無法啟動的問題

    問題:專案麒麟系統服務器的kdump服務無法啟動,沒有相關日志無法定位問題。 1、查看服務狀態是關閉的,重啟系統也無法啟動 systemctl status kdump 2、修改grub引數,修改“crashkernel”為“512M(有的機器數值太大太小都會導致報錯,建議從128M開始試,或者加個 ......

    uj5u.com 2023-04-12 09:59:01 more
  • 你是不是暴露了?

    作者:袁首京 原創文章,轉載時請保留此宣告,并給出原文連接。 如果您是計算機相關從業人員,那么應該經歷不止一次網路安全專項檢查了,你肯定是收到過資訊系統技術檢測報告,要求你加強風險監測,確保你提供的系統服務堅實可靠了。 沒檢測到問題還好,檢測到問題的話,有些處理起來還是挺麻煩的,尤其是線上正在運行的 ......

    uj5u.com 2023-04-05 16:52:56 more
  • 細節拉滿,80 張圖帶你一步一步推演 slab 記憶體池的設計與實作

    1. 前文回顧 在之前的幾篇記憶體管理系列文章中,筆者帶大家從宏觀角度完整地梳理了一遍 Linux 記憶體分配的整個鏈路,本文的主題依然是記憶體分配,這一次我們會從微觀的角度來探秘一下 Linux 內核中用于零散小記憶體塊分配的記憶體池 —— slab 分配器。 在本小節中,筆者還是按照以往的風格先帶大家簡單 ......

    uj5u.com 2023-04-05 16:44:11 more