操作系統基礎知識大全_第1頁
操作系統基礎知識大全_第2頁
操作系統基礎知識大全_第3頁
操作系統基礎知識大全_第4頁
操作系統基礎知識大全_第5頁
已閱讀5頁,還剩45頁未讀 繼續免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

操作系統基礎知識大全

操作系統基礎知識筆記

一、操作系統相關概念

計算機軟件:系統軟件和應用軟件。

計算機系統資源:硬件資源、軟件資源。

硬件資源:中央處理器、存儲器、輸入、輸出等物理設備。

軟件資源:以文件形式保存到存儲器上的程序和數據信息。

定義:有效地組織和管理系統的各種軟/硬件資源,合理組織計算機系統

工作流程,控制程序的執行,并給用戶提供一個良好的環境和友好的接口。

操作系統作用:通過資源管理提高計算機系統的效率、改善人家界面提高

良好的工作環境。

吞吐量:計算機在單位時間內處理工作的能力。

二、操作系統的特征與功能

操作系統的特征:并發性、共享性、虛擬性、隨機性。

2.1、操作系統的功能

1、進程管理:實際上是對處理機的執行時間進行管理,采用多道程序等

技術將西的時間合理分配給每個任務。比如:進程控制、進程同步、進程

通信、進程調度。

2、文件管理:主要有存儲空間管理、目錄管理、文件讀寫。

3、存儲管理:對主存儲器空間進行管理,主要包括存儲空間分配回收、

存儲保護、地址映射、主存擴充等。

4、設備管理:對硬件設備的管理。包括分配、啟動、完成、回收。

5、作業管理:包括任務、界面管理、人機交互、語音控制、虛擬現實等。

三、操作系統分類

1、批處理操作系統

分為單道批處理、多道批處理。

單道批處理:早期的操作系統,一次只有一個作業裝入內存執行。作業由

用戶程序、數據和作業說明書組成。一個作業運行結束后,自動調入同批的

下一個作業。

多道批處理:允許多個作業裝入內存執行,在任意時刻,作業都處于開始

和結束點之間。

多道批處理系統特點:多道、宏觀上并行運行、微觀上串行運行。

2、分時操作系統

分時操作系統是將CPU的工作劃分為很短的時間片。輪流為各個終端的用

戶服務。

分時操作系統特點:多路性、獨立性、交互性、及時性。

3,實時操作系統

實時操作系統對交互能力要求不高,要能對外來信息足夠快的速度響應和

處理。分為實時控制系統和實時信息處理系統。

實時控制系統:主要用于生產過程的自動控制,比如自動采集、飛機的自

動駕駛等。

實時信息處理系統:主要是實時信息處理,比如飛機訂票系統、情報檢索

系統等。

4、網絡操作系統

網絡操作系統使互聯網能方便有效的共享網絡資源,為網絡用戶提供各種

服務軟件和有關協議的幾何。比如電子郵件、文件傳輸、共享硬盤等。

網絡操作系統分為如下三類:

1、集中式:系統的基本單元由一臺主機和若干臺主機相連的終端構成,

將多臺主機連接處理形成網絡。比如UNI_。

2、客戶端/服務器模式:該模式分為客戶端和服務器。服務器是網絡控制

的中心,向客戶端提供多種服務,客戶端主要是訪問服務端的資源。

3、對等模式(P2P):相當于每一臺客戶端都可以給其他客戶端提供資源服

務。

5、分布式操作系統

分布式操作系統是由多個分散的計算機經連接而成的計算機系統,系統中

的計算機無主次之分,任意兩臺計算機都可以交換信息。分布式操作系統能

直接對各類資源進行動態分配和調度、任務劃分、信息傳輸協調工作,為用

戶提供一個統一的界面、標準的接口,用戶通過這一界面實現所需要的操作

和使用系統資源。

6、微機操作系統

目前主流的操作系統有Linu—、MacOS>Windows0

7、嵌入式操作系統

嵌入式操作系統運行在嵌入式智能芯片環境中,對整個智能芯片以及操

作、控制、部件裝置等資源進行統一協調、處理、指揮、控制。

嵌入式操作系統特點:微型化、可定制、實時性、可靠性、易移植性。

操作系統基礎知識

一、概述

1.操作系統基本特征

1.并發

并發是指宏觀上在一段時間內能同時運行多個程序,而并行則指同一時刻

能運行多個指令。

并行需要硬件支持,如多流水線或者多處理器。

操作系統通過引入進程和線程,使得程序能夠并發運行。

2.共享

共享是指系統中的資源可以被多個并發進程共同使用。

有兩種共享方式:互斥共享和同時共享。

互斥共享的資源稱為臨界資源,例如打印機等,在同一時間只允許一個進

程訪問,需要用同步機制來實現對臨界資源的訪問。

3.虛擬

虛擬技術把一個物理實體轉換為多個邏輯實體。

利用多道程序設計技術,讓每個用戶都覺得有一個計算機專門為他服務。

主要有兩種虛擬技術:時分復用技術和空分復用技術。例如多個進程能在

同一個處理器上并發執行使用了時分復用技術,讓每個進程輪流占有處理器,

每次只執行一小個時間片并快速切換。

4.異步

異步指進程不是一次性執行完畢,而是走走停停,以不可知的速度向前推

進。

但只要運行環境相同,OS需要保證程序運行的結果也要相同。

2.操作系統基本功能

1.進程管理

進程控制、進程同步、進程通信、死鎖處理、處理機調度等。

2.內存管理

內存分配、地址映射、內存保護與共享、虛擬內存等。

3.文件管理

文件存儲空間的管理、目錄管理、文件讀寫管理和保護等。

4.設備管理

