當(dāng)前位置:首頁 > 嵌入式培訓(xùn) > 嵌入式學(xué)習(xí) > 講師博文 > Linux下多任務(wù)編程之任務(wù)、進(jìn)程和線程詳解,厲害的
Linux下多任務(wù)編程之任務(wù)、進(jìn)程和線程詳解,厲害的
時間:2018-06-25 來源:未知
多任務(wù)是指用戶可以在同一時間內(nèi)運(yùn)行多個應(yīng)用程序。Linux就是一種支持多任務(wù)的操作系統(tǒng),它支持多進(jìn)程、多線程等多任務(wù)處理和任務(wù)之間的多種通信機(jī)制。掌握多任務(wù)開發(fā),會有助于大家成為系統(tǒng)工程師。
Linux下多任務(wù)機(jī)制的介紹
Linux就是一個支持多任務(wù)的操作系統(tǒng),它比單任務(wù)系統(tǒng)的功能增強(qiáng)了許多。當(dāng)多任務(wù)操作系統(tǒng)使用某種任務(wù)調(diào)度策略允許兩個或更多進(jìn)程并發(fā)共享一個處理器時,事實上處理器在某一時刻只會給一個任務(wù)提供服務(wù)。由于任務(wù)調(diào)度機(jī)制保證了不同的任務(wù)之間的切換速度十分迅速,因此給人多個任務(wù)同時運(yùn)行的錯覺。多任務(wù)系統(tǒng)中有3個功能單位:任務(wù)、進(jìn)程和線程,下面分別進(jìn)程介紹。
1、任務(wù)
任務(wù)是一個邏輯概念,指由一個軟件完成的活動,或者是一系列共同達(dá)到某一個目的的操作。通常一個任務(wù)是一個程序的一次運(yùn)行,一個任務(wù)包含一個或多個完成獨(dú)立功能的子任務(wù),這個獨(dú)立的子任務(wù)就是進(jìn)程或線程。例如,一個殺毒軟件的一次運(yùn)行是一個任務(wù),目的是從各種病毒的侵害中保護(hù)計算機(jī)系統(tǒng),這個任務(wù)包含多個獨(dú)立功能的子任務(wù)(進(jìn)程或線程),包含實時監(jiān)控功能、定時查殺功能、防火墻功能及用戶交互功能等。個人理解:就好比假設(shè)一個應(yīng)用程序中由一個或多個可執(zhí)行文件共同執(zhí)行組成,那么此應(yīng)用程序的一次執(zhí)行就是一個任務(wù),而這些可執(zhí)行文件的執(zhí)行就是一個進(jìn)程的執(zhí)行,而可執(zhí)行文件是由一個線程或多個線程構(gòu)成的,當(dāng)只有一個線程構(gòu)成了這個進(jìn)程,則此時進(jìn)程和線程就是一樣的概念(可執(zhí)行文件的一次運(yùn)行)。
2、進(jìn)程
進(jìn)程的基本概念
進(jìn)程是一個具有獨(dú)立功能的程序在某個數(shù)據(jù)集上的一次動態(tài)執(zhí)行過程,它是系統(tǒng)進(jìn)行資源分配和調(diào)度的基本單位(個人理解:系統(tǒng)好比是一個大型的任務(wù),由多個進(jìn)程(可執(zhí)行文件)構(gòu)成,而資源分配和資源調(diào)度分別都是一個進(jìn)程,所以進(jìn)程是系統(tǒng)進(jìn)行資源分配和調(diào)度的基本單位)。一次任務(wù)的運(yùn)行可以并發(fā)激活多個進(jìn)程,這些進(jìn)程相互合作來完成該任務(wù)的一個最終的目標(biāo)。
進(jìn)程具有并發(fā)性、動態(tài)性、交互性、獨(dú)立性和異步性等主要特性
· 并發(fā)性:指的是系統(tǒng)中多個進(jìn)程可以同時并發(fā)執(zhí)行,相互之間不受干擾。
· 動態(tài)性:指的是進(jìn)程都有完整的生命周期,而且在進(jìn)程的生命周期內(nèi),進(jìn)程的狀態(tài)是不斷變化的。另外,進(jìn)程具有動態(tài)的地址空間(包括代碼、數(shù)據(jù)和進(jìn)程控制塊)。
· 交互性:指的是進(jìn)程在執(zhí)行過程中可能會與其他進(jìn)程發(fā)生直接和間接的交互操作,如進(jìn)程同步和進(jìn)程互斥等,需要為此添加一定的進(jìn)程處理機(jī)制。
· 獨(dú)立性:指的是進(jìn)程是一個相對完整的資源分配和調(diào)度的基本單位,各個進(jìn)程的地址空間是相互獨(dú)立的,只有采用某些特定的通信機(jī)制才能實現(xiàn)進(jìn)程間的通信。
· 異步性:指的是每個進(jìn)程都按照各自獨(dú)立的、不可預(yù)知的速度向前執(zhí)行。
進(jìn)程和程序是有本質(zhì)的區(qū)別:程序是靜態(tài)的一段代碼,是一些保存在非易失性存儲器的指令的有序集合,沒有任何執(zhí)行的概念;而進(jìn)程是一個動態(tài)的概念,它是程序執(zhí)行的過程,包括動態(tài)創(chuàng)建、調(diào)度和消亡的整個過程,它是程序執(zhí)行和資源管理的最小單位。
Linux系統(tǒng)中包括以下幾種類型的過程:
· 交互式過程:這類進(jìn)程進(jìn)程與用戶進(jìn)程交互,因此要花很多時間等待用戶的交互操作(鍵盤和鼠標(biāo)操作等)。當(dāng)接收到用戶的交互操作后,這類進(jìn)程應(yīng)該很快被允許,而且相應(yīng)時間的變化也應(yīng)該很小,否則用戶就會覺得系統(tǒng)反應(yīng)遲鈍或者不太穩(wěn)定。典型的交互式進(jìn)程有shell命令進(jìn)程、文本編輯器和圖形應(yīng)用程序運(yùn)行等。
· 批處理進(jìn)程:這類進(jìn)程不必與用戶進(jìn)行交互,因此進(jìn)程在后臺運(yùn)行。因為這類進(jìn)程通常不必很快地相應(yīng),因此往往受到調(diào)度器的“慢待”。典型的批處理進(jìn)程有編譯器的編譯操作、數(shù)據(jù)庫搜索引擎等。
· 實時進(jìn)程:這類進(jìn)程通常對調(diào)度響應(yīng)時間有很高的要求,一般不會被低優(yōu)先級的進(jìn)程阻塞。它們不僅要求很短的響應(yīng)時間,而且更重要的是響應(yīng)時間的變化應(yīng)該很小。典型的實時進(jìn)程有視頻和音頻的應(yīng)用程序、實時數(shù)據(jù)采集系統(tǒng)程序等。
Linux下的進(jìn)程結(jié)構(gòu)
進(jìn)程不但包括程序的指令和數(shù)據(jù),而且包括程序計數(shù)器和處理器的所有寄存器以及存儲臨時數(shù)據(jù)的進(jìn)程堆棧,因此正在執(zhí)行的進(jìn)程包括處理器當(dāng)前的一切活動。
因為Linux是一個多進(jìn)程的操作系統(tǒng),所以其他的進(jìn)程必須等到系統(tǒng)將處理器使用權(quán)分配各自己之后才能運(yùn)行。當(dāng)正在運(yùn)行的進(jìn)程等待其他的系統(tǒng)資源時,Linux內(nèi)核將取得處理器的控制權(quán),并將處理器分配給其他正在等待的進(jìn)程,它按照內(nèi)核中的調(diào)度算法決定處理器分配給哪個進(jìn)程。
內(nèi)核將所有進(jìn)程存放在雙向循環(huán)鏈表(進(jìn)程鏈表)中,其中鏈表的頭是init_task描述符。鏈表的每一項都是類型為task_struct,稱為進(jìn)程描述符的結(jié)構(gòu),該結(jié)構(gòu)包含了與一個進(jìn)程相關(guān)的所有信息,定義在
下面詳細(xì)講解task_struct結(jié)構(gòu)中最為重要的兩個域:state(進(jìn)程狀態(tài))和pid(進(jìn)程標(biāo)識符,即進(jìn)程號)。
1)進(jìn)程狀態(tài),Linux中的進(jìn)程有以下幾種狀態(tài)
· 運(yùn)行狀態(tài)(TASK_RUNNING):進(jìn)程當(dāng)前正在運(yùn)行,或者正在運(yùn)行隊列中等待調(diào)度。
創(chuàng)建一個task.c文件,task.c文件內(nèi)容如下:

