微吼云上線多路互動直播服務 加速多場景互動直播落地
745
2025-04-01
文章目錄
其他文章
管道
消息隊列
共享內存
信號量
信號
Socket
總結
其他文章
操作系統——概述
操作系統——內存管理
操作系統——進程和線程
操作系統——進程間通信
操作系統——文件系統
操作系統——設備管理
操作系統——網絡系統
每個進程的用戶地址空間都是獨立的,一般而言是不能互相訪問的,但內核空間是每個進程都共享的,所以進程之間要通信必須通過內核。
進程間通信目的一般有共享數據,數據傳輸,消息通知,進程控制等。以 Unix/Linux 為例,介紹幾種重要的進程間通信方式:共享內存,管道,消息隊列,信號量,信號
管道
如果你學過 Linux 命令,那你肯定很熟悉「|」這個豎線。
$ ps auxf | grep mysql
1
上面命令行里的「|」豎線就是一個管道,它的功能是將前一個命令(ps auxf)的輸出,作為后一個命令(grep mysql)的輸入,從這功能描述,可以看出管道傳輸數據是單向的,如果想相互通信,我們需要創建兩個管道才行。
同時,我們得知上面這種管道是沒有名字,所以「|」表示的管道稱為匿名管道,用完了就銷毀。
管道還有另外一個類型是命名管道,也被叫做 FIFO,因為數據是先進先出的傳輸方式。
在使用命名管道前,先需要通過 mkfifo 命令來創建,并且指定管道名字:
$ mkfifo myPipe
1
myPipe 就是這個管道的名稱,基于 Linux 一切皆文件的理念,所以管道也是以文件的方式存在,我們可以用 ls 看一下,這個文件的類型是 p,也就是 pipe(管道) 的意思:
$ ls -l prw-r--r--. 1 root root 0 Jul 17 02:45 myPipe
1
2
接下來,我們往 myPipe 這個管道寫入數據:
$ echo "hello" > myPipe // 將數據寫進管道 // 停住了 ...
1
2
你操作了后,你會發現命令執行后就停在這了,這是因為管道里的內容沒有被讀取,只有當管道里的數據被讀完后,命令才可以正常退出。
于是,我們執行另外一個命令來讀取這個管道里的數據:
$ cat < myPipe // 讀取管道里的數據 hello
1
2
可以看到,管道里的內容被讀取出來了,并打印在了終端上,另外一方面,echo 那個命令也正常退出了。
我們可以看出,管道這種通信方式效率低,不適合進程間頻繁地交換數據。當然,它的好處,自然就是簡單,同時也我們很容易得知管道里的數據已經被另一個進程讀取了。
我們可以得知,對于匿名管道,它的通信范圍是存在父子關系的進程。因為管道沒有實體,也就是沒有管道文件,只能通過 fork 來復制父進程 fd 文件描述符,來達到通信的目的。
在 shell 里面執行 A | B命令的時候,A 進程和 B 進程都是 shell 創建出來的子進程,A 和 B 之間不存在父子關系,它倆的父進程都是 shell。
另外,對于命名管道,它可以在不相關的進程間也能相互通信。因為命令管道,提前創建了一個類型為管道的設備文件,在進程里只要使用這個設備文件,就可以相互通信。
消息隊列
前面說到管道的通信方式是效率低的,因此管道不適合進程間頻繁地交換數據。
對于這個問題,消息隊列的通信模式就可以解決。比如,A 進程要給 B 進程發送消息,A 進程把數據放在對應的消息隊列后就可以正常返回了,B 進程需要的時候再去讀取數據就可以了。同理,B 進程要給 A 進程發送消息也是如此。
再來,消息隊列是保存在內核中的消息鏈表,在發送數據時,會分成一個一個獨立的數據單元,也就是消息體(數據塊),消息體是用戶自定義的數據類型,消息的發送方和接收方要約定好消息體的數據類型,所以每個消息體都是固定大小的存儲塊,不像管道是無格式的字節流數據。如果進程從消息隊列中讀取了消息體,內核就會把這個消息體刪除。

