1. 程式人生 > >淺析作業系統的程序、執行緒區別

淺析作業系統的程序、執行緒區別

系統的硬體組成

為了理解一個程式執行時發生了什麼,需要理解一個典型系統的硬體組織.
image
- 匯流排

匯流排(Bus)是計算機各種功能部件之間傳送資訊的公共通訊幹線,它是由導線組成的傳輸線束。

按照計算機所傳輸的資訊種類,計算機的匯流排可以劃分為資料匯流排、地址匯流排和控制匯流排,分別用來傳輸資料、資料地址和控制訊號。

  • I/O裝置
    顯示器、滑鼠、鍵盤等,每個I/O裝置都通過一個控制器或介面卡與 I/O 匯流排相連,從而傳遞資訊.

  • 主存
    一個臨時儲存裝置,在處理器執行程式時,用來存放程式和程式處理的邏輯.

  • 處理器
    計算器能夠執行命令的核心,它是解釋(執行)儲存在主存中指令的引擎,從系統通電開始到系統斷電,CPU 一直在不斷低執行指令。

作業系統

對於以上所說的硬體設施,我們的程式一般不會直接訪問這些硬體裝置,轉而通過作業系統來控制。
作業系統有2個基本功能
1) 對普通程式對硬體的訪問進行限制,防止硬體被失控的應用程式濫用
2) 嚮應用程式提供簡單一直的機制來控制複雜而又大相徑庭的低階硬體裝置.

作業系統通過幾個基本概念( 程序,虛擬儲存器,檔案)來實現這2個功能

image

其中,檔案是對 I/O 裝置的抽象,虛擬儲存器是對 主存和磁碟I/O裝置的抽象表示,程序是對 處理器、主存 和 I/O裝置的抽象表示

程序

程序的概念

為了實現多工系統,現代作業系統提出了程序的概念,在linux系統初期,程序作為 CPU 排程的基本單位,後來由於作業系統普遍引入了執行緒的概念,執行緒成為了CPU排程的基本單位

,而程序只能作為資源擁有的基本單位

作業系統中,程序提供給應用程式兩個重要的抽象:
1) 它在獨立地使用處理器 (通過程序排程,CPU時間片分配)
1) 它在獨立地使用主存(通過程序的虛擬地址空間對映)

程序描述符

有了抽象的概念 ,再來對照著看程序在linux中的實際表示形式,在linux中 task_struct描述符結構體表示一個程序的控制塊,這個 task_struct結構記錄了這個程序所有的context (程序上下文資訊)

struct task_struct{
    //列出部分欄位
    volatitle long state;//表示程序當前的狀態 ,-1表示不可執行,0表示可執行,>0表示停止
void *stack; //程序核心棧 unsigned int ptrace; pid_t pid;//程序號 pid_t tgid;//程序組號 struct mm_struct *mm,*active_mm //使用者空間 核心空間 struct list_head thread_group;//該程序的所有執行緒連結串列 struct list_head thread_node; int leader;//表示程序是否為會話主管 struct thread_struct thread;//該程序在特定CPU下的狀態 //等等欄位:包括一些 表示 使用的檔案描述符、該程序被CPU排程的次數、時間、父子、兄弟程序等資訊 }

虛擬儲存器(虛擬地址空間)

虛擬儲存器是一個抽象的概念,它為每一個程序提供一個假象,即每個程序都在獨佔的使用主存(整個記憶體區域)。
虛擬儲存器對應 上文中的 mm_struct結構,每個程序都有自己獨立的 mm_struct 結構,各個程序都在自己的地址空間中活動,互不干擾。

image

一個虛擬儲存器空間包含以下內容

Text Segment、Data Segment、BBS

TextSegment 表示程式的程式碼段

DataSegment 表示 已經初始化且初值非0的全域性變數和靜態區域性變數
BBS表示未初始化或初始值為0的全域性變數和靜態區域性變數

作業系統負責這些段的載入並分配記憶體空間,這些段在編譯期就分段完成。

Stack(棧)

棧:用於存放 區域性變數函式引數函式返回地址

Heap(堆)

動態分配的記憶體,由使用者自己管理和釋放,堆的大小可以在執行時動態地拓展和收縮(C函式呼叫),
因為空閒地址空間時不連續的,堆在作業系統中是用連結串列來儲存的。

Kernerl Space(核心空間)

核心空間 屬於作業系統的一部分,常駐記憶體。作業系統不允許普通的使用者應用程式讀寫這個區域的內容或者直接呼叫核心空間定義的函式。

虛擬儲存器在核心中的結構圖

image

task_struct中的一個欄位指向了 mm_struct,它描述了虛擬儲存器當前的狀態,其中 pdg
指向了第一級頁表的基地址,而 mmap指向一個 vm_area_structs 區域結構的連結串列,每個vm_area_structs都描述了當前虛擬地址空間的一個區域。

虛擬儲存器是如何使用主存作為快取的

物理儲存器和虛擬儲存器都是用頁來作為磁碟和記憶體的儲存單元。
因為有頁表的存在,沒有必要把虛擬儲存器(在磁碟上)的所有頁快取在記憶體中 ,即使當CPU訪問虛擬儲存器中的頁不存在記憶體中時,系統會通過查表,把需要的頁從磁碟中拷貝到記憶體中(涉及到頁面排程演算法)。
image

程序上下文

程序上下文:一個程序在執行的時候,CPU的所有暫存器中的值、程序的狀態以及堆疊上的內容。核心在進行程序的切換時,需要儲存當前程序的所有狀態,即儲存當前程序的上下文,以便再次執行該程序時,能夠恢復切換時的狀態,繼續執行。

執行緒

執行緒在linux中有時被稱為輕量級程序(LightweightProcess,LWP),是程式執行流的最小單元。一個標準的執行緒由執行緒ID,當前指令指標(PC),暫存器集合和堆疊組成。另外,執行緒是程序中的一個實體,是被系統獨立排程和分派的基本單位,執行緒自己不擁有系統資源,只擁有一點兒在執行中必不可少的資源,但它可與同屬一個程序的其它執行緒共享程序所擁有的全部資源(共享整個虛擬地址空間)。

執行緒的實現

在linux中,實際上執行緒和程序使用的是同一個結構體 task_struct,在 linux 眼裡,不會區分程序和執行緒,核心在程序任務排程時,僅僅是根據排程演算法選擇一個 task_struct,至於這個task_struct,到底是程序、還是執行緒 核心並不關心。
屬於同一個程序有一個執行緒組的概念,當建立一個執行緒時,建立一個 task_struct結構體,但是這個 task_struct 共享程序的 虛擬記憶體 (也就是 task_struct 結構中的 mm_sruct mm欄位)。正是由於多執行緒共享 mm_struct mm

一個多執行緒在核心中的資料模型圖

image
在linux C中,使用 fork()函式建立程序,使用pthread_create建立執行緒,
上圖中,右邊的task_struct 是由左邊的 task_struct 通過 pthread_create()建立的。這2個task_struct 通過指標共享一個mm_struct。

上下文切換

當CPU 執行從一個執行緒(程序)到另一個執行緒(程序)時,需要先儲存當前工作 執行緒的上下文資訊,以便下次回來時重新執行。這些資訊包括,
多執行緒同多程序一樣,存在一個上下文切換的消耗,但是執行緒的上下文切換要比程序小的多\
image

簡單的邏輯描述

