每當我們執行一個程序時,對于操作系統來講就創建了一個進程,在這個過程中,伴隨著資源的分配和釋放。可以認為進程是一個程序的一次執行過程。進程的內存空間是相互獨立的,一般而言是不能相互訪問的。但很多情況下進程間需要互相通信,來完成系統的某項功能。進程通過與內核及其它進程之間的互相通信來協調它們的行為。進程間通信的方式通常由以下幾種:
管道分為有名管道和無名管道。無名管道是一種半雙工的通信方式,數據只能單向流動,而且只能在具有親緣關系的進程間使用.進程的親緣關系一般指的是父子關系。無明管道一般用于兩個不同進程之間的通信。當一個進程創建了一個管道,并調用fork創建自己的一個子進程后,父進程關閉讀管道端,子進程關閉寫管道端,這樣提供了兩個進程之間數據流動的一種方式。有名管道也是一種半雙工的通信方式,但是它允許無親緣關系進程間的通信。
信號量是一個計數器,可以用來控制多個線程對共享資源的訪問.,它不是用于交換大批數據,而用于多線程之間的同步.它常作為一種鎖機制,防止某進程在訪問資源時其它進程也訪問該資源.因此,主要作為進程間以及同一個進程內不同線程之間的同步手段.Linux提供了一組精心設計的信號量接口來對信號進行操作,它們不只是針對二進制信號量,下面將會對這些函數進行介紹,但請注意,這些函數都是用來對成組的信號量值進行操作的。它們聲明在頭文件sys/sem.h中。
信號是一種比較復雜的信方式,用于通知接收進程某個事件已經發生。
消息隊列是消息的鏈表,存放在內核中并由消息隊列標識符標識.消息隊列克服了信號傳遞信息少,管道只能承載無格式字節流以及緩沖區大小受限等特點.消息隊列是UNIX下不同進程之間可實現共享資源的一種機制,UNIX允許不同進程將格式化的數據流以消息隊列形式發送給任意進程.對消息隊列具有操作權限的進程都可以使用msget完成對消息隊列的操作控制.通過使用消息類型,進程可以按任何順序讀信息,或為消息安排優先級順序。
socket,即套接字是一種通信機制,憑借這種機制,客戶/服務器(即要進行通信的進程)系統的開發工作既可以在本地單機上進行,也可以跨網絡進行。也就是說它可以讓不在同一臺計算機但通過網絡連接計算機上的進程進行通信。也因為這樣,套接字明確地將客戶端和服務器區分開來。
共享內存就是映射一段能被其他進程所訪問的內存,這段共享內存由一個進程創建,但多個進程都可以訪問.共享內存是最快的IPC(進程間通信)方式,它是針對其它進程間通信方式運行效率低而專門設計的.它往往與其他通信機制,如信號量,配合使用,來實現進程間的同步與通信。
本文主要介紹共享內存的通信方式
- 共享內存概念
在Linux系統中,每個進程都有獨立的虛擬內存空間,也就是說不同的進程訪問同一段虛擬內存地址所得到的數據是不一樣的,這是因為不同進程相同的虛擬內存地址會映射到不同的物理內存地址上。但有時候為了讓不同進程之間進行通信,需要讓不同進程共享相同的物理內存,Linux通過 共享內存 來實現這個功能。
共享內存就是允許兩個或多個進程共享一定的存儲區。就如同 malloc() 函數向不同進程返回了指向同一個物理內存區域的指針。當一個進程改變了這塊地址中的內容的時候,其它進程都會察覺到這個更改。因為數據不需要在客戶機和服務器端之間復制,數據直接寫到內存,不用若干次數據拷貝,所以這是最快的一種通信方式。
- 共享內存的簡單使用
- 獲取共享內存
要使用共享內存,首先需要使用 shmget() 函數獲取共享內存,shmget() 函數的聲明如下:int shmget(key_t key, size_t size, int shmflg);
參數 key 一般由 ftok() 函數生成,用于標識系統的唯一IPC資源。
參數 size 指定創建的共享內存大小。
參數 shmflg 指定 shmget() 函數的動作,比如傳入 IPC_CREAT 表示要創建新的共享內存。
函數調用成功時返回一個新建或已經存在的的共享內存標識符,取決于shmflg的參數。失敗返回-1,并設置錯誤碼。
- 關聯共享內存
shmget() 函數返回的是一個標識符,而不是可用的內存地址,所以還需要調用 shmat() 函數把共享內存關聯到某個虛擬內存地址上。shmat() 函數的聲明如下:void *shmat(int shmid, const void *shmaddr, int shmflg);
參數 shmid 是 shmget() 函數返回的標識符。
參數 shmaddr 是要關聯的虛擬內存地址,如果傳入0,表示由系統自動選擇合適的虛擬內存地址。
參數 shmflg 若指定了 SHM_RDONLY 位,則以只讀方式連接此段,否則以讀寫方式連接此段。
函數調用成功返回一個可用的指針(虛擬內存地址),出錯返回-1
- 取消關聯共享內存
當一個進程不需要共享內存的時候,就需要取消共享內存與虛擬內存地址的關聯。取消關聯共享內存通過 shmdt() 函數實現,聲明如下:int shmdt(const void *shmaddr);
參數 shmaddr 是要取消關聯的虛擬內存地址,也就是 shmat() 函數返回的值。
函數調用成功返回0,出錯返回-1。
- 刪除共享內存
當一個共享內存不再使用時,就需要刪除它,刪除共享內存的函數聲明如下:int shmctl(int shm_id, int command, struct shmid_ds *buf);
參數shm_id是shmget函數返回的共享內存標識符。
參數command填IPC_RMID。
參數buf填0。
5、使用舉例
下面通過一個例子來介紹一下共享內存的使用方法。在這個例子中,有兩個進程,分別為 進程A 和 進程B,進程A 創建一塊共享內存,然后寫入數據,進程B 獲取這塊共享內存并且讀取其內容。
進程A代碼內容:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define SHM_PATH "/tmp/shm"
#define SHM_SIZE 128
int main(int argc, char *argv[])
{
??? int shmid;
??? char *addr;
??? key_t key = ftok(SHM_PATH, 0x6666);
??? shmid = shmget(key, SHM_SIZE, IPC_CREAT|IPC_EXCL|0666);
??? if (shmid < 0) {
??????? printf("failed to create share memory\n");
??????? return -1;
??? }
??? addr = shmat(shmid, NULL, 0);
??? if (addr <= 0) {
??????? printf("failed to map share memory\n");
??????? return -1;
??? }
??? sprintf(addr, "%s", "Hello World\n");
??? return 0;
}
進程B代碼內容:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define SHM_PATH "/tmp/shm"
#define SHM_SIZE 128
int main(int argc, char *argv[])
{
??? int shmid;
??? char *addr;
??? key_t key = ftok(SHM_PATH, 0x6666);
??? char buf[128];
??? shmid = shmget(key, SHM_SIZE, IPC_CREAT);
??? if (shmid < 0) {
??????? printf("failed to get share memory\n");
??????? return -1;
??? }
??? addr = shmat(shmid, NULL, 0);
??? if (addr <= 0) {
??????? printf("failed to map share memory\n");
??????? return -1;
??? }
??? strncpy(buf, addr, 128);
??? printf("%s", buf);
??? return 0;
}
使用gcc編譯,gcc -o A.out A.c ,gcc -o B.out B.c 。編譯完成后,測試時先運行進程A,然后再運行進程B,可以看到進程B會打印出 “Hello World”,說明共享內存已經創建成功并且讀取。
- 使用共享內存的優缺點
- 優點
我們可以看到使用共享內存進行進程間的通信真的是非常方便,而且函數的接口也簡單,數據的共享還使進程間的數據不用傳送,而是直接訪問內存,也加快了程序的效率。同時,它也不像匿名管道那樣要求通信的進程有一定的父子關系。
- 缺點
共享內存沒有提供同步的機制,這使得我們在使用共享內存進行進程間通信時,往往要借助其他的手段來進行進程間的同步工作。