進程概述

又來到了一個老生常談的問題,應用層軟件開發的程序員要不要了解和深刻學習操做系統呢? 今天就這個問題開始,來談談操做系統中能夠說是最重要的一個概念--進程html

操做系統最主要的兩個職能是管理各類資源和爲應用程序提供系統調用接口。這其中關鍵的部分是,cpu到進程的抽象,物理內存到地址空間(虛擬內存)的抽象,磁盤到文件的抽象,而其中後兩部分以進程爲基礎,因此嘛,咱重點來討論進程,以及與進程密切相關的線程。linux

.先說說概念程序員

進程(process)算法

狹義的定義:進程就是一段程序的執行過程。shell

廣義定義:進程是一個具備必定獨立功能的程序關於某次數據集合的一次運行活動,它是操做系統分配資源的基本單元。編程

簡單來說進程的概念主要有兩點:第一,進程是一個實體。每個進程都有它本身的地址空間,通常狀況下,包括文本區域(text region)、數據區域(data region)和堆棧(stack region)。文本區域存儲處理器執行的代碼;數據區域存儲變量和進程執行期間使用的動態分配的內存;堆棧區域存儲着活動過程當中調用的指令和本地變量。第二,進程是一個「執行中的程序」。程序是一個沒有生命的實體,只有處理器賦予程序生命時,它才能成爲一個活動的實體,咱們稱其爲進程。數組

進程狀態:進程有三個狀態,就緒,運行和阻塞。就緒狀態其實就是獲取了除cpu外的全部資源,只要處理器分配資源立刻就能夠運行。運行態就是獲取了處理器分配的資源,程序開始執行,阻塞態,當程序條件不夠時,須要等待條件知足時候才能執行,如等待I/O操做的時候,此刻的狀態就叫阻塞態。緩存

說說程序,程序是指令和數據的有序集合,其自己沒有任何運動的含義,是一個靜態的概念,而進程則是在處理機上的一次執行過程,它是一個動態的概念。進程是包含程序的,進程的執行離不開程序,進程中的文本區域就是代碼區,也就是程序。安全

線程(thread)session

一般在一個進程中能夠包含若干個線程,固然一個進程中至少有一個線程,否則沒有存在的意義。線程能夠利用進程所擁有的資源,在引入線程的操做系統中,一般都是把進程做爲分配資源的基本單位,而把線程做爲獨立運行和獨立調度的基本單位,因爲線程比進程更小,基本上不擁有系統資源,故對它的調度所付出的開銷就會小得多,能更高效的提升系統多個程序間併發執行的程度。

多線程(multiThread)

在一個程序中,這些獨立運行的程序片斷叫做「線程」(Thread),利用它編程的概念就叫做「多線程處理」。多線程是爲了同步完成多項任務,不是爲了提升運行效率,而是爲了提升資源使用效率來提升系統的效率。線程是在同一時間須要完成多項任務的時候實現的。

最簡單的比喻多線程就像火車的每一節車箱,而進程則是火車。車箱離開火車是沒法跑動的,同理火車也不可能只有一節車箱。多線程的出現就是爲了提升效率。

2、說說區別

1、進程與線程的區別:

進程和線程的主要差異在於它們是不一樣的操做系統資源管理方式。進程有獨立的地址空間,一個進程崩潰後,在保護模式下不會對其它進程產生影響,而線程只是一個進程中的不一樣執行路徑。線程有本身的堆棧和局部變量,但線程之間沒有單獨的地址空間,一個線程死掉就等於整個進程死掉因此多進程的程序要比多線程的程序健壯,但在進程切換時,耗費資源較大,效率要差一些。但對於一些要求同時進行而且又要共享某些變量的併發操做,只能用線程,不能用進程。

1) 簡而言之,一個程序至少有一個進程,一個進程至少有一個線程.

2) 線程的劃分尺度小於進程,使得多線程程序的併發性高。

3) 另外,進程在執行過程當中擁有獨立的內存單元,而多個線程共享內存,從而極大地提升了程序的運行效率。

4) 線程在執行過程當中與進程仍是有區別的。每一個獨立的線程有一個程序運行的入口、順序執行序列和程序的出口。可是線程不可以獨立執行,必須依存在應用程序中,由應用程序提供多個線程執行控制。

5) 從邏輯角度來看,多線程的意義在於一個應用程序中,有多個執行部分能夠同時執行。但操做系統並無將多個線程看作多個獨立的應用,來實現進程的調度和管理以及資源分配。這就是進程和線程的重要區別。

3、說說優缺點

線程和進程在使用上各有優缺點:線程執行開銷小,但不利於資源的管理和保護;而進程正相反。同時,線程適合於在SMP(多核處理機)機器上運行,而進程則能夠跨機器遷移。

4、說說進程和線程的細節,底層構成 和 調度

(一)進程相關的數據結構

爲了管理進程,內核必須對每一個進程所作的事情進行清楚的描述,例如,內核必須知道進程的優先級,它是在CPU上運行仍是由於某些事而被阻塞,給它分配了什麼樣的地址空間,容許它訪問哪一個文件等。

這些正是進程描述符的做用---進程描述符都是task_struct 數據結構,它的字段包含了與一個進程相關的全部信息。下圖顯示了Linux進程描述符

談談進程的基本信息。

1)標識一個進程--PID

每一個進程都必須擁有它本身的進程描述符;進程和進程描述符之間有很是嚴格的一一對應關係,因此咱們能夠方便地使用32位進程描述符地址標識進程。

進程描述符指針(task_struct*)指向這些地址。內核對進程的大部份引用都是經過進程描述符指針進行的。

另外一方面,類Unix橾做系統容許用戶使用一個叫作進程標識符processID(PID)的數來標識進程,PID存放在task_struct的pid字段中。PID被順序編號,新建立進程的PID一般是前一個進程的PID加1。不過,PID的值有一個上限,當內核使用的PID達到這個峯值的時候,就必須開始循環使用已閒置的小PID號。在缺省狀況下,最大的PID號是32767。

系統管理員能夠經過往/proc/sys/kernel/pid_max 這個文件中寫入一個更小的值來減少PID的上限值,使PID的上限小於32767。在64位體系結構中,系統管理員能夠把PID的上限擴大到4194304。

