論文:Linux程序和執行緒的基礎與管理

才智咖 人氣:3.86K

一、程序的基本概念

論文:Linux程序和執行緒的基礎與管理

程式是為了完成某種任務而設計的軟體,比如vi是程式。什麼是程序呢? 程序就是執行中的程式。一個執行著程式,可能有多個程序。比如Web伺服器是Apache伺服器,當管理員啟動服務後,可能會有好多人來訪問,也就是說許多使用者同時請求httpd,Apache伺服器將會建立多個httpd程序來對其進行服務。

首先我們看看程序的定義。程序是一個具有獨立功能的程式關於某個資料集合的一次可以併發執行的執行活動,是處於活動狀態的計算機程式。程序作為構成系統的基本細胞,不僅是系統內部獨立執行的實體,而且是獨立競爭資源的基本實體。瞭解程序的本質,對於理解、描述和設計作業系統有著極為重要的意義。瞭解程序的活動、狀態,也有利於編制複雜程式。

二、程序的屬性

程序的定義:一個程序是一個程式的一次執行的過程;程式是靜態的,它是一些儲存在磁碟上的可執行的程式碼和資料集合;程序是一個動態的概念,它是Linux系統的基本的排程單位。

一個程序由如下元素組成:

程式讀取的上下文,它表示程式讀取執行的狀態。 程式當前執行的目錄。 程式服務的檔案和目錄。 程式訪問的許可權。 記憶體和其他分配給程序的系統資源。

Linux程序中最知名的屬性就是它的程序號(Process Idenity Number,PID)和它的父程序號(Parent Process ID,PPID)。PID、PPID都是非零正整數。一個PID唯一地標識一個程序。一個程序建立新程序稱為建立了子程序(Child Process)。相反地,建立子程序的程序稱為父程序。所有程序追溯其祖先最終都會落到進號為1的程序身上,這個程序叫做init程序,是核心自舉後第一個啟動的程序。init程序扮演終結父程序的角色。因為init程序永遠不會終止,所以系統總是可以確信它的存在,並在必要的時候以它為參照。如果某個程序它在衍生出來的全部子程序結束之前被終止,就會出現必須以init為參照的情況。此時那些失去了父程序的子程序就都會以init作為它們的父程序。如果執行一下ps-af命令,可以列出許多父程序ID為1的程序來。Linux提供了一條pstree命令,允許使用者檢視系統內正在執行的各個程序之間的繼承關係。直接在命令列中輸入pstree即可,程式會以樹狀結構方式列出系統中正在執行的各程序之間的繼承關係。

三、理解Linux下程序的結構

Linux中一個程序在記憶體裡有三部分資料,就是“資料段”、“堆疊段”、“程式碼段”。基於I386相容的中央處理器,都有上述三種段暫存器,以方便作業系統的執行,如下圖所示。

程式碼段

資料段

堆疊段

程式碼段是存放了程式程式碼的資料,假如機器中有數個程序執行相同的一個程式,那麼它們就可以使用同一個程式碼段。而資料段則存放程式的全域性變數、常數及動態資料分配的資料空間。堆疊段存放的就是子程序的返回地址、子程式的引數及程式的區域性變數。堆疊段包含在程序控制塊PCB(Process Control Block)中。PCB處於程序核心堆疊的底部,不需要額外分配空間。

四、程序狀態

現在我們來看看,程序在生存週期中的各種狀態及狀態的轉換。下面是Linux系統的程序狀態模型的各種狀態。

使用者狀態:程序在使用者狀態下執行的狀態。 核心狀態:程序在核心狀態下執行的狀態。 記憶體中就緒:程序沒有執行,但處於就緒狀態,只要核心排程它,就可以執行。 記憶體中睡眠:程序正在睡眠並且程序儲存在記憶體中,沒有被交換到SWAP裝置。 就緒且換出:程序處於就緒狀態,但是必須把它換入記憶體,核心才能再次排程它執行。 睡眠且換出:程序正在睡眠,且被換出記憶體。 被搶先:程序從核心狀態返回使用者狀態時,核心搶先於它做了上下文切換,排程了另一個程序。原先這個程序就處於被搶先狀態。 僵死狀態(zombie):程序呼叫exit結束,程序不再存在,但在程序表項中仍有記錄,該記錄可由父程序收集。

