主頁 > 後端開發 > 行程

行程

2023-04-28 13:05:22 後端開發

行程、輕量級行程和執行緒

行程在教科書中通常定義:行程是程式執行時的一個實體,可以把它看作充分描述程式已經執行到何種程度的資料結構的匯集,

從內核的觀點,行程的目的就是擔當分配系統資源(CPU時間、記憶體等)的物體,

 

當一個行程被創建時,他幾乎于父行程相同,它接受父行程地址空間的一個(邏輯)拷貝,并從行程創建系統呼叫的下一條指令開始執行于父行程相同的代碼,盡管父子行程可以共享含有程式代碼(正文)的頁,但是他們各自有獨立的資料拷貝(堆疊和堆),因此子行程對一個記憶體單元的修改對父行程是不可見的

 

Unix中一個行程由幾個執行緒組成,每個執行緒都代表行程的一個執行流,從內核觀點來看,多執行緒應用程式僅僅是一個普通行程,多執行緒應用程式多個執行流的創建、處理、調度整個都是在用戶態進行的,

 

Linux使用輕量級行程對多執行緒應用程式提供更好的支持,兩個輕量級行程基本上可以共享一些資源,諸如地址空間、打開的檔案等,只要其中一修改共享資源,另一個就立即查看這種修改,

 

實作多執行緒應用程式的一個簡單方式就是輕量級行程與每個執行緒關聯起來,保證共享資源的同時,每個執行緒都可以由內核獨立調度,

 

 

行程描述符

行程描述符都是task_struct型別結構,它的欄位包含了與一個行程相關的所有資訊,

 

行程狀態

行程描述符中的state欄位描述了行程當前所處的狀態,

??可運行狀態(TASK_RUNNING):行程要么在CPU執行,要么準備執行,

??可中斷的等待狀態(TASK_INTERRUPTIBLE):行程被掛起(睡眠),直到某個條件為真,產生一個硬體中斷,釋放當前的行程資源,或傳遞一個喚醒的信號,

??不可中斷的等待狀態(TASK_UNINTERRUPTIBLE):與可中斷的等待狀態類似,有個例外信號傳遞不能改變狀態,用的很少,某些情況下(行程必須等待,直到一個不能被中斷的事件發生),例如,當行程打開一個設備檔案,其相應的驅動程式開始探測相應的硬體設備時會用到,在探測完成前,設備驅動程式不能被中斷,否則,硬體設備會處于不可預知的狀態,

??暫停狀態(TASK_STOPPED):行程執行被暫停,當行程收到SIGTOP、SIGTSTP、SIGTTIN或SIGTTOU信號后,進入暫停狀態,

??跟蹤狀態(TASK_TRACED):行程的執行已由debugger程式暫停,當行程被另一個行程監控時,任何信號都可以把這個行程置于該狀態,

 

還有兩個狀態既可以放在行程描述符的state欄位,又可以放在exit_state欄位中,當行程被終止時,行程的狀態才會變成兩種狀態的一種:

??僵死狀態(EXIT_ZOMBIE):行程的執行被終止,但是父行程還沒有發布wait4()或waitpid()系統呼叫來回傳有關死亡行程的資訊,發布wait()類系統呼叫前,內核不能丟棄包含在死行程描述符中的資料,因為父行程可能還需要它,

??僵死撤銷狀態(EXIT_DEAD):最終狀態,由于父行程剛發出wait4()或waitpid()系統呼叫,因而行程由系統洗掉,為了防止其他執行執行緒在同一個行程上也執行wait()類系統呼叫(這是一種競爭條件),而把行程的狀態由僵死改為僵死撤銷狀態,

 

 

行程鏈表

采用雙向鏈表把所有行程的描述符鏈接起來,

每個task_struct結構都包含一個list_head型別的tasks欄位,這個型別的prev和next欄位分別指向前面和后面的task_struct元素,

