IPC形式除了管道、FIFO、信號量以外,還有共享內存區和消息隊列。這裡主要堆共享內存進行介紹。
共享內存區是可用IPC形式中最快的。一旦這樣的內存區映射到共享它的進程地址空間,這些進程間數據的傳遞就不再涉及內核。共享內存與其他進程通信方式相比較,不需要復制數據,直接讀寫內存,是一種效率非常高的進程通信方案。但它本身不提供同步訪問機制,需要我們自己控制。在LINUX中,只要把共享內存段連接到進程的地址空間中,這個進程就可以訪問共享內存中的地址了。為了實現往該共享內存區存放和取出數據的進程間的同步性,防止自己寫入的數據被自己讀出。這就到了我們之前提到的信號量大顯身手的時候了。
共享內存是系統創建的特殊地址空間,允許不相關的多個進程使用這個內存空間,即多個進程能夠使用同一塊內存中的數據。
LINUX系統提供的共享內存操作函數與信號量、消息隊列等類似,主要有以下幾個:
(1)int shmget(key_t key,int shmsz,int shmflg);
Shmget()函數分配一塊新的共享內存。key是指ftok所得到的鍵值。Shmsz指明共享內存的大小,以字節為單位,shmflg的設置是標志信息
如果shmget()函數調用成功則返回共享內存的ID;否則返回-1.
(2)void *shmat(int shmid,const void *shmaddr, int shmflg);
Shmat()函數的作用是連接共享內存與某個進程的地址空間。
Shmid是shmget()函數返回的共享內存ID。Shmaddr是共享內存連接到進程中的存放地址,一般設置為空指針,表示交由系統完成這個工作。
如果shmaddr為0 則此段連接到由內核選擇的第一個可用地址上,這是推薦的使用方式
如果shmaddr非零,並且沒有指定SHM_RND,則此段鏈接到addr所指的地址上
如果shmaddr非零且指定SHM_RND,則此段鏈接到shmaddr - (addr mod ulus SHMLBA)所表示的地址上。SHM_RND的意思是低邊界地址倍數,它總是2的乘方。該算式是將地址向下取最近的一個SHMLBA的倍數
Shmflg設置共享內存的控制選項,有兩個可能取值:SHM_RND(與shmaddr參數相關)與SHM_RDONLY(只允許讀)。如果shmat()函數調用成功則返回指向共享內存的指針;否則返回-1.
(3)int shmdt (const void *shmaddr);
Shmdt()函數用來解除進程與共享內存區域的關聯,使當前進程不能繼續訪問共享內存。參數shmaddr是shmat()函數返回的指針。如果操作成功則返回0;失敗則返回-1.
(4)int shmctl(int shmid, int cmd,struct shmid_ds *buf);
Shmctl()函數實現對共享內存區域的控制操作。
cmd:指明所要執行的操作,常用的有以下命令:
IPC_STAT:調用shmctl函數,執IPC_STAT命令,獲取一個已存在內存區的大小
IPC_SET :調用shmctl函數,執行IPC_SET命令,設置一個已存在內存區中的值
IPC_RMID:調用shmctl函數,執行IPC_RMID命令,刪出共享內存區對象。
下面是一個用信號量和共享內存聯合一起實現的一個服務器與客戶端之間通信的例子,思路是這樣的服務器和客戶端根據兩個信號量進行溝通,信號量1負責服務器的寫和客戶端的讀,而信號量2負責服務器的讀和客戶端的寫(因為半雙工管道的原因,不能同時控制讀寫)。而讀寫的信息存放在共享內存中,服務器寫進數據到共享內存,並發出消息給客戶端,客戶端在內存中取出數據讀。而信號量的作用就在於同步控制,根據0阻塞進程的特性,防止服務器自己寫的數據被自己讀的錯誤發生。
服務器端代碼:
ser.cpp
#include "utili.h"
int main(int argc, char *argv[])
{
/*創建一個唯一的鍵值並判斷是否創建成功*/
key_t key_id;
key_id = ftok(argv[1], ID);
if(key_id == -1){
printf("ftok error.\n");
exit(1);
}
//用上面創建的鍵值創建一塊大小為1024的共享內存。要麼創建要麼返回EEXIST錯誤,權限為所數用戶、所數組、其他用戶可讀可寫。並判斷它是否創建成功
key_t shm_id = shmget(key_id, 1024, IPC_CREAT|IPC_EXCL|0666);
if(shm_id == -1){
printf("shmget error.\n");
exit(1);
}
//在創建的共享內存中建立連接,地址由系統分配,並檢測連接是否成功
char* addr = (char *)shmat(shm_id, NULL, 0);
if(addr == (char *)-1){
printf("shmat error.\n");
shmctl(shm_id, IPC_RMID, NULL);
exit(1);
}
//重新創建一個新的鍵值並判斷是否創建成功
key_t sem_key, sem_id;
sem_key = ftok(argv[1], ID1);
if(sem_key == -1){
printf("sem ftok error.\n");
exit(1);
}
//用上面創建的鍵值創建兩個信號量集合,並判斷信號量集合是否創建成功
sem_id = semget(sem_key, 2, IPC_CREAT|IPC_EXCL|0666);
if(sem_id == -1){
printf("semget error.\n");
exit(1);
}
//給兩個信號量都賦初值0
union semun init;
init.val = 0;
semctl(sem_id, 0, SETVAL, init);
semctl(sem_id, 1, SETVAL, init);
//定義兩個sembuf的結構體,用來控制信號量資源數
struct sembuf p = {0, -1, 0};
struct sembuf v = {1, 1, 0};
while(1){
//服務器寫數據【此時0號信號量的值為0,處於阻塞狀態,所以它不會將自己寫入的數據讀出】
printf("Ser:>");
gets(addr);
//判斷它寫入的數據是否為quit,
if(strncmp(addr, "quit", 4) == 0){
semop(sem_id, &v, 1); //用結果體v對相應的信號量進行設置。保證結束信息可以令客戶端讀到,防止客戶端一直在等待服務器端的數據
shmdt(addr); //斷開共享內存連接
shmctl(shm_id, IPC_RMID, NULL); //刪除共享內存區
//刪除之前創建的兩個信號量集
semctl(sem_id, 0, IPC_RMID);
semctl(sem_id, 1, IPC_RMID);
break;
}
//如果輸入數據部為quit,用結果體v對相應的信號量進行設置。保證信息可以令客戶端讀到
semop(sem_id, &v, 1);
//服務器讀數據【此時1號信號量的值為0,客戶端讀操作處於阻塞狀態】
semop(sem_id, &p, 1);//改變0號信號量的值,讓服務器的讀操作處於運行狀態
printf("Cli:>%s\n", addr);
//比較客戶端傳來的數據是不是quit,如果是quit則斷開連接,並將值前創建的共享內存區和信號量刪除掉然後退出。
if(strncmp(addr, "quit", 4) == 0){
shmdt(addr);
semctl(shm_id, IPC_RMID, NULL);
semctl(sem_id, 0, IPC_RMID);
semctl(sem_id, 1, IPC_RMID);
break;
}
}
return 0;
}
客戶端程序:
cli.cpp:
#include "utili.h"
int main(int argc, char * argv[])
{
//用與服務器端同樣的路徑與數值創建一個相同的鍵值,並判斷是否創建成功
key_t key_id = ftok(argv[1], ID);
if(key_id == -1){
printf("ftok error.\n");
exit(1);
}
//利用該鍵值查找服務器端所創建的共享內存的id號,並判斷是否查找成功
key_t shm_id = shmget(key_id, 0, 0);
if(shm_id == -1){
printf("shmget error.\n");
exit(1);
}
//與共享內存建立連接,並保證創建連接成功
char *addr = (char *)shmat(shm_id,NULL,0);
if(addr == (void *)-1)
{
printf("shmat error.\n");
exit(1);
}
//重新創建一個與服務器中相同的sem_key,用來創建信號量集
key_t sem_key = ftok(argv[1],ID1);
if(sem_key == -1)
{
printf("sem ftok error.\n");
exit(1);
}
//查找服務器創建的兩個信號量集的id號
key_t sem_id = semget(sem_key, 0, 0);
if(sem_id == -1)
{
printf("semget error.\n");
exit(1);
}
//創建兩個sembuf型的結構體,p、v對應操作的信號量下標量semnum(第一個參數)應該與服務器端相反,這樣才能實現同步
struct sembuf p = {1,-1,0};
struct sembuf v = {0,1,0};
while(1){
//客戶端讀數據,並判斷服務器端傳來的數據是否為quit,如果為quit直接程序結束
semop(sem_id, &p, 1);
printf("Ser:>%s\n", addr);
if(strncmp(addr, "quit", 4) == 0){
break;
}
//所讀到的數據不為quit,客戶端進行寫數據【此時1號信號量的值為0,客戶端的讀操作阻塞】
printf("Cli:>");
gets(addr);
//如果客戶端傳送的數據為quit,則將0號信號量的值加1,然後在退出程序,保證服務器端可以讀到數據,從而斷開連接,防止服務器端一直等待客戶端的數據
if(strncmp(addr, "quit", 4) == 0){
semop(sem_id, &v, 1);
break;
}
//如果不為quit,則將0號信號量的值加1,讓服務器的讀操作處於運行狀態
semop(sem_id, &v, 1);
}
return 0;
}
頭文件:
utili.h:
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define ID 0xFF
#define ID1 0xFE
union semun
{
int val; /* value for SETVAL */
struct semid_ds *buf; /* buffer for IPC_STAT & IPC_SET */
unsigned short *array; /* array for GETALL & SETALL */
struct seminfo *__buf; /* buffer for IPC_INFO */
void *__pad;
};
程序運行結果:
這裡才真正的體現了信號量的作用,它可以實現同步機制,其實相比於共享內存,IPC形式中的消息隊列本身就帶有同步形式,後序會對其繼續進行整理,歡迎來訪~~。