進程與線程學習筆記

進程與線程學習筆記


1、系統調用


在程序狀態字(Program Status Word, PSW)寄存器中有一個二進制位控制CPU的兩種工作模式(內核態和用戶態)。在內核態運行時,CPU可以執行指令集中的每一條指令,操作系統在內核態下運行,從而可以訪問整個硬件。用戶程序在用戶態下運行,僅允許執行整個指令集中的一個子集。一般而言,在用戶態中有關I/O和內存保護的所有指令都是禁止的。爲了從操作系統中獲得服務,用戶程序使用系統調用(system call)陷入內核,TRAP(陷阱)指令把用戶態切換成內核態,並啓用OS,當有關工作完成之後,在系統調用後面的指令把控制權返回給用戶程序。


下面列出了一些重要的 POSIX 系統調用

pid = fork()

創建與父進程相同的子進程

pid = waitpid(pid, &statloc, options)

等待一個進程終止

s = execve(name, argv, environp)

替換一個進程的地址空間

exit(status)

中止進程執行並返回狀態

s = kill(pid, signal)

發送信號給一個進程

fd = open(file, how, …)

打開一個文件供讀、寫或兩者

s = close(fd)

關閉一個打開的文件

n = read(fd, buffer, nbytes)

把數據從一個文件讀到緩衝區

n = write(fd, buffer, nbytes)

把數據從緩衝區寫到一個文件中

position = lseek(fd, offset, whence)

移動文件指針

s = stat(name, &buf)

獲取文件的狀態信息

s = mkdir(name, mode)

創建一個新目錄

s = rmdir(name)

刪除一個空目錄


在Unix中,系統調用和系統調用所使用的庫過程之間幾乎是一一對應的關係。且進程之間存在層次關係,稱爲進程樹,所有進程都屬於以init(OS啓動進程)爲根的一棵樹。

在Windows中,我們通過API獲得操作系統的服務,但大部分API和系統調用不存在對應關係。且不存在父進程和子進程的概念,進程創建之後,創建者和被創建者是平等的。

注意:fork()創建一個原有進程的精確副本,子進程可以通過execve()替換不同的映像文件,從而執行不同的程序;waitpid()用於掛起調用進程,直到其它(特定的)進程退出。kill()供用戶進程發送信號,若一個進程準備好捕捉一個特定的信號,那麼,在信號到來時,運行一個信號處理程序,如果該進程沒有準備好,那麼信號的到來會殺死該進程。


2、進程


進程(process)本質上是正在執行的一個程序,是容納運行一個程序所需要所有信息的容器。與一個進程相關的是進程的地址空間(address space)和進程表(process table)。進程的地址空間包括代碼段、數據段、堆棧段。下面畫出了進程的三種狀態,以及狀態之間的切換:




在多任務系統中,CPU使用某種調度算法在不同進程間來回快速切換,就好像這幾個進程在併發執行一樣。進程間的切換是通過中斷(interrupt)來實現的。OS維護一張表格,即進程表,每個進程佔用一個進程表項(也稱進程控制塊),該表項包含了進程狀態的重要信息,包括程序計數器(保存了下一條指令的內存地址)、堆棧指針、內存分配狀況、所打開文件的狀態、賬號和調度信息,以及其他在進程由運行態轉換到就緒態或阻塞態時必須保存的信息,從而保證該進程隨後能再次啓動,就像從未被中段過一樣。

線程是把進程的兩項功能——獨立分配資源與被調度分派執行分離開來。進程作爲系統資源分配和保護的獨立單位,不需要頻繁地切換。線程作爲系統調度和分派的基本單位,能輕裝運行,會被頻繁地調度和切換。


3、線程(thread)


在傳統的操作系統中,進程有一個地址空間和一個控制線程;而在多線程系統中,一個進程的地址空間中同時運行多個控制線程。

 

  


多線程的優點:

(1) 應用程序中往往同時發生着多種活動,其中某些活動隨時間的推移會被阻塞。通過將這些應用程序分解成可以準並運行的多個順序線程,程序設計模型會變得更加簡單。

(2) 線程比進程更輕量級,更容易創建和撤銷。

(3) 如果應用程序中存在大量的計算和I/O處理,擁有多個線程允許這些活動彼此重疊進行,從而會加快程序執行的速度。

(4) 在多CPU系統中,多線程可以實現真正的並行運算。


在同一個進程中並行運行多個線程,是對在同一臺計算機上並行運行多個進程的模擬。因此,線程也被稱爲輕量級進程(lightweight process)。與進程調度類似,CPU在線程之間快速切換,製造了線程並行運行的假象。不同的進程之間有獨立的地址空間,而線程之間有完全一樣的地址空間,這意味着它們共享同樣的全局變量。

