文章目錄
- 管道
- 匿名管道 pipe
- 命名管道 FIFO
- 共享內存
- 共享內存的使用流程:
- 消息隊列
- 信號量
- 套接字
在之前的博客中講過,虛擬空間出現的其中一個目的就是解決 進程沒有獨立性,可能訪問同一塊物理內存 的問題。因為這種獨立性,進程之間無法直接進行通信,操作系統為了解決這種問題,提出了多種適用于不同情境下的通信方式:
- 數據傳輸:管道、消息隊列
- 數據共享:共享內存
- 進程控制:信號量
管道
管道的本質其實就是內核中的一塊緩沖區,多個進程通過訪問同一個緩沖區就可以實現進程間的通信。自 Linux 2.6.11
內核起,管道容量的大小默認是 65536
字節,但可以通過 fcntl函數
來修改管道容量。
管道分為兩種:匿名管道、命名管道
匿名管道 pipe
因為沒有具體的文件描述符,所以只能用于具有親緣(父子)關系的進程之間的通信。
原理: 父進程在創建管道的時候操作系統會返回管道的文件描述符,然后生成子進程時子進程會通過拷貝父進程的 pcb
來獲取到這個管道的描述符,所以他們可以通過這個文件描述符來訪問同一個管道,來實現進程間的通信。而不具備親緣關系的進程則無法通過這個文件描述符來訪問同一個管道。
返回值:成功返回 0
,失敗返回 -1
。
也就是說讀/寫操作的流程是:
- 關閉 讀/寫 端
- 進行 寫/讀 操作
- 寫/讀 完關閉 寫/讀 端
圖解父子進程通過管道通信的流程:
一開始父進程創建管道
父進程fork創建子進程
關閉多余描述符
代碼示例:示例一 示例二
如果沒有特意規定,那么父子進程究竟是誰先執行是不確定的,假設如果子進程還沒寫入,父進程卻已經開始讀了,這時候應該是會讀不到東西的,會在屏幕上輸出空行,但是這種情況并沒有發生,這里就牽扯到了管道的讀寫特性:
- 如果管道中沒有數據,則調用
read
讀取數據會阻塞。 - 如果管道中數據滿了,則調用
write
寫入數據會阻塞。 - 如果管道的所有 讀端 被關閉,繼續調用
write
時,會因為無法讀出而產生異常導致進程退出。 - 如果管道的所有 寫端 被關閉,繼續調用
read
時,read
讀完管道中的所有數據后不再阻塞,返回0
退出。
命名管道 FIFO
命名管道也是內核中的一塊緩沖區,但是它 具有標識符 。這個標識符是一個可見于文件系統的管道文件,能夠被其他進程找到并打開管道文件來獲取管道的操作句柄,多個進程可以通過打開這個管道文件來訪問同一塊緩沖區來實現通信。
- 可以在沒有親緣關系(非父子)的進程之間進行通信,這是與無名管道最大的區別。
- 他是以一個特性的文件存儲在文件系統中, 所以對他的操作與其路徑名有關聯。
接口:
int mkfifo(const char *filename,mode_t mode);
filename
——管道的標識符,通過這個標識符來訪問管道,創建之前這個標識符必須不存在。
mode
——權限掩碼。
返回值——若成功則返回0
,否則返回-1
。
代碼示例: 命名管道實現進程的信息傳遞【mkfifo函數、open函數】
open 打開命名管道的特性:
- 若文件以只讀打開,則會阻塞,直到文件被以寫的方式打開。
- 若文件以只寫打開,則會阻塞,直到文件被以讀的方式打開。
管道的特性:
- 管道是半雙工通信(可以選擇方向的單向傳輸),這個可以從上面的示意圖看出來。
- 管道的讀寫特性(無論命名匿名都一樣)。
- 管道聲明周期隨進程,打開管道的所有進程退出后管道就會被釋放。
- 管道提供字節流傳輸服務。
- 命名管道額外有一個打開特性,只讀打開會阻塞直到被以寫打開,只寫打開會阻塞直到被以讀打開。
- 管道自帶同步和互斥。
共享內存
共享內存即在 物理內存 上開辟一塊空間,然后 多個進程 通過 頁表 將這 同一個物理內存 映射到自己的 虛擬地址空間 中,通過自己的 虛擬地址空間 來訪問這塊 物理內存 ,達到了數據共享的目的。
也正是因為這種特性,使得 共享內存成為了最快的進程間通信的方式 ,因為它 直接通過虛擬地址來訪問物理內存,比前面的管道和后面的消息隊列 少了內核態和用戶態的幾次數據拷貝和交互 。
特點:
- 生命周期隨內核。
- 由于多個進程可以同時操作,因此需要進行同步。但不自帶同步與互斥,而是借助信號量來實現同步與互斥。
共享內存的使用流程:
1. 創建共享內存
頭文件:
#include <sys/ipc.h>
#include <sys/shm.h>
定義函數:
int shmget(key_t key, size_t size, int shmflg)
參數:
key:這個共享內存段名字
size:共享內存大小
shmflg:由九個權限標志構成,它們的用法和創建文件時使用的mode模式標志是一樣的。返回值:成功返回共享內存標識符,失敗返回-1。
2. 將共享內存映射到虛擬地址空間
頭文件:
#include <sys/types.h>
#include <sys/shm.h>
定義函數:
void *shmat(int shmid, const void *shmaddr, int shmflg)
參數:
shmid: 共享內存標識
shmaddr:指定連接的地址
shmflg:權限標志返回值:成功返回指向共享內存映射在虛擬地址空間的指針(即首地址),失敗返回-1。
3. 共享內存管理
頭文件:
#include <sys/types.h>
#include <sys/shm.h>
定義函數:
int shmctl(int shmid, int cmd, struct shmid_ds *buf)
參數:
shmid:由shmget返回的共享內存標識碼
cmd:將要采取的動作
buf:指向一個保存著共享內存的模式狀態和訪問權限的數據結構返回值:成功返回0,失敗返回-1
4. 解除映射關系
頭文件:
#include <sys/types.h>
#include <sys/shm.h>
定義函數:
int shmdt(const void *shmaddr)
參數:shmaddr: 由shmat所返回的指針
返回值:成功返回0,失敗返回-1
消息隊列
概念:
- 消息隊列是內核中的一個優先級隊列,多個進程通過訪問同一個隊列,進行添加節點或者獲取節點來實現通信。
- 消息隊列是消息的連接表, 放在內核中, 一個消息隊列由一個標識符表示。
- 消息隊列是面向記錄的, 其中的消息具有特定的格式以及特定的優先級。
- 消息隊列獨立于發送和接受進程中, 也就是當一個進程被銷毀, 他在消息隊列中的信息是不會被刪除的。
- 消息隊列中的內容可以實現隨機查詢, 也就是消息不一定要以
FIFO
讀取, 也可以按消息的類型讀取。
特性:
- 自帶同步與互斥
- 生命周期隨內核
流程:
1. 創建消息隊列
頭文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
定義函數:
int msgget(key_t key, int msgflg)
參數:
key:消息隊列對象的關鍵字
msgflg:消息隊列的建立標志和存取權限返回值:成功執行時,返回消息隊列標識值。失敗返回-1
2. 進程可以向隊列中添加/獲取節點
頭文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
添加節點:
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
獲取節點:
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
參數:
msqid:消息隊列對象的標識符
msgp:消息緩沖區指針
msgsz:消息數據的長度
msgtyp:決定從隊列中返回哪條消息
msgflg:消息隊列狀態返回值:成功執行時,返回0。失敗返回-1
3. 刪除消息隊列
頭文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
定義函數:
int msgctl(int msqid, int cmd, struct msqid_ds *buf)
參數:
msqid:消息隊列對象的標識符
cmd:函數要對消息隊列進行的操作
buf:取出系統保存的消息隊列的 msqid_ds 數據,并將其存入參數 buf 指向的 msqid_ds 結構中返回值:成功執行時,返回0。失敗返回-1
信號量
概念:
- 信號量與IPC結構不同,它其實是 內核中的一個計數器和阻塞隊列 ,通過信號量來對 臨界資源的訪問進行控制,來 實現進程間的同步與互斥 ,而不是用于進程間消息的通信的。
- 信號量用于進程間同步,若要在進程間傳遞數據需要結合共享內存。
- 信號量基于操作系統的
PV
操作,程序對信號量的操作都是原子操作。 - 每次對信號量的 PV 操作不僅限于對信號量值加 1 或減 1,而且可以加減任意正整數。
例如:有一個能容納 n
人的餐廳,用一個計數器表示 n
,如果有人進入則 n - 1
,如果有人出來則 n + 1
,只有 n > 0
時才能進入,如果 n <= 0
時,則說明沒有位置,需要將進程掛起并放入阻塞隊列中,直到有人出來使資源釋放時,才能將后續進程從阻塞隊列中喚醒獲取資源。
- 同步:通過條件判斷實現臨界資源訪問的合理性
- 互斥:通過同一時間的唯一訪問來實現臨界資源訪問的安全性
POSIX信號量: POSIX
信號量和 SystemV
信號量作用相同,都是用于同步操作,達到無沖突的訪問共享資源目的。 但 POSIX
可以用于線程間同步。
流程:
#include <semaphore.h>//初始化信號量
int sem_init(sem_t *sem, int pshared, unsigned int value);
/*
參數:
pshared:0表示線程間共享,非零表示進程間共享
value:信號量初值
*///銷毀信號量
int sem_destroy(sem_t *sem);//等待信號量
int sem_wait(sem_t *sem);
//功能:等待信號量,會將信號量的值減1//發布信號量
int sem_post(sem_t *sem);
//功能:發布信號量,表示資源使用完畢,可以歸還資源了。將信號量值加1。
套接字
使用套接字也可以實現進程間的通信,與其他機制不同的是,它可以實現不同機器之前的進程間的通信。