【技術篇】linux進程間通訊--共享內存

(一)什麼是共享內存

       共享內存是在兩個正在運行的進程之間傳遞數據的一種非常有效的方式。共享內存的具體實現是不同進程共享的內存安排爲同一段物理地址。共享內存是最高效的IPC 機制,因爲他不涉及進程之間的任何數據傳輸。這種高效率帶來的問題是,我們必須借用其他的輔助方法來同步進程間對共享內存的訪問,否則會產生靜態條件,因此,共享內存一般會和其他進程間通信一起使用。具體信息可通過https://blog.csdn.net/ypt523/article/details/79958188 瞭解;

(二)共享內存的使用

    linux共享內存的API都定義在sys/shm.h頭文件中,包括4個系統調用:shmget、shmat、shmdt和shmctl;

  2.1 shmget系統調用

   shmget系統調用創建一段新的共享內存,或者獲取一段已經存在的共享內存,其定義如下:

#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);

【參數key】和semget系統調用一樣,key參數是一個鍵值,它有效地爲共享內存段命名,shmget函數成功時返回一個與key相關的共享內存標識符(非負整數),用於後續的共享內存函數。調用失敗返回-1。不相關的進程可以通過該函數的返回值訪問同一共享內存,它代表程序可能要使用的某個資源,程序對所有共享內存的訪問都是間接的,程序先通過調用shmget函數並提供一個鍵,再由系統生成一個相應的共享內存標識符(shmget函數的返回值),只有shmget函數才直接使用信號量鍵,所有其他的信號量函數使用由semget函數返回的信號量標識符。

【參數size】指定共享內存的大小,單位是字節。

【參數shmflg】和semget系統調用的sem_flag參數相同,有9個比特的權限標誌;如果要創建新的共享內存,需要使用IPC_CREAT,IPC_EXCL,如果是已經存在的,可以使用IPC_CREAT或直接傳0。。

【函數返回值】成功時返回一個新建或已經存在的的共享內存標識符,取決於shmflg的參數。失敗返回-1並設置錯誤碼。

       在shmget用於創建內存,這段共享內存的所有字節都會被初始化爲0,與之關聯的內核數據結構shmid_ds將被創建並初始化,shmid_ds結構體的定義和初始化參照《linux高性能服務器編程》251頁瞭解

  2.2 shmat系統調用

  共享內存被創建/獲取之後,我們並不能立即訪問它,而是需要先用shmat 函數將之關聯到進程的地址空間中,函數定義如下:

#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);

[參數shmid]:共享存儲段的標識符。

[參數*shmaddr]:指定將共享內存關聯到進程的那塊地址空間,最終的效果會受到shmflg參數的可選標誌SHM_RAND的影響,但推薦做法是將shmaddr 設置爲NULL,則存儲段連接到由內核選擇的第一個可以地址上(由系統決定,可以確保代碼的可移植性)

[參數shmflg]:有SHM_RND、SHM_REMAP、SHM_RDONLY、SHM_EXEC;具體含義參照《linux高性能服務器編程》252-253頁詳細瞭解;注意:若指定了SHM_RDONLY位,則以只讀方式連接此段,否則以讀寫方式連接此段。

[返回值]:成功返回共享存儲段的指針(虛擬地址),並且內核將使其與該共享存儲段相關的shmid_ds結構中的shm_nattch計數器加1(類似於引用計數);出錯返回-1。

  2.3 shmdt系統調用

  在使用完共享內存之後,我們需要使用shmdt函數將之從進程的地址空間中分離出來,函數定義如下:

#include <sys/shm.h>
int shmdt(const void *shmaddr);

[參數*shmaddr]:連接以後返回的地址。

[返回值]:成功返回0,並將shmid_ds結構體中的 shm_nattch計數器減1;出錯返回-1。

  2.4 shmctl系統調用

   和信號量的semctl函數一樣,用來控制共享內存,它的原型如下:

#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

[參數shmid]:共享存儲段標識符。

[參數cmd]:指定的執行操作,設置爲IPC_RMID時表示可以刪除共享內存,詳細參數瞭解參考《linux高性能服務器編程》251頁。

[參數*buf]:設置爲NULL即可。

[返回值]:成功返回0,失敗返回-1。

(三)代碼演示

     下面就以兩個不相關的進程來說明進程間如何通過共享內存來進行通信。其中一個文件shmread.c創建共享內存,並讀取其中的信息,另一個文件shmwrite.c向共享內存中寫入數據。爲了方便操作和數據結構的統一,爲這兩個文件定義了相同的數據結構,定義在文件shmdata.c中。結構shared_use_st中的written作爲一個可讀或可寫的標誌,非0:表示可讀,0表示可寫,text則是內存中的文件。

shmdata.h:

#ifndef _SHMDATA_H_HEADER
#define _SHMDATA_H_HEADER
 
#define TEXT_SZ 2048
 
struct shared_use_st
{
	int written;//作爲一個標誌,非0:表示可讀,0表示可寫
	char text[TEXT_SZ];//記錄寫入和讀取的文本
};
 
#endif

shmread.c:

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/shm.h>
#include "shmdata.h"
 