由於各個線程都可以訪問進程地址空間的每一個內存地址,所以一個線程可以讀、寫,甚至清除另一個線程的堆棧。也就是說,線程之間是沒有保護的。我們知道,不同的進程可能屬於不同的用戶所擁有,它們之間可能存在敵意。但用戶創建多線程是爲了它們之間的合作而不是彼此爭鬥。除了共享地址空間外,所有線程還共享同一個打開的文件集、子進程、報警以及相關信號等。但要注意的是,每個線程都有自己的堆棧、程序計數器、寄存器等信息,這些不是共享的。


下面列出了幾個 POSIX 線程標準

Pthread_create

創建一個新線程

Pthread_exit

結束調用的線程

Pthread_join

阻塞調用線程直到某個其它(特定)線程退出

Pthread_yield

主動釋放CPU從而讓另外一個線程運行

Pthread_attr_init

創建並初始化一個線程的屬性結構

Pthread_attr_destroy

刪除一個線程的屬性結構


與進程不同的是,無法利用時鐘中斷強制線程讓出CPU,所以我們設法使線程行爲“高尚”起來,並且隨着時間的推移自動交出CPU,以便讓其他線程有機會運行,這正是Pthread_yield系統調用的作用。


3.1 在用戶空間中實現線程

有兩種主要的方法實現線程包:在用戶空間中和在內核中。前者是把整個線程包放在用戶空間中,內核對線程包一無所知,仍按正常的單線程進程方式管理。



在用戶空間實現線程,線程在一個運行時系統(即線程庫)的頂部運行,這個運行時系統是一個管理線程的過程的集合,包括pthread_create、pthread_exit、pthread_join、pthread_yield等過程。每個進程需要其專門的線程表(thread table),用來跟蹤該進程中的線程。線程表由運行時系統管理,當一個線程轉換到就緒狀態或阻塞狀態時,在該線程表中存放重啓該線程所需的信息,與內核在進程表中存放的進程的信息完全一樣。
當某個線程做了一些會引起在本地阻塞的事情之後,例如等待進程中另一個線程完成某些工作,它調用一個運行時系統的過程,這個過程檢查該線程是否必須進入阻塞狀態。如果是,它在線程表中保持該線程的寄存器,並查看錶中可運行的就緒線程,並把新線程的保存值重新裝入機器的寄存器中。只要堆棧指針和程序計數器一被切換,新線程就又自動投入運行。

在用戶空間中實現線程包的優點:

(1) 用戶級線程包可以在不支持線程的操作系統上實現。 

(2) 線程切換至少要比陷入內核要快一個數量級。在線程完成運行時,它調用thread_yield可以把該線程的信息保存在線程表中;進而,它可以調用線程調度程序來選擇另一個要運行的線程。保存該線程狀態的過程和調度程序都只是本地過程,所以啓動它們比進行內核調用效率更高。另一方面,不需要陷阱,不需要上下文切換,也不需要對內存高速緩存進行刷新,這使得線程調度非常快捷。

(3) 允許每個進程有自己定製的調度算法。

(4) 具有較好的可擴展性,這是因爲在內核空間中內核線程需要一些固定表格空間和堆棧空間,當內核線程的數量非常大,就會出現問題。


在用戶空間中實現線程包的缺點:

(1) 如何實現阻塞系統調用而不影響其它線程。一種方法是使用非阻塞版本的系統調用;另一種方法是使用包裝器(jacket 或 wrapper),在進行阻塞系統調用之前檢查是否會引起阻塞,如果調用會被阻塞,有關的調用就不進行,代之以運行另一個線程。

(2) 在一個單獨的進程內部,沒有時鐘中斷,所以不能用輪轉調度的方式調度線程。如果一個線程開始運行,那麼在該進程中的其他線程就不能運行,除非第一個線程自動放棄CPU。


3.2 在內核中實現線程包

在內核中實現線程,此時不再需要運行時系統。另外,每個進程中也沒有線程表,相反,在內核中用來記錄系統中所有線程的線程表。當一個線程阻塞時,內核可以根據其選擇,可以運行同一個進程中的另一個線程,或者運行另一個進程中的線程。而在用戶級線程中,運行時系統始終運行自己進程中的線程,直到內核剝奪它的CPU爲止。
內核級線程的缺點是:應用程序線程在用戶態運行,而線程調度和管理在內核實現。在同一進程中,控制權從一個線程轉移到另一個線程,需要用戶態-內核態-用戶態的模式切換,系統開銷較大。
綜上:用戶級線程和內核級線程之間的差別在於性能。用戶級線程的切換需要少量的機器指令,而內核級線程需要完整的上下文切換,修改內存映像,使高速緩存失效,這導致了若干數量級的延遲。另一方面,在使用內核級線程時,一旦線程阻塞在I/O就不需要像在用戶級線程中那樣將整個進程掛起。

上面我們分別研究了用戶級線程和內核級線程,如果能夠將兩者結合起來,可以實現混合式線程,如下圖所示。


採用混合式線程,內核只識別內核級線程,並對其進行調度。其中一些內核級線程會被多個用戶級線程多路複用,在這種模型中,每個內核級線程有一個可以輪流使用的用戶級線程集合。


