C语言多任务,多进程,多线程
多任務的概念人們已經(jīng)非常熟悉了,它是指用戶可以在同一時間內(nèi)運行多個應用程序。Linux就是一種支持多任務的操作系統(tǒng),它支持多進程、多線程等多任務處理和任務之間的多種通信機制。
Linux下多任務機制的介紹
多任務處理是指用戶在同一時間內(nèi)運行多個應用程序,每個應用程序被稱做一個任務。Linux就是一個支持多任務的操作系統(tǒng),它比單任務系統(tǒng)的功能增強了許多。當多任務操作系統(tǒng)使用某種任務調(diào)度策略允許兩個或更多進程并發(fā)共享一個處理器時,事實上處理器在某一時刻只會給一個任務提供服務。由于任務調(diào)度機制保證了不同的任務之間的切換速度十分迅速,因此給人多個任務同時運行的錯覺。多任務系統(tǒng)中有3個功能單位:任務、進程和線程,下面分別進程介紹。
1、任務
任務是一個邏輯概念,指由一個軟件完成的活動,或者是一系列共同達到某一個目的的操作。通常一個任務是一個程序的一次運行,一個任務包含一個或多個完成獨立功能的子任務,這個獨立的子任務就是進程或線程。例如,一個殺毒軟件的一次運行是一個任務,目的是從各種病毒的侵害中保護計算機系統(tǒng),這個任務包含多個獨立功能的子任務(進程或線程),包含實時監(jiān)控功能、定時查殺功能、防火墻功能及用戶交互功能等。個人理解:就好比假設一個應用程序中由一個或多個可執(zhí)行文件共同執(zhí)行組成,那么此應用程序的一次執(zhí)行就是一個任務,而這些可執(zhí)行文件的執(zhí)行就是一個進程的執(zhí)行,而可執(zhí)行文件是由一個線程或多個線程構(gòu)成的,當只有一個線程構(gòu)成了這個進程,則此時進程和線程就是一樣的概念(可執(zhí)行文件的一次運行)。
2、進程
進程的基本概念
進程是一個具有獨立功能的程序在某個數(shù)據(jù)集上的一次動態(tài)執(zhí)行過程,它是系統(tǒng)進行資源分配和調(diào)度的基本單位(個人理解:系統(tǒng)好比是一個大型的任務,由多個進程(可執(zhí)行文件)構(gòu)成,而資源分配和資源調(diào)度分別都是一個進程,所以進程是系統(tǒng)進行資源分配和調(diào)度的基本單位)。一次任務的運行可以并發(fā)激活多個進程,這些進程相互合作來完成該任務的一個最終的目標。
進程具有并發(fā)性、動態(tài)性、交互性、獨立性和異步性等主要特性
- 并發(fā)性:指的是系統(tǒng)中多個進程可以同時并發(fā)執(zhí)行,相互之間不受干擾。
- 動態(tài)性:指的是進程都有完整的生命周期,而且在進程的生命周期內(nèi),進程的狀態(tài)是不斷變化的。另外,進程具有動態(tài)的地址空間(包括代碼、數(shù)據(jù)和進程控制塊)。
- 交互性:指的是進程在執(zhí)行過程中可能會與其他進程發(fā)生直接和間接的交互操作,如進程同步和進程互斥等,需要為此添加一定的進程處理機制。
- 獨立性:指的是進程是一個相對完整的資源分配和調(diào)度的基本單位,各個進程的地址空間是相互獨立的,只有采用某些特定的通信機制才能實現(xiàn)進程間的通信。
- 異步性:指的是每個進程都按照各自獨立的、不可預知的速度向前執(zhí)行。
- 交互式過程:這類進程進程與用戶進程交互,因此要花很多時間等待用戶的交互操作(鍵盤和鼠標操作等)。當接收到用戶的交互操作后,這類進程應該很快被允許,而且相應時間的變化也應該很小,否則用戶就會覺得系統(tǒng)反應遲鈍或者不太穩(wěn)定。典型的交互式進程有shell命令進程、文本編輯器和圖形應用程序運行等。
- 批處理進程:這類進程不必與用戶進行交互,因此進程在后臺運行。因為這類進程通常不必很快地相應,因此往往受到調(diào)度器的“慢待”。典型的批處理進程有編譯器的編譯操作、數(shù)據(jù)庫搜索引擎等。
- 實時進程:這類進程通常對調(diào)度響應時間有很高的要求,一般不會被低優(yōu)先級的進程阻塞。它們不僅要求很短的響應時間,而且更重要的是響應時間的變化應該很小。典型的實時進程有視頻和音頻的應用程序、實時數(shù)據(jù)采集系統(tǒng)程序等。
Linux下的進程結(jié)構(gòu)
進程不但包括程序的指令和數(shù)據(jù),而且包括程序計數(shù)器和處理器的所有寄存器以及存儲臨時數(shù)據(jù)的進程堆棧,因此正在執(zhí)行的進程包括處理器當前的一切活動。 因為Linux是一個多進程的操作系統(tǒng),所以其他的進程必須等到系統(tǒng)將處理器使用權(quán)分配各自己之后才能運行。當正在運行的進程等待其他的系統(tǒng)資源時,Linux內(nèi)核將取得處理器的控制權(quán),并將處理器分配給其他正在等待的進程,它按照內(nèi)核中的調(diào)度算法決定處理器分配給哪個進程。 內(nèi)核將所有進程存放在雙向循環(huán)鏈表(進程鏈表)中,其中鏈表的頭是init_task描述符。鏈表的每一項都是類型為task_struct,稱為進程描述符的結(jié)構(gòu),該結(jié)構(gòu)包含了與一個進程相關的所有信息,定義在<include/linux/sched.h>文件中。task_struct內(nèi)核結(jié)構(gòu)比較大,它能完整地描述一個進程,如進程的狀態(tài)、進程的基本信息、進程標識符、內(nèi)存相關信息、父進程相關信息、與進程相關的終端信息、當前工作目錄、打開的文件信息、所接收到的信號信息等。 下面詳細講解task_struct結(jié)構(gòu)中最為重要的兩個域:state(進程狀態(tài))和pid(進程標識符,即進程號)。 1)進程狀態(tài),Linux中的進程有以下幾種狀態(tài)- 運行狀態(tài)(TASK_RUNNING):進程當前正在運行,或者正在運行隊列中等待調(diào)度。
保存后,輸入gcc task.c -o task編譯生成二進制代碼task,輸入./task運行task進程
打開另一個終端,輸入ps -aux查看進程狀態(tài):(ps -axjf?可查看進程有哪些子進程,ps -e?也 可以查到進程的狀態(tài),但只顯示進程的PID、TTY、TIME和CMD)ps工具標識進程的5中狀態(tài)碼:D 不可中斷 uninterruptible sleep (usually IO)R 運行 runnable (on run queue)S 中斷 sleepingT 停止 traced or stoppedZ 僵尸 a defunct ("zombie") process注:其它狀態(tài)還包括W(無駐留頁),<(高優(yōu)先級進程),N(低優(yōu)先級進程),L(內(nèi)存鎖頁)每列對應關系:USER:進程所有者 ? ? ?PID:進程ID ? ?%CPU:占用CPU的使用率 ??%MEM:占用內(nèi)存的使用率 ??VSZ:占用虛擬內(nèi)存大小?RSS:占用內(nèi)存大小?TTY:終端次要裝置號碼 ??STAT:進程狀態(tài) ?START:進程啟動時間 ? ?TIME:進程消耗cup時間 ??COMMAND:命令的名稱和參數(shù)
- 可中斷的阻塞狀態(tài)(TASK_INTERRUPTIBLE):進程處于阻塞(睡眠)狀態(tài),正在等待某些事件發(fā)生或能夠占用某些資源。處在這種狀態(tài)下的進程可以被信號中斷。接收到信號或被顯式的喚醒呼叫(如調(diào)用wake_up系列宏:wake_up、wake_up_interruptible等)喚醒之后,進程轉(zhuǎn)變?yōu)門ASK_RUNNING狀態(tài)。
- 不可中斷的阻塞狀態(tài)(TASK_UNINTERRUPTIBLE):此進程狀態(tài)類似于可中斷的阻塞狀態(tài)(TASK_INTERRUPTILBE),只是它不會處理信號,把信號傳遞到這種狀態(tài)下的進程不能改變它的狀態(tài)。在一些特定的情況下(進程必須等待,直到某些不可被中斷的事件發(fā)生),這種狀態(tài)是很有用的。只有在它所等待的事件發(fā)生時,進程被顯式的喚醒呼叫喚醒。
- 可終止的阻塞狀態(tài)(TASK_KILLABLE):Linux內(nèi)核2.6.25引入了一種新的進程狀態(tài),名為TASK_KILLABLE。該狀態(tài)的運行機制類似于TASK_UNINTERRUPTILBE,只不過在該狀態(tài)下的進程可以響應致命信號。它可以替代有效但可能無法終止的不可中斷的阻塞狀態(tài),以及易于喚醒安全性欠佳的可中斷的阻塞狀態(tài)。
- 暫停狀態(tài)(TASK_STOPPED):進程的執(zhí)行被暫停,當進程收到SIGTOP、SIGTSTP、SIGTTIN、SIGTTOU等信號時,就會進入暫停狀態(tài)。
- 跟蹤狀態(tài)(TASK_TRACED):進程的執(zhí)行被調(diào)試器暫停。當一個進程被另一個進程監(jiān)控是(如調(diào)試器使用ptrace()系統(tǒng)調(diào)用監(jiān)控測試程序),任何信號都可以把這個進程置于跟蹤狀態(tài)。
- 僵尸狀態(tài)(EXIT_ZOMBIE):進程運行結(jié)束,父進程尚未使用wait函數(shù)族(如使用waitpid()函數(shù))等系統(tǒng)調(diào)用來“收尸”,即等待父進程銷毀它。處于該狀態(tài)下的進程“實體”已經(jīng)放棄了幾乎所有的內(nèi)存空間,沒有任何可執(zhí)行代碼,也不能調(diào)度,僅僅在進程列表保留一個位置,記載該進程的退出狀態(tài)等信息供其他進程收集。
- 僵尸撤銷狀態(tài)(EXIT_DEAD):這是最終狀態(tài),父進程調(diào)用wait函數(shù)族“收尸”后,進程徹底有系統(tǒng)刪除。
內(nèi)核可以使用set_task_state和set_current_state宏來改變指定進程的狀態(tài)和當前執(zhí)行進程的狀態(tài)。 2)進程標識符 Linux內(nèi)核通過唯一的進程標識符PID來標識每個進程。PID存放進程描述符的pid字段中,新創(chuàng)建的PID通常是前一個進程的PID加1,不過PID的值有上限(最大值 = PID_MAX_DEFAULT - 1,通常為32767),我們可以在終端輸入 vim /proc/sys/kernel/pid_max 來確定該系統(tǒng)的進程數(shù)上限。 當系統(tǒng)啟動后,內(nèi)核通常作為一個進程的代表。一個指向task_struct的宏current用來記錄正在運行的進程。current經(jīng)常作為進程描述符結(jié)構(gòu)指針的形式出現(xiàn)在內(nèi)核代碼中,例如,current->pid表示處理器正在執(zhí)行進程的PID。當系統(tǒng)需要查看所有的進程時,則調(diào)用for_each_process()宏,這將比系統(tǒng)搜索數(shù)組的速度要快得多。 在Linux中獲得當前進程的進程號(PID)和父進程號(PPID)的系統(tǒng)調(diào)用函數(shù)分別為getpid()和getppid()。 測試代碼:
測試結(jié)果:
輸入?ps -axjf?命令查看所有進程與父進程
我們在次輸入ps -aux命令查看所有進程,可以得知父進程為bash
進程的創(chuàng)建、執(zhí)行和終止
1)進程的創(chuàng)建和執(zhí)行 許多操作系統(tǒng)提供的都是產(chǎn)生進程的機制,也就是說,首先在新的地址空間里創(chuàng)建進程、讀入可執(zhí)行文件、最后在開始執(zhí)行。Linux中進程的穿件很特別,它把上述步驟分解到兩個單獨的函數(shù)中去執(zhí)行:fork()和exec函數(shù)族。首先fork()函數(shù)通過復制當前進程創(chuàng)建一個子進程,子進程與父進程的區(qū)別在于不同的PID、PPID和某些資源及統(tǒng)計量。exec函數(shù)族負責讀取可執(zhí)行文件并將其載入地址空間開始運行。 要注意的是,Linux中的fork()函數(shù)使用的是寫時復制頁的技術(shù),也就是內(nèi)核在創(chuàng)建進程時,其資源并沒有被復制過來,資源的復制僅僅只有在需要寫入數(shù)據(jù)時才發(fā)生,在此之前只是以只讀的方式共享數(shù)據(jù)。寫時復制技術(shù)可以使Linux擁有快速執(zhí)行的能力,因此這個優(yōu)化是非常重要的。 2)進程的終止 進程終結(jié)也需要做很多繁瑣的收尾工作,系統(tǒng)必須保證回收進程所占的資源,并通知父進程。Linux首先把終止的進程設置為僵尸狀態(tài),這時,進程無法投入運行,它的存在只為父進程提供信息,申請死亡。父進程得到信息后,開始調(diào)用wait函數(shù)族,最后終止子進程,子進程占用的所有資源被全部釋放。進程的內(nèi)存結(jié)構(gòu)
Linux操作系統(tǒng)采用虛擬內(nèi)存管理技術(shù),使得每個進程都有各自互不干涉的進程地址空間。該地址空間是大小為4GB的線性虛擬空間(當然是指32位系統(tǒng)),用戶所看到和接觸到的都是該虛擬地址,無法看到實際的物理內(nèi)存地址。利用這種虛擬地址不但能起到保護操作系統(tǒng)的效果(用戶不能直接訪問物理內(nèi)存),而且更重要的是,用戶程序可以使用比實際物理內(nèi)存更大的地址空間。 我們可以通過命令getconf LONG_BIT 來查詢當前自己的系統(tǒng)是多少位的?我的安裝的UbuntKylin是64位的,即實際內(nèi)存最大可能達到2^64 = 128GB。(2^10 = 1kb,2^30 = 1GB)。 4GB的進程地址空間會被分出兩部分:用戶空間與內(nèi)核空間。用戶地址空間是從0~3GB(0xC0000000),內(nèi)存地址空間占據(jù)3GB~4GB。用戶進程通常情況下只能訪問用戶控件的虛擬地址,不能訪問內(nèi)核空間的虛擬地址。只有用戶進程使用系統(tǒng)調(diào)用(代表用戶進程在內(nèi)核執(zhí)行)時可以訪問內(nèi)核空間的虛擬空間。每當進程切換時,用戶空間就會跟著變化;而內(nèi)核空間有內(nèi)核負責映射,它并不會跟著進程改變而改變,是固定的。內(nèi)核空間地址有自己對應的頁表,用戶進程各自用不同的頁表。每個進程用戶空間都是完全獨立、互不相干的。進程的虛擬內(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ū)域。
運行此程序:
輸入?size task
text:存放的是代碼 ? ? data:存放的是初始化過的全局變量或靜態(tài)變量 ? bss:存放的是未初始化的全局變量或靜態(tài)變量 輸入命令?cat /proc/3834/maps?? 其中3834是task的PID
3、線程
前面已經(jīng)提到,進程是系統(tǒng)中程序執(zhí)行和資源分配的基本單位。每個進程都擁有自己的數(shù)據(jù)段、代碼段和堆棧段,這就造成了進程進程切換等操作時需要較復雜的上下文切換等動作。為了進一步減少處理機制的空轉(zhuǎn)時間,支持多處理器及減少上下文切換開銷,進程在演化中出現(xiàn)了另一個概念——線程。它是進程內(nèi)獨立的一條運行路線,是處理器調(diào)用的最小單元,也可以成為輕量級進程。線程可以對進程的內(nèi)存空間和資源進程訪問,并與同一個進程中的其他線程共享。因此,線程上下文切換的開銷比創(chuàng)建進程小得多。 一個進程可以擁有多個線程,每個線程必須有一個父進程。線程不擁有系統(tǒng)資源,它只具有運行所必需的一些數(shù)據(jù),如堆棧、寄存器與線程控制塊(TCB),線程與其父進程的其他線程共享該進程所擁有的全部資源。要注意的是,由線程共享了進程的資源和地址空間,因此,任何線程對系統(tǒng)資源的操作都會給其他線程帶來影響。由此可知,多線程中的同步是非常重要的問題。在多線程系統(tǒng)中,進程與線程的關系如圖所示:在Linux系統(tǒng)中,線程可以分為以下3種: 用戶級線程 用戶級線程主要解決的是上下文切換的問題,它的調(diào)度算法和調(diào)度過程全部由用戶自己選擇決定,在運行時不需要特定的內(nèi)核支持。在這里,操作系統(tǒng)往往會提供一個用戶空間的線程庫,該線程庫提供了線程的創(chuàng)建、調(diào)度和撤銷等功能,而內(nèi)核仍然僅對進程進行管理。如果一個進程中的某一個線程調(diào)用了一個阻塞的系統(tǒng)調(diào)用函數(shù),那么該進程好吧該進程中的其他所有線程也同時被阻塞。這種用戶級線程的主要缺點是在一個進程的多個線程的調(diào)度中無法發(fā)揮多處理器的優(yōu)勢。 輕量級進程 輕量級進程是內(nèi)核支持的用戶線程,是內(nèi)核線程的一種抽象對象。每個線程擁有一個或多個輕量級進程,而每個輕量級進程分別被綁定在一個內(nèi)核線程上。 內(nèi)核線程 內(nèi)核線程允許不同進程中的線程按照同一相對優(yōu)先調(diào)度方法進行調(diào)度,這樣就可以發(fā)揮多處理器的并發(fā)優(yōu)勢。現(xiàn)在大多數(shù)系統(tǒng)都采用用戶級線程與核心級線程并存的方法。一個用戶級線程可以對應一個或幾個核心級線程,也就是“一對一”或“多對一”模型。這樣既可以滿足多處理系統(tǒng)的需要,也可以最大限度地減少調(diào)度開銷。 使用線程機制大大加快了上下文切換速度,而節(jié)省了很多資源。但是因為在用戶態(tài)和內(nèi)核態(tài)均要實現(xiàn)調(diào)度管理,所有會增加實現(xiàn)的復雜度和引起優(yōu)先級翻轉(zhuǎn)的可能性。同時,一個多線程程序的同步設計與調(diào)試也會增加程序?qū)崿F(xiàn)的難道。
總結(jié)
以上是生活随笔為你收集整理的C语言多任务,多进程,多线程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android 之HierarchyVi
- 下一篇: kafka seek方法