Linux只支持輕量級進程,不支持線程,但爲了彌補這樣的缺陷,Linux引入線程組的概念。一個線程組中的全部線程使用和該線程組的領頭線程相同的PID,也就是該組中第一個輕量級進程的PID,它被存入進程描述符的tgid字段中。getpid()系統調用返回當前進程的tgid值而不是pid值,所以,一個多線程應用的全部線程共享相同的PID。絕大多數進程都屬於一個線程組;而線程組的領頭線程其tgid與pid的值相同,於是getpid()系統調用對這類進程所起的做用和通常進程是同樣的。

因此,咱們得出一個重要的結論,Linux雖不支持線程,可是它有具有支持線程的操做系統的全部特性,後面講解輕量級進程的概念中還會詳細討論。

2)進程描述符定位

進程是動態實體,其生命週期範圍從幾毫秒到幾個月,所以內核必須同時處理不少進程,並把對應的進程描述符放在動態內存中,而不是放在永久分配給內核的內存區(3G之上的線性地址)。

那麼,怎麼找到被動態分配的進程描述符呢?咱們須要在3G之上線性地址的內存區爲每一個進程設計一個塊—thread_union。

對每一個進程來講,咱們須要給其分配兩個頁面,即8192個字節的塊,Linux把兩個不一樣數據結構緊湊地存放在一個單獨爲進程分配的存儲區域內:一個是內核態的進程堆棧,另外一個是緊挨着進程描述符的小數據結構thread_info,叫作線程描述符。

考慮到效率問題,內核讓這8k的空間佔據連續兩個頁框並讓第一個頁框的起始地址是2^13的倍數。當幾乎沒有可用的動態內存空間時,就會很難找到這樣的兩個連續頁框,由於空閒空間可能存在大量的碎片(注意,這裏是物理空間,見「夥伴系統算法」博文)。所以,在80x86體系結構中,在編譯時能夠進行設置,以使內核棧和線程描述符跨越一個單獨的頁框(由於主要存在的單頁的碎片)。在「Linux中的分段」的博文中咱們已經知道,內核態的進程訪問處於內核數據段的棧,也就是咱們Linux在3G以上內存空間爲每一個進程設計這麼一個棧的目的,這個棧不一樣於用戶態的進程所用的棧。由於內核控制路徑使用不多的棧,所以只須要幾千個字節的內核態堆棧。因此,對棧和thread_info來講,8KB足夠了。不過,若是隻使用一個頁框存放這兩個結構的話,內核要採用一些額外的棧以防止中斷和異常的深度嵌套而引發的溢出。

下圖顯示了在2頁(8KB)內存區中存放兩種數據結構的方式。線程描述符駐留於這個內存區的開始位置,而棧從末端向下增加。該圖還顯示瞭如何經過task字段與task_struct結構相互關聯。

struct thread_info {

struct task_struct    *task;        /* main task structure */

struct exec_domain    *exec_domain;    /* execution domain */

unsigned long        flags;        /* low level flags */

unsigned long        status;        /* thread-synchronous flags */

__u32            cpu;        /* current CPU */

__s32            preempt_count; /* 0 => preemptable, <0 => BUG */

mm_segment_t        addr_limit;    /* thread address space:0-0xBFFFFFFF for user-thead  0-0xFFFFFFFF for kernel-thread*/

struct restart_block    restart_block;

unsigned long           previous_esp;   /* ESP of the previous stack in caseof nested (IRQ) stacks*/

__u8            supervisor_stack[0];

};

esp爲CPU棧指針寄存器,用來存放棧頂單元的地址。在80x86系統中,棧起始於末端,並朝這個內存區的起始方向增加。從用戶態切換到內核態之後,進程的內核棧老是空的,所以,esp寄存器指向這個棧的頂端。

一旦數據寫入堆棧,esp的值就遞減。特別要注意,這裏的數據是指內核數據,其實用得不多,因此大多數時候這個內核棧是空的。由於thread_info

結構是52個字節的長度,因此內核棧能擴展到8140個字節。C語言使用下列聯合結構,方便地表示一個進程的線程描述符和內核棧:

union thread_union {

struct thread_info thread_info;

unsigned long stack[2048]; /* 1024 for 4KB stacks */

};

內核使用alloc_thread_info 和 free_thread_info宏分配和釋放存儲thread_info結構和內核棧的內存區。

3)標識當前進程

咱們再從效率的觀點來看,剛纔所講的thread_info結構與內核態堆棧之間的緊密結合提供的主要好處還在:內核很容易從esp寄存器的值得到當前在CPU上正在運行進程的thread_info結構的地址。事實上,若是thread_union的長度是8K(213字節),則內核屏蔽掉esp的低13位有效位就能夠得到thread_info結構的基地址;而若是thread_union的長度是4K,內核須要蔽掉esp的低12位有效位。這項工做由current_thread_info()函數來完成,它產生以下一些彙編指令:

movl $0xffffe000,%ecx /* or 0xfffff000 for 4KB stacks */

andl %esp,%ecx

movl %ecx,p

這三條指令執行後,p就是在執行指令的CPU上運行的當前進程的thread_info結構的指針。不過,進程最經常使用的是進程描述符的地址,而不是thread_info結構的地址。爲了得到當前在CPU上運行進程的描述符指針,內核要調用current宏,該宏本質上等價於current_thread_info( )->task,它產生以下彙編指令:

movl $0xffffe000,%ecx /* or 0xfffff000 for 4KB stacks */

andl %esp,%ecx

movl (%ecx),p

由於task字段在thread_info結構中的偏移量爲0,因此執行完這三條指令以後,p就是CPU上運行進程的描述符指針。

current宏常常做爲進程描述符字段的前綴出如今內核代碼中,例如,current->pid返回在CPU上正在執行CPU的進程的PID。

4)進程鏈表

Linux內核把進程鏈表把全部進程的描述符連接起來。每一個task_struct結構都包含一個list_head類型的tasks字段,這個類型的prev和next字段分別指向前面和後面的的task_struct元素。

進程鏈表的頭是init_task描述符,它是所謂的0進程或swapper進程的進程描述符。init_task的tasks.prev字段指向鏈表中最後插入的進程描述符的tasks字段。

SET_LINKS 和 REMOVE_LINKS 宏分別用於從進程鏈表中插入和刪除一個進程描述符。這些宏考慮了進程間的父子關係。

另外,還有一個頗有用的宏就是for_each_process,它的功能是掃描整個進程鏈表,其定義以下:

#define for_each_process(p) /

for (p=&init_task; (p=list_entry((p)->tasks.next, /

struct task_struct, tasks) /

) != &init_task; )

5)state字段