現在我們從程序的建立到退出來看看程序的狀態轉化。需要說明的是,程序在它的生命週期裡並不一定要經歷所有狀態。

五、Linux程序的建立

fork函式在Linux下產生新的程序的系統呼叫,這個函式名是英文中“分叉”的意思。為什麼取這個名字呢? 因為一個程序在執行中,如果使用了fork,就產生了另一個程序,於是程序就“分叉”了,所以這個名字取得很形象。fork的語法如下所示:

#include

#include

pid_t fork();

在Linux網路程式設計中經常用到fork()系統呼叫。例如在一個客戶機/Web伺服器構建的網路環境中,Web伺服器往往可以滿足許多客戶端的請求。如果一個客戶機要訪問Web伺服器,需要傳送一個請求,此時由伺服器生成一個父程序,然後父程序通過fork()系統呼叫產生一個子程序,此時客戶機的請求由子程序完成。父程序可以再度回到等待狀態不斷服務其他客戶端。原理如下圖所示。

有一個更簡單的執行其他程式的函式system,引數string傳遞給一個命令直譯器(一般為sh)執行,即string被解釋為一條命令,由sh執行該命令。若引數string為一個空指標,則檢查命令直譯器是否存在。該命令可以和同命令列下的命令形式相同,但由於命令作為一個引數放在系統呼叫中,應注意編譯時對特殊意義字元的處理。命令的查詢是按PATH環境變數的定義執行的。命令所生成的後果一般不會對父程序程式設計造成影響。返回值:當引數為空指標時,只有當命令直譯器有效時返回值為非零。若引數不為空指標,返回值為該命令的返回狀態(同waitpid())的返回值。命令無效或語法錯誤則返回非零值,所執行的命令被終止。其他情況則返回-1.它是一個較高層的函式,實際上相當於在shell下執行一條命令,除了system之外,系統呼叫exec來執行一個可執行檔案,來代替當前程序的執行映像。系統呼叫exit的功能是終止發出呼叫的程序。sleep函式呼叫用來指定程序掛起的秒數。wait函式族用來等待和控制程序。poppen函式和system函式類似,區別是它用管道方式處理輸出。

父程序和子程序的關係是管理和被管理的關係,當父程序終止時,子程序也隨之而終止。但子程序終止時,父程序並不一定終止。比如httpd伺服器執行時,我們可以殺掉其子程序,父程序並不會因為子程序的終止而終止。

六、程序的管理

1、啟動程序

輸入需要執行的程式的程式名,執行一個程式,其實也就是啟動了一個程序。在Linux系統中,每個程序都具有一個程序號(PID),用於系統識別和排程程序。啟動一個程序有兩個主要途徑:手工啟動和排程啟動,後者是事先進行設定,根據使用者要求自動啟動。由使用者輸入命令,直接啟動一個程序便是手工啟動程序。但手工啟動程序又可以分為很多種,根據啟動的程序型別不同;性質不同,實際結果也不一樣。

(1)前臺啟動

前臺啟動是手工啟動一個程序的最常用的方式。使用者鍵入一個命令“df”,就已經啟動了一個程序,而且是一個前臺的程序。這時候系統其實已經處於多程序狀態。有許多執行在後臺的、系統啟動時就已經自動啟動的程序正在悄悄執行著。有的使用者在鍵入“df”命令以後趕緊使用“ps -x”檢視,卻沒有看到df程序,會覺得很奇怪。其實這裡因為df這個程序結束太快,使用ps檢視時該程序已經執行結束了。如果啟動一個比較耗時的程序,例如在根命令下執行:find,然後使用ps aux檢視,就會看到在裡面有一個find程序。