行程鏈表的頭是init_task描述符,它是所謂的0行程(process0)或swapper行程的行程描述符,init_task的task.prev欄位指向鏈表中最后插入的行程描述符的tasks欄位,

 

TASK_RUNNING狀態的行程鏈表

當內核尋找一個新行程在CPU上運行時,必須考慮可運行行程,

提高調度程式運行速度的訣竅時建立多個可運行的程式鏈表,每種行程優先權對應一個不同的鏈表,每個task_struct描述符包含一個list_head型別的欄位run_list,用于保存可運行程式的優先級,在多處理器系統中,每個CPU都有自己的運行佇列,即他自己的行程鏈表集,

運行佇列的主要資料結構是組成運行佇列的行程描述符鏈表,由一個單獨的priio_array_t資料結構來實作,

 

行程間的關系

程式創建具有父子關系,如果一個行程創建多個子行程時,則子行程之間具有兄弟關系,

行程0和行程1是由內核創建的,行程1(init)是所有行程的祖先,

 

為了加速查找,引入了4個散串列,沒中過型別的PID需要他自己的散串列,

Linux利用雙向鏈表來處理沖突的PID,每一個表項都是由沖突的行程描述符組成的雙向鏈表,

 

等待佇列

等待佇列由雙向鏈表實作,其元素包括指向行程描述符的指標,等待佇列頭是一個型別為wait_queue_head_t的資料結構,