完成用戶的I/O請求,方便用戶使用各種設備,并提高設備的利用率。

主要包括緩沖管理、設備分配、設備處理、虛擬設備等。

3.系統調用

如果一個進程在用戶態需要使用內核態的功能,就進行系統調用從而陷入

內核,由操作系統代為完成。

4.大內核和微內核

1.大內核

大內核是將操作系統功能作為一個緊密結合的整體放到內核。

由于各模塊共享信息,因此有很高的性能。

2.微內核

由于操作系統不斷復雜,因此將一部分操作系統功能移出內核,從而降低

內核的復雜性。移出的部分根據分層的原則劃分成若干服務,相互獨立。

在微內核結構下,操作系統被劃分成小的、定義良好的模塊,只有微內核

這一個模塊運行在內核態,其余模塊運行在用戶態。

因為需要頻繁地在用戶態和核心態之間進行切換,所以會有一定的性能損

失。

5.中斷分類

1.外中斷

由CPU執行指令以外的事件引起,如I/O完成中斷,表示設備輸入/輸

出處理已經完成,處理器能夠發送下一個輸入/輸出請求。此外還有時鐘中斷、

控制臺中斷等。

2.異常

由CPU執行指令的內部事件引起,如非法操作碼、地址越界、算術溢出

等。

6.什么是堆和棧?說一下堆棧都存儲哪些數據?

棧區(stack)一由編譯器自動分配釋放,存放函數的參數值,局部變量

的值等。其操作方式類似于數據結構中的棧。

堆區(heap)——般由程序員分配釋放,若程序員不釋放,程序結束時

可能由OS回收。

數據結構中這兩個完全就不放一塊來講,數據結構中棧和隊列才是好基

友,我想新手也很容易區分。

我想需要區分的情況肯定不是在數據結構話題下,而大多是在OS關于不

同對象的內存分配這塊上。

簡單講的話,在C語言中:

inta[N];//goonastackint—a=(int—)malloc(sizeof(int)—

N);//goonaheap

7.如何理解分布式鎖?

分布式鎖,是控制分布式系統之間同步訪問共享資源的一種方式。在分布

式系統中,常常需要協調他們的動作。如果不同的系統或是同一個系統的不

同主機之間共享了一個或一組資源,那么訪問這些資源的時候,往往需要互

斥來防止彼此干擾來保證一致性,在這種情況下,便需要使用到分布式鎖。

二、進程管理

1.進程與線程

1.進程

進程是資源分配的基本單位,用來管理資源(例如:內存,文件,網絡等

資源)

進程控制塊(ProcessControlBlock,PCB)描述進程的基本信息和運行

狀態,所謂的創建進程和撤銷進程,都是指對PCB的操作。(PCB是描述進程

的數據結構)

下圖顯示了4個程序創建了4個進程,這4個進程可以并發地執行。

2.線程

線程是獨立調度的基本單位。

一個進程中可以有多個線程,它們共享進程資源。

QQ和瀏覽器是兩個進程,瀏覽器進程里面有很多線程,例如HTTP請求

線程、事件響應線程、渲染線程等等,線程的并發執行使得在瀏覽器中點擊

一個新鏈接從而發起HTTP請求時,瀏覽器還可以響應用戶的其它事件。

3.區別

(一)擁有資源

進程是資源分配的基本單位,但是線程不擁有資源,線程可以訪問隸屬進

程的資源。

(二)調度

線程是獨立調度的基本單位,在同一進程中,線程的切換不會引起進程切

換,從一個進程內的線程切換到另一個進程中的線程時,會引起進程切換。

(三)系統開銷

由于創建或撤銷進程時,系統都要為之分配或回收資源,如內存空間、I/O

設備等,所付出的開銷遠大于創建或撤銷線程時的開銷。類似地,在進行進

程切換時,涉及當前執行進程CPU環境的保存及新調度進程CPU環境的設

置,而線程切換時只需保存和設置少量寄存器內容,開銷很小。

(四)通信方面

進程間通信(IPC)需要進程同步和互斥手段的輔助,以保證數據的一致

性。而線程間可以通過直接讀/寫同一進程中的數據段(如全局變量)來進行通

信。

2.進程狀態的切換(生命周期)

就緒狀態(ready):等待被調度

運行狀態(running)

阻塞狀態(waiting):等待資源

應該注意以下內容:

只有就緒態和運行態可以相互轉換,其它的都是單向轉換。就緒狀態的進

程通過調度算法從而獲得CPU時間,轉為運行狀態;而運行狀態的進程,在

分配給它的CPU時間片用完之后就會轉為就緒狀態,等待下一次調度。

阻塞狀態是缺少需要的資源從而由運行狀態轉換而來,但是該資源不包括

CPU時間,缺少CPU時間會從運行態轉換為就緒態。

進程只能自己阻塞自己,因為只有進程自身才知道何時需要等待某種事件

的發生

3.進程調度算法

不同環境的調度算法目標不同,因此需要針對不同環境來討論調度算法。

1.批處理系統

批處理系統沒有太多的用戶操作,在該系統中,調度算法目標是保證吞吐

量和周轉時間(從提交到終止的時間)0

1.1先來先服務

先來先服務first-comefirst-serverd(FCFS)

按照請求的順序進行調度。

有利于長作業,但不利于短作業,因為短作業必須一直等待前面的長作業

執行完畢才能執行,而長作業又需要執行很長時間,造成了短作業等待時間

過長。

1.2短作業優先

短作業優先shortestjobfirst(SJF)

按估計運行時間最短的順序進行調度。

長作業有可能會餓死,處于一直等待短作業執行完畢的狀態。因為如果一

直有短作業到來,那么長作業永遠得不到調度。

1.3最短剩余時間優先