進程描述符task_struct結構的state字段描述了進程當前所處的狀態。它由一組標誌組成,其中每一個標誌描述一種可能的進程狀態。在當前的Linux版本中,這些狀態是互斥的,所以,嚴格意義上來講,只能設置一種狀態,其他的標誌位將被清除。下面是可能的狀態:

可運行狀態(TASK_RUNNING)

進程要麼在CPU上執行,要麼準備執行。

可中斷的等待狀態(TASK_INTERRUPTIBLE)

進程被掛起(睡眠),直到某個條件變爲真。產生一個硬件中斷、釋放進程正在等待的系統資源、或傳遞一個信號都是能夠喚醒進程的條件(把進程狀態放回到TASK_RUNNING)。

不可中斷的等待狀態(TASK_UNINTERRUPTIBLE)

與可中斷的等待狀態相似,但有一個例外,把信號傳遞到該睡眠進程時,不能改變它的狀態。這種狀態不多用到,但在一些特定條件下(進程必須等待,直到一個不能被中斷的時事件發生),這種狀態是頗有用的。例如,當進程打開一個設備文件,其相應的設備驅動程序開始探測相應的硬件設備時會用到這種狀態。探測完成之前,設備驅動程序不能被中斷,不然,硬件設備會處於不可預知的狀態。

暫停狀態(TASK_STOPPED)

進程的執行被暫停。當進程接收到SIGSTOP、SIGTSTP、SIGTTIN或SIGTTOU信號後,進人暫停狀態。

跟蹤狀態(TASK_TRACED)

進程的執行已由debugger程序暫停。當一個進程被另外一個進程監控時(例如debugger執行ptrace()系統調用監控一個測試程序)任何信號均可以把這個進程置於TASK_TRACED狀態。

還有兩個進程狀態既能夠存放在進程描述符的state字段啊中,也能夠存放在exit_state中字段中。從這兩個字段的名稱能夠看出,只有當進程的執行被終止時,進程的狀態纔會變成此兩種中的一種:

僵死狀態(EXIT_ZOMBIE)

進程的執行被終止,可是父進程還沒發佈wait4()或waitpid()系統調用來返回有關死亡進程的信息。發佈wait()類系統調用前,內核不能丟棄包含在死進程描述符中的數據,由於父進程可能還須要它。

僵死撤銷狀態(EXIT_DEAD)

終狀態:因爲父進程剛發出wait4()或waitpid()系統調用,於是進程由系統刪除。爲了防止其餘執行線程在同一個進程上也執行wait()類系統調用(這也是一種競爭條件),而把進程的狀態由僵死(EXIT_ZOMBIE)狀態改成僵死撤銷狀態(EXIT_DEAD)

state字段的值一般用一個簡單的賦值語句設置,例如:

p->state = TASK_RUNNING;

內核也使用set_task_state和set_current_state宏:它們分別設置指定進程的狀態和當前執行進程的狀態。此外,這些宏確保編譯程序或CPU控制單元不把賦值操做和其餘指令混合。混合指令的順序有時會致使災難性的後果。

6)TASK_RUNNING狀態的進程鏈表

當內核尋找到一個新進程在CPU上運行時,必須只考慮可運行進程(即處在TASK_RUNNING狀態的進程)。

早先的Linux版本把全部的可運行進程都放在同一個叫作運行隊列(runqueue)的鏈表中,因爲維持鏈表中的進程優先級排序的開銷過大,所以,早期的調度程序不得不爲選擇「最佳」可運行進程而掃描整個隊列。

Linux 2.6實現的運行隊列有所不一樣。其目的是讓調度程序能在固定的時間內選出「最佳」可運行隊列,與進程中可運行的進程數無關。

提升調度程序運行速度的訣竅是創建多個可運行進程鏈表,每種進程優先級對應一個不一樣的鏈表。每一個task_struct描述符包含一個list_head類型的字段run_list。若是進程的優先權等於k(其取值範圍從0到139),run_list字段就把該進程的優先級鏈入優先級爲k的可運行進程的鏈表中。此外,在多處理器系統中,每一個CPU都有它本身的運行隊列,即它本身的進程鏈表集。這是一個經過使數據結構更復雜來改善性能的典型例子:調度程序的操做效率的確更高了,但運行隊列的鏈表卻爲此被拆分紅140個不一樣的隊列!

內核必須爲系統中每一個運行隊列保存大量的數據,不過運行隊列的主要數據結構仍是組成運行隊列的進程描述符鏈表,全部這些鏈表都由一個單獨的prio_array_t數據結構來實現。

enqueue_task(p,array)函數把進程描述符(p參數)插入到某個運行隊列的鏈表(基於prio_array_t結構的array參數),其代碼本質上等同於以下代碼:

list_add_tail(&p->run_list, &array->queue[p->prio]);

__set_bit(p->prio, array->bitmap);

array->nr_active++;

p->array = array;

進程描述符的prio字段存放進程的動態優先權,而array字段是一個指針,指向當前運行隊列的proo_array_t數據結構。相似地,dequeue_task(p,array)函數從運行隊列的鏈表中刪除一個進程的描述符。

7)進程間關係

父子兄弟關係:

程序建立的進程具備父/子關係。若是一個進程建立多個子進程時,則子進程之間具備兄弟關係。進程0和進程1是由內核建立的;進程1(init)是全部進程的祖先。

在進程描述符中引入幾個字段來表示這些關係,咱們假設擁有該task_struct結構的這個進程叫P:

real_parent——指向建立了P進程的描述符,若是進程P的父進程不存在,就指向進程1的描述符(所以,若是用戶運行了一個後臺進程並且退出了shell,後臺進程就會變成init的子進程)。

parent——指向P的當前父進程(這種進程的子進程終止時,必須向父進程發信號)。它的值一般與reak_parent一致,但偶爾也能夠不一樣,例如,當另外一個進程發出監控P的ptrace系統調用請求時。

children——鏈表的頭部,鏈表中全部的元素都是P建立的子進程。

sibling——指向兄弟進程鏈表中的下一個元素或前一個元素的指針,這些兄弟進程的父進程跟P是同樣的。

下圖顯示了一組進程間的親屬關係,進程P0建立了P1,P2,P3,進程P3又建立了P4。

其餘關係:此外,進程之間還存在其餘關係:一個進程多是一個進程組或登陸會話的領頭進程,也多是一個線程組的領頭進程,他還可能跟蹤其餘進程的執行,下面就列出進程描述符中的一些字段,這些字段創建起了進程P和其餘進程之間的關係:

group_leader——P所在進程組的領頭進程的描述符指針

signal->pgrp——P所在進程組的領頭進程的PID