消息隊列生命周期隨內核,如果沒有釋放消息隊列或者沒有關閉操作系統,消息隊列會一直存在,而前面提到的匿名管道的生命周期,是隨進程的創建而建立,隨進程的結束而銷毀。
缺點:
消息隊列通信過程中,存在用戶態與內核態之間的數據拷貝開銷,因為進程寫入數據到內核中的消息隊列時,會發生從用戶態拷貝數據到內核態的過程,同理另一進程讀取內核中的消息數據時,會發生從內核態拷貝數據到用戶態的過程。
共享內存
消息隊列的讀取和寫入的過程,都會有發生用戶態與內核態之間的消息拷貝過程。那共享內存的方式,就很好的解決了這一問題。
現代操作系統,對于內存管理,采用的是虛擬內存技術,也就是每個進程都有自己獨立的虛擬內存空間,不同進程的虛擬內存映射到不同的物理內存中。所以,即使進程 A 和 進程 B 的虛擬地址是一樣的,其實訪問的是不同的物理內存地址,對于數據的增刪查改互不影響。
共享內存的機制,就是拿出一塊虛擬地址空間來,映射到相同的物理內存中。這樣這個進程寫入的東西,另外一個進程馬上就能看到了,都不需要拷貝來拷貝去,傳來傳去,大大提高了進程間通信的速度。
信號量
用了共享內存通信方式,帶來新的問題,那就是如果多個進程同時修改同一個共享內存,很有可能就沖突了。例如兩個進程都同時寫一個地址,那先寫的那個進程會發現內容被別人覆蓋了。
為了防止多進程競爭共享資源,而造成的數據錯亂,所以需要保護機制,使得共享的資源,在任意時刻只能被一個進程訪問。正好,信號量就實現了這一保護機制。
信號量其實是一個整型的計數器,主要用于實現進程間的互斥與同步,而不是用于緩存進程間通信的數據。
信號量表示資源的數量,控制信號量的方式有兩種原子操作:
一個是 P 操作,這個操作會把信號量減去 -1,相減后如果信號量 < 0,則表明資源已被占用,進程需阻塞等待;相減后如果信號量 >= 0,則表明還有資源可使用,進程可正常繼續執行。
另一個是 V 操作,這個操作會把信號量加上 1,相加后如果信號量 <= 0,則表明當前有阻塞中的進程,于是會將該進程喚醒運行;相加后如果信號量 > 0,則表明當前沒有阻塞中的進程;
P 操作是用在進入共享資源之前,V 操作是用在離開共享資源之后,這兩個操作是必須成對出現的。
接下來,舉個例子,如果要使得兩個進程互斥訪問共享內存,我們可以初始化信號量為 1。
具體的過程如下:
進程 A 在訪問共享內存前,先執行了 P 操作,由于信號量的初始值為 1,故在進程 A 執行 P 操作后信號量變為 0,表示共享資源可用,于是進程 A 就可以訪問共享內存。
若此時,進程 B 也想訪問共享內存,執行了 P 操作,結果信號量變為了 -1,這就意味著臨界資源已被占用,因此進程 B 被阻塞。
直到進程 A 訪問完共享內存,才會執行 V 操作,使得信號量恢復為 0,接著就會喚醒阻塞中的線程 B,使得進程 B 可以訪問共享內存,最后完成共享內存的訪問后,執行 V 操作,使信號量恢復到初始值 1。
可以發現,信號初始化為 1,就代表著是互斥信號量,它可以保證共享內存在任何時刻只有一個進程在訪問,這就很好的保護了共享內存。
另外,在多進程里,每個進程并不一定是順序執行的,它們基本是以各自獨立的、不可預知的速度向前推進,但有時候我們又希望多個進程能密切合作,以實現一個共同的任務。
例如,進程 A 是負責生產數據,而進程 B 是負責讀取數據,這兩個進程是相互合作、相互依賴的,進程 A 必須先生產了數據,進程 B 才能讀取到數據,所以執行是有前后順序的。
那么這時候,就可以用信號量來實現多進程同步的方式,我們可以初始化信號量為 0。
具體過程:
如果進程 B 比進程 A 先執行了,那么執行到 P 操作時,由于信號量初始值為 0,故信號量會變為 -1,表示進程 A 還沒生產數據,于是進程 B 就阻塞等待;
接著,當進程 A 生產完數據后,執行了 V 操作,就會使得信號量變為 0,于是就會喚醒阻塞在 P 操作的進程 B;
最后,進程 B 被喚醒后,意味著進程 A 已經生產了數據,于是進程 B 就可以正常讀取數據了。
可以發現,信號初始化為 0,就代表著是同步信號量,它可以保證進程 A 應在進程 B 之前執行。
信號
信號一般用于一些異常情況下的進程間通信,是一種異步通信,它的數據結構一般就是一個數字
在 Linux 操作系統中, 為了響應各種各樣的事件,提供了幾十種信號,分別代表不同的意義。我們可以通過 kill -l 命令,查看所有的信號:
$ kill -l 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2 63) SIGRTMAX-1 64) SIGRTMAX
1
2
3
4
5
6
7
8
9
10
11
12
13
14
運行在 shell 終端的進程,我們可以通過鍵盤輸入某些組合鍵的時候,給進程發送信號。例如
Ctrl+C 產生 SIGINT 信號,表示終止該進程;
Ctrl+Z 產生 SIGTSTP 信號,表示停止該進程,但還未結束;
如果進程在后臺運行,可以通過 kill 命令的方式給進程發送信號,但前提需要知道運行中的進程 PID 號,例如:
kill -9 1050 ,表示給 PID 為 1050 的進程發送 SIGKILL 信號,用來立即結束該進程;
所以,信號事件的來源主要有硬件來源(如鍵盤 Cltr+C )和軟件來源(如 kill 命令)。
信號是進程間通信機制中唯一的異步通信機制
進程需要為信號設置相應的監聽處理,當收到特定信號時,執行相應的操作,類似很多編程語言里的通知機制。
Socket
前面提到的管道、消息隊列、共享內存、信號量和信號都是在同一臺主機上進行進程間通信,那要想跨網絡與不同主機上的進程之間通信,就需要 Socket 通信了。
實際上,Socket 通信不僅可以跨網絡與不同主機的進程間通信,還可以在同主機上進程間通信。
總結
Linux 任務調度
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。