最短剩余時間優先shortestremainingtimene_t(SRTN)

按估計剩余時間最短的順序進行調度。

2.交互式系統

交互式系統有大量的用戶交互操作,在該系統中調度算法的目標是快速地

進行響應。

2.1時間片輪轉

將所有就緒進程按FCFS(先來先服務)的原則排成一個隊列,每次調度

時,把CPU時間分配給隊首進程,該進程可以執行一個時間片。當時間片用

完時,由計時器發出時鐘中斷,調度程序便停止該進程的執行,并將它送往

就緒隊列的末尾,同時繼續把CPU時間分配給隊首的進程。

時間片輪轉算法的效率和時間片的大小有很大關系。因為進程切換都要保

存進程的信息并且載入新進程的信息,如果時間片太小,會導致進程切換得

太頻繁,在進程切換上就會花過多時間。

2.2優先級調度

為每個進程分配一個優先級,按優先級進行調度。

為了防止低優先級的進程永遠等不到調度,可以隨著時間的推移增加等待

進程的優先級。

2.3多級反饋隊列

如果一個進程需要執行100個時間片,如果采用時間片輪轉調度算法,

那么需要交換100次。

多級隊列是為這種需要連續執行多個時間片的進程考慮,它設置了多個隊

列,每個隊列時間片大小都不同,例如1,2,4,8,..。進程在第一個隊列沒執

行完,就會被移到下一個隊列。這種方式下,之前的進程只需要交換7次。

每個隊列優先權也不同,最上面的優先權最高。因此只有上一個隊列沒有

進程在排隊,才能調度當前隊列上的進程。

可以將這種調度算法看成是時間片輪轉調度算法和優先級調度算法的結

合。

3.實時系統

實時系統要求一個請求在一個確定時間內得到響應。

分為硬實時和軟實時,前者必須滿足絕對的截止時間,后者可以容忍一定

的超時。

參考資料:

操作系統典型調度算法/語言中文網

4.進程同步

L臨界區

對臨界資源進行訪問的那段代碼稱為臨界區。

為了互斥訪問臨界資源,每個進程在進入臨界區之前,需要先進行檢查。

//entrysection//criticalsection;//e_itsection

2.同步與互斥

同步:多個進程按一定順序執行;

互斥:多個進程在同一時刻只有一個進程能進入臨界區。

3.信號量

P和V是來源于兩個荷蘭語詞匯,P0--prolaag(荷蘭語,嘗試減少

的意思),V()--ve意。。g(荷蘭語,增加的意思)

信號量(Semaphore)是一個整型變量,可以對其執行down和up操作,

也就是常見的P和V操作。

down:如果信號量大于0,執行-1操作;如果信號量等于0,進程睡

眠,等待信號量大于0;(阻塞)

up:對信號量執行+1操作,喚醒睡眠的進程讓其完成down操作。(喚

醒)

down和up操作需要被設計成原語,不可分割,通常的做法是在執行這

些操作的時候屏蔽中斷。

如果信號量的取值只能為0或者1,那么就成為了互斥量(Mute_),0

表示臨界區已經加鎖,1表示臨界區解鎖。

typedefintsemaphore;semaphoremute_=1;voidPl()

{down(&mute_);〃臨界區up(&mute_);}voidP2(){down(femute—);//

臨界區up(&mute_);)

使用信號量實現生產者-消費者問題

問題描述:使用一個緩沖區來保存物品,只有緩沖區沒有滿,生產者才可

以放入物品;只有緩沖區不為空,消費者才可以拿走物品。

因為緩沖區屬于臨界資源,因此需要使用一個互斥量mute_來控制對緩

沖區的互斥訪問。

為了同步生產者和消費者的行為,需要記錄緩沖區中物品的數量。數量可

以使用信號量來進行統計,這里需要使用兩個信號量:empty記錄空緩沖區

的數量,full記錄滿緩沖區的數量。其中,empty信號量是在生產者進程中

使用,當empty不為0時,生產者才可以放入物品;full信號量是在消費

者進程中使用,當full信號量不為0時,消費者才可以取走物品。

注意,不能先對緩沖區進行加鎖,再測試信號量。也就是說,不能先執行

down(mute—)再執行down(empty)o如果這么做了,那么可能會出現這種情

況:生產者對緩沖區加鎖后,執行down(empty)操作,發現empty=0,此

時生產者睡眠。消費者不能進入臨界區,因為生產者對緩沖區加鎖了,也就

無法執行up(empty)操作,empty永遠都為0,那么生產者和消費者就會一

直等待下去,造成死鎖。

itdefineNlOOtypedefintsemaphore;semaphoremute—=1;semaphore

empty=N;semaphorefull=0;voidproducer(){while(TRUE){intitem

=produce_item();//生產一個產品〃down(feempty)和down(&mute—)

不能交換位置,否則造成死鎖down(&empty);//記錄空緩沖區的數量,這

里減少一個產品空間down(femute_);//互斥鎖insert_item(item);

up(&mute_);//互斥鎖up(&full);//記錄滿緩沖區的數量,這里增加一

個產品}}voidconsumer(){while(TRUE){down(&full);//記錄滿緩沖

區的數量,減少一個產品down(&mute—);//互斥鎖intitem=

remove_item();up(&mute—);//互斥鎖up(&empty);〃記錄空緩沖區的

數量,這里增加一個產品空間consume_item(item);}}

4.管程

管程(甚遭:Monitors,也稱為監視器)是一種程序結構,結構內的多個

子程序(對象或模塊)形成的多個工作線程互斥訪問共享資源。

使用信號量機制實現的生產者消費者問題需要客戶端代碼做很多控制,而

