Linux進程間通信
@TOC
零、前言
本章主要講解學習linux中本系統下的進程間通信
一、進程間通信介紹
概念:
進程間通信簡稱IPC(Interprocess communication),進程間通信就是在不同進程之間傳播或交換信息
進程間通信目的:
數據傳輸:一個進程需要將它的數據發送給另一個進程
資源共享:多個進程之間共享同樣的資源
通知事件:一個進程需要向另一個或一組進程發送消息,通知它(它們)發生了某種事件(如進程終止時要通知父進程)
進程控制:有些進程希望完全控制另一個進程的執行(如Debug進程),此時控制進程希望能夠攔截另一個進程的所有陷入和異常,并能夠及時知道它的狀態改變
進程間通信本質:讓不同的進程看到同一份資源
由于進程之間具有獨立性,代碼數據獨立擁有,若想實現通信,可以通過向第三方資源(實際上就是操作系統提供的一段內存區域)寫入或是讀取數據,進而實現進程之間的通信
進程間通信發展:
管道->System V進程間通信->POSIX進程間通信
進程間通信分類:
管道
匿名管道pipe;命名管道
System V IPC
System V 消息隊列;System V 共享內存;System V 信號量
POSIX IPC
消息隊列;共享內存;信號量;互斥量;條件變量;讀寫鎖
二、管道
概念:
管道是Unix中最古老的進程間通信的形式,我們把從一個進程連接到另一個進程的一個數據流稱為一個“管道”
示圖:統計當前使用云服務器上的登錄用戶個數
注:who命令用于查看當前云服務器的登錄用戶(一行顯示一個用戶);wc -l用于統計當前的行數
1、匿名管道
概念:
匿名管道用于本地具有親戚關系的進程之間通信,常用與父子進程間通信
pipe函數原型:
#include
功能:
創建一無名管道
參數:
fd:文件描述符數組,是一個輸出型參數,拿到打開的管道文件的問文件描述符,其中fd[0]表示讀端文件,fd[1]表示寫端文件
返回值:成功返回0,失敗返回錯誤代碼
示圖:
示例:父子進程匿名管道通信
#include
效果:
共享管道原理:
對于同個文件可以以讀方式和以寫方式打開,文件在文件系統雖然只有一份,但是在進程的PCB中的文件結構體中的文件地址數組中可以保存兩份,一份指向文件的讀端口,一份指向文件的寫端口
管道通過系統接口創建管道文件資源,并構建文件與PCB的映射關系,當fork創建子進程時父子進程就見到同一份文件資源,依靠管道文件的緩沖區選擇性進行單向的實時讀寫
注:如果是刷新到磁盤上再進行讀寫非常影響效率
單向讀寫:
父進程進行讀,子進程進行寫;父進程進行寫,子進程進行讀
示圖:
注意:
只有在先fork之前讀寫打開文件,父子進程才能共享相同的文件指針數組,進一步靈活控制讀寫
管道只能夠進行單向通信,關閉對應的讀寫端也是為了避免誤操作
從管道寫端寫入的數據會被內核緩沖,直到從管道的讀端被讀取
以文件描述符視角理解:
以內核角度理解:
注意:
管道就是特殊的文件,管道的使用和文件一致
但是依靠管道通信的本質上依靠管道的緩沖區進行讀寫,其緩沖并不會真正的刷新到磁盤上
管道讀寫規則:
寫端不寫,讀端無數據可讀
O_NONBLOCK disable:read調用阻塞,即進程暫停執行,進行等待寫端寫入數據
O_NONBLOCK enable:read調用返回-1,errno值為EAGAIN
寫端不寫,并將寫端文件關閉
如果所有管道寫端對應的文件描述符被關閉,則read返回0
讀端不讀,寫端一直寫
O_NONBLOCK disable: write調用阻塞,直到有進程讀走管道緩沖區的數據
O_NONBLOCK enable: write調用返回-1,errno值為EAGAIN
讀端不讀,并將讀端文件關閉
如果所有管道讀端對應的文件描述符被關閉,則write操作會產生信號SIGPIPE,進而可能導致write進程被終止退出
示圖:
數據寫入的原子性
當要寫入的數據量不大于PIPE_BUF時,linux將保證寫入的原子性
當要寫入的數據量大于PIPE_BUF時,linux將不再保證寫入的原子性
注:原子性是指 一個操作是不可中斷的,要么全部執行成功要么全部執行失敗,即使是在多個線程一起執行的時候,一個操作一旦開始,就不會被其他線程所干擾
管道特點:
只能用于具有共同祖先的進程(具有親緣關系的進程)之間進行通信;通常父子進程之間就可應用該管道
管道提供流式服務,面向字節流,讀寫以字節為單位進行
進程退出,管道釋放,所以管道的生命周期隨進程內核會對管道操作進行同步與互斥,即保證數據的原子性
管道是半雙工的,數據只能向一個方向流動;需要雙方通信時,需要建立起兩個管道
示圖:
3、命名管道
概念:
對于匿名管道應用的一個限制就是只能在具有共同祖先(具有親緣關系)的進程間通信
如果我們想在不相關的進程之間交換數據,可以使用FIFO文件來做這項工作,它經常被稱為命名管道
命名管道創建命令:
mkfifo filename
示例:
命名管道創建函數原型:
#include
注:第一個參數即為管道的名稱,第二個參數即為創建管道文件的權限,創建成功返回0,否則返回-1
示例:
int main() { mkfifo("fifo", 0644); return 0; }
匿名管道與命名管道的區別
匿名管道由pipe函數創建并打開,依靠父子進程的共享特性看到同一份文件資源
命名管道由mkfifo函數創建并主動調用函數打開,依靠文件路徑的唯一性讓不同進行找到并打開同一份文件資源
FIFO(命名管道)與pipe(匿名管道)之間唯一的區別在它們創建與打開的方式不同,一但這些工作完成之后,它們具有相同的語義
命名管道的打開規則
如果當前打開操作是為讀而打開FIFO時
O_NONBLOCK disable:阻塞直到有相應進程為寫而打開該FIFO
O_NONBLOCK enable:立刻返回成功
如果當前打開操作是為寫而打開FIFO時
O_NONBLOCK disable:阻塞直到有相應進程為讀而打開該FIFO
O_NONBLOCK enable:立刻返回失敗,錯誤碼為ENXIO
示例:用命名管道實現server&client通信
server.c: #include
效果:
三、system V共享內存
1、共享內存概念及原理
概念:
管道通信本質是基于文件的,也就是說操作系統并沒有為此做過多的設計工作,而system V IPC是操作系統特地設計的一種通信方式;但是不管怎么樣,它們的本質都是一樣的,都是在想盡辦法讓不同的進程看到同一份由操作系統提供的資源
共享內存區是最快的IPC形式。一旦這樣的內存映射到共享它的進程的地址空間,這些進程間數據傳遞不再涉及到內核,換句話說是進程不再通過執行進入內核的系統調用來傳遞彼此的數據
system V IPC提供的通信方式有以下三種:
system V共享內存
system V消息隊列
system V信號量
注:system V共享內存和system V消息隊列是以傳送數據為目的的,而system V信號量是為了保證進程間的同步與互斥而設計的,雖然system V信號量和通信好像沒有直接關系,但屬于通信范疇
共享內存的基本原理:
用戶申請共享內存:OS在物理內存當中申請一塊內存空間
進程主動掛接共享內存:OS將這塊內存空間分別與各個進程的進程地址空間建立映射關系(共享內存映射進進程地址空間的共享區)
各進程看到同一空間資源:OS將映射后的的共享內存的虛擬地址返回給進程
示圖:
注:這里所說的開辟物理空間、建立映射等操作都是調用系統接口完成的,也就是說這些動作都由操作系統來完成
共享內存數據結構:
各個進程都可以進行申請共享內存,那么共享內存的需求就可能非常多,而OS也需要進行對共享內容的管理,而管理的本質就是:先描述,再組織
所以共享內存除了在內存當中真正開辟空間之外,系統一定還要為共享內存維護相關的內核數據結構
shmid_ds結構定義:
struct shmid_ds { struct ipc_perm shm_perm; /* operation perms */ int shm_segsz; /* size of segment (bytes) */ __kernel_time_t shm_atime; /* last attach time */ __kernel_time_t shm_dtime; /* last detach time */ __kernel_time_t shm_ctime; /* last change time */ __kernel_ipc_pid_t shm_cpid; /* pid of creator */ __kernel_ipc_pid_t shm_lpid; /* pid of last operator */ unsigned short shm_nattch; /* no. of current attaches */ unsigned short shm_unused; /* compatibility */ void *shm_unused2; /* ditto - used by DIPC */ void *shm_unused3; /* unused */ };
注意:
當申請了一塊共享內存后,為了讓要實現通信的進程能夠找到同一個共享內存進行掛接,每一個共享內存結構體中會存儲一個key值,這個key值用于標識系統中共享內存的唯一性
上面共享內存數據結構的第一個成員shm_perm,每個共享內存的key值存儲在shm_perm這個結構體變量當中
ipc_perm結構體的定義:
struct ipc_perm{ __kernel_key_t key; __kernel_uid_t uid; __kernel_gid_t gid; __kernel_uid_t cuid; __kernel_gid_t cgid; __kernel_mode_t mode; unsigned short seq; };
共享內存使用過程:
調用系統接口進行在物理內存中申請共享內存空間
調用接口將申請到的共享內存掛接到地址空間,建立映射關系
使用之后調用接口將共享內存與地址空間去關聯,取消映射關系
調用接口釋放共享內存空間,將物理內存歸還給系統
2、共享內存使用接口介紹
1、共享內存資源的查看
如何查看共享內存資源:
使用ipcs命令查看有關進程間通信設施的信息
選項:
-q:列出消息隊列相關信息 -m:列出共享內存相關信息 -s:列出信號量相關信息
注:單獨使用ipcs命令時,會默認列出消息隊列、共享內存以及信號量相關的信息
示圖:
ipcs輸出信息含義:
注:key標識共享內存唯一性的方式,而shmid是用于用戶指明操作對象,key和shmid之間的關系類似于inode和fd之間的的關系
2、共享內存的創建和釋放
ftok函數的函數原型:
key_t ftok(const char *pathname, int proj_id);
解釋:
功能:將一個已存在的路徑名pathname和一個整數標識符proj_id轉換成一個key值,稱為IPC鍵值,在使用shmget函數獲取共享內存時,這個key值會被填充進維護共享內存的數據結構當中
注意:
pathname所指定的文件必須存在且可存?。皇褂胒tok函數生成key值存在可能會產生沖突
進行通信的各個進程在使用ftok函數獲取key值時,需要采用同樣的路徑名和和整數標識符,進而生成同一種key值找到同一份共享內存
shmget函數的函數原型:
#include
解釋:
功能:向系統申請共享內存
參數:第一個參數key,表示待創建共享內存在系統當中的唯一標識;第二個參數size,表示待創建共享內存的大??;第三個參數shmflg,表示創建共享內存的方式
返回值:shmget調用成功,返回一個有效的共享內存標識符,用于進行操作;shmget調用失敗,返回-1
注:這里shmget函數的返回值實際上就是共享內存的句柄,這個句柄可以在用戶層標識共享內存,當共享內存被創建后,我們在后續使用共享內存的相關接口時,都是需要通過這個句柄對指定共享內存進行各種操作
第三個參數shmflg常用組合方式:
示例:
#include
效果:
注意:
進程運行完畢后,申請的共享內存依舊存在,即共享內存的生命周期是隨內核的,也就是說共享內存并不會主動隨進程的退出而釋放
如果進程不主動刪除創建的共享內存,那么共享內存就會一直存在,直到關機重啟(system V IPC都是如此)
在命令行中我們可以使用命令ipcrm -m shmid釋放共享內存
示圖:
shmctl函數的函數原型:
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
解釋:
功能:控制對應的共享內存資源
參數:第一個參數shmid表示要控制的共享內存;第二個參數cmd,表示具體的控制動作;第三個參數buf,用于獲取或設置所控制共享內存的數據結構
返回值:shmctl調用成功,返回0;shmctl調用失敗,返回-1
shmctl函數的第二個參數常用的傳入選項:
注:一般使用接口進行釋放對應的共享內存資源
3、共享內存的鏈接與去連接
shmat函數的函數原型:
void *shmat(int shmid, const void *shmaddr, int shmflg);
解釋:
功能:將共享內存與進程建立映射關系
參數:第一個參數表示要關聯的共享內存的對應的shmid;第二個參數shmaddr指定共享內存映射到進程地址空間的某一地址,通常設置為NULL,表示讓內核自己決定一個合適的地址位置;第三個參數shmflg,表示關聯共享內存時設置的某些屬性,一般設置為0
返回值:shmat調用成功,返回共享內存映射到進程地址空間中的起始地址;shmat調用失敗,返回(void*)-1
shmat函數第三個參數的常用傳入選項:
shmdt函數的函數原型:
int shmdt(const void *shmaddr);
解釋:
功能:取消共享內存與進程的映射關系
參數:待去關聯共享內存的起始地址,即調用shmat函數時得到的起始地址
返回值:shmdt調用成功,返回0;shmdt調用失敗,返回-1
4、接口使用示例
示例:
servershm.c: #include
效果:
注:共享內存沒有進行同步與互斥,讀端并不會管寫端寫的原子性
3、共享內存與管道對比
共享內存通信方式需要進行的拷貝次數最少,由此速度最快
對于管道通信數據傳輸過程:將數據先寫到管道緩沖區,再沖管道緩沖區中讀取數據
共享內存通信數據傳輸過程:直接對共享內存進行讀寫
共享內存也是有缺點的,管道是自帶同步與互斥機制的,但是共享內存并沒有提供任何的保護機制,包括同步與互斥
4、消息隊列/信號量
消息隊列概念:
消息隊列提供了一個從一個進程向另外一個進程發送一塊數據的方法
每個數據塊都被認為是有一個類型,接收者進程接收的數據塊可以有不同的類型值
特性方面:
IPC資源必須刪除,否則不會自動清除,除非重啟,所以system V IPC資源的生命周期隨內核
消息隊列的基本原理:
消息隊列實際上就是在系統當中創建了一個隊列,隊列當中的每個成員都是一個數據塊,這些數據塊都由類型和信息兩部分構成
兩個互相通信的進程通過某種方式看到同一個消息隊列,這兩個進程向對方發數據時,都在消息隊列的隊尾添加數據塊,這兩個進程獲取數據塊時,都在消息隊列的隊頭取數據塊
信號量概念:
信號量主要用于同步和互斥的,進程之間存在對資源的競爭性,但是資源有限,需要保證對象獲取資源的個數在承受范圍之內
就相當于每個進程在獲取資源之前,需要先通過信號量獲取獲得資源的一個憑證,就像一個預定機制一樣
也就是說,每個進行也需要競爭獲取信號量資源,即信號量也是一個臨界資源,此時就需要信號量本身就原子的,其對應的操作具有原子性
從本質上來說,信號量是用來描述臨界資源數目的一個計數器
注意:
由于各進程要求共享資源,而且有些資源需要互斥使用,因此各進程間競爭使用這些資源,進程的這種關系為進程的互斥
系統中某些資源一次只允許一個進程使用,稱這樣的資源為臨界資源或互斥資源,在進程中涉及到互斥資源的程序段叫臨界區
特性方面:
IPC資源必須刪除,否則不會自動清除,除非重啟,所以system V IPC資源的生命周期隨內核
任務調度 數據結構
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。