tgid——P所在線程組的領頭進程的PID

signal->session——P的登陸會話領頭進程的PID

ptrace_children——鏈表的頭,該鏈表包含全部被debugger程序跟蹤的P的子進程

ptrace_list——指向所跟蹤進程其實際父進程鏈表的前一個和下一個元素(用於P被跟蹤的時候)

8)PID定位task_struct

再來,內核必須能從進程的PID導出對應的進程描述符指針。例如,爲kill()系統調用提供服務時就會發生這種狀況:當進程P1但願向另外一個進程P2發送一個信號時,P1調用kill()系統調用,其參數爲P2的PID,內核從這個PID導出其對應的進程描述符,而後從該task_struct中取出記錄掛起信號的數據結構指針。

那麼如何獲得這個task_struct呢?首先想到for_each_process(p)。不行,雖然順序掃描進程鏈表並檢查進程描述符的pid字段是可行的,但至關低效。爲了加速查找,Linux內核引入了4個散列表。須要4個散列表是由於進程描述符包含了表示不一樣類型PID的字段,並且每種類型的PID須要它本身的散列表:

PIDTYPE_PID    pid    進程的PID

PIDTYPE_TGID    tgid    線程組領頭進程的PID

PIDTYPE_PGID    pgrp    進程組領頭進程的PID

PIDTYPE_SID    session    會話領頭的PID

內核初始化期間動態地爲4個散列表分配空間,並把它們的地址存入pid_hash數組。一個散列表的長度依賴於可用的RAM的容量,例如:一個系統擁有512MB的RAM,那麼每一個散列表就被存在4個頁框中,可擁有2048個表項。

用pid_hashfn宏把PID轉化爲表索引:

#define pid_hashfn(x) hash_long((unsigned long) x, pidhash_shift)

變量pidhash_shift用來存放表索引的長度(以位爲單位的長度,在咱們這裏是11位)。不少散列函數都使用hash_long(),在32位體系結構中它基本等價於:

unsigned long hash_long(unsigned long val, unsigned int bits)

{

unsigned long hash = val * 0x9e370001UL;

return hash >> (32 - bits);

}

由於咱們這裏的pidhash_shift等於11,因此pid_hashfn的取值範圍是0到2^11 - 1=2047。

正如計算機科學的基礎課程所闡述的那樣,散列函數並不總能確保PID與表的索引一一對應。兩個不一樣的PID散列到相同的表索引稱爲衝突(colliding)。Linux利用鏈表來處理衝突的PID:每一個表項是由衝突的進程描述符組成的雙向循環鏈表

(二)進程調度

1)進程調度的目標

1.高效性:高效意味着在相同的時間下要完成更多的任務,調度程序會被頻繁的執行,因此調度程序要儘量高效。

2.增強交互性能:在系統至關的負載下,也要保證系統的響應時間

3.保證公平和避免飢渴

4.SMP調度:調度程序必須支持多處理系統

5.軟實時調度:系統必須有效的調用實時進程,但不保證必定知足其要求。

2)進程優先級

進程提供了兩種優先級,一種是普通的進程優先級,一種是實時進程優先級。

前者適用SCHED_NORMAL調度策略,後者可選SCHED_FIFO或SCHED_RR調度策略,任什麼時候候,實時進程的優先級都高於普通進程,實時進程只會被更高級的實時進程搶佔,同級實時進程之間是按照FIFO(一次機會作完)或者RR(屢次輪轉)規則調度的。

實時進程,只有靜態優先級,由於內核不會再根據休眠等因素對其靜態優先級作調整,其範圍在0~MAX_RT_PRIO-1間。默認MAX_RT_PRIO配置爲100,也即,默認的實時優先級範圍是0~99。而nice值,影響的是優先級在MAX_RT_PRIO~MAX_RT_PRIO+40範圍內的進程。

不一樣與普通進程,系統調度時,實時優先級高的進程老是先於優先級低的進程執行,直到實時優先級高的實時進程沒法執行。實時進程老是被認爲處於活動狀態。若是有數個 優先級相同的實時進程,那麼系統就會按照進程出如今隊列上的順序選擇進程,假設當前CPU運行的實時進程A的優先級爲a,而此時有個優先級爲b的實時進程B進入可運行狀態,那麼只要b

不一樣調度策略的實時進程只有在相同優先級時纔有可比性:

1. 對於FIFO的進程,意味着只有當前進程執行完畢纔會輪到其餘進程執行。因而可知至關霸道。

2. 對於RR的進程。一旦時間片消耗完畢,則會將該進程置於隊列的末尾,而後運行其餘相同優先級的進程,若是沒有其餘相同優先級的進程,則該進程會繼續執行。

總而言之,對於實時進程,高優先級的進程就是大爺。它執行到無法執行了,才輪到低優先級的進程執行。

普通進程的調度

Linux對於普通的進程,根據動態優先級進行調度,而動態優先級是由靜態優先級調整而來,Linux下,靜態優先級是用戶不可見的,隱藏在內核中,而內核提供給用戶一個能夠影響靜態優先級的接口,那就是nice值。

關係以下:

static_prio =MAX_RT_PRIO+nice+20

nice值的範圍是-20~19,於是靜態優先級範圍在100~139之間,nice數值越大就使得static_prio越大,最終進程優先級就越低。

咱們前面也說了,系統調度時,還會考慮其餘因素,於是會計算出一個叫進程動態優先級的東西,根據此來實施調度。由於,不只要考慮靜態優先級,也要考慮進程

的屬性。例如若是進程屬於交互式進程,那麼能夠適當的調高它的優先級,使得界面反應地更加迅速,從而使用戶獲得更好的體驗。Linux2.6

在這方面有了較大的提升。Linux2.6認爲,交互式進程能夠從平均睡眠時間這樣一個measurement進行判斷。進程過去的睡眠時間越多,則越有

可能屬於交互式進程。則系統調度時,會給該進程更多的獎勵(bonus),以便該進程有更多的機會可以執行。獎勵(bonus)從0到10不等。

系統會嚴格按照動態優先級高低的順序安排進程執行。動態優先級高的進程進入非運行狀態,或者時間片消耗完畢纔會輪到動態優先級較低的進程執行。動態優先級的計算主要考慮兩個因素:靜態優先級,進程的平均睡眠時間也即bonus。計算公式以下,

dynamic_prio = max (100, min (static_prio - bonus + 5, 139))

爲何根據睡眠和運行時間肯定獎懲分數是合理的