管程把控制的代碼獨立出來,不僅不容易出錯,也使得客戶端代碼調用更容

易。

管程是為了解決信號量在臨界區的PV操作上的配對的麻煩,把配對的

PV操作集中在一起,生成的一種并發編程方法。其中使用了條件變量這種同

步機制。

c語言不支持管程,下面的示例代碼使用了類Pascal語言來描述管程。

示例代碼的管程提供了insert()和remove()方法,客戶端代碼通過調用

這兩個方法來解決生產者-消費者問題。

monitorProducerConsumerintegeri;conditionc;procedure

insert();begin//...end;procedureremove();begin//...end;end

monitor;

管程有一個重要特性:在一個時刻只能有一個進程使用管程。進程在無法

繼續執行的時候不能一直占用管程,否者其它進程永遠不能使用管程。

管程引入了條件變量以及相關的操作:wait。和signal()來實現同

步操作。對條件變量執行waitO操作會導致調用進程阻塞,把管程讓出來

給另一個進程持有。signal。操作用于喚醒被阻塞的進程。

使用管程實現生產者-消費者問題

//管程monitorProducerConsumerconditionfull,empty;integer

count:=0;conditionc;procedureinsert(item:integer);beginif

count=Nthenwait(full);insert_item(item);count:=count+1;if

count=1thensignal(empty);end;functionremove:integer;beginif

count=0thenwait(empty);remove=remove_item;count:=count-1;

ifcount=N-1thensignal(full);end;endmonitor;//生產者客戶端

procedureproducerbeginwhiletruedobeginitem=produce_item;

ProducerConsumer.insert(item);endend;//消費者客戶端procedure

consumerbeginwhiletruedobeginitem=ProducerConsumer.remove;

consume_item(item);endend;

5.經典同步問題

生產者和消費者問題前面已經討論過了。

1.讀者-寫者問題

允許多個進程同時對數據進行讀操作,但是不允許讀和寫以及寫和寫操作

同時發生。讀者優先策略

Rcount:讀操作的進程數量(Rcount=0)

CountMute—:對于Rcount進行加鎖(CountMute—=1)

WriteMute_:互斥量對于寫操作的加鎖(WriteMute_=1)

Rcount=0;semaphoreCountMute—=1;semaphoreWriteMute_=1;void

writer0{while(true){sem_wait(WriteMute_);//TODOwrite();

sem_post(WriteMute_);}}//讀者優先策略void

reader(){while(true){sem_wait(CountMute_);if(Rcount==0)

sem_wait(WriteMute_);Rcount++;sem_post(CountMute_);//TODO

read();sem_wait(CountMute—);Rcount一;if(Rcount==0)

sem_post(WriteMute_);sem_post(CountMute_);}}

2.哲學家進餐問題

五個哲學家圍著一張圓桌,每個哲學家面前放著食物。哲學家的生活有兩

種交替活動:吃飯以及思考。當一個哲學家吃飯時,需要先拿起自己左右兩

邊的兩根筷子,并且一次只能拿起一根筷子。

—方案一:―下面是一種錯誤的解法,考慮到如果所有哲學家同時拿

起左手邊的筷子,那么就無法拿起右手邊的筷子,造成死鎖。

itdefineN5//哲學家個數voidphilosopher(inti)//哲學家編號:

0-4{while(TRUE){think();//哲學家在思考take_fork(i);//去拿

左邊的叉子take_fork((i+1)%N);//去拿右邊的叉子eat();//吃面

條中….put_fork(i);//放下左邊的叉子put_fork((i+1)%N);〃放

下右邊的叉子}}

方案二:對拿叉子的過程進行了改進,但仍不正確

#defineN5//哲學家個數while(1)//去拿兩把叉子{take_fork(i);

//去拿左邊的叉子if(fork((i+l)%N)){//右邊叉子還在嗎

take_fork((i+1)%N);//去拿右邊的叉子break;//兩把叉子均到手}