保存后,輸入gcc task.c -o task編譯生成二進(jìn)制代碼task,輸入./task運(yùn)行task進(jìn)程

打開另一個終端,輸入ps -aux查看進(jìn)程狀態(tài):(ps -axjf 可查看進(jìn)程有哪些子進(jìn)程,ps -e 也 可以查到進(jìn)程的狀態(tài),但只顯示進(jìn)程的PID、TTY、TIME和CMD)
ps工具標(biāo)識進(jìn)程的5中狀態(tài)碼:
D 不可中斷 uninterruptible sleep (usually IO)
R 運(yùn)行 runnable (on run queue)
S 中斷 sleeping
T 停止 traced or stopped
Z 僵尸 a defunct ("zombie") process
注:其它狀態(tài)還包括W(無駐留頁),<(高優(yōu)先級進(jìn)程),N(低優(yōu)先級進(jìn)程),L(內(nèi)存鎖頁)
每列對應(yīng)關(guān)系:
USER:進(jìn)程所有者
PID:進(jìn)程ID
%CPU:占用CPU的使用率
%MEM:占用內(nèi)存的使用率
VSZ:占用虛擬內(nèi)存大小
RSS:占用內(nèi)存大小
TTY:終端次要裝置號碼
STAT:進(jìn)程狀態(tài)
START:進(jìn)程啟動時間
TIME:進(jìn)程消耗cup時間
COMMAND:命令的名稱和參數(shù)