int main()
{
	int running = 1;//程序是否繼續運行的標誌
	void *shm = NULL;//分配的共享內存的原始首地址
	struct shared_use_st *shared;//指向shm
	int shmid;//共享內存標識符
	//創建共享內存
	shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666|IPC_CREAT);
	if(shmid == -1)
	{
		fprintf(stderr, "shmget failed\n");
		exit(EXIT_FAILURE);
	}
	//將共享內存連接到當前進程的地址空間
	shm = shmat(shmid, 0, 0);
	if(shm == (void*)-1)
	{
		fprintf(stderr, "shmat failed\n");
		exit(EXIT_FAILURE);
	}
	printf("\nMemory attached at %X\n", (int)shm);
	//設置共享內存
	shared = (struct shared_use_st*)shm;
	shared->written = 0;
	while(running)//讀取共享內存中的數據
	{
		//沒有進程向共享內存定數據有數據可讀取
		if(shared->written != 0)
		{
			printf("You wrote: %s", shared->text);
			sleep(rand() % 3);
			//讀取完數據,設置written使共享內存段可寫
			shared->written = 0;
			//輸入了end,退出循環(程序)
			if(strncmp(shared->text, "end", 3) == 0)
				running = 0;
		}
		else//有其他進程在寫數據,不能讀取數據
			sleep(1);
	}
	//把共享內存從當前進程中分離
	if(shmdt(shm) == -1)
	{
		fprintf(stderr, "shmdt failed\n");
		exit(EXIT_FAILURE);
	}
	//刪除共享內存
	if(shmctl(shmid, IPC_RMID, 0) == -1)
	{
		fprintf(stderr, "shmctl(IPC_RMID) failed\n");
		exit(EXIT_FAILURE);
	}
	exit(EXIT_SUCCESS);
}

shmwrite.c:

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/shm.h>
#include "shmdata.h"
 
int main()
{
	int running = 1;
	void *shm = NULL;
	struct shared_use_st *shared = NULL;
	char buffer[BUFSIZ + 1];//用於保存輸入的文本
	int shmid;
	//創建共享內存
	shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666|IPC_CREAT);
	if(shmid == -1)
	{
		fprintf(stderr, "shmget failed\n");
		exit(EXIT_FAILURE);
	}
	//將共享內存連接到當前進程的地址空間
	shm = shmat(shmid, (void*)0, 0);
	if(shm == (void*)-1)
	{
		fprintf(stderr, "shmat failed\n");
		exit(EXIT_FAILURE);
	}
	printf("Memory attached at %X\n", (int)shm);
	//設置共享內存
	shared = (struct shared_use_st*)shm;
	while(running)//向共享內存中寫數據
	{
		//數據還沒有被讀取,則等待數據被讀取,不能向共享內存中寫入文本
		while(shared->written == 1)
		{
			sleep(1);
			printf("Waiting...\n");
		}
		//向共享內存中寫入數據
		printf("Enter some text: ");
		fgets(buffer, BUFSIZ, stdin);
		strncpy(shared->text, buffer, TEXT_SZ);
		//寫完數據,設置written使共享內存段可讀
		shared->written = 1;
		//輸入了end,退出循環(程序)
		if(strncmp(buffer, "end", 3) == 0)
			running = 0;
	}
	//把共享內存從當前進程中分離
	if(shmdt(shm) == -1)
	{
		fprintf(stderr, "shmdt failed\n");
		exit(EXIT_FAILURE);
	}
	sleep(2);
	exit(EXIT_SUCCESS);
}

再來看看運行的結果:

分析:
1、程序shmread創建共享內存,然後將它連接到自己的地址空間。在共享內存的開始處使用了一個結構struct_use_st。該結構中有個標誌written,當共享內存中有其他進程向它寫入數據時,共享內存中的written被設置爲0,程序等待。當它不爲0時,表示沒有進程對共享內存寫入數據,程序就從共享內存中讀取數據並輸出,然後重置設置共享內存中的written爲0,即讓其可被shmwrite進程寫入數據。

2、程序shmwrite取得共享內存並連接到自己的地址空間中。檢查共享內存中的written,是否爲0,若不是,表示共享內存中的數據還沒有被完,則等待其他進程讀取完成,並提示用戶等待。若共享內存的written爲0,表示沒有其他進程對共享內存進行讀取,則提示用戶輸入文本,並再次設置共享內存中的written爲1,表示寫完成,其他進程可對共享內存進行讀操作。

(四)共享內存的優缺點

  優點:我們可以看到使用共享內存進行進程間的通信真的是非常方便,而且函數的接口也簡單,數據的共享還使進程間的數據不用傳送,而是直接訪問內存,也加快了程序的效率。同時,它也不像匿名管道那樣要求通信的進程有一定的父子關係。

  缺點:共享內存沒有提供同步的機制,這使得我們在使用共享內存進行進程間通信時,往往要藉助其他的手段來進行進程間的同步工作。


參考文章:

https://blog.csdn.net/ypt523/article/details/79958188

https://blog.csdn.net/ljianhui/article/details/10253345

https://blog.csdn.net/qq_41727218/article/details/82772056

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章