else{//右邊叉子已不在put_fork(i);//放下左邊的叉子

wait_some_time0;//等待一會兒}}

方案三:等待時間隨機變化。可行,但非萬全之策

#defineN5//哲學家個數while(l)//去拿兩把叉子{take_fork(i);

//去拿左邊的叉子if(fork((i+l)%N)){//右邊叉子還在嗎

take_fork((i+1)%N);//去拿右邊的叉子break;//兩把叉子均到手}

else{//右邊叉子已不在put_fork(i);//放下左邊的叉子

wait_random_time();//等待隨機長時間}}

方案四:互斥訪問。正確,但每次只允許一人進餐

semaphoremute—//互斥信號量,初值Ivoidphilosopher(inti)//

哲學家編號i:0-4{while(TRUE){think();//哲學家在思考P(mute_);

//進入臨界區take_fork(i);//去拿左邊的叉子take_fork((i+1)%N);

//去拿右邊的叉子eat();//吃面條中….put_fork(i);//放下左邊的

叉子put_fork((i+1)%N);//放下右邊的叉子V(mute_);〃退出臨

界區})

正確方案如下:

為了防止死鎖的發生,可以設置兩個條件(臨界資源):

必須同時拿起左右兩根筷子;

只有在兩個鄰居都沒有進餐的情況下才允許進餐。

//I.必須由一個數據結構,來描述每個哲學家當前的狀態#defineN

5#defineLEFTi//左鄰居#defineRIGHT(i+1)%N//右鄰居#define

THINKING0#defineHUNGRYl#defineEATING2typedefintsemaphore;int

state[N];〃跟蹤每個哲學家的狀態〃2.該狀態是一個臨界資源,對它的

訪問應該互斥地進行semaphoremute—=1;〃臨界區的互斥〃3.一個哲

學家吃飽后,可能要喚醒鄰居,存在著同步關系semaphores[N];〃每個哲

學家一個信號量voidphilosopher(inti){while(TRUE){think();

take_two(i);eat();put_tow(i);}}voidtake_two(inti){P(&mute_);

//進入臨界區stateEi]=HUNGRY;//我餓了test(i);//試圖拿兩把叉

子V(&mute_);//退出臨界區P(&s[i]);//沒有叉子便阻塞}void

put_tow(i){P(&mute—);state[i]=THINKING;test(LEFT);test(RIGHT);

V(&mute_);}voidtest(i){//嘗試拿起兩把筷子if(state[i]==HUNGRY

&&state[LEFT]!=EATING&&state[RIGHT]!=EATING){state[i]=EATING;

V(&s[i])://通知第i個人可以吃飯了}}

6.進程通信

進程同步與進程通信很容易混淆,它們的區別在于:

進程同步:控制多個進程按一定順序執行

進程通信:進程間傳輸信息

進程通信是一種手段,而進程同步是一種目的。也可以說,為了能夠達到

進程同步的目的,需要讓進程進行通信,傳輸一些進程同步所需要的信息。

_進程通信方式

直接通信

發送進程直接把消息發送給接收進程,并將它掛在接收進程的消息緩沖隊

列上,接收進程從消息緩沖隊列中取得消息。

Send和Receive原語的使用格式如下:

Send(Receiver,message);〃發送一個消息message給接收進程

ReceiverReceive(Sender,message);〃接收Sender進程發送的消息message

間接通信

間接通信方式是指進程之間的通信需要通過作為共享數據結構的實體。該

實體用來暫存發送進程發給目標進程的消息。

發送進程把消息發送到某個中間實體中,接收進程從中間實體中取得消

息。這種中間實體一般稱為信箱,這種通信方式又稱為信箱通信方式。該通

信方式廣泛應用于計算機網絡中,相應的通信系統稱為電子郵件系統。

1.管道

管道是通過調用pipe函數創建的,fd[0]用于讀,fd[l]用于寫。

ttincludeintpipe(intfd[2]);

它具有以下限制:

只支持半雙工通信(單向傳輸);

只能在父子進程中使用。

2.命名管道

也稱為命名管道,去除了管道只能在父子進程中使用的限制。

Sincludeintmkfifo(constchar_path,mode_tmode);int

mkfifoat(intfd,constchar_path,mode_tmode);

FIFO常用于客戶-服務器應用程序中,FIFO用作匯聚點,在客戶進程和

服務器進程之間傳遞數據。

3.消息隊列

間接(內核)

相比于FIFO,消息隊列具有以下優點:

消息隊列可以獨立于讀寫進程存在,從而避免了FIFO中同步管道的打開

和關閉時可能產生的困難;

避免了FIFO的同步阻塞問題,不需要進程自己提供同步方法;

讀進程可以根據消息類型有選擇地接收消息,而不像FIFO那樣只能默認

地接收。

4.信號量

它是一個計數器,用于為多個進程提供對共享數據對象的訪問。

5.共享內存

允許多個進程共享一個給定的存儲區。因為數據不需要在進程之間復制,

所以這是最快的一種IPCo

需要使用信號量用來同步對共享存儲的訪問。

多個進程可以將同一個文件映射到它們的地址空間從而實現共享內存。另

外_SI共享內存不是使用文件,而是使用使用內存的匿名段。

6.套接字

與其它通信機制不同的是,它可用于不同機器間的進程通信。

7.線程間通信和進程間通信

線程間通信

synchronized同步

這種方式,本質上就是“共享內存”式的通信。多個線程需要訪問同一

個共享變量,誰拿到了鎖(獲得了訪問權限),誰就可以執行。

while輪詢的方式

在這種方式下,ThreadA不斷地改變條件,ThreadB不停地通過while語

句檢測這個條件(list,size()==5)是否成立,從而實現了線程間的通信。

但是這種方式會浪費CPU資源。

之所以說它浪費資源,是因為JVM調度器將CPU交給ThreadB執行時,

它沒做啥“有用”的工作,只是在不斷地測試某個條件是否成立。

就類似于現實生活中,某個人一直看著手機屏幕是否有電話來了,而不是:

在干別的事情,當有電話來時,響鈴通知TA電話來了。

wait/notify機制

當條件未滿足時,ThreadA調用wait()放棄CPU,并進入阻塞狀態。(不

像while輪詢那樣占用CPU)

當條件滿足時,ThreadB調用notify()通知線程A,所謂通知線程A,

就是喚醒線程A,并讓它進入可運行狀態。

管道通信

java.io.PipedlnputStream和java.io.PipedOutputStream進行通信

進程間通信

管道(Pipe):管道可用于具有親緣關系進程間的通信,允許一個進程和

另一個與它有共同祖先的進程之間進行通信。

命名管道(namedpipe):命名管道克服了管道沒有名字的限制,因此,

除具有管道所具有的功能外,它還允許無親緣關系進程間的通信。命名管

道在文件系統中有對應的文件名。命名管道通過命令mkfifo或系統調用

mkfifo來創建。

信號(Signal):信號是比較復雜的通信方式,用于通知接受進程有某種

事件發生,除了用于進程間通信外,進程還可以發送信號給進程本身;Linu_

除了支持Uni_早期信號語義函數sigal外,還支持語義符合Posi_.1標準

的信號函數sigaction(實際上,該函數是基于BSD的,BSD為了實現可靠信

號機制,又能夠統一對外接口,用sigaction函數重新實現了signal函數)。

消息(Message)隊列:消息隊列是消息的鏈接表,包括Posi_消息隊列

systemV消息隊列。有足夠權限的進程可以向隊列中添加消息,被賦予讀權

限的進程則可以讀走隊列中的消息。消息隊列克服了信號承載信息量少,管

道只能承載無格式字節流以及緩沖區大小受限等缺

共享內存:使得多個進程可以訪問同一塊內存空間,是最快的可用IPC

形式。是針對其他通信機制運行效率較低而設計的。往往與其它通信機制,

如信號量結合使用,來達到進程間的同步及互斥。

內存映射(mappedmemory):內存映射允許任何多個進程間通信,每一個

使用該機制的進程通過把一個共享的文件映射到自己的進程地址空間來實現

它。

信號量(semaphore):主要作為進程間以及同一進程不同線程之間的同步

手段。

套接口(Socket):更為一般的進程間通信機制,可用于不同機器之間的

進程間通信。起初是由Uni_系統的BSD分支開發出來的,但現在一般可以移

植到其它類Uni一系統上:linu—和SystemV的變種都支持套接字。

8.進程操作

Linu_進程結構可由三部分組成:

代碼段(程序)

數據段(數據)

堆棧段(控制塊PCB)

進程控制塊是進程存在的惟一標識,系統通過PCB的存在而感知進程的存

在。系統通過PCB對進程進行管理和調度。PCB包括創建進程、執行進程、

退出進程以及改變進程的優先級等。

一般程序轉換為進程分以下幾個步驟:

內核將程序讀入內存,為程序分配內存空間

內核為該進程分配進程標識符PID和其他所需資源

內核為進程保存PID及相應的狀態信息,把進程放到運行隊列中等待執

行,程序轉化為進程后可以被操作系統的調度程序調度執行了

在UNI—里,除了進程0(即PID=0的交換進程,SwapperProcess)以

外的所有進程都是由其他進程使用系統調用fork創建的,這里調用fork

創建新進程的進程即為父進程,而相對應的為其創建出的進程則為子進程,

因而除了進程0以外的進程都只有一個父進程,但一個進程可以有多個子進

程。操作系統內核以進程標識符(ProcessIdentifier,即PID)來識別進程。

進程0是系統引導時創建的一個特殊進程,在其調用fork創建出一個子進

程(即PID=1的進程1,又稱init)后,進程0就轉為交換進程(有時也被

稱為空閑進程),而進程1(init進程)就是系統里其他所有進程的祖先。

進程0:Linu一引導中創建的第一個進程,完成加載系統后,演變為進程

調度、交換及存儲管理進程。進程1:init進程,由0進程創建,完成

系統的初始化.是系統中所有其它用戶進程的祖先進程。

Linu_中1號進程是由0號進程來創建的,因此必須要知道的是如何創

建0號進程,由于在創建進程時,程序一直運行在內核態,而進程運行在用

戶態,因此創建0號進程涉及到特權級的變化,即從特權級0變到特權級

3,Linu—是通過模擬中斷返回來實現特權級的變化以及創建0號進程,通

過將0號進程的代碼段選擇子以及程序計數器EIP直接壓入內核態堆棧,然

后利用iret匯編指令中斷返回跳轉到0號進程運行。

創建一個進程

進程是系統中基本的執行單位。Linu_系統允許任何一個用戶進程創建

一個子進程,創建成功后,子進程存在于系統之中,并且獨立于父進程。該

子進程可以接受系統調度,可以得到分配的系統資源。系統也可以檢測到子

進程的存在,并且賦予它與父進程同樣的權利。

Linu_系統下使用fork()函數創建一個子進程,其函數原型如下:

#includepid_tfork(void);

在討論forkO函數之前,有必要先明確父進程和子進程兩個概念。除了

0號進程(該進程是系統自舉時由系統創建的)以外,Linu_系統中的任何一

個進程都是由其他進程創建的。創建新進程的進程,即調用forkO函數的

進程就是父進程,而新創建的進程就是子進程。

forkO函數不需要參數,返回值是一個進程標識符(PID)。對于返回值,

有以下3種情況:

對于父進程,forkO函數返回新創建的子進程的ID。

對于子進程,fork。函數返回0o由于系統的0號進程是內核進程,所

以子進程的進程標識符不會是0,由此可以用來區別父進程和子進程。

如果創建出錯,則forkO函數返回T。

forkO函數會創建一個新的進程,并從內核中為此進程分配一個新的可

用的進程標識符(PID),之后,為這個新進程分配進程空間,并將父進程的

進程空間中的內容復制到子進程的進程空間中,包括父進程的數據段和堆棧

段,并且和父進程共享代碼段(寫時復制)。這時候,系統中又多了一個進程,

這個進程和父進程一模一樣,兩個進程都要接受系統的調度。

注意:由于在復制時復制了父進程的堆棧段,所以兩個進程都停留在了

forkO函數中,等待返回。因此,forkO函數會返回兩次,一次是在父進

程中返回,另一次是在子進程中返回,這兩次的返回值是不一樣的。

下面給出的示例程序用來創建一個子進程,該程序在父進程和子進程中分

別輸出不同的內容。

#includeSincludeSincludeintmain(void){pid_tpid;//保存進

程IDpid=forkO;//創建一個新進程if(pid<0){//fork出錯

printf("fai1tofork\n");e—it(1);}elseif(pid==0){//子進程//

打印子進程的進程IDprintf("thisischild,pidis:%u\n",getpidO);)

else(//打印父進程和其子進程的進程IDprintf("thisisparent,pid

is:%u,child-pidis:%u\n/z,getpid(),pid);}return0;}

程序運行結果如下:

$./forkParent,PID:2598,Sub-processPID:2599Sub-process,PID:

2599,PPID:2598

由于創建的新進程和父進程在系統看來是地位平等的兩個進程,所以運行

機會也是一樣的,我們不能夠對其執行先后順序進行假設,先執行哪一個進

程取決于系統的調度算法。如果想要指定運行的順序,則需要執行額外的操

作。正因為如此,程序在運行時并不能保證輸出順序和上面所描述的一致。

getpidO是獲得當前進程的pid,而getppidO則是獲得父進程的id。

父子進程的共享資源

子進程完全復制了父進程的地址空間的內容,包括堆棧段和數據段的內

容。子進程并沒有復制代碼段,而是和父進程共用代碼段。這樣做是存在其

合理依據的,因為子進程可能執行不同的流程,那么就會改變數據段和堆棧

段,因此需要分開存儲父子進程各自的數據段和堆棧段。但是代碼段是只讀

的,不存在被修改的問題,因此這一個段可以讓父子進程共享,以節省存儲

空間,如下圖所示。

下面給出一個示例來說明這個問題。該程序定義了一個全局變量global.

一個局部變量stack和一個指針heapo該指針用來指向一塊動態分配的內

存區域。之后,該程序創建一個子進程,在子進程中修改global、stack和

動態分配的內存中變量的值。然后在父子進程中分別打印出這些變量的值。

由于父子進程的運行順序是不確定的,因此我們先讓父進程額外休眠2秒,

以保證子進程先運行。

#include#include#include//全局變量,在數據段中intglobal;int

mainO{pid_tpid;intstack=1;//局部變量,在棧中int_heap;heap

=(int_)malloc(sizeof(int));//動態分配的內存,在堆中_heap=2;

pid=forkO;//創建一個子進程if(pid<0){//創建子進程失敗

printf(/zfai1tofork\n");e_it(1);}elseif(pid==0){//子進程,

改變各變量的值global++;//修改棧、堆和數據段stack++;(—heap)++;

printf("thechild,data:%d,stack:%d,heap:%d\n”,global,stack,

_heap);e_it(0);〃子進程運行結束}〃父進程休眠2秒鐘,保證子

進程先運行sleep(2);//輸出結果printf(z/theparent,data:%d,

stack:%d,heap:%d\n//,global,stack,_heap);return0;}

程序運行效果如下:

$./forkinsub-process,global:2,stack:2,heap:3In

parent-process,global:1,stack:1,heap:2

由于父進程休眠了2秒鐘,子進程先于父進程運行,因此會先在子進程中

修改數據段和堆棧段中的內容。因此不難看出,子進程對這些數據段和堆棧

段中內容的修改并不會影響到父進程的進程環境。

fork。函數的出錯情況

有兩種情況可能會導致forkO函數出錯:

系統中已經有太多的進程存在了

調用fork。函數的用戶進程太多了

一般情況下,系統都會對一個用戶所創建的進程數加以限制。如果操作系

統不對其加限制,那么惡意用戶可以利用這一缺陷攻擊系統。下面是一個利

用進程的特性編寫的一個病毒程序,該程序是一個死循環,在循環中不斷調

用fork。函數來創建子進程,直到系統中不能容納如此多的進程而崩潰為止。

下圖展示了這種情況:

^includeintmainO(while(1)forkO;/_不斷地創建子進程,使系

統中進程溢滿—/return0;}

創建共享空間的子進程

進程在創建一個新的子進程之后,子進程的地址空間完全和父進程分開。

父子進程是兩個獨立的進程,接受系統調度和分配系統資源的機會均等,因

此父進程和子進程更像是一對兄弟。如果父子進程共用父進程的地址空間,

則子進程就不是獨立于父進程的。

Linu_環境下提供了一個與forkO函數類似的函數,也可以用來創建一

個子進程,只不過新進程與父進程共用父進程的地址空間,其函數原型如下:

#includepid_tvfork(void);

vfork()和fork()函數的區別有以下兩點:

vforkO函數產生的子進程和父進程完全共享地址空間,包括代碼段、數

據段和堆棧段,子進程對這些共享資源所做的修改,可以影響到父進程。由

此可知,vfork()函數與其說是產生了一個進程,還不如說是產生了一個線

程。

vforkO函數產生的子進程一定比父進程先運行,也就是說父進程調用了

vforkO函數后會等待子進程運行后再運行。

下面的示例程序用來驗證以上兩點。在子進程中,我們先讓其休眠2秒

以釋放CPU控制權,在前面的forkO示例代碼中我們已經知道這樣會導致

其他線程先運行,也就是說如果休眠后父進程先運行的話,則第1點則為假;

否則為真。第2點為真,則會先執行子進程,那么全局變量便會被修改,如

果第1點為真,那么后執行的父進程也會輸出與子進程相同的內容。代碼如

下:

//?filevfork.c//@briefvforkOusagettinclude#include#include

intglobal=1;intmain(void){pid_tpid;intstack=1;int_heap;

heap=(int_)malloc(sizeof(int));eap=1;pid=vforkO:if(pid

<0){perror(""failtovfork");ex___t(-l);}elseif(pid==0)

{//sub-process,changevaluessleep(2);//releasecpucontrolling

global=999;stack=888;____eap=777;//printallvaluesprintf(Z/In

sub-process,global:%d,stack:%d,heap:%d\n//,global,stack,eap);

ex___t(0);}else{//parent-processprintf(z,Inparent-process,

global:%d,stack:%d,heap:%d\nzz,global,stack,eap);}return0;}

程序運行效果如下:

$./vforklnsub-process,global:999,stack:888,heap:777In

parent-process,global:999,stack:888,heap:777

在函數內部調用vfork

在使用vfork()函數時應該注意不要在任何函數中調用vfork()函數。

下面的示例是在一個非main函數中調用了vfork0函數。該程序定義了一

個函數門(),該函數內部調用了vfork。函數。之后,又定義了一個函數

f2(),這個函數沒有實際的意義,只是用來覆蓋函數fl()調用時的棧幀。

main函數中先調用f1()函數,接著調用f2()函數。

#include#include#includeintfl(void){vfork();return0;}int

f2(inta,intb){returna+b;}intmain(void){intc;fl();c=f2(1,2);

printf("%d\n",c);return0;}

程序運行效果如下:

$./vfork3Segmentationfault(coredumped)

通過上面的程序運行結果可以看出,一個進程運行正常,打印出了預期結

果,而另一個進程似乎出了問題,發生了段錯誤。出現這種情況的原因可以

用下圖來分析一下:

左邊這張圖說明調用vfork。之后產生了一個子進程,并且和父進程共

享堆棧段,兩個進程都要從fl()函數返回。由于子進程先于父進程運行,

所以子進程先從fl()函數中返回,并且調用f2()函數,其棧幀覆蓋了原

來fl()函數的棧幀。當子進程運行結束,父進程開始運行時,就出現了右

圖的情景,父進程需要從fl()函數返回,但是fl()函數的棧幀已經被f2()

函數的所替代,因此就會出現父進程返回出錯,發生段錯誤的情況。

由此可知,使用vfork。函數之后,子進程對父進程的影響是巨大的,

其同步措施勢在必行。

退出進程

當一個進程需要退出時,需要調用退出函數。Linu—環境下使用e_it()

函數退出進程,其函數原型如下:

#includevoide_it(intstatus);

e_it()函數的參數表示進程的退出狀態,這個狀態的值是一個整型,保

存在全局變量$?中,在shell中可以通過echo$?來檢查退出狀態值。

注意:這個退出函數會深入內核注銷掉進程的內核數據結構,并且釋放掉

進程的資源。

e_it函數與內核函數的關系

e_it函數是一個標準的庫函數,其內部封裝了Linu_系統調用

e_it()函數。兩者的主要區別在于e_it()函數會在用戶空間做一些善后

工作,例如清理用戶的I/O緩沖區,將其內容寫入磁盤文件等,之后才進

入內核釋放用戶進程的地址空間;而e_it()函數直接進入內核釋放用戶進

程的地址空間,所有用戶空間的緩沖區內容都將丟失。

設置進程所有者

每個進程都有兩個用戶ID,實際用戶ID和有效用戶ID。通常這兩個ID

的值是相等的,其取值為進程所有者的用戶ID。但是,在有些場合需要改變

進程的有效用戶ID。Linu_環境下使用setuid()函數改變一個進程的實

際用戶ID和有效用戶ID,其函數原型如下:

#includeintsetuid(uid_tuid);

setuidO函數的參數表示改變后的新用戶ID,如果成功修改當前進程的

實際用戶ID和有效用戶ID,函數返回值為0;如果失敗,則返回-1。只有

兩種用戶可以修改進程的實際用戶ID和有效用戶ID:

根用戶:根用戶可以將進程的實際用戶ID和有效用戶ID更換。

其他用戶:其該用戶的用戶ID等于進程的實際用戶ID或者保存的用戶

IDo

也就是說,用戶可以將自己的有效用戶ID改回去。這種情況多出現于下

面的情況:一個進程需要具有某種權限,所以將其有效用戶ID設置為具有

這種權限的用戶ID,當進程不需要這種權限時,進程還原自己之前的有效用

戶ID,使自己的權限復原。下面給出一個修改的示例:

#include#include#includeintmain(void){FILE—fp;uid_tuid;

uid_teuid;uid=getuidO;/—得到進程的實際用戶ID一/euid=

geteuid();/—得到進程的有效用戶ID一/printf(,?theuidis:%d\n”,

uid);printf("theeuidis:%d\n”,euid);if(setuid(8000)==-1){/_

改變進程的實際用戶ID和有效用戶ID_/perrorCfailtosetuid");

e_it(1);}printf("afterchanging'、);uid=getuidO;/_再次得

到進程的實際用戶ID_/euid=geteuid();/_再次得到進程的有效用戶

ID—/printf(/ztheuidis:%d\n”,uid);printf(z,theeuidis:%d\n”,

euid);return0;}

程序運行效果如下:

$./setuidtheuidis:Otheeuidis:Oafterchangingtheuidis:

8000theeuidis:8000

本節參考:

《后臺開發:核心技術與應用實踐》

《Linu_+C程序設計大全》十一章:進程控制

9.孤兒進程和僵尸進程

基本概念

我們知道在Uni_/Linu—中,正常情況下,子進程是通過父進程創建的,

子進程在創建新的進程。子進程的結束和父進程的運行是一個異步過程,即

父進程永遠無法預測子進程到底什么時候結束。當一個進程完成它的工作終

止之后,它的父進程需要調用waitO或者waitpidO系統調用取得子進程

的終止狀態。

孤兒進程:一個父進程退出,而它的一個或多個子進程還在運行,那么那

些子進程將成為孤兒進程。孤兒進程將被init進程(進程號為1)所收養,并

由init進程對它們完成狀態收集工作—o.

僵尸進程:一個進程使用fork創建子進程,如果子進程退出,而父進程

并沒有調用wait或waitpid獲取子進程的狀態信息,那么子進程的進程描

述符仍然保存在系統中。這種進程稱之為僵尸進程。

問題及危害

Uni_提供了一種機制可以保證只要父進程想知道子進程結束時的狀態

信息,就可以得到。這種機制就是:在每個進程退出的時候,內核釋放該進

程所有的資源,包括打開的文件,占用的內存等。但是仍然為其保留一定的

信息(包括進程號

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

最新文檔

評論

0/150

提交評論