struct __wait_queue_head{
    spinlock_t lock;
    struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;

因為等待佇列是由中斷處理函式和主要內核函式修改的,因此必須對其雙向鏈表進行保護以免對其進行訪問,同步是通過等待佇列頭中的lock自旋鎖達到,

等待佇列鏈表中的元素型別為wait_queue_t:

struct __wait_queue{
    unsigned int flags;
    struct task_struct * task;
    wait_queue_func_t func;
    struct list_head task_list;
};
typedef struct __wait_queue wait_queue_t;

描述符地址存放在task欄位中,task_list欄位包含的是指標,由這個指標把一個元素連接到等待相同事件的行程鏈表中,

有兩種睡眠行程:互斥行程(flags=1)由內核有選擇的喚醒,而非互斥行程(flags=0)總是由內核在事件發生時喚醒,等待訪問臨界資源的行程就是互斥的,等待相關事件的行程時非互斥的,func欄位表示喚醒的方式,

一旦定義了一個元素,必須把它插入等待佇列,add_wait_queue()函式把一個非互斥行程插入等待佇列鏈表的第一個位置,add_wait_queue_exclusive()函式把一個互斥行程插入等待佇列鏈表的最后一個位置,remove_wait_queue()函式從等待佇列鏈表中洗掉一個行程,waitqueue_active()函式檢查一個給定的等待佇列是否為空,

 

wake_up_locked宏和wake_up宏相類似,僅有的不同是當wait_queue_head_t中的自旋鎖已經被持有時要呼叫wake_up_locked;

例如,wake_up宏等價于下列代碼:

void wake_up(wait_queue_head_t *q){
    struct list_head *tmp;
    wait_queue_t *curr;
    list_for_each(tmp,wait_queue_t,task_list){
        curr = list_entry(tmp,wait_queue_t,task_list);
        if(curr->func(curr, TASK_INTERRUPTIBLE|TASK_UNINTERRUPTBLE,0,NULL) && curr->flags)
            break;
    }
}

list_for_each宏掃描雙向鏈表中的所有項,即等待佇列的所有行程,對每一項,list_entry宏都計算wait_queue_t變數對應的地址,這個變數的func欄位存放喚醒函式的地址,它試圖喚醒由等待佇列元素的task欄位標識的行程,如果一個行程已經被有效地喚醒并且行程是互斥的,回圈結束,

因為所有的非互斥行程總是在雙向鏈表的開始位置,而所有的互斥行程在雙向鏈表的尾部,所以函式總是先喚醒非互斥行程然后再喚醒互斥行程,如果有行程存在的話,

 

行程切換

為了控制行程的執行,內核必須有能力掛起正在CPU上運行的行程,并恢復以前掛起的某個行程的執行,這種行為被稱為行程切換、任務切換或背景關系切換,

 

硬體背景關系

行程恢復執行前必須裝入暫存器的一組資料稱為硬體背景關系,硬體背景關系是行程可執行背景關系的一個子集,因為可執行背景關系包含行程執行所需要的所有資訊,在Linux中,行程硬體背景關系的一部分存放在TSS段,而剩余部分存放在內核態堆疊中,

prev--切換出的行程的描述符       next--切換進的行程的描述符

我們把行程切換定義為這樣的行為:保存prev硬體上背景關系,用next硬體背景關系代替prev,

 

執行行程切換

從本質上說,每個行程切換由兩部組成:

1、切換頁全域目錄以安裝一個新的地址空間;

2、切換內核態堆疊和硬體背景關系,因為硬體背景關系提供了內核執行新行程所需要的所有資訊,包含CPU暫存器,

 

switch_to宏

行程切換的第二步由switch_to宏執行,它是內核中與硬體關系最密切的歷程之一,

他有三個引數,prev、next 和 last,

在任何行程切換中涉及到三個行程而不是兩個行程,假設內核決定暫停行程A而激活行程B,在shedule()函式中,prev指向A的描述符而next指向B的描述符,switch_to宏一旦使A暫停,A的執行流就凍結,隨后,當內核想再次激活行程A,就必須暫停另一個行程C,于是就要用prev指向C而next指向A來執行另一個switch_to宏,

switch_to宏的最后一個引數是輸出引數,它表示宏把行程C的描述符地址寫在記憶體的什么位置了,

 

 

創建行程

clone()、fork()及 vfork()系統呼叫

在Linux中,輕量級行程是由名為clone()的函式創建的,

 

傳統的fork()系統呼叫在Linux中是用clone()實作的,其中clone()的flags引數指定為SIGCHLD信號及所有清0的clone標志,而它的child_stack引數是父行程當前的堆疊指標,因此,父行程和子行程暫時共享一個用戶態堆疊,

要感謝寫時復制機制,通常只要父子行程中有一個試圖去改變,則立即各自的到用戶態堆疊的一份拷貝,

 

do_fork()函式負責處理clone()、fork()和vfork()系統呼叫,利用輔助函式 copy_process()來創建行程描述符以及子行程所需要的內核資料結構,

 

do_fork()之后有了處于可運行狀態的完整子行程,但他還沒有實際運行,調度程式決定何時把CPU交給這個子行程,在以后的行程切換中,調度程式將繼續完善子行程,然后,在fork()、vfork()、clone()系統呼叫結束后,新行程將開始執行,系統呼叫的回傳值放在eax暫存器中,回傳給子行程的值是0,回傳給父行程的是子行程的PID,

 

 

 

內核執行緒

現代作業系統將一些重要的任務交給內核執行緒,內核執行緒不受不必要的用戶態背景關系的拖累,這些任務包括重繪磁盤高速快取,交換出不用的頁框,維護網路連接等等,在Linux中,內核執行緒在以下幾個方面不用于普通行程:

??內核執行緒之運行在內核態,而普通行程既可以運行在內核態,也可以運行在用戶態,

??內核執行緒只使用大于PAGE_OFFSET的線性地址空間,普通行程可以用4GB的線性地址空間,

 

創建一個內核執行緒

kernel_thread()函式創建一個新的內核執行緒,它接受的引數由:所要指向的內核函式的地址(fn)、要傳遞給函式的引數(arg)、一組clone標志(flags),該函式本質以下面方式呼叫do_fork():

do_fork(flags|CLONE_UNTRACED, 0, pregs, 0, NULL, NULL);

CLONE_VM標志避免復制呼叫行程的頁表,CLONE_UNTRACED標志保證不會有任何行程跟蹤,

傳遞給do_fork()的引數pregs表示內核堆疊的地址,copy_thread()函式將從這里找到新執行緒初始化CPU暫存器的值,

 

 

行程0

所有行程的祖先叫做行程0,idel行程或因為歷史的淵源叫做swapper行程,它是在Linux的初始化階段從無到有創建的一個內核執行緒,這個祖先行程使用下列靜態分配的資料結構:

??存放在 init_task 變數中的行程描述符,由 INIT_TASK 宏完成對它的初始化

??存放在 init_thread_union 變數中的 thread_info 描述符和內核堆疊,由 INIT_THREAD_INFO 宏完成對它們的初始化

 

start_kernel()函式初始化內核需要的所有資料結構,激活中斷,創建另一個叫行程1 的內核執行緒(一般叫做init行程):

kernel_thread(init, NULL, CLONE_FS|CLONE_SIGHAND);

新創建的內核執行緒的PID為1,并于行程0共享每行程所有的內核資料結構,此外,當調度程式選擇到它時,init 行程開始執行init()函式,

在多處理器系統中,每個CPU都有一個行程0,打開電源,計算機的BIOS就啟動某一個CPU,同時禁用其他CPU,運行在CPU 0上的swapper行程初始化內核資料結構,然后激活其他的CPU,并通過copy_process()函式創建另外的swapper行程,把0傳遞給新創建的swapper行程作為它們的新PID,

 

行程1(init行程)

由行程0創建的內核執行緒執行init()函式,init()依此完成內核初始化,init()呼叫execve()系統呼叫裝入可執行程式init,結果,init內核執行緒變成一個普通行程,且擁有自己的每行程內核資料結構,在系統關閉之前,init行程一直存活,因為它創建和監控在作業系統外層執行的所有行程的活動,

 

其他內核執行緒

Linux使用很多其他內核執行緒,其中一些在初始化階段創建,一直運行到系統關閉,而其他一些在內核必須執行一個任務時“按需”創建,這種任務在內核的執行背景關系中得到很好的執行,

 

 

行程撤銷

 

行程終止

在Linux2.6中有兩個終止用戶態應用的系統呼叫:

??exit_group()系統呼叫,它終止整個執行緒組,即整個基于多執行緒的應用,do_group_exit()是實作這個系統呼叫的主要內核函式,這是C庫函式應該呼叫的系統呼叫,

??exit()系統呼叫,它終止某一個執行緒,而不管該執行緒所屬執行緒組中的所有其他執行緒,do_exit()是實作這個系統呼叫的主要內核函式,這是被諸如 pthread_exit()的Linux執行緒庫的函式所呼叫的系統呼叫,

 

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

標籤:C

上一篇:如何將 Spire.Doc for C++ 集成到 C++ 程式中

下一篇:返回列表

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

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • 行程

