Linux環境進程間通訊(五): 共享內存(下)

linux下進程間通訊的幾種主要手段:html

  1. 管道(Pipe)及有名管道(named pipe):管道可用於具備親緣關係進程間的通訊,有名管道克服了管道沒有名字的限制,所以,除具備管道所具備的功能外,它還容許無親緣關係進程間的通訊; 
  2. 信號(Signal):信號是比較複雜的通訊方式,用於通知接受進程有某種事件發生,除了用於進程間通訊外,進程還能夠發送信號給進程自己;linux除了支持Unix早期信號語義函數sigal外,還支持語義符合Posix.1標準的信號函數sigaction(實際上,該函數是基於BSD的,BSD爲了實現可靠信號機制,又可以統一對外接口,用sigaction函數從新實現了signal函數); 
  3. 報文(Message)隊列(消息隊列):消息隊列是消息的連接表,包括Posix消息隊列system V消息隊列。有足夠權限的進程能夠向隊列中添加消息,被賦予讀權限的進程則能夠讀走隊列中的消息。消息隊列克服了信號承載信息量少,管道只能承載無格式字節流以及緩衝區大小受限等缺點。 
  4. 共享內存:使得多個進程能夠訪問同一塊內存空間,是最快的可用IPC形式。是針對其餘通訊機制運行效率較低而設計的。每每與其它通訊機制,如信號量結合使用,來達到進程間的同步及互斥。 
  5. 信號量(semaphore):主要做爲進程間以及同一進程不一樣線程之間的同步手段。 
  6. 套接口(Socket):更爲通常的進程間通訊機制,可用於不一樣機器之間的進程間通訊。起初是由Unix系統的BSD分支開發出來的,但如今通常能夠移植到其它類Unix系統上:Linux和System V的變種都支持套接字。 

本文講述進程間通訊方法——共享內存node

原文:http://www.ibm.com/developerworks/cn/linux/l-ipc/part5/index2.htmllinux

系統調用mmap()經過映射一個普通文件實現共享內存。系統V則是經過映射特殊文件系統shm中的文件實現進程間的共享內存通訊。也就是說,每一個共享內存區域對應特殊文件系統shm中的一個文件(這是經過shmid_kernel結構聯繫起來的),後面還將闡述。編程

一、系統V共享內存原理數組

進程間須要共享的數據被放在一個叫作IPC共享內存區域的地方,全部須要訪問該共享區域的進程都要把該共享區域映射到本進程的地址空間中去。系統V共享內存經過shmget得到或建立一個IPC共享內存區域,並返回相應的標識符。內核在保證shmget得到或建立一個共享內存區,初始化該共享內存區相應的shmid_kernel結構注同時,還將在特殊文件系統shm中,建立並打開一個同名文件,並在內存中創建起該文件的相應dentry及inode結構,新打開的文件不屬於任何一個進程(任何進程均可以訪問該共享內存區)。全部這一切都是系統調用shmget完成的。安全

注:每個共享內存區都有一個控制結構struct shmid_kernel,shmid_kernel是共享內存區域中很是重要的一個數據結構,它是存儲管理和文件系統結合起來的橋樑,定義以下:網絡

struct shmid_kernel /* private to the kernel */
{	
	struct kern_ipc_perm	shm_perm;
	struct file *		shm_file;
	int			id;
	unsigned long		shm_nattch;
	unsigned long		shm_segsz;
	time_t			shm_atim;
	time_t			shm_dtim;
	time_t			shm_ctim;
	pid_t			shm_cprid;
	pid_t			shm_lprid;
};

該結構中最重要的一個域應該是shm_file,它存儲了將被映射文件的地址。每一個共享內存區對象都對應特殊文件系統shm中的一個文件,通常狀況下,特殊文件系統shm中的文件是不能用read()、write()等方法訪問的,當採起共享內存的方式把其中的文件映射到進程地址空間後,可直接採用訪問內存的方式對其訪問。數據結構

這裏咱們採用[1]中的圖表給出與系統V共享內存相關數據結構:函數

 

正如消息隊列和信號燈同樣,內核經過數據結構struct ipc_ids shm_ids維護系統中的全部共享內存區域。上圖中的shm_ids.entries變量指向一個ipc_id結構數組,而每一個ipc_id結構數組中有個指向kern_ipc_perm結構的指針。到這裏讀者應該很熟悉了,對於系統V共享內存區來講,kern_ipc_perm的宿主是shmid_kernel結構,shmid_kernel是用來描述一個共享內存區域的,這樣內核就可以控制系統中全部的共享區域。同時,在shmid_kernel結構的file類型指針shm_file指向文件系統shm中相應的文件,這樣,共享內存區域就與shm文件系統中的文件對應起來。測試

在建立了一個共享內存區域後,還要將它映射到進程地址空間,系統調用shmat()完成此項功能。因爲在調用shmget()時,已經建立了文件系統shm中的一個同名文件與共享內存區域相對應,所以,調用shmat()的過程至關於映射文件系統shm中的同名文件過程,原理與mmap()大同小異。

二、系統V共享內存API

對於系統V共享內存,主要有如下幾個API:shmget()、shmat()、shmdt()及shmctl()。

#include <sys/ipc.h>
#include <sys/shm.h>

shmget()用來得到共享內存區域的ID,若是不存在指定的共享區域就建立相應的區域。shmat()把共享內存區域映射到調用進程的地址空間中去,這樣,進程就能夠方便地對共享區域進行訪問操做。shmdt()調用用來解除進程對共享內存區域的映射。shmctl實現對共享內存區域的控制操做。這裏咱們不對這些系統調用做具體的介紹,讀者可參考相應的手冊頁面,後面的範例中將給出它們的調用方法。