  1. 程序A執行中
  2. 時鐘中斷髮生,ring1->ring0 ,時鐘中斷處理程式啟動
  3. 程序排程,下一個應執行的程序B 被指定
  4. 程序B被恢復,ring0->ring1
  5. 程序B執行中

ring 是CPU的特權級別,ring0 是核心級別

多執行緒 vs 多程序

  • 多執行緒之間堆記憶體共享,而程序相互獨立,執行緒間通訊可以直接基於共享記憶體來實現,比程序的常用的那些多程序通訊方式更輕量。
  • 在上下文切換來說,不管是多執行緒還是都程序都涉及到暫存器、棧的儲存,但是執行緒不需要切換 頁面對映(虛擬記憶體空間)、檔案描述符等,所以執行緒的上下文切換也比多程序輕量
  • 多程序比多執行緒更安全,一個程序基本上不會影響另外一個程序
    在實際的開發中,一般不同任務間(可以把一個執行緒、程序叫做一個任務)需要通訊,使用多執行緒的場景比多程序多。但是多程序有更高的容錯性,一個程序的crash不會導致整個系統的崩潰,在任務安全性較高的情況下,採用多程序。

協程、共行程式、Coroutine

直接通過協程的特性來理解
- 協程是使用者模式下的輕量級執行緒,作業系統核心對協程一無所知
- 協程的排程完全有應用程式來控制,作業系統不管這部分的排程
- 一個執行緒可以包含一個或多個協程
- 協程擁有自己的暫存器上下文和棧,協程排程切換時,將暫存器上下紋和棧儲存起來,在切換回來- - 時恢復先前保運的寄存上下文和棧
- 協程能保留上一次呼叫時的狀態,看到這裡各種生成器(生成器是被閹割的協程)的概念浮現出來了。。
Windows下的實現叫纖程

棧溢位

public class Stack {
    static  int time = 0;
    public static void main(String[] args) {
        test();
    }
    public static void test(){
        time++;
        System.out.println("time ="+time);
        test();
    }
}
//time =6957Exception in thread "main" java.lang.StackOverflowError

Java 在建立執行緒時設定棧大小

    /**
    *     * <p>The virtual machine is free to treat the {@code stackSize}
     * parameter as a suggestion.  If the specified value is unreasonably low
     * for the platform, the virtual machine may instead use some
     * platform-specific minimum value; if the specified value is unreasonably
     * high, the virtual machine may instead use some platform-specific
     * maximum.  Likewise, the virtual machine is free to round the specified
     * value up or down as it sees fit (or to ignore it completely).
    */
    public Thread(ThreadGroup group, Runnable target, String name,
                  long stackSize) {
        init(group, target, name, stackSize);
    }

堆溢位

public class Heap {
    public static void main(String[] args)
    {
        ArrayList v=new ArrayList();
        for(int i=0;i<25;i++)
            v.add(new byte[1*1024*1024*1024]);
    }
}
//Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
//  at Heap.main(Heap.java:13)

Java 虛擬機器指定堆大小 (堆是執行緒共享區域)

Java虛擬機器的堆大小如何設定:命令列
 java –Xms128m   //JVM初始分配的堆記憶體
     –Xmx512m   //VM最大允許分配的堆記憶體,按需分配
     –XX:PermSize=64m   //JVM初始分配的永久代記憶體
     –XX:MaxPermSize=128m //JVM最大允許分配的永久代,按需分配