· 可中斷的阻塞狀態(tài)(TASK_INTERRUPTIBLE):進(jìn)程處于阻塞(睡眠)狀態(tài),正在等待某些事件發(fā)生或能夠占用某些資源。處在這種狀態(tài)下的進(jìn)程可以被信號中斷。接收到信號或被顯式的喚醒呼叫(如調(diào)用wake_up系列宏:wake_up、wake_up_interruptible等)喚醒之后,進(jìn)程轉(zhuǎn)變?yōu)門ASK_RUNNING狀態(tài)。
· 不可中斷的阻塞狀態(tài)(TASK_UNINTERRUPTIBLE):此進(jìn)程狀態(tài)類似于可中斷的阻塞狀態(tài)(TASK_INTERRUPTILBE),只是它不會處理信號,把信號傳遞到這種狀態(tài)下的進(jìn)程不能改變它的狀態(tài)。在一些特定的情況下(進(jìn)程必須等待,直到某些不可被中斷的事件發(fā)生),這種狀態(tài)是很有用的。只有在它所等待的事件發(fā)生時,進(jìn)程被顯式的喚醒呼叫喚醒。
· 可終止的阻塞狀態(tài)(TASK_KILLABLE):Linux內(nèi)核2.6.25引入了一種新的進(jìn)程狀態(tài),名為TASK_KILLABLE。該狀態(tài)的運(yùn)行機(jī)制類似于TASK_UNINTERRUPTILBE,只不過在該狀態(tài)下的進(jìn)程可以響應(yīng)致命信號。它可以替代有效但可能無法終止的不可中斷的阻塞狀態(tài),以及易于喚醒安全性欠佳的可中斷的阻塞狀態(tài)。
· 暫停狀態(tài)(TASK_STOPPED):進(jìn)程的執(zhí)行被暫停,當(dāng)進(jìn)程收到SIGTOP、SIGTSTP、SIGTTIN、SIGTTOU等信號時,就會進(jìn)入暫停狀態(tài)。
· 跟蹤狀態(tài)(TASK_TRACED):進(jìn)程的執(zhí)行被調(diào)試器暫停。當(dāng)一個進(jìn)程被另一個進(jìn)程監(jiān)控是(如調(diào)試器使用ptrace()系統(tǒng)調(diào)用監(jiān)控測試程序),任何信號都可以把這個進(jìn)程置于跟蹤狀態(tài)。
· 僵尸狀態(tài)(EXIT_ZOMBIE):進(jìn)程運(yùn)行結(jié)束,父進(jìn)程尚未使用wait函數(shù)族(如使用waitpid()函數(shù))等系統(tǒng)調(diào)用來“收尸”,即等待父進(jìn)程銷毀它。處于該狀態(tài)下的進(jìn)程“實體”已經(jīng)放棄了幾乎所有的內(nèi)存空間,沒有任何可執(zhí)行代碼,也不能調(diào)度,僅僅在進(jìn)程列表保留一個位置,記載該進(jìn)程的退出狀態(tài)等信息供其他進(jìn)程收集。
· 僵尸撤銷狀態(tài)(EXIT_DEAD):這是最終狀態(tài),父進(jìn)程調(diào)用wait函數(shù)族“收尸”后,進(jìn)程徹底有系統(tǒng)刪除。
它們之間的轉(zhuǎn)換關(guān)系如圖所示:

內(nèi)核可以使用set_task_state和set_current_state宏來改變指定進(jìn)程的狀態(tài)和當(dāng)前執(zhí)行進(jìn)程的狀態(tài)。
2)進(jìn)程標(biāo)識符
Linux內(nèi)核通過唯一的進(jìn)程標(biāo)識符PID來標(biāo)識每個進(jìn)程。PID存放進(jìn)程描述符的pid字段中,新創(chuàng)建的PID通常是前一個進(jìn)程的PID加1,不過PID的值有上限(最大值 = PID_MAX_DEFAULT - 1,通常為32767),我們可以在終端輸入 vim /proc/sys/kernel/pid_max 來確定該系統(tǒng)的進(jìn)程數(shù)上限。
當(dāng)系統(tǒng)啟動后,內(nèi)核通常作為一個進(jìn)程的代表。一個指向task_struct的宏current用來記錄正在運(yùn)行的進(jìn)程。current經(jīng)常作為進(jìn)程描述符結(jié)構(gòu)指針的形式出現(xiàn)在內(nèi)核代碼中,例如,current->pid表示處理器正在執(zhí)行進(jìn)程的PID。當(dāng)系統(tǒng)需要查看所有的進(jìn)程時,則調(diào)用for_each_process()宏,這將比系統(tǒng)搜索數(shù)組的速度要快得多。
在Linux中獲得當(dāng)前進(jìn)程的進(jìn)程號(PID)和父進(jìn)程號(PPID)的系統(tǒng)調(diào)用函數(shù)分別為getpid()和getppid()。
測試代碼:

測試結(jié)果:

輸入 ps -axjf 命令查看所有進(jìn)程與父進(jìn)程

我們在次輸入ps -aux命令查看所有進(jìn)程,可以得知父進(jìn)程為bash