注:shmget的內部實現包含了許多重要的系統V共享內存機制;shmat在把共享內存區域映射到進程空間時,並不真正改變進程的頁表。當進程第一次訪問內存映射區域訪問時,會由於沒有物理頁表的分配而致使一個缺頁異常,而後內核再根據相應的存儲管理機制爲共享內存映射區域分配相應的頁表。

三、系統V共享內存限制

在/proc/sys/kernel/目錄下,記錄着系統V共享內存的一下限制,如一個共享內存區的最大字節數shmmax,系統範圍內最大共享內存區標識符數shmmni等,能夠手工對其調整,但不推薦這樣作。

在[2]中,給出了這些限制的測試方法,再也不贅述。

四、系統V共享內存範例

本部分將給出系統V共享內存API的使用方法,並對比分析系統V共享內存機制與mmap()映射普通文件實現共享內存之間的差別,首先給出兩個進程經過系統V共享內存通訊的範例:

/***** testwrite.c *******/
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
typedef struct{
	char name[4];
	int age;
} people;
main(int argc, char** argv)
{
	int shm_id,i;
	key_t key;
	char temp;
	people *p_map;
	char* name = "/dev/shm/myshm2";
	key = ftok(name,0);
	if(key==-1)
		perror("ftok error");
	shm_id=shmget(key,4096,IPC_CREAT);	
	if(shm_id==-1)
	{
		perror("shmget error");
		return;
	}
	p_map=(people*)shmat(shm_id,NULL,0);
	temp='a';
	for(i = 0;i<10;i++)
	{
		temp+=1;
		memcpy((*(p_map+i)).name,&temp,1);
		(*(p_map+i)).age=20+i;
	}
	if(shmdt(p_map)==-1)
		perror(" detach error ");
}
/********** testread.c ************/
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
typedef struct{
	char name[4];
	int age;
} people;
main(int argc, char** argv)
{
	int shm_id,i;
	key_t key;
	people *p_map;
	char* name = "/dev/shm/myshm2";
	key = ftok(name,0);
	if(key == -1)
		perror("ftok error");
	shm_id = shmget(key,4096,IPC_CREAT);	
	if(shm_id == -1)
	{
		perror("shmget error");
		return;
	}
	p_map = (people*)shmat(shm_id,NULL,0);
	for(i = 0;i<10;i++)
	{
	printf( "name:%s\n",(*(p_map+i)).name );
	printf( "age %d\n",(*(p_map+i)).age );
	}
	if(shmdt(p_map) == -1)
		perror(" detach error ");
}

testwrite.c建立一個系統V共享內存區,並在其中寫入格式化數據;testread.c訪問同一個系統V共享內存區,讀出其中的格式化數據。分別把兩個程序編譯爲testwrite及testread,前後執行./testwrite及./testread 則./testread輸出結果以下:

name: b	age 20;	name: c	age 21;	name: d	age 22;	name: e	age 23;	name: f	age 24;
name: g	age 25;	name: h	age 26;	name: I	age 27;	name: j	age 28;	name: k	age 29;

經過對試驗結果分析,對比系統V與mmap()映射普通文件實現共享內存通訊,能夠得出以下結論:

一、 系統V共享內存中的數據,歷來不寫入到實際磁盤文件中去;而經過mmap()映射普通文件實現的共享內存通訊能夠指定什麼時候將數據寫入磁盤文件中。 注:前面講到,系統V共享內存機制實際是經過映射特殊文件系統shm中的文件實現的,文件系統shm的安裝點在交換分區上,系統從新引導後,全部的內容都丟失。

二、 系統V共享內存是隨內核持續的,即便全部訪問共享內存的進程都已經正常終止,共享內存區仍然存在(除非顯式刪除共享內存),在內核從新引導以前,對該共享內存區域的任何改寫操做都將一直保留。

三、 經過調用mmap()映射普通文件進行進程間通訊時,必定要注意考慮進程什麼時候終止對通訊的影響。而經過系統V共享內存實現通訊的進程則否則。 注:這裏沒有給出shmctl的使用範例,原理與消息隊列大同小異。

結論:

共享內存容許兩個或多個進程共享一給定的存儲區,由於數據不須要來回複製,因此是最快的一種進程間通訊機制。共享內存能夠經過mmap()映射普通文件(特殊狀況下還能夠採用匿名映射)機制實現,也能夠經過系統V共享內存機制實現。應用接口和原理很簡單,內部機制複雜。爲了實現更安全通訊,每每還與信號燈等同步機制共同使用。

共享內存涉及到了存儲管理以及文件系統等方面的知識,深刻理解其內部機制有必定的難度,關鍵還要牢牢抓住內核使用的重要數據結構。系統V共享內存是以文件的形式組織在特殊文件系統shm中的。經過shmget能夠建立或得到共享內存的標識符。取得共享內存標識符後,要經過shmat將這個內存區映射到本進程的虛擬地址空間。

參考資料

[1] Understanding the Linux Kernel, 2nd Edition, By Daniel P. Bovet, Marco Cesati , 對各主題闡述得重點突出,脈絡清晰。

[2] UNIX網絡編程第二卷:進程間通訊,做者:W.Richard Stevens,譯者:楊繼張,清華大學出版社。對mmap()有詳細闡述。

[3] Linux內核源代碼情景分析(上),毛德操、胡希明著,浙江大學出版社,給出了mmap()相關的源代碼分析。

[4]shmget、shmat、shmctl、shmdt手冊

相關文章
相關標籤/搜索