睡眠和CPU耗時反應了進程IO密集和CPU密集兩大瞬時特色,不一樣時期,一個進程可能便是CPU密集型也是IO密集型進程。對於表現爲IO密集的進程,應該常常運行,但每次時間片不要太長。對於表現爲CPU密集的進程,CPU不該該讓其常常運行,但每次運行時間片要長。交互進程爲例,假如以前其其大部分時間在於等待CPU,這時爲了調高相應速度,就須要增長獎勵分。另外一方面,若是此進程老是耗盡每次分配給它的時間片,爲了對其餘進程公平,就要增長這個進程的懲罰分數。能夠參考CFS的virtutime機制.

3)現代方法CFS

再也不單純依靠進程優先級絕對值,而是參考其絕對值,綜合考慮全部進程的時間,給出當前調度時間單位內其應有的權重,也就是,每一個進程的權重X單位時間=應獲cpu時間,可是這個應得的cpu時間不該過小(假設閾值爲1ms),不然會由於切換得不償失。可是,當進程足夠多時候,確定有不少不一樣權重的進程得到相

同的時間——最低閾值1ms,因此,CFS只是近似徹底公平。

4)Linux進程狀態機

進程是經過fork系列的系統調用(fork clone,vfork)來建立的,內核,內核模塊也能夠經過kernel_thread函數建立內核進程,這些建立子進程的函數本質上都完成了相同的功能——將調用進程複製一份,獲得子進程。(能夠經過選項參數來決定各類資源是共享、仍是私有。)那麼既然調用進程處於TASK_RUNNING狀態(不然,它若不是正在運行,又怎麼進行調用?),則子進程默認也處於TASK_RUNNING狀態。

另外,在系統調用clone和內核函數kernel_thread也接受CLONE_STOPPED選項,從而將子進程的初始狀態置爲 TASK_STOPPED。

進程建立後,狀態可能發生一系列的變化,直到進程退出。而儘管進程狀態有好幾種,可是進程狀態的變遷卻只有兩個方向——從TASK_RUNNING狀態變爲非TASK_RUNNING狀態、或者從非TASK_RUNNING狀態變爲TASK_RUNNING狀態。總之,TASK_RUNNING是必經之路,不可能兩個非RUN狀態直接轉換。

也就是說,若是給一個TASK_INTERRUPTIBLE狀態的進程發送SIGKILL信號,這個進程將先被喚醒(進入TASK_RUNNING狀態),而後再響應SIGKILL信號而退出(變爲TASK_DEAD狀態)。並不會從TASK_INTERRUPTIBLE狀態直接退出。

進程從非TASK_RUNNING狀態變爲TASK_RUNNING狀態,是由別的進程(也多是中斷處理程序)執行喚醒操做來實現的。執行喚醒的

進程設置被喚醒進程的狀態爲TASK_RUNNING,而後將其task_struct結構加入到某個CPU的可執行隊列中。因而被喚醒的進程將有機會被

調度執行。

而進程從TASK_RUNNING狀態變爲非TASK_RUNNING狀態,則有兩種途徑:

一、響應信號而進入TASK_STOPED狀態、或TASK_DEAD狀態;

二、執行系統調用主動進入TASK_INTERRUPTIBLE狀態(如nanosleep系統調用)、或TASK_DEAD狀態(如exit系統調用);或因爲執行系統調用須要的資源得不到滿     足,而進入TASK_INTERRUPTIBLE狀態或TASK_UNINTERRUPTIBLE狀態(如select系統調用)。

顯然,這兩種狀況都只能發生在進程正在CPU上執行的狀況下。

經過ps命令咱們可以查看到系統中存在的進程,以及它們的狀態:R(TASK_RUNNING),可執行狀態。

只有在該狀態的進程纔可能在CPU上運行。而同一時刻可能有多個進程處於可執行狀態,這些進程的task_struct結構(進程控制塊)被放入對應CPU的可執行隊列中(一個進程最多隻能出如今一個CPU的可執行隊列中)。進程調度器的任務就是從各個CPU的可執行隊列中分別選擇一個進程在該CPU上運行。

只要可執行隊列不爲空,其對應的CPU就不能偷懶,就要執行其中某個進程。通常稱此時的CPU「忙碌」。對應的,CPU「空閒」就是指其對應的可執行隊列爲空,以至於CPU無事可作。

有人問,爲何死循環程序會致使CPU佔用高呢?由於死循環程序基本上老是處於TASK_RUNNING狀態(進程處於可執行隊列中)。除非一些很是極端狀況(好比系統內存嚴重緊缺,致使進程的某些須要使用的頁面被換出,而且在頁面須要換入時又沒法分配到內存……),不然這個進程不會睡眠。因此CPU的可執行隊列老是不爲空(至少有這麼個進程存在),CPU也就不會「空閒」。

不少操做系統教科書將正在CPU上執行的進程定義爲RUNNING狀態、而將可執行可是還沒有被調度執行的進程定義爲READY狀態,這兩種狀態在linux下統一爲 TASK_RUNNING狀態。

S(TASK_INTERRUPTIBLE),可中斷的睡眠狀態。

處於這個狀態的進程由於等待某某事件的發生(好比等待socket鏈接、等待信號量),而被掛起。這些進程的task_struct結構被放入對應事件的等待隊列中。當這些事件發生時(由外部中斷觸發、或由其餘進程觸發),對應的等待隊列中的一個或多個進程將被喚醒。

經過ps命令咱們會看到,通常狀況下,進程列表中的絕大多數進程都處於TASK_INTERRUPTIBLE狀態(除非機器的負載很高)。畢竟CPU就這麼一兩個,進程動輒幾十上百個,若是不是絕大多數進程都在睡眠,CPU又怎麼響應得過來。

D(TASK_UNINTERRUPTIBLE),不可中斷的睡眠狀態。

與TASK_INTERRUPTIBLE狀態相似,進程處於睡眠狀態,可是此刻進程是不可中斷的。不可中斷,指的並非CPU不響應外部硬件的中斷,而是指進程不響應異步信號。

絕大多數狀況下,進程處在睡眠狀態時,老是應該可以響應異步信號的。不然你將驚奇的發現,kill -9居然殺不死一個正在睡眠的進程了!因而咱們也很好理解,爲何ps命令看到的進程幾乎不會出現TASK_UNINTERRUPTIBLE狀態,而老是TASK_INTERRUPTIBLE狀態。