進(jìn)程的創(chuàng)建、執(zhí)行和終止
1)進(jìn)程的創(chuàng)建和執(zhí)行
許多操作系統(tǒng)提供的都是產(chǎn)生進(jìn)程的機(jī)制,也就是說,首先在新的地址空間里創(chuàng)建進(jìn)程、讀入可執(zhí)行文件、最后在開始執(zhí)行。Linux中進(jìn)程的穿件很特別,它把上述步驟分解到兩個單獨(dú)的函數(shù)中去執(zhí)行:fork()和exec函數(shù)族。首先fork()函數(shù)通過復(fù)制當(dāng)前進(jìn)程創(chuàng)建一個子進(jìn)程,子進(jìn)程與父進(jìn)程的區(qū)別在于不同的PID、PPID和某些資源及統(tǒng)計量。exec函數(shù)族負(fù)責(zé)讀取可執(zhí)行文件并將其載入地址空間開始運(yùn)行。
要注意的是,Linux中的fork()函數(shù)使用的是寫時復(fù)制頁的技術(shù),也就是內(nèi)核在創(chuàng)建進(jìn)程時,其資源并沒有被復(fù)制過來,資源的復(fù)制僅僅只有在需要寫入數(shù)據(jù)時才發(fā)生,在此之前只是以只讀的方式共享數(shù)據(jù)。寫時復(fù)制技術(shù)可以使Linux擁有快速執(zhí)行的能力,因此這個優(yōu)化是非常重要的。
2)進(jìn)程的終止
進(jìn)程終結(jié)也需要做很多繁瑣的收尾工作,系統(tǒng)必須保證回收進(jìn)程所占的資源,并通知父進(jìn)程。Linux首先把終止的進(jìn)程設(shè)置為僵尸狀態(tài),這時,進(jìn)程無法投入運(yùn)行,它的存在只為父進(jìn)程提供信息,申請死亡。父進(jìn)程得到信息后,開始調(diào)用wait函數(shù)族,最后終止子進(jìn)程,子進(jìn)程占用的所有資源被全部釋放。
進(jìn)程的內(nèi)存結(jié)構(gòu)
Linux操作系統(tǒng)采用虛擬內(nèi)存管理技術(shù),使得每個進(jìn)程都有各自互不干涉的進(jìn)程地址空間。該地址空間是大小為4GB的線性虛擬空間(當(dāng)然是指32位系統(tǒng)),用戶所看到和接觸到的都是該虛擬地址,無法看到實際的物理內(nèi)存地址。利用這種虛擬地址不但能起到保護(hù)操作系統(tǒng)的效果(用戶不能直接訪問物理內(nèi)存),而且更重要的是,用戶程序可以使用比實際物理內(nèi)存更大的地址空間。
我們可以通過命令getconf LONG_BIT 來查詢當(dāng)前自己的系統(tǒng)是多少位的?

我的安裝的UbuntKylin是64位的,即實際內(nèi)存最大可能達(dá)到2^64 = 128GB。(2^10 = 1kb,2^30 = 1GB)。
4GB的進(jìn)程地址空間會被分出兩部分:用戶空間與內(nèi)核空間。用戶地址空間是從0~3GB(0xC0000000),內(nèi)存地址空間占據(jù)3GB~4GB。用戶進(jìn)程通常情況下只能訪問用戶控件的虛擬地址,不能訪問內(nèi)核空間的虛擬地址。只有用戶進(jìn)程使用系統(tǒng)調(diào)用(代表用戶進(jìn)程在內(nèi)核執(zhí)行)時可以訪問內(nèi)核空間的虛擬空間。每當(dāng)進(jìn)程切換時,用戶空間就會跟著變化;而內(nèi)核空間有內(nèi)核負(fù)責(zé)映射,它并不會跟著進(jìn)程改變而改變,是固定的。內(nèi)核空間地址有自己對應(yīng)的頁表,用戶進(jìn)程各自用不同的頁表。每個進(jìn)程用戶空間都是完全獨(dú)立、互不相干的。進(jìn)程的虛擬內(nèi)存地址空間如圖所示:

其中用戶空間包括以下幾個功能區(qū)域:
· 只讀段:包含程序代碼(.init和.exit)和只讀數(shù)據(jù)(.rodata)
· 數(shù)據(jù)段:存放的是全局變量和靜態(tài)變量。其中可讀可寫數(shù)據(jù)段(.data)存放已經(jīng)初始化的全局變量和靜態(tài)變量,BSS數(shù)據(jù)段(.bss)存放未初始化的全局變量和靜態(tài)變量
· 堆:由系統(tǒng)自動分配釋放,存放函數(shù)的參數(shù)值、局部變量的值、返回地址等
· 堆棧:存放動態(tài)分配的數(shù)據(jù),一般由程序員動態(tài)分配和釋放。若程序員不釋放,程序結(jié)束時可能由操作系統(tǒng)回收。
· 共享庫的內(nèi)存映射區(qū)域:這是Linux動態(tài)連接器和其他共享庫代碼的映射區(qū)域。
由于在Linux系統(tǒng)中每一個進(jìn)程都會有/proc文件系統(tǒng)下與之對應(yīng)的一個目錄(如將init進(jìn)程的相關(guān)信息在/proc/1 目錄下的文件中描述,1表示init進(jìn)程的進(jìn)程號),因此通過proc文件系統(tǒng)可以查看某個進(jìn)程的地址空間的映射情況。
測試代碼:

運(yùn)行此程序:

輸入 size task

text:存放的是代碼 data:存放的是初始化過的全局變量或靜態(tài)變量 bss:存放的是未初始化的全局變量或靜態(tài)變量
輸入命令 cat /proc/3834/maps 其中3834是task的PID

3、線程
前面已經(jīng)提到,進(jìn)程是系統(tǒng)中程序執(zhí)行和資源分配的基本單位。每個進(jìn)程都擁有自己的數(shù)據(jù)段、代碼段和堆棧段,這就造成了進(jìn)程進(jìn)程切換等操作時需要較復(fù)雜的上下文切換等動作。為了進(jìn)一步減少處理機(jī)制的空轉(zhuǎn)時間,支持多處理器及減少上下文切換開銷,進(jìn)程在演化中出現(xiàn)了另一個概念——線程。它是進(jìn)程內(nèi)獨(dú)立的一條運(yùn)行路線,是處理器調(diào)用的最小單元,也可以成為輕量級進(jìn)程。線程可以對進(jìn)程的內(nèi)存空間和資源進(jìn)程訪問,并與同一個進(jìn)程中的其他線程共享。因此,線程上下文切換的開銷比創(chuàng)建進(jìn)程小得多。
一個進(jìn)程可以擁有多個線程,每個線程必須有一個父進(jìn)程。線程不擁有系統(tǒng)資源,它只具有運(yùn)行所必需的一些數(shù)據(jù),如堆棧、寄存器與線程控制塊(TCB),線程與其父進(jìn)程的其他線程共享該進(jìn)程所擁有的全部資源。要注意的是,由線程共享了進(jìn)程的資源和地址空間,因此,任何線程對系統(tǒng)資源的操作都會給其他線程帶來影響。由此可知,多線程中的同步是非常重要的問題。在多線程系統(tǒng)中,進(jìn)程與線程的關(guān)系如圖所示:

在Linux系統(tǒng)中,線程可以分為以下3種:
用戶級線程
用戶級線程主要解決的是上下文切換的問題,它的調(diào)度算法和調(diào)度過程全部由用戶自己選擇決定,在運(yùn)行時不需要特定的內(nèi)核支持。在這里,操作系統(tǒng)往往會提供一個用戶空間的線程庫,該線程庫提供了線程的創(chuàng)建、調(diào)度和撤銷等功能,而內(nèi)核仍然僅對進(jìn)程進(jìn)行管理。如果一個進(jìn)程中的某一個線程調(diào)用了一個阻塞的系統(tǒng)調(diào)用函數(shù),那么該進(jìn)程好吧該進(jìn)程中的其他所有線程也同時被阻塞。這種用戶級線程的主要缺點是在一個進(jìn)程的多個線程的調(diào)度中無法發(fā)揮多處理器的優(yōu)勢。
輕量級進(jìn)程
輕量級進(jìn)程是內(nèi)核支持的用戶線程,是內(nèi)核線程的一種抽象對象。每個線程擁有一個或多個輕量級進(jìn)程,而每個輕量級進(jìn)程分別被綁定在一個內(nèi)核線程上。
內(nèi)核線程
內(nèi)核線程允許不同進(jìn)程中的線程按照同一相對優(yōu)先調(diào)度方法進(jìn)行調(diào)度,這樣就可以發(fā)揮多處理器的并發(fā)優(yōu)勢。現(xiàn)在大多數(shù)系統(tǒng)都采用用戶級線程與核心級線程并存的方法。一個用戶級線程可以對應(yīng)一個或幾個核心級線程,也就是“一對一”或“多對一”模型。這樣既可以滿足多處理系統(tǒng)的需要,也可以最大限度地減少調(diào)度開銷。
使用線程機(jī)制大大加快了上下文切換速度,而節(jié)省了很多資源。但是因為在用戶態(tài)和內(nèi)核態(tài)均要實現(xiàn)調(diào)度管理,所有會增加實現(xiàn)的復(fù)雜度和引起優(yōu)先級翻轉(zhuǎn)的可能性。同時,一個多線程程序的同步設(shè)計與調(diào)試也會增加程序?qū)崿F(xiàn)的難道。