    行程、輕量級行程和執行緒 行程在教科書中通常定義:行程是程式執行時的一個實體,可以把它看作充分描述程式已經執行到何種程度的資料結構的匯集。 從內核的觀點,行程的目的就是擔當分配系統資源(CPU時間、記憶體等)的物體。 當一個行程被創建時,他幾乎于父行程相同。它接受父行程地址空間的一個(邏輯)拷貝,并從進 ......

    uj5u.com 2023-04-28 13:05:22 more
  • 如何將 Spire.Doc for C++ 集成到 C++ 程式中

    Spire.Doc for C++ 是一個專業的 Word 庫,供開發人員在任何型別的 C++ 應用程式中閱讀、創建、編輯、比較和轉換 Word 檔案。 本文演示了如何以兩種不同的方式將 Spire.Doc for C++ 集成到您的 C++ 應用程式中。 通過 NuGet 安裝 Spire.Doc ......

    uj5u.com 2023-04-28 07:59:10 more
  • 線上問題排查回答(轉載)

    面試官:「你是怎么定位線上問題的?」 這個面試題我在兩年社招的時候遇到過,前幾天面試也遇到了。我覺得我每一次都答得中規中矩,今天來梳理復盤下,下次又被問到的時候希望可以答得更好。 下一次我應該會按照這個思路去答: 1、如果線上出現了問題,我們更多的是希望由監控告警發現我們出了線上問題,而不是等到業務 ......