而TASK_UNINTERRUPTIBLE狀態存在的意義就在於,內核的某些處理流程是不能被打斷的。若是響應異步信號,程序的執行流程中就會被插入一段用於處理異步信號的流程(這個插入的流程可能只存在於內核態,也可能延伸到用戶態),因而原有的流程就被中斷了(參見《linux異步信號handle淺析》)。

在進程對某些硬件進行操做時(好比進程調用read系統調用對某個設備文件進行讀操做,而read系統調用最終執行到對應設備驅動的代碼,並與對應的物理設備進行交互),可能須要使用TASK_UNINTERRUPTIBLE狀態對進程進行保護,以免進程與設備交互的過程被打斷,形成設備陷入不可控的狀態。(好比read系統調用觸發了一次磁盤到用戶空間的內存的DMA,若是DMA進行過程當中,進程因爲響應信號而退出了,那麼DMA正在訪問的內存可能就要被釋放了。)這種狀況下的TASK_UNINTERRUPTIBLE狀態老是很是短暫的,經過ps命令基本上不可能捕捉到。

linux系統中也存在容易捕捉的TASK_UNINTERRUPTIBLE狀態。執行vfork系統調用後,父進程將進入TASK_UNINTERRUPTIBLE狀態,直到子進程調用exit或exec。

經過下面的代碼就能獲得處於TASK_UNINTERRUPTIBLE狀態的進程:

#include

void main() {

if (!vfork()) sleep(100);

}

向進程發送一個SIGSTOP信號,它就會因響應該信號而進入TASK_STOPPED狀態(除非該進程自己處於TASK_UNINTERRUPTIBLE狀態而不響應信號)。(SIGSTOP與SIGKILL信號同樣,是很是強制的。不容許用戶進程經過signal系列的系統調用從新設置對應的信號處理函數。)

向進程發送一個SIGCONT信號,可讓其從TASK_STOPPED狀態恢復到TASK_RUNNING狀態。

當進程正在被跟蹤時,它處於TASK_TRACED這個特殊的狀態。「正在被跟蹤」指的是進程暫停下來,等待跟蹤它的進程對它進行操做。好比在gdb中對被跟蹤的進程下一個斷點,進程在斷點處停下來的時候就處於TASK_TRACED狀態。而在其餘時候,被跟蹤的進程仍是處於前面提到的那些狀態。

對於進程自己來講,TASK_STOPPED和TASK_TRACED狀態很相似,都是表示進程暫停下來。

而TASK_TRACED狀態至關於在TASK_STOPPED之上多了一層保護,處於TASK_TRACED狀態的進程不能響應SIGCONT信號而被喚醒。只能等到調試進程經過ptrace系統調用執行PTRACE_CONT、PTRACE_DETACH等操做(經過ptrace系統調用的參數指定操做),或調試進程退出,被調試的進程才能恢復TASK_RUNNING狀態。

Z(TASK_DEAD - EXIT_ZOMBIE),退出狀態,進程成爲殭屍進程。

進程在退出的過程當中,處於TASK_DEAD狀態。

在這個退出過程當中,進程佔有的全部資源將被回收,除了task_struct結構(以及少數資源)之外。因而進程就只剩下task_struct這麼個空殼,故稱爲殭屍。

之因此保留task_struct,是由於task_struct裏面保存了進程的退出碼、以及一些統計信息。而其父進程極可能會關心這些信息。好比在shell中,$?變量就保存了最後一個退出的前臺進程的退出碼,而這個退出碼每每被做爲if語句的判斷條件。

固然,內核也能夠將這些信息保存在別的地方,而將task_struct結構釋放掉,以節省一些空間。可是使用task_struct結構更爲方便,由於在內核中已經創建了從pid到task_struct查找關係,還有進程間的父子關係。釋放掉task_struct,則須要創建一些新的數據結構,以便讓父進程找到它的子進程的退出信息。

父進程能夠經過wait系列的系統調用(如wait四、waitid)來等待某個或某些子進程的退出,並獲取它的退出信息。而後wait系列的系統調用會順便將子進程的屍體(task_struct)也釋放掉。

子進程在退出的過程當中,內核會給其父進程發送一個信號,通知父進程來「收屍」。這個信號默認是SIGCHLD,可是在經過clone系統調用建立子進程時,能夠設置這個信號。

經過下面的代碼可以製造一個EXIT_ZOMBIE狀態的進程:

#include

void main() {

if (fork())

while(1) sleep(100);

}

編譯運行,而後ps一下:

kouu@kouu-one:~/test$ ps -ax | grep a\.out

10410 pts/0      S+       0:00 ./a.out

10411 pts/0      Z+       0:00 [a.out]

10413 pts/1      S+       0:00 grep a.out

只要父進程不退出,這個殭屍狀態的子進程就一直存在。那麼若是父進程退出了呢,誰又來給子進程「收屍」?

當進程退出的時候,會將它的全部子進程都託管給別的進程(使之成爲別的進程的子進程)。託管給誰呢?多是退出進程所在進程組的下一個進程(若是存在的話),或者是1號進程。因此每一個進程、每時每刻都有父進程存在。除非它是1號進程。

1號進程,pid爲1的進程,又稱init進程。

linux系統啓動後,第一個被建立的用戶態進程就是init進程。它有兩項使命:

一、執行系統初始化腳本,建立一系列的進程(它們都是init進程的子孫);

二、在一個死循環中等待其子進程的退出事件,並調用waitid系統調用來完成「收屍」工做;

init進程不會被暫停、也不會被殺死(這是由內核來保證的)。它在等待子進程退出的過程當中處於TASK_INTERRUPTIBLE狀態,「收屍」過程當中則處於TASK_RUNNING狀態。

X(TASK_DEAD - EXIT_DEAD),退出狀態,進程即將被銷燬。

而進程在退出過程當中也可能不會保留它的task_struct。好比這個進程是多線程程序中被detach過的進程(進程?線程?參見《linux線程淺析》)。或者父進程經過設置SIGCHLD信號的handler爲SIG_IGN,顯式的忽略了SIGCHLD信號。(這是posix的規定,儘管子進程的退出信號能夠被設置爲SIGCHLD之外的其餘信號。)

此時,進程將被置於EXIT_DEAD退出狀態,這意味着接下來的代碼當即就會將該進程完全釋放。因此EXIT_DEAD狀態是很是短暫的,幾乎不可能經過ps命令捕捉到。

5)調度觸發的時機

調度的觸發主要有以下幾種狀況:

一、當前進程(正在CPU上運行的進程)狀態變爲非可執行狀態。

進程執行系統調用主動變爲非可執行狀態。好比執行nanosleep進入睡眠、執行exit退出、等等;