(2)後臺啟動

直接從後臺手工啟動一個程序用得比較小一些,除非是該程序甚為耗時,且使用者也不急著需要結果。假設使用者要啟動一個需要長時間執行的格式化文字檔案的程序,為了不使整個shell在格式化過程中都處於“癱瘓”狀態,從後臺啟動這個程序是明智的選擇。

2、程序排程

當需要中斷一個前臺程序的時候,通常使用Ctrl+C組合鍵。但是對於一個後臺程序,就不是一個組合鍵所能解決的了,這時就必須使用kill命令。該命令可以終止後臺程序。至於終止後臺程序的原因有很多,或許是該程序佔用的CPU時間過多;或許是該程序已經掛死。這種情況是經常發生的。kill命令的工作原理是:向Linux系統的核心傳送一個系統操作訊號和某個程式的程序標識號,然後系統核心就可以對程序標識號指定的程序進行操作。

七、Linux的第一個程序:init

init是Linux系統執行的第一個程序,程序ID為1,是系統所有程序的起點,主要用來執行一些開機初始化指令碼和監視程序。Linux系統在完成核內引導以後就開始執行init程式,init程式需要讀取配置檔案/etc/inittab。Inittab是一個不可執行的文字檔案,它由若干行命令所組成。

在RHEL 4系統中,inittab配置檔案的內容如下所示:

#

#inittab

#

#

#author

#

#Default runlevels used by rhs are:

#0 - halt (do not set initdefault to this)

#1 - single user mode

#2 - multiuser,without nfs (the same as 3, if you do not haver networking)

#3 - full multiuser mode

#4 - unused

#5 - X11

#6 - reboot (do not set initdefault to this)

#

//表示當前預設執行級別為5,啟動系統進入圖形化介面

id:5:initdefault:

//啟動時自動執行/etc/rc.d/nit指令碼

#system initialization.

si::sysinit:/etc/rc.d/nit

10:0:wait:/etc/rc.d/rc 0

11:1:wait:/etc/rc.d/rc 1

12:2:wait:/etc/rc.d/rc 2

13:3:wait:/etc/rc.d/rc 3

14:4:wait:/etc/rc.d/rc 4

//當執行級別為5時,以5為引數執行/etc/rc.d/rc指令碼,init將等待其返回

15:5:wait:/etc/rc.d/rc 5

16:6:wait:/etc/rc.d/rc 6

//在啟動過程中允許按[ctrl-alt-]重啟系統

#trap ctrl-alt-

ca::ctrlaltdel:/sbin/shutdown -t3 -r now

#

..................................

#

//在執行級別2、3、4、5以上ttyX為引數執行/sbin/mingetty程式,開啟ttyX終端用於使用者登入,如果程序退出則再次執行mingetty程式

#run gettys in standard runlevels

1:2345:respawn:/sbin/mingetty tty1

2:2345:respawn:/sbin/mingetty tty2

3:2345:respawn:/sbin/mingetty tty3

4:2345:respawn:/sbin/mingetty tty4

5:2345:respawn:/sbin/mingetty tty5

6:2345:respawn:/sbin/mingetty tty6

//在級別5上執行xdm程式,提供xdm圖形方式登入介面,並在退出時重新執行

x:5:respawn:/etc/x11/prefdm -nodaemon

#run xdm in runleverl 5

Inittab配置檔案每行的基本格式如下。

id:runlevels:action:procees

其中某些部分可以為空,下面我們逐一介紹。

1、id

1~2個字元,配置行的惟一標識,在配置檔案中不能重複。

2、runlevels

配置行適用的執行級別,在這裡可填入多個執行級別,比如12345或者35等。

Linux有7個執行級別:

0:關機

1:單使用者字元介面

2:不具備網路檔案系統(NFS)功能的多使用者字元介面

3:具有網路功能的多使用者字元介面

4:保留不用

5:具有網路功能的圖形使用者介面

6:重新啟動系統

3、action

init有如下幾種行為,如下表所示。

init行為

