溫故Linux后端編程(二):進程·全家桶
文章目錄
前言
進程概念問答錄
什么是進程
進程為何而生
程序與進程的區別與聯系
進程的三種基本狀態
進程狀態間的裝換
進程的掛起
進程控制塊(PCB)
進程調度算法
非剝奪方式
剝奪方式
先進先出(FIFO)
最短處理機運行期優先調度算法
最高響應比優先調度算法
優先級調度算法
動態優先級
時間片輪轉調度算法
前后臺調度算法
多級反饋隊列輪轉算法
進程依次執行時可能發生的三種情況
進程調度的時機和過程
進程調度的時機
進程調度的過程
進程原語
fork
進程的產生方式:
exec族
wait/waitpid
進程間間相互關系
同步機制應遵循的準則
讀者-寫者問題
哲學家進餐問題
解決辦法:
死鎖
產生死鎖的原因
產生死鎖的四個必要條件
解決死鎖的基本辦法
預防死鎖
避免死鎖
銀行家算法
示例
檢測死鎖
資源分配圖
死鎖的解除
進程間通信
管道
消息隊列
內存共享映射(SHM)
文件內存映射(MMP)
網絡流通信(Socket)
Socket在進程間通信的優勢
使用TCP長連接通信
最后的驚喜
前言
不知不覺,就到大三了。
不知不覺,就要開始找暑期實習了。
溫故而知新嘛。(數據結構復習兩天發現不對,我還是更喜歡這個。)
所以就來了。
進程概念問答錄
linux的進程操作方式主要有:產生進程、終止進程、進程間通信。
以下內容為簡述。
什么是進程
一段程序的執行過程。
進程(Process)是計算機中的程序關于某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位,是操作系統結構的基礎。在早期面向進程設計的計算機結構中,進程是程序的基本執行實體;在當代面向線程設計的計算機結構中,進程是線程的容器。程序是指令、數據及其組織形式的描述,進程是程序的實體。
一堆官方話,就·一段程序的執行過程。
進程為何而生
為了使程序在多道程序環境下能夠并發執行,并對并發執行的程序加以控制和描述,引入進程的概念。
程序段、數據段及進程控制塊三部分構成了一個進程的實體。
程序與進程的區別與聯系
(1)進程是程序的一次執行,是一個動態的概念,程序是完成某個特定功能的指令的有序序列,是一個靜態的概念
(2)一個進程可以執行一個或幾個程序,同一程序也可能由多個進程同時執行
(3)進程是系統進行資源分配和調度的一個獨立單位,程序則不是
(4)程序可以作為一種軟件資源長期保存,而進程是程序的一次執行過程,它是臨時的,有生命期的
進程是具有結構的
總結一句話:
進程是程序的運行
進程的三種基本狀態
就緒狀態
當進程已分配到除CPU以外的所有必要的資源后,只要能再獲得處理機便可立即執行,這時的狀態稱為就緒狀態
執行狀態
指進程已獲得處理機,其程序正在執行
阻塞狀態
進程因發生某種事件(如I/O請求、申請緩沖空間等)而暫停執行時的狀態,亦即進程的執行受到阻塞,故稱這種狀態為阻塞狀態,有時也稱為“等待”狀態或“睡眠”狀態。
進程狀態間的裝換
進程的掛起
在進程中,CTRL+C。
終端用戶的需要 當終端用戶在自己的程序運行期間發現有可疑問題時,往往希望暫時使自己的進程靜止下來。 父進程的需要 父進程常常希望考察和修改子進程或者當要協調各子進程間的活動 操作系統的需要 操作系統有時需要掛起某些進程,檢查運行中資源的使用情況及進行記賬,以便改善系統運行的性能。 對換的需要 為了緩解內存緊張的情況,即將內存中處于阻塞狀態的進程換至輔存上,使進程又處于一種有別于阻塞狀態的新狀態。 負荷調節的需要
1
2
3
4
5
6
7
8
9
10
11
12
13
進程控制塊(PCB)
-進程控制塊記錄進程信息 -操作系統是根據進程控制塊PCB來對并發執行的進程進行控制和管理的 -PCB是進程存在的唯一標志
1
2
3
-進程標識符信息
進程標識符用于唯一地標識一個進程,通常有外部標識符和內部標識符
①外部標識符。由創建者提供,通常由字母、數字所組成,往往是由用戶(進程)在訪問該進程時使用。
②內部標識符。這是為了方便系統使用而設置的。在所有操作系統中都為每一個進程賦予一個惟一的整數作為內部標識符,它通常就是一個進程的序號。
進程調度算法
進程調度就是系統按照某種算法把CPU動態地分配給某一就緒進程。進程調度工作是通過進程調度程序來完成的。
進程調度程序的主要功能:
-選擇進程占有CPU
-進行進程上下文的切換
分派程序一旦把處理機分配給某進程后便讓它一直運行下去,直到進程完成或發生某事件(如提出I/O請求)而阻塞時才把處理機分配給另一進程
-優點:簡單,系統開銷小,
-缺點:貌似公正,可能導致系統性能的惡化
該方式規定,當一個進程正在運行時,系統可以基于某種原則,剝奪已分配給它的處理機,將之分配給其它進程
剝奪原則:優先權原則、短進程優先原則、時間片原則
先進先出(FIFO)
-算法:把處理機分配給最先進入就緒隊列的進程 -優點:易于實現 -缺點:表面上公平,服務質量不佳 、對短進程不利
1
2
3
最短處理機運行期優先調度算法
-算法:從就緒隊列中選出“下一個CPU執行期”最短的進程,為之分配處理機使之執行
-優點:可獲得較好的調度性能
-缺點: 進程的CPU執行期難以準確得到、對長進程不利
最高響應比優先調度算法
-算法:響應比=(等待時間+要求的服務時間)/要求的服務時間 ,每次選取響應比最高的進程調度 -優點:所以對短進程有利,并且考慮了等待時間 -缺點:計算響應比有一定的系統開銷
1
2
3
優先級調度算法
-算法:將CPU分配給就緒隊列中優先級最高的進程
-靜態優先級
在進程創建時確立,確定后運行期間保持不變。確立依據有:進程的類型、進程對資源的需求、用戶申請的優先級
優點:簡單
缺點:不能動態反映進程特點,系統調度性能差
動態優先級
進程在開始創建時,根據某種原則確定一個優先級后,隨著進程執行時間的變化,其優先級不斷地進行動態調整 確定依據:根據進程占有的CPU時間的長短來決定,占有時間越長優先級越低;根據進程等待CPU的時間來決定,時間越長優先級越高
1
2
時間片輪轉調度算法
算法:通常用在分時系統,它輪流地調度系統中所有就緒進程,使就緒進程依次獲得一個時間片的運行時間
時間片長短確定遵循原則
既要保證系統各個用戶進程及時地得到響應,又不要由于時間片太短而增加調度的開銷,降低系統的效率
前后臺調度算法
-算法:該方法用在批處理和分時相結合的系統中。將分時用戶作業放在前臺,把批處理作業放在后臺。系統對前臺作業按照時間片輪轉法進行調度,僅當前臺無作業時,才把處理機分配給后臺作業的進程。后臺進程通常按先來先服務方式運行
-優點:使分時用戶進程得到及時響應,又提高了系統資源的利用率
多級反饋隊列輪轉算法
-系統設置多個不同優先級的就緒隊列,每次調度總是先調度優先級高的隊列,僅當該隊列空時,才調度次高優先級隊列。
-通常剛創建的進程和因請求I/O未用完時間片的進程排在最高優先級隊列,在這個隊列中運行2-3個時間片未完成的進程排列下一個較低優先級隊列。
-不論什么時候,只要較高優先級隊列有進程進入,立即轉進程調度,及時調度較高優先級隊列進程。
-優點:能較好地滿足各類作業的用戶要求,既能使分時用戶作業得到滿意的響應,又能使批處理用戶的作業獲得較合理的周轉時間
進程依次執行時可能發生的三種情況
-進程未用完一個時間片便結束,這時系統應提前進行調度 -進程在執行過程中提出I/O請求而阻塞,系統應將它放入相應的阻塞隊列并引起調度 -進程用完一個時間片后尚未完成。系統應將它重新放到就緒隊列的末尾,等待下次執行
1
2
3
進程調度的時機和過程
-正在執行的進程運行完畢 -正在執行的進程調用阻塞原語將自己阻塞起來進入等待狀態 -在采用搶占式優先級調度時,有優先級高于正在運行進程的進程進入就緒隊列 -在分時系統中時間片已經用完 -CPU方式是可剝奪時,就緒隊列中的某個進程 優先級變得高于當前運行進程的優先級
1
2
3
4
5
-進程調度所依賴的數據結構通常是調度隊列,由于調度的原因不同,在單處理器系統中設置了多種等待隊列
-只有就緒隊列中的進程能夠獲得處理器而最終運行,其他隊列中的進程從隊列中出來后,必須進入就緒隊列才能分配處理器
-隊列數據結構的建立結構與調度算法密切相關
-進程調度算法只是決定哪一個進程將獲得處理機,而將處理機分配給該進程的具體操作是由分派程序完成的
進程原語
fork
#include
1
2
3
功能:子進程復制父進程中的0~3g空間和PCB,但ID號不同。
fork調用一次返回兩次
父進程中返回子進程id (就是大于0的意思)
子進程返回0
讀時共享寫時復制,可保高效
與之相關函數:
#include
1
2
3
4
5
進程的產生有多種方式,但是追本溯源是相通的。
(1)復制父進程的系統環境(放心,只要是你開的進程,肯定有父進程) (2)在內核中建立進程結構 (3)將結構插入到進程列表,便于維護 (4)分配資源給該進程 (5)復制父進程的內存映射消息 (6)管理文件描述符和鏈接點 (7)通知父進程
1
2
3
4
5
6
7
下面是一張進程列表的圖,命令:pstree。
可以看到init是所有進程的父進程,其他進程都是由init進程直接或間接fork出來的。
exec族
fork子進程是為了執行新程序(fork創建了子進程后,子進程和父進程同時被OS調度執行,因此子進程可以單獨的執行一個程序,這個程序宏觀上將會和父進程程序同時進行)
使用exec族函數運行新的可執行程序。exec族函數可以直接把一個編譯好的可執行程序直接加載運行。
有了exec族函數后,典型的父子進程程序是這樣的:子進程需要運行的程序被單獨編寫、單獨編譯鏈接成一個可執行程序(hello)。主進程為父進程,fork創建了子進程后在子進程中exec來執行hello,達到父子進程分別做不同程序同時(宏觀上)運行的效果。
#include
1
2
3
4
5
6
7
8
9
10
exec函數族裝入并運行程序path/file,并將參數arg0(arg1, arg2, argv[], envp[])傳遞給子程序,出錯返回-1.
看一下后綴:
下面我找到一些通俗易懂的栗子,算是讓我明白了一點:
#ifdef HAVE_CONFIG_H #include
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
wait/waitpid
這里幾個概念:
僵尸進程:子進程退出,父進程沒有及時回收,子進程成為僵尸進程
孤兒進程:父進程退出,而子進程沒有退出,子進程成為孤兒進程
init進程:1號進程,負責收留孤兒進程,成為他們的父進程
有幾種方式終止進程: (1)main返回 (2)調用exit (3)調用_exit (4)調用abort(給自己發送異常終止信號) (5)由一個信號終止
1
2
3
4
5
6
#include
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
進程間間相互關系
-資源共享關系
-相互合作關系
同步機制應遵循的準則
- 空閑讓進 - 忙則等待 - 有限等待 - 讓權等待
1
2
3
4
讀者-寫者問題
只要求讀的進程稱為“reader進程”,其他進程稱為“writer進程”。
允許多個reader進程同時讀一個共享對象,但決不允許一個writer進程和其他reader進程或writer進程同時訪問共享對象
所謂讀者-寫者問題(The Reader-Writer Problem)是只保證一個writer進程必須與其他進程互斥地訪問共享對象的同步問題
哲學家進餐問題
有五個哲學家,他們的生活方式是交替地進行思考和進餐。哲學家們共用一張圓桌,分別坐在周圍的五張椅子上。在圓桌上有五個碗和五支筷子,平時一個哲學家進行思考,饑餓時便試圖取用其左、右最靠近他的筷子,只有在他拿到兩支筷子時才能進餐。進餐畢,放下筷子繼續思考。
①至多只允許四個哲學家同時進餐,以保證至少有一個哲學家能夠進餐,最終總會釋放出他所使用過的兩支筷子,從而可使更多的哲學家進餐。
②僅當哲學家的左、右兩支筷子均可用時才允許他拿起筷子進餐。
③規定奇數號哲學家先拿他左邊的筷子,然后再去拿他右邊的筷子;而偶數號哲學家則相反。
死鎖
在多道程序系統中,若對資源的管理、分配和使用不當,也會產生一種危險,即在一定條件下會導致系統發生一種隨機性錯誤——死鎖(參考上面兩個問題)。
多個進程所共享的資源不足,引起它們對資源的競爭而產生死鎖
-競爭可剝奪和非剝奪性資源
-競爭非剝奪性資源
進程運行過程中,請求和釋放資源的順序不當,而導致進程死鎖
-進程推進順序合法
-進程推進順序非法
說白了,就是競態。
互斥條件
進程要求對所分配的資源進行排它性控制,即在一段時間內某資源僅為一進程所占有
請求和保持條件
當進程因請求資源而阻塞時,對已獲得的資源保持不放
不剝奪條件
進程已獲得的資源,在未使用完之前,不能被剝奪,只能在使用完時由自己釋放
環路等待條件
在發生死鎖時必然存在一個進程—資源的環形鏈
預防死鎖
通過設置某些限制條件,以破壞產生死鎖的四個必要條件中的一個或幾個,來防止發生死鎖。
避免死鎖
在資源的動態分配過程中,使用某種方法去防止系統進入不安全狀態,從而避免了死鎖的發生。
檢測死鎖
檢測死鎖方法允許系統運行過程中發生死鎖。但通過系統所設置的檢測機構,可以及時檢測出死鎖的發生,并精確地確定與死鎖有關的進程和資源,然后采取適當措施,從系統中消除所發生的死鎖
解除死鎖
解除死鎖是與檢測死鎖相配套的一種設施,用于將進程從死鎖狀態下解脫出來
系統要求所有進程一次性地申請其所需的全部資源,若系統有足夠的資源分配給一進程時,便一次把所有其所需的資源分配給該進程,摒棄“請求”條件;在分配時只要有一種資源要求不能滿足,則已有的其它資源也全部不分配給該進程,摒棄“保持”條件(靜態資源分配法).
所謂安全狀態,是指系統能按某種進程順序如(P1,P2,…,Pn)(稱
若系統不存在這樣一個安全序列,則稱系統處于不安全狀態
如果不按照安全順序分配資源,則系統可能由安全狀態進入不安全狀態 ,可能發生死鎖
可利用資源向量Available:是一個具有m個元素的數組,其中每一個元素代表一類 可利用的資源數目 ,如果Available[j]=k,表示系統中現有Rj類資源有k個
最大需求矩陣Max:是一個n×m的矩陣,它定義了系統中n個進程中的每一個進程,對m類資源的最大需求,如果Max(i,j)=k,表示進程i需要Rj類資源的最大數目為k
分配矩陣Allocation:一個n×m的矩陣,它定義了系統中每一類資源當前已分配給每一個進程的資源數,如果Allocation(i,j)=k,表示進程i當前已分得Rj類資源的數目為k
需求矩陣Need:是一個n×m的矩陣,用以表示每一個進程尚需的各類資源數,如果Need[i,j]=k,表示進程i還需要Rj類資源k個,方能完成其任務
Need(i,j)=Max(i,j)-Allocation(i,j)
為了檢測死鎖,系統必須①保存有關資源的請求和分配信息,②提供一種算法,利用這些信息來檢測系統是否已進入死鎖狀態
資源分配圖是由一組結點N和 一組邊E所組成的一對偶G =(N,E)
結點分為兩種結點:進程結點和資源結點;邊表示進程和資源的請求分配關系
-剝奪資源
-撤銷進程
進程間通信
之前寫了很多篇,直到···
管道
在shell中管道用“|”表示。管道的歷史很悠久了。
可以理解為內存中的一個緩沖區,用于將某個進程的數據流導入,由某一個進程導出,實現通信。
這種管道是沒有名字,所以「|」表示的管道稱為匿名管道,
用完了就銷毀
。
注意,這個匿名管道是特殊的文件,只存在于內存,不存于文件系統中。
匿名管道用于有血緣關系的進程間通信。
在 shell 里面執行 A | B命令的時候,A 進程和 B 進程都是 shell 創建出來的子進程,A 和 B 之間不存在父子關系,它倆的父進程都是 shell。
如果要用管道進行無血緣關系之間的進程通信,用FIFO有名管道。
局勢到這里已經很清楚了,管道具有:“召之即來,揮之即去,且不占文件系統位置”的特性,適合用于shell中的“一次性博弈”。
如果是“長期博弈”,它不行,得往后看。
消息隊列
相對于管道的種種限制,消息隊列就顯得明智多了,因為它存著“尾款”啊,所以在系統中存留的必要性就大了。
1、消息隊列是內核地址空間中的內部鏈表,通過Linux內核在不同的進程間傳遞消息。
2、消息順序的發送到消息隊列中,并以幾種不同的方式從隊列中獲取。
3、內核中的消息隊列是通過IPC標識符來進行區別的,不同消息隊列之間是互相獨立的。
4、每個消息隊列中的消息又構成一個獨立的鏈表。
我把它看作一個“豐巢”。
但是吧,消息隊列固然有它的局限性在:
消息隊列不適合比較大數據的傳輸,因為在內核中每個消息體都有一個最大長度的限制,同時所有隊列所包含的全部消息體的總長度也是有上限。在 Linux 內核中,會有兩個宏定義 MSGMAX 和 MSGMNB,它們以字節為單位,分別定義了一條消息的最大長度和一個隊列的最大長度。
消息隊列還是很贊的,單看上面這些特性,
比管道占地方,比后面要講的SHM慢,傳輸消息大小也不如MMP
,還異步,靠。
就是這么個“一無是處”的通信方式,硬生生把自己的劣勢轉化為了優勢,不然它都不知道被壓到哪個箱底去了。
對,就是
異步
。
隨著“異步業務”的出現(如雙十一流量削峰、秒殺系統等),消息隊列突然就成了香餑餑,
它能承載的消息比管道多,它也不追求速度,占用的內存還比MMP小
,簡直就是用來做流量削峰、解耦、異步的神器。
RobbieMQ、RocketMQ、Kafka。消息隊列:解耦、異步、流量削峰。當下流行的幾款MQ以及新手上路該如何選擇MQ?
消息隊列火了,命運也真是神奇啊。
內存共享映射(SHM)
1、共享內存是在多個進程之間共享內存區域的一種進程間的通信方式。
2、它是在多個進程間通過對指定內存段進行映射實現內存共享的。
3、這是IPC最快捷的方式,因為它沒有中間商賺差價。
4、多個進程間共享的是同一塊物理空間,僅僅是掛載地址不同而已,因此不需要進行復制,可以直接使用這段空間。
優點很明顯了,快,承載數據量也夠,一般用來進行進程間數據包通信(數據包不會太大,而且量多)。
但是如果要進行大文件操作,那這個就有點吃不消了,不是說不行,是揚短避長了哈哈哈,千里馬非要它去拉磨。
文件內存映射(MMP)
1、mmap()函數用來將文件或者設備映射到內存中。
2、mmap的特點是按需調頁。最開始只申請vma,并不調真正的頁。當對某些頁進行引用的時候,會引起一個缺頁中斷,再將頁面調入到內存當中,這樣避免了對內存的浪費。
mmap的優勢: 操作文件就像操作內存一樣,適合于對較大文件的讀寫。
mmap的缺點:
1、文件如果很小,比如60bytes,由于在內存當中的組織都是按頁組織的,將文件調入到內存當中是一個頁4k,這樣其他的4096-60=4036 bytes的內存空間就會浪費掉了。
網絡流通信(Socket)
首先,就是分布式系統,這點當看到Socket做進程間通信的時候我就想到了。Socket進程間通信可以跨主機,具有伸縮性,把進程分布到不同的服務器上,改改端口就能用了。相反,其他IPC都不能跨機器。
其次,我兄弟天天給我吹他學的服務器到后面可以跨平臺,Windows和Linux上交互,我問他他說還沒學到,那現在呢?就讓我先劇透吧哈哈哈哈。
在編程上,TCP sockets和pipe都是操作文件描述符,用來收發字節流,都可以read/write/fcntl/select/poll等,不同的是,TCP是全雙工的,pipe是半雙工的,不方便。
就拿最快的IPC,shm共享內存來說,就那么好用?對于技術不過硬的朋友來說,那可真的是坑坑洼洼,反正我是鼻青臉腫了。
或許有人會說,具體問題具體分析,單機就用shm,分布式就用TCP,我想問問,有意思嗎?有意思嗎?就那么喜歡為一個功能寫兩份代碼啊。
在比對一下shm與TCP,TCP是字節流協議,只能順序讀取,有寫緩沖;shm是消息協議,一個進程把內容寫入虛擬地址,由另一個進程來讀走,基本上可以說是阻塞。
其實我是很喜歡shm通信的,甚至都封裝了動態庫,在我的“動態庫”專欄下就能找到,但是吧,叛變就是這么的快哈哈哈。
再者,IPC通信崩潰會怎樣?段錯誤;TCP呢?網絡掉線,哪個好找?一目了然。
TCP一斷掉,哪里斷了哪里重連就好,IPC呢?那就得全部重頭來過。
使用TCP長連接通信的好處有兩點:
容易定位分布式系統中的服務之間的依賴關系。
只要在機器上運行netstat -tpna | grep : port 就能立刻列出用到某服務的客戶端地址,然后在客戶端上用netstat或lsof命令找出是哪個進程發起的連接。這樣在遷移服務的時候可以有效的防止出現outage。
TCP短連接和UDP連接則不具備這一特性。
通過收發隊列的長度也比較容易定位網絡中或程序故障。在正常運行時,netstat打印的Recv-Q和Send-Q都接近于0,或者在0附近波動。如果Recv-Q保持不變或持續增加,一般是服務進程的處理速度變慢,可能是死鎖或阻塞了。如果Send-Q保持不變或持續增長,那可能是對方服務器太忙,沒空理你,也有可能是網絡中某個路由器或者交換機掛了,甚至對方掉線了。
下面是個示例:
它還能跨語言!!!!!
就到這里啦,評論,一鍵三連哦(如果你真的看到這里的話)
Linux 任務調度
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。