linux c进程和线程脑图,进程和线程
關于進程和線程,你需要理解下面這張腦圖中的重點
進程
操作系統中最核心的概念就是?進程,進程是對正在運行中的程序的一個抽象。操作系統的其他所有內容都是圍繞著進程展開的。
在多道程序處理的系統中,CPU 會在進程間快速切換,使每個程序運行幾十或者幾百毫秒。然而,嚴格意義來說,在某一個瞬間,CPU 只能運行一個進程,然而我們如果把時間定位為 1 秒內的話,它可能運行多個進程。這樣就會讓我們產生并行的錯覺。因為 CPU 執行速度很快,進程間的換進換出也非常迅速,因此我們很難對多個并行進程進行跟蹤。所以,操作系統的設計者開發了用于描述并行的一種概念模型(順序進程),使得并行更加容易理解和分析。
進程模型
一個進程就是一個正在執行的程序的實例,進程也包括程序計數器、寄存器和變量的當前值。從概念上來說,每個進程都有各自的虛擬 CPU,但是實際情況是 CPU 會在各個進程之間進行來回切換。
如上圖所示,這是一個具有 4 個程序的多道處理程序,在進程不斷切換的過程中,程序計數器也在不同的變化。
在上圖中,這 4 道程序被抽象為 4 個擁有各自控制流程(即每個自己的程序計數器)的進程,并且每個程序都獨立的運行。當然,實際上只有一個物理程序計數器,每個程序要運行時,其邏輯程序計數器會裝載到物理程序計數器中。當程序運行結束后,其物理程序計數器就會是真正的程序計數器,然后再把它放回進程的邏輯計數器中。
從下圖我們可以看到,在觀察足夠長的一段時間后,所有的進程都運行了,「但在任何一個給定的瞬間僅有一個進程真正運行」。
因此,當我們說一個 CPU 只能真正一次運行一個進程的時候,即使有 2 個核(或 CPU),「每一個核也只能一次運行一個線程」。
由于 CPU 會在各個進程之間來回快速切換,所以每個進程在 CPU 中的運行時間是無法確定的。并且當同一個進程再次在 CPU 中運行時,其在 CPU 內部的運行時間往往也是不固定的。
這里的關鍵思想是認識到一個進程所需的條件,進程是某一類特定活動的總和,它有程序、輸入輸出以及狀態。
進程的創建
操作系統需要一些方式來創建進程。下面是一些創建進程的方式:系統初始化(init):啟動操作系統時,通常會創建若干個進程。
正在運行的程序執行了創建進程的系統調用(比如 fork)
用戶請求創建一個新進程:在許多交互式系統中,輸入一個命令或者雙擊圖標就可以啟動程序,以上任意一種操作都可以選擇開啟一個新的進程,在基本的 UNIX 系統中運行 X,新進程將接管啟動它的窗口。
初始化一個批處理工作
從技術上講,在所有這些情況下,讓現有流程執行流程是通過創建系統調用來創建新流程的。該進程可能是正在運行的用戶進程,是從鍵盤或鼠標調用的系統進程或批處理程序。這些就是系統調用創建新進程的過程。該系統調用告訴操作系統創建一個新進程,并直接或間接指示在其中運行哪個程序。
在 UNIX 中,僅有一個系統調用來創建一個新的進程,這個系統調用就是?fork。這個調用會創建一個與調用進程相關的副本。在 fork 后,一個父進程和子進程會有相同的內存映像,相同的環境字符串和相同的打開文件。
在 Windows 中,情況正相反,一個簡單的 Win32 功能調用?CreateProcess,會處理流程創建并將正確的程序加載到新的進程中。這個調用會有 10 個參數,包括了需要執行的程序、輸入給程序的命令行參數、各種安全屬性、有關打開的文件是否繼承控制位、優先級信息、進程所需要創建的窗口規格以及指向一個結構的指針,在該結構中新創建進程的信息被返回給調用者。「在 Windows 中,從一開始父進程的地址空間和子進程的地址空間就是不同的」。
進程的終止
進程在創建之后,它就開始運行并做完成任務。然而,沒有什么事兒是永不停歇的,包括進程也一樣。進程早晚會發生終止,但是通常是由于以下情況觸發的
正常退出(自愿的)?:多數進程是由于完成了工作而終止。當編譯器完成了所給定程序的編譯之后,編譯器會執行一個系統調用告訴操作系統它完成了工作。這個調用在 UNIX 中是?exit?,在 Windows 中是?ExitProcess。
錯誤退出(自愿的):比如執行一條不存在的命令,于是編譯器就會提醒并退出。
嚴重錯誤(非自愿的)
被其他進程殺死(非自愿的)?:某個進程執行系統調用告訴操作系統殺死某個進程。在 UNIX 中,這個系統調用是 kill。在 Win32 中對應的函數是?TerminateProcess(注意不是系統調用)。
進程的層次結構
在一些系統中,當一個進程創建了其他進程后,父進程和子進程就會以某種方式進行關聯。子進程它自己就會創建更多進程,從而形成一個進程層次結構。
UNIX 進程體系
在 UNIX 中,進程和它的所有子進程以及子進程的子進程共同組成一個進程組。當用戶從鍵盤中發出一個信號后,該信號被發送給當前與鍵盤相關的進程組中的所有成員(它們通常是在當前窗口創建的所有活動進程)。每個進程可以分別捕獲該信號、忽略該信號或采取默認的動作,即被信號 kill 掉。整個操作系統中所有的進程都隸屬于一個單個以 init 為根的進程樹。
Windows 進程體系
相反,Windows 中沒有進程層次的概念,Windows 中所有進程都是平等的,唯一類似于層次結構的是在創建進程的時候,父進程得到一個特別的令牌(稱為句柄),該句柄可以用來控制子進程。然而,這個令牌可能也會移交給別的操作系統,這樣就不存在層次結構了。而在 UNIX 中,進程不能剝奪其子進程的?進程權。(這樣看來,還是 Windows 比較渣)。
進程狀態
盡管每個進程是一個獨立的實體,有其自己的程序計數器和內部狀態,但是,進程之間仍然需要相互幫助。當一個進程開始運行時,它可能會經歷下面這幾種狀態
圖中會涉及三種狀態:運行態,運行態指的就是進程實際占用 CPU 時間片運行時
就緒態,就緒態指的是可運行,但因為其他進程正在運行而處于就緒狀態
阻塞態,除非某種外部事件發生,否則進程不能運行
進程的實現
操作系統為了執行進程間的切換,會維護著一張表,這張表就是?進程表(process table)。每個進程占用一個進程表項。該表項包含了進程狀態的重要信息,包括程序計數器、堆棧指針、內存分配狀況、所打開文件的狀態、賬號和調度信息,以及其他在進程由運行態轉換到就緒態或阻塞態時所必須保存的信息。
下面展示了一個典型系統中的關鍵字段
典型的進程表表項中的一些字段
第一列內容與進程管理有關,第二列內容與?存儲管理有關,第三列內容與文件管理有關。
現在我們應該對進程表有個大致的了解了,就可以在對單個 CPU 上如何運行多個順序進程的錯覺做更多的解釋。與每一 I/O 類相關聯的是一個稱作?中斷向量(interrupt vector)?的位置(靠近內存底部的固定區域)。它包含中斷服務程序的入口地址。假設當一個磁盤中斷發生時,用戶進程 3 正在運行,則中斷硬件將程序計數器、程序狀態字、有時還有一個或多個寄存器壓入堆棧,計算機隨即跳轉到中斷向量所指示的地址。這就是硬件所做的事情。然后軟件就隨即接管一切剩余的工作。
當中斷結束后,操作系統會調用一個 C 程序來處理中斷剩下的工作。在完成剩下的工作后,會使某些進程就緒,接著調用調度程序,決定隨后運行哪個進程。然后將控制權轉移給一段匯編語言代碼,為當前的進程裝入寄存器值以及內存映射并啟動該進程運行,下面顯示了中斷處理和調度的過程。
硬件壓入堆棧程序計數器等
硬件從中斷向量裝入新的程序計數器
匯編語言過程保存寄存器的值
匯編語言過程設置新的堆棧
C 中斷服務器運行(典型的讀和緩存寫入)
調度器決定下面哪個程序先運行
C 過程返回至匯編代碼
匯編語言過程開始運行新的當前進程
一個進程在執行過程中可能被中斷數千次,但關鍵每次中斷后,被中斷的進程都返回到與中斷發生前完全相同的狀態。
線程
在傳統的操作系統中,每個進程都有一個地址空間和一個控制線程。事實上,這是大部分進程的定義。不過,在許多情況下,經常存在同一地址空間中運行多個控制線程的情形,這些線程就像是分離的進程。下面我們就著重探討一下什么是線程
線程的使用
或許這個疑問也是你的疑問,為什么要在進程的基礎上再創建一個線程的概念,準確的說,這其實是進程模型和線程模型的討論,回答這個問題,可能需要分三步來回答:
多線程之間會共享同一塊地址空間和所有可用數據的能力,這是進程所不具備的
線程要比進程更輕量級,由于線程更輕,所以它比進程更容易創建,也更容易撤銷。在許多系統中,創建一個線程要比創建一個進程快 10 - 100 倍。
第三個原因可能是性能方面的探討,如果多個線程都是 CPU 密集型的,那么并不能獲得性能上的增強,但是如果存在著大量的計算和大量的 I/O 處理,擁有多個線程能在這些活動中彼此重疊進行,從而會加快應用程序的執行速度
經典的線程模型
進程中擁有一個執行的線程,通常簡寫為?線程(thread)。線程會有程序計數器,用來記錄接著要執行哪一條指令;線程實際上 CPU 上調度執行的實體。
下圖我們可以看到三個傳統的進程,每個進程有自己的地址空間和單個控制線程。每個線程都在不同的地址空間中運行
下圖中,我們可以看到有一個進程三個線程的情況。每個線程都在相同的地址空間中運行。
線程不像是進程那樣具備較強的獨立性。同一個進程中的所有線程都會有完全一樣的地址空間,這意味著它們也共享同樣的全局變量。由于每個線程都可以訪問進程地址空間內每個內存地址,「因此一個線程可以讀取、寫入甚至擦除另一個線程的堆棧」。線程之間除了共享同一內存空間外,還具有如下不同的內容:
上圖左邊的是同一個進程中每個線程共享的內容,上圖右邊是每個線程中的內容。也就是說左邊的列表是進程的屬性,右邊的列表是線程的屬性。
「線程之間的狀態轉換和進程之間的狀態轉換是一樣的」。
每個線程都會有自己的堆棧,如下圖所示:
線程系統調用
進程通常會從當前的某個單線程開始,然后這個線程通過調用一個庫函數(比如?thread_create)創建新的線程。線程創建的函數會要求指定新創建線程的名稱。創建的線程通常都返回一個線程標識符,該標識符就是新線程的名字。
當一個線程完成工作后,可以通過調用一個函數(比如?thread_exit)來退出。緊接著線程消失,狀態變為終止,不能再進行調度。在某些線程的運行過程中,可以通過調用函數例如?thread_join?,表示一個線程可以等待另一個線程退出。這個過程阻塞調用線程直到等待特定的線程退出。在這種情況下,線程的創建和終止非常類似于進程的創建和終止。
另一個常見的線程是調用?thread_yield,它允許線程自動放棄 CPU 從而讓另一個線程運行。這樣一個調用還是很重要的,因為不同于進程,線程是無法利用時鐘中斷強制讓線程讓出 CPU 的。
POSIX 線程
POSIX 線程 通常稱為 pthreads是一種獨立于語言而存在的執行模型,以及并行執行模型。
它允許程序控制時間上重疊的多個不同的工作流程。每個工作流程都稱為一個線程,可以通過調用 POSIX Threads API 來實現對這些流程的創建和控制。可以把它理解為線程的標準。
?
POSIX Threads 的實現在許多類似且符合POSIX的操作系統上可用,例如「FreeBSD、NetBSD、OpenBSD、Linux、macOS、Android、Solaris」,它在現有 Windows API 之上實現了「pthread」。
IEEE 是世界上最大的技術專業組織,致力于為人類的利益而發展技術。
?
線程調用描述
pthread_create創建一個新線程
pthread_exit結束調用的線程
pthread_join等待一個特定的線程退出
pthread_yield釋放 CPU 來運行另外一個線程
pthread_attr_init創建并初始化一個線程的屬性結構
pthread_attr_destory刪除一個線程的屬性結構
所有的 Pthreads 都有特定的屬性,每一個都含有標識符、一組寄存器(包括程序計數器)和一組存儲在結構中的屬性。這個屬性包括堆棧大小、調度參數以及其他線程需要的項目。
線程實現
主要有三種實現方式
在用戶空間中實現線程;
在內核空間中實現線程;
在用戶和內核空間中混合實現線程。
下面我們分開討論一下
在用戶空間中實現線程
第一種方法是把整個線程包放在用戶空間中,內核對線程一無所知,它不知道線程的存在。所有的這類實現都有同樣的通用結構
在用戶空間中實現多線程
線程在運行時系統之上運行,運行時系統是管理線程過程的集合,包括前面提到的四個過程:pthread_create, pthread_exit, pthread_join 和 pthread_yield。
在內核中實現線程
當某個線程希望創建一個新線程或撤銷一個已有線程時,它會進行一個系統調用,這個系統調用通過對線程表的更新來完成線程創建或銷毀工作。
在內核中實現多線程
內核中的線程表持有每個線程的寄存器、狀態和其他信息。這些信息和用戶空間中的線程信息相同,但是位置卻被放在了內核中而不是用戶空間中。另外,內核還維護了一張進程表用來跟蹤系統狀態。
所有能夠阻塞的調用都會通過系統調用的方式來實現,當一個線程阻塞時,內核可以進行選擇,是運行在同一個進程中的另一個線程(如果有就緒線程的話)還是運行一個另一個進程中的線程。但是在用戶實現中,運行時系統始終運行自己的線程,直到內核剝奪它的
CPU 時間片(或者沒有可運行的線程存在了)為止。
混合實現
結合用戶空間和內核空間的優點,設計人員采用了一種內核級線程的方式,然后將用戶級線程與某些或者全部內核線程多路復用起來
用戶線程與內核線程的多路復用
在這種模型中,編程人員可以自由控制用戶線程和內核線程的數量,具有很大的靈活度。采用這種方法,內核只識別內核級線程,并對其進行調度。其中一些內核級線程會被多個用戶級線程多路復用。
總結
以上是生活随笔為你收集整理的linux c进程和线程脑图,进程和线程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 广告商为何还是不回推特打广告,行业高管称
- 下一篇: linux系统网络对时,Linux系统网