行為

描述

respawn

啟動並監視第4項指定的process,若process終止則重啟它

wait

執行第4項指定的process,並等待它執行完備

once

執行第4項指定的process

boot

不論在哪個執行等級,系統啟動時都會執行第4項指定的process

bootwait

不論在哪個執行等級,系統啟動時都會執行第4項指定的process,且一直等它執行完備

off

關閉任何動作,相當於忽略該配置行

ondemand

進入ondemand執行等級時,執行第4項指定的process

initdefault

系統啟動後進入的執行等級,該行不需要指定process

sysinit

不論在哪個執行等級,系統會在執行boot及bootwait之前執行第4項指定的'process

powerwait

當系統的供電不足時執行第4項指定的process,且一直等它執行完備

powerfailnow

當系統的供電嚴重不足時執行第4項指定的process

ctrlaltdel

當用戶按下ctrl+alt+del 時執行的操作

kbrequest

當用戶按下特殊的組合鍵時執行第4項指定的process,此組合鍵需在keymaps檔案定義

4、process

Process為init執行的程序,這些程序都儲存在目錄/etc/rc.d/rcX中,其中的X代表執行級別,rc程式接收X引數,然後執行/etc/rc.d/rc.X下面的程式。使用如下命令可以檢視/etc/rc.d目錄內容。

#ls –l /etc/rc.d/

total 112

drwxr-xr-x 2 root root 4096 3/15 14:44 init.d

-rxwr-xr-x 1 root root 2352 2004-3-17 rc

drwxr-xr-x 2 root root 4096 3/15 14:44 rc0.d

drwxr-xr-x 2 root root 4096 3/15 14:44 rc1.d

drwxr-xr-x 2 root root 4096 3/15 14:44 rc2.d

drwxr-xr-x 2 root root 4096 3/15 14:44 rc3.d

drwxr-xr-x 2 root root 4096 3/15 14:44 rc4.d

drwxr-xr-x 2 root root 4096 3/15 14:44 rc5.d

drwxr-xr-x 2 root root 4096 3/15 14:44 rc6.d

-rxwr-xr-x 1 root root 2200 2004-3-17 l

-rxwr-xr-x 1 root root 2352 2004-3-17 nit

…………

使用如下命令檢視/etc/rc.d/rc5.d的內容。

#ls –l /etc/rc.d/rc5.d

這些檔案都是符號連結,以S打頭的標識啟動該程式,而以K打頭的標識終止該程式,後面的數字標識執行順序,越小越先執行,剩下的標識程式名。系統啟動或者切換到該執行級別時會執行以S打頭的程式,系統切換到該執行級別時會執行以K打頭的程式。

這個目錄下的程式可通過chkconfig程式進行管理,當然這個目錄下的程式需要符合一定規範,如果瞭解shell程式設計,可以檢視這些符號連結所指向的程式的原始碼。

init也是一個程序,和普通的程序具有一樣的屬性。比如修改了/etc/inittab,想讓修改馬上生效,可通過執行“kill-SIGHUP 1”來實現,也可通過執行“init q”來實現。

八、Linux的執行緒簡介

1、Linux執行緒的定義

執行緒(thread)是在共享記憶體空間中併發的多道執行路徑,它們共享一個程序的資源,如檔案描述和訊號處理。在兩個普通程序(非執行緒)間進行切換時,核心準備從一個程序的上下文切換到另一個程序的上下文要花費很大的開銷。這裡上下文切換的主要任務是儲存老程序CPU狀態並載入新程序的儲存狀態,用新程序的記憶體映像替換程序的記憶體映像。執行緒允許你的程序在幾個正在執行的任務之間進行切換,而不必執行前面提到的完整的上下文。另外本文介紹的執行緒是針對POSIX執行緒的,也就是Pthread。也因為Linux對它的支援最好,相對程序而言,執行緒是一個更加接近於執行體的概念,它可以與同進程中的其他執行緒共享資料,但擁有自己的棧空間,擁有獨立的執行序列。在序列程式基礎上引入執行緒和程序是為了提高程式的併發度,從而提高程式執行效率和響應時間。也可以將執行緒和輕量級程序(LWP)視為等同的,但其實在不同的系統/實現中有不同的解釋,LWP更恰當的解釋為一個虛擬CPU或核心的執行緒。它可以幫助使用者態執行緒實現一些特殊的功能。Pthread是一種標準化模型,它用來把一個程式分成一組能夠同時執行的任務。