    uj5u.com 2023-04-28 07:57:59 more
  • 行程

    行程、輕量級行程和執行緒 行程在教科書中通常定義:行程是程式執行時的一個實體,可以把它看作充分描述程式已經執行到何種程度的資料結構的匯集。 從內核的觀點,行程的目的就是擔當分配系統資源(CPU時間、記憶體等)的物體。 當一個行程被創建時,他幾乎于父行程相同。它接受父行程地址空間的一個(邏輯)拷貝,并從進 ......

    uj5u.com 2023-04-28 07:54:32 more
  • WPF教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 WPF(Windows Presentation Foundation)是微軟推出的基于Windows 的用戶界面框架,屬于.NET Framework的一部分。它提供了統一的編程模型、語言和框架,真正做到了分離界面設計人員與開發人員的作業;同時它提供了全新的多媒體互動用戶圖形界面。 WP ......

    uj5u.com 2023-04-27 10:22:35 more
  • SpringBoot SpringSecurity 介紹(基于記憶體的驗證)

    SpringBoot 集成 SpringSecurity + MySQL + JWT 附原始碼,廢話不多直接盤 SpringBoot已經為用戶采用默認配置,只需要引入pom依賴就能快速啟動Spring Security。 目的:驗證請求用戶的身份,提供安全訪問 優勢:基于Spring,配置方便,減少大 ......

    uj5u.com 2023-04-27 10:09:09 more
  • 從原理聊JVM(三):詳解現代垃圾回收器Shenandoah和ZGC

    現代的垃圾回收器為了低停頓的目標可謂將“并發”二字玩到極致,Shenandoah在G1基礎上做了非常多的優化來使回收階段并行,而ZGC直接采用了染色指標、NUMA等黑科技,目的都是為了讓Java開發者可以更多的將精力放在如何使用物件讓程式更好的運行,剩下的一切交給GC,我們所做的只需享受現代化GC技... ......

    uj5u.com 2023-04-27 10:05:14 more
  • SpringBoot SpringSecurity 介紹(基于記憶體的驗證)

    SpringBoot 集成 SpringSecurity + MySQL + JWT 附原始碼,廢話不多直接盤 SpringBoot已經為用戶采用默認配置,只需要引入pom依賴就能快速啟動Spring Security。 目的:驗證請求用戶的身份,提供安全訪問 優勢:基于Spring,配置方便,減少大 ......

    uj5u.com 2023-04-27 10:05:02 more
  • 淺談errgroup的使用以及原始碼分析

    本文講解的是golang.org/x/sync這個包中的errgroup 1、errgroup 的基礎介紹 學習過 Go 的朋友都知道 Go 實作并發編程是比較容易的事情,只需要使用go關鍵字就可以開啟一個 goroutine。那對于并發場景中,如何實作goroutine的協調控制呢?常見的一種方式 ......

    uj5u.com 2023-04-27 07:29:54 more
  • boot-admin整合Quartz實作動態管理定時任務

    淄博燒烤爆紅出了圈,當你坐在八大局的燒烤攤,面前是火爐、烤串、小餅和蘸料,音樂響起,啤酒倒滿,燒烤靈魂的party即將開場的時候,你系統中的Scheduler(除錯器),也自動根據設定的Trigger(觸發器),從容優雅的啟動了一系列的Job(后臺定時任務)。作業一切早有安排,又何須費心勞神呢?因為 ......

    uj5u.com 2023-04-27 07:29:47 more