4、進程調度機制


進程切換的代價是比較高的:首先必須從用戶態切換到內核態,然後保存當前進程的狀態以便以後重新裝載,接着通過調度程序(scheduler)選定一個新進程,之後將新進程的內存映像重新裝入,最後新進程開始運行。可見,選擇一個好的進程調度算法(scheduling algorithm)非常重要。通常我們將進程劃分兩類:I/O密集型(IO-bound)和計算密集型(computer-bound),更多的進程傾向爲I/O-bound,這是因爲CPU的技術進步速度遠快於磁盤。

何時調度:
(1) 在創建一個新進程之後,需要決定是運行父進程還是運行子進程。
(2) 在一個進程退出時必須做出調度決策,從就緒進程隊列中選擇一個進程運行。
(3) 當一個進程被阻塞在I/O或信號量或其他原因時,必須選擇另外一個進程運行。
(4) 在一個I/O中斷髮生時,必須做出調度決策。如果中斷來自I/O設備,當該設備完成工作後,被阻塞等待該I/O的進程就可以成爲就緒進程,等待調度。

根據如何處理時鐘中斷,可以把調度算法分爲兩類:
(1) 搶佔式:進程運行某個固定時段的最大值,在該時刻結束時被掛起,並由調度程序挑選另一個進程運行(根據優先級)。
(2) 非搶佔式:進程運行直至被阻塞(阻塞在I/O上或等待另一個進程),或者直到該進程自動釋放CPU。

在不同類型的系統中,調度策略也不相同,下面介紹三種環境下的調度機制:

4.1 批處理系統中的調度

批處理系統主要應用商業領域或科研單位等大型機構,批處理系統的三個主要指標爲:吞吐量(單位時間完成的作業數量);週轉時間(平均完成每個作業所需的時間);CPU利用率。如下面所示,假如有4個作業需要處理,每個作業需要的CPU單位時間標記在括號裏,這兩種調度方法的吞吐量是一樣的,都需要20個CPU單位時間。但週轉時間不一樣,第一個的週轉時間爲(8+12+16+20)/4=14;第二個的週轉時間爲(4+8+12+20)/4=11。

----A(8)-----B(4)-----C(4)-----D(4)----- 

----D(4)-----B(4)-----C(4)-----A(8)-----


(1) 先來先服務(非搶佔式):使用一個隊列記錄所有就緒進程,從隊列頭部選擇一個要運行的進程;要添加一個新的作業或阻塞一個進程,只要把該作業或進程追加到隊尾即可。先來先服務對於I/O-bond進程來說,CPU利用率很低。
(2) 最短作業優先(非搶佔式):如果各個作業的CPU時間是可預期的,將進程按照它們各自所需要的CPU時間排序,優先執行佔用CPU時間短的作業,以達到最短的作業週轉時間。
(3) 最短剩餘時間優先(搶佔式):最短作業優先的搶佔式版本,調度程序總是選擇剩餘時間最短的那個進程運行。當然,有關的運行時間必須提前掌握。

4.2 交互式系統中的調度

交互式系統在個人計算機、服務器等系統中比較常見,它對響應時間和均衡性要求比較高。
(1) 輪轉調度:每個進程被分配一個時間段,稱爲時間片(quantum),即允許該進程在該時間段中運行。如果在時間片結束時刻該進程還在運行,則將剝奪CPU並分配給另一個進程。如果該進程在時間片結束前阻塞或結束,則CPU立即切換。注意:時間片設置太短會導致過多的進程切換,降低CPU效率;而設得太長又可能引起對短的交互請求的響應時間變長。
(2) 優先級調度:輪轉調度隱含一個假設,即所有進程同等重要。而優先級調度給每個進程賦予一個優先級,並允許優先級高的進程先運行。爲了防止高優先級進程無休止地運行下去,調度程序可以在每個時鐘中斷降低當前進程的優先級。例如I/O-bound進程可以被賦予較高的優先級,以改善對用戶的交互性。
(3) 多級隊列:
(4) 最短進程優先:從當前進程中選擇需要CPU時間最短的進程運行,以獲得最短的響應時間。不過,這裏需要某種算法來推測各個進程的運行時間。
(5) 保證調度:假設每個進程需要的CPU時間是可預期的,系統跟蹤各個進程自創建以來已使用的CPU時間,然後計算出各個進程真正獲得的CPU時間和應獲得的CPU時間之比,調度程序優先讓這個比率值比較低的進程運行。
(6) 彩票調度:系統向將進程發放彩票,調度程序隨機抽出一張彩票,擁有該彩票的進程獲得資源。各個進程擁有的彩票份額與它們獲得的CPU時間大致成正比。

(7) 公平分享調度:考慮進程的擁有者,做到讓不同用戶的進程獲得大致相等的CPU時間。


4.3 實時系統中的調度
實時系統是一種時間起着主導作用的系統,它對截止時間的要求比較高。










發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章