2、什麼場合使用Pthread,即執行緒

(1)在返回前阻塞的I/O任務能夠使用一個執行緒處理I/O,同時繼續執行其他處理任務。

(2)在有一個或多個任務受不確定性事件,比如網路通訊的可獲得性影響的場合,能夠使用執行緒處理這些非同步事件,同時繼續執行正常的處理。

(3)如果某些程式功能比其他的功能更重要,可以使用執行緒以保證所有功能都出現,但那些時間密集型的功能具有更高的優先順序。

以上三點可以歸納為:在檢查程式中潛在的並行性時,也就是說在要找出能夠同時執行任務時使用Pthread。上面已經介紹了,Linux程序模型提供了執行多個程序的能力,已經可以進行並行或併發程式設計,可是純種能夠讓你對多個任務的控制程式更好、使用資源更少,因為一個單一的資源,如全域性變數,可以由多個執行緒共享。而且,在擁有多個處理器的系統上,多執行緒應用會比用多個程序實現的應用執行速度更快。

3、Linux程序和執行緒的發展

1999年1月釋出的Linux 2.2核心中,程序是通過系統呼叫fork建立的,新的程序是原來程序的子程序。需要說明的是,在2.2.x版本中,不存在真正意義上的執行緒(thread)。Linux中常用的執行緒Pthread實際上是通過程序來模擬的。也就是說Linux中的執行緒也是通過fork建立的,是“輕”程序。Linux 2.2只預設允許4096個程序/執行緒同時執行。高端系統同時要服務上千個使用者,所以這顯然是一個問題,它一度是阻礙Linux進入企業級市場的一大因素。

2001年1月釋出的Linux 2.4核心消除了這個限制,並且允許在系統執行中動態調整程序數上限。因此,程序數現在只受制於實體記憶體的多少。在高階伺服器上,即使安裝了512MB記憶體,現在也能輕而易舉地同時支援1萬6千個程序。

2003年12月釋出的2.6核心,程序排程經過重新編寫,去掉了以前版本中效率不高的演算法。以前,為了決定下一步要執行哪一個任務,程序排程程式要檢視每一個準備好的任務,並且經過計算機來決定哪一個任務相對來更為重要。程序標識號(PID)的數目也從32000升到10億。核心內部的大改變之一就是Linux的執行緒框架被重寫,以使NPTL(Native POSIX Thread Library)可以運行於其上。對於執行負荷繁重的執行緒應用的Pentium Pro及更先進的處理器而言,這是一個主要的效能提升,也是企業級應用中的很多高端系統一直以來所期待的。執行緒框架的改變包含Linux執行緒空間中的許多新的概念,包括執行緒組、執行緒各自的本地儲存區、POSIX風格訊號,以及其他改變。改進後的多執行緒和記憶體管理技術有助於更好地執行大型多媒體應用軟體。

4、總結

執行緒和程序在使用上各有優缺點:執行緒執行開銷小,但不利於資源的管理和保護;而程序正相反。同時,執行緒適合於在對稱處理器的計算機上執行,而程序則可以跨機器遷移。另外,程序可以擁有資源,執行緒共享程序擁有的資源。程序間的切換必須儲存在程序控制塊PCB(Process Control Block)中。同一個程序的多個執行緒間的切換不用那麼麻煩。最後一個例項來作為本文的結束:當你在一臺Linux PC上開啟兩個OICQ,每一個OICQ是一個程序;而當你在一個OICQ上和多人聊天時,每一個聊天視窗就是一個執行緒。