進程請求的資源得不到知足而被迫進入睡眠狀態。好比執行read系統調用時,磁盤高速緩存裏沒有所須要的數據,從而睡眠等待磁盤IO;

進程響應信號而變爲非可執行狀態。好比響應SIGSTOP進入暫停狀態、響應SIGKILL退出、等等;

二、搶佔。進程運行時,非預期地被剝奪CPU的使用權。這又分兩種狀況:進程用完了時間片、或出現了優先級更高的進程。

優先級更高的進程受正在CPU上運行的進程的影響而被喚醒。如發送信號主動喚醒,或由於釋放互斥對象(如釋放鎖)而被喚醒;

內核在響應時鐘中斷的過程當中,發現當前進程的時間片用完;

內核在響應中斷的過程當中,發現優先級更高的進程所等待的外部資源的變爲可用,從而將其喚醒。好比CPU收到網卡中斷,內核處理該中斷,發現某個socket可讀,因而喚醒正在等待讀這個socket的進程;再好比內核在處理時鐘中斷的過程當中,觸發了定時器,從而喚醒對應的正在nanosleep系統調用中睡眠的進程;

6)內核搶佔

理想狀況下,只要知足「出現了優先級更高的進程」這個條件,當前進程就應該被馬上搶佔。可是,就像多線程程序須要用鎖來保護臨界區資源同樣,內核中也存在不少這樣的臨界區,不大可能隨時隨地都能接收搶佔。

linux 2.4時的設計就很是簡單,內核不支持搶佔。進程運行在內核態時(好比正在執行系統調用、正處於異常處理函數中),是不容許搶佔的。必須等到返回用戶態時纔會觸發調度(確切的說,是在返回用戶態以前,內核會專門檢查一下是否須要調度);

linux 2.6則實現了內核搶佔,可是在不少地方仍是爲了保護臨界區資源而須要臨時性的禁用內核搶佔。

也有一些地方是出於效率考慮而禁用搶佔,比較典型的是spin_lock。spin_lock是這樣一種鎖,若是請求加鎖得不到知足(鎖已被別的進程佔有),則當前進程在一個死循環中不斷檢測鎖的狀態,直到鎖被釋放。

爲何要這樣忙等待呢?由於臨界區很小,好比只保護「i+=j++;」這麼一句。若是由於加鎖失敗而造成「睡眠-喚醒」這麼個過程,就有些得不償失了。

那麼既然當前進程忙等待(不睡眠),誰又來釋放鎖呢?其實已獲得鎖的進程是運行在另外一個CPU上的,而且是禁用了內核搶佔的。這個進程不會被其餘進程搶佔,因此等待鎖的進程只有可能運行在別的CPU上。(若是隻有一個CPU呢?那麼就不可能存在等待鎖的進程了。)

而若是不由用內核搶佔呢?那麼獲得鎖的進程將可能被搶佔,因而可能好久都不會釋放鎖。因而,等待鎖的進程可能就不知何年何月得償所望了。

對於一些實時性要求更高的系統,則不能容忍spin_lock這樣的東西。寧肯改用更費勁的「睡眠-喚醒」過程,也不能由於禁用搶佔而讓更高優先級的進程等待。好比,嵌入式實時linux montavista就是這麼幹的。

因而可知,實時並不表明高效。不少時候爲了實現「實時」,仍是須要對性能作必定讓步的。

7)多處理器下的負載均衡

前面咱們並無專門討論多處理器對調度程序的影響,其實也沒有什麼特別的,就是在同一時刻能有多個進程並行地運行而已。那麼,爲何會有「多處理器負載均衡」這個事情呢?

若是系統中只有一個可執行隊列,哪一個CPU空閒了就去隊列中找一個最合適的進程來執行。這樣不是很好很均衡嗎?

的確如此,可是多處理器共用一個可執行隊列會有一些問題。顯然,每一個CPU在執行調度程序時都須要把隊列鎖起來,這會使得調度程序難以並行,可能致使系統性能降低。而若是每一個CPU對應一個可執行隊列則不存在這樣的問題。

另外,多個可執行隊列還有一個好處。這使得一個進程在一段時間內老是在同一個CPU上執行,那麼極可能這個CPU的各級cache中都緩存着這個進程的數據,頗有利於系統性能的提高。

因此,在linux下,每一個CPU都有着對應的可執行隊列,而一個可執行狀態的進程在同一時刻只能處於一個可執行隊列中。

因而,「多處理器負載均衡」這個麻煩事情就來了。內核須要關注各個CPU可執行隊列中的進程數目,在數目不均衡時作出適當調整。何時須要調整,以多大力度進程調整,這些都是內核須要關心的。固然,儘可能不要調整最好,畢竟調整起來又要耗CPU、又要鎖可執行隊列,代價仍是不小的。

另外,內核還得關心各個CPU的關係。兩個CPU之間,多是相互獨立的、多是共享cache的、甚至多是由同一個物理CPU經過超線程技術虛擬出來的……CPU之間的關係也是實現負載均衡的重要依據。關係越緊密,進程在它們之間遷移的代價就越小。參見《linux內核SMP負載均衡淺析》。

優先級繼承

因爲互斥,一個進程(設爲A)可能由於等待進入臨界區而睡眠。直到正在佔有相應資源的進程(設爲B)退出臨界區,進程A才被喚醒。

可能存在這樣的狀況:A的優先級很是高,B的優先級很是低。B進入了臨界區,可是卻被其餘優先級較高的進程(設爲C)搶佔了,而得不到運行,也就沒法退出臨界區。因而A也就沒法被喚醒。

A有着很高的優先級,可是如今卻淪落到跟B一塊兒,被優先級並不過高的C搶佔,致使執行被推遲。這種現象就叫作優先級反轉。

出現這種現象是很不合理的。較好的應對措施是:當A開始等待B退出臨界區時,B臨時獲得A的優先級(仍是假設A的優先級高於B),以便順利完成處理過程,退出臨界區。以後B的優先級恢復。這就是優先級繼承的方法。

中斷處理線程化

在linux下,中斷處理程序運行於一個不可調度的上下文中。從CPU響應硬件中斷自動跳轉到內核設定的中斷處理程序去執行,到中斷處理程序退出,整個過程是不能被搶佔的。

一個進程若是被搶佔了,能夠經過保存在它的進程控制塊(task_struct)中的信息,在以後的某個時間恢復它的運行。而中斷上下文則沒有task_struct,被搶佔了就無法恢復了。

中斷處理程序不能被搶佔,也就意味着中斷處理程序的「優先級」比任何進程都高(必須等中斷處理程序完成了,進程才能被執行)。可是在實際的應用場景中,可能某些實時進程應該獲得比中斷處理程序更高的優先級。

因而,一些實時性要求更高的系統就給中斷處理程序賦予了task_struct以及優先級,使得它們在必要的時候可以被高優先級的進程搶佔。可是顯然,作這些工做是會給系統形成必定開銷的,這也是爲了實現「實時」而對性能作出的一種讓步。

(三)進程同步與互斥

多進程系統中避免不了進程之間的相互關係,最主要是兩種關係--同步和互斥。

進程同步 是進程間直接的相互做用,是合做進程間的有意識的行爲。咱們也要有必定的同步機制保證它們的執行次序。

進程互斥是進程之間發生的一種間接性做用,通常是程序不但願的。一般的狀況是兩個或兩個以上的進程須要同時訪問某個共享變量。咱們通常將發生可以問共享變量的程序段稱爲臨界區。兩個進程不能同時進入臨界區,不然就會致使數據的不一致,產生與時間有關的錯誤。解決互斥問題應該知足互斥和公平兩個原則,即任意時刻只能容許一個進程處於同一共享變量的臨界區,並且不能讓任一進程無限期地等待。互斥問題能夠用硬件方法解決,也能夠用軟件方法。

同步是說進程的合做關係,互斥是說進程對資源的競爭關係。

信號量、管程

二,管程:參考自http://hi.baidu.com/zucenaa/blog/item/e63d22277c9d9c09918f9de2.html

信號量機制功能強大,但使用時對信號量的操做分散,並且難以控制,讀寫和維護都很困難。所以後

來又提出了一種集中式同步進程——管程。其基本思想是將共享變量和對它們的操做集中在一個模塊中,操做系統或併發程序就由這樣的模塊構成。這樣模塊之間聯

系清晰,便於維護和修改,易於保證正確性。

管程做爲一個模塊,它的類型定義以下:

monitor_name = MoNITOR;

共享變量說明;

define 本管程內部定義、外部可調用的函數名錶;

use 本管程外部定義、內部可調用的函數名錶;

內部定義的函數說明和函數體

{

共享變量初始化語句;

}

從語言的角度看,管程主要有如下特性:

(1)模塊化。管程是一個基本程序單位,能夠單獨編譯;

(2)抽象數據類型。管程是中不只有數據,並且有對數據的操做;

(3)信息掩蔽。管程外能夠調用管程內部定義的一些函數,但函數的具體實現外部不可見;

對於管程中定義的共享變量的全部操做都侷限在管程中,外部只能經過調用管程的某些函數來間接訪問這些變量。所以管程有很好的封裝性。

爲了保證共享變量的數據一致性,管程應互斥使用。 管程一般是用於管理資源的,所以管程中有進程等待隊列和相應的等待和喚醒操做。在管程入口有一個等待隊列,稱爲入口等待隊列。當一個已進入管程的進程等待時,就釋放管程的互斥使用權;當已進入管程的一個進程喚醒另外一個進程時,二者必須有一個退出或中止使用管程。在管程內部,因爲執行喚醒操做,可能存在多個等待進程(等待使用管程),稱爲緊急等待隊列,它的優先級高於入口等待隊列。

所以,一個進程進入管程以前要先申請,通常由管程提供一個enter過程;離開時釋放使用權,若是緊急等待隊列不空,則喚醒第一個等待者,通常也由管程提供外部過程leave。

管程內部有本身的等待機制。管程能夠說明一種特殊的條件型變量:var c:condition;其實是一個指針,指向一個等待該條件的PCB隊列。對條件型變量可執行wait和signal操做:(聯繫P和V; take和give)

wait(c):若緊急等待隊列不空,喚醒第一個等待者,不然釋放管程使用權。執行本操做的進程進入C隊列尾部;

signal(c):若C隊列爲空,繼續原進程,不然喚醒隊列第一個等待者,本身進入緊急等待隊列尾部。

(四)進程間通訊(IPC)

進程間通訊主要包括 管道,系統IPC(包括消息隊列,信號量,共享內存), SOCKET.

管道分爲有名管道和無名管道,無名管道只能用於親屬進程之間的通訊,而有名管道則可用於無親屬關係的進程之間。

消息隊列用於運行於同一臺機器上的進程間通訊,與管道類似;

消息隊列用於運行於同一臺機器上的進程間通訊,與管道類似;

共享內存一般由一個進程建立,其他進程對這塊內存區進行讀寫。獲得共享內存有兩種方式:映射/dev/mem設備和內存映像文件。前一種方式不給系統帶來額外的開銷,但在現實中並不經常使用,由於它控制存取的是實際的物理內存;

本質上,信號量是一個計數器,它用來記錄對某個資源(如共享內存)的存取情況。通常說來,爲了得到共享資源,進程須要執行下列操做:

(1)測試控制該資源的信號量;

(2)若此信號量的值爲正,則容許進行使用該資源,進程將進號量減1;

(3)若此信號量爲0,則該資源目前不可用,進程進入睡眠狀態,直至信號量值大於0,進程被喚醒,轉入步驟(1);

(4)當進程再也不使用一個信號量控制的資源時,信號量值加1,若是此時有進程正在睡眠等待此信號量,則喚醒此進程。

套接字通訊並不爲Linux所專有,在全部提供了TCP/IP協議棧的操做系統中幾乎都提供了socket,而全部這樣操做系統,對套接字的編程方法幾乎是徹底同樣的。

管道:速度慢,容量有限,只有父子進程能通信

FIFO(命名管道):任何進程間都能通信,但速度慢,命名管道可用於非父子進程,命名管道就是FIFO,管道是先進先出的通信方式

消息隊列:容量受到系統限制,且要注意第一次讀的時候,要考慮上一次沒有讀完數據的問題

信號量:不能傳遞複雜消息,只能用來同步

共享內存區:可以很容易控制容量,速度快,但要保持同步,好比一個進程在寫的時候,另外一個進程要注意讀寫的問題,至關於線程中的線程安全,固然,共享內存區一樣能夠用做線程間通信,不過沒這個必要,線程間原本就已經共享了同一進程內的一塊內存。

線程

線程是CPU調度的最小單位,多個線程共享一個進程的地址空間。

線程包含線程ID,程序計數器,寄存器和棧。

做者:簡書的王布斯 連接:https://www.jianshu.com/p/7ce30a806c51 來源:簡書 著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。
相關文章
相關標籤/搜索