Linux下多任務間通訊和同步-mmap共享內存

Linux下多任務間通訊和同步-mmap共享內存linux

嵌入式開發交流羣280352802,歡迎加入!數據結構

1.簡介

共享內存能夠說是最有用的進程間通訊方式.兩個不用的進程共享內存的意思是:同一塊物理內存被映射到兩個進程的各自的進程地址空間.一個進程能夠及時看到另外一個進程對共享內存的更新,反之亦然.異步

採用共享內存通訊的一個顯而易見的好處效率高,由於進程能夠直接讀寫內存,而不須要任何數據的複製.對於向管道和消息隊列等通訊等方式,則須要在內核和用戶空間進行四次的數據複製,而共享內存則只須要兩次數據複製:一次從輸入文件到共享內存區,另外一個從共享內存區到輸出文件.實際上,進程之間在共享內存時,並不老是讀寫少許數據後就解除映射,有新的通訊時,再從新創建共享內存區域.而是保持共享區域,知道通訊完畢爲止,這樣,數據內容就一直保存在共享內存中,並無寫回文件.共享內存中的內容每每是在解除映射時才寫回文件的.所以,採用共享內存的通訊方式效率很是高.函數

linux從2.2內核開始就支持多種共享內存方式,如mmap系統調用,Posix共享內存,以及System V共享內存.本文主要介紹mmap系統調用的原理及應用.後續的文章會講解System V共享內存.spa

2.mmap系統調用

mmap系統調用是的是的進程間經過映射同一個普通文件實現共享內存.普通文件被映射到進程地址空間後,進程能夠向像訪問普通內存同樣對文件進行訪問,沒必要再調用read,write等操做.與mmap系統調用配合使用的系統調用還有munmap,msync等.命令行

實際上,mmap系統調用並非徹底爲了用於共享內存而設計的.它自己提供了不一樣於通常對普通文件的訪問方式,是進程能夠像讀寫內存同樣對普通文件操做.而Posix或System V的共享內存則是純粹用於共享內存的,固然mmap實現共享內存也是主要應用之一.設計

 

#include <sys/mman.h>
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);

各個參數的說明以下:code

 

 

  • start:映射區的開始地址,設置爲0時表示由系統決定映射區的起始地址.
  • length:映射區的長度.長度單位是之內存頁爲單位.
  • prot:指望的內存保護標誌,不能與文件的打開模式衝突.是如下的某個值,能夠經過or運算合理地組合在一塊兒.
    • PROT_EXEC //頁內容能夠被執行
    • PROT_READ //頁內容能夠被讀取
    • PROT_WRITE //頁能夠被寫入
    • PROT_NONE //頁不可訪問
  • flags:指定映射對象的類型,映射選項和映射頁是否能夠共享.它的值能夠是一個或者多個如下位的組合體.
    • MAP_FIXED //使用指定的映射起始地址,若是由start和len參數指定的內存區重疊於現存的映射空間,重疊部分將會被丟棄.若是指定的起始地址不可用,操做將會失敗.而且起始地址必須落在頁的邊界上.
    • MAP_SHARED //與其它全部映射這個對象的進程共享映射空間.對共享區的寫入,至關於輸出到文件.直到msync()或者munmap()被調用,文件實際上不會被更新.
    • MAP_PRIVATE //創建一個寫入時拷貝的私有映射.內存區域的寫入不會影響到原文件.這個標誌和以上標誌是互斥的,只能使用其中一個.
    • MAP_DENYWRITE //這個標誌被忽略.
    • MAP_EXECUTABLE //同上
    • MAP_NORESERVE //不要爲這個映射保留交換空間.當交換空間被保留,對映射區修改的可能會獲得保證.當交換空間不被保留,同時內存不足,對映射區的修改會引發段違例信號.
    • MAP_LOCKED //鎖定映射區的頁面,從而防止頁面被交換出內存.
    • MAP_GROWSDOWN //用於堆棧,告訴內核VM系統,映射區能夠向下擴展.
    • MAP_ANONYMOUS //匿名映射,映射區不與任何文件關聯.
    • MAP_ANON //MAP_ANONYMOUS的別稱,再也不被使用.
    • MAP_FILE //兼容標誌,被忽略.
    • MAP_32BIT //將映射區放在進程地址空間的低2GB,MAP_FIXED指定時會被忽略.當前這個標誌只在x86-64平臺上獲得支持。
    • MAP_POPULATE //爲文件映射經過預讀的方式準備好頁表.隨後對映射區的訪問不會被頁違例阻塞.
    • MAP_NONBLOCK //僅和MAP_POPULATE一塊兒使用時纔有意義.不執行預讀,只爲已存在於內存中的頁面創建頁表入口.
  • fd:有效的文件描述詞.通常是由open()函數返回,其值也能夠設置爲-1,此時須要指定flags參數中的MAP_ANON,代表進行的是匿名映射.
  • offset:被映射對象內容的起點.通常設爲0,表示從文件頭開始映射.

函數的返回值爲最後文件映射到進程空間的地址,進程能夠直接操做起始地址爲該值的有效地址.系統調用mmap用於共享內存時有下面兩種經常使用的方式:對象

 

 

  • 1)使用普通文件提供的內存映射:適用於任何進程之間.此時,須要打開或建立一個文件,而後再調用mmap,這種方式有許多特色和要注意的地方,咱們在後面或舉例子說明.
  • 2)使用特殊文件提東匿名內存映射:適用於具備親緣關係的進程之間.因爲父子進程特殊的親緣關係,在父進程中吊牌用mmap,而後調用fork.那麼在調用fork以後,子進程急促繼承父進程匿名映射後的地址空間,一樣也繼承mmap返回的地址,這樣,父子進程就能夠經過映射區進行通訊了.注意,mmap返回的地址,須要由父進程共同維護.

 

對於任意的兩個進程可使用第一種方式,而對於具備親緣關係的進程實現共享內存最好的方式應該是採用匿名內存映射的方式.此時,沒必要指定具體的文件,只要設定相應的標誌便可,後面有相應的例子.繼承

3.munmap系統調用

該系統調用在進程地址空間中解除一個映射關係,當映射關係解除後,對原來映射地址的訪問將致使段錯誤發生.其函數原型爲:

 

 

#include<sys/mman.h>
int munmap(void *start, size_t length);

 

其中,參數addr是調用mmap時返回的地址,len是映射區的大小.

4.msync系統調用

通常來講,進程在映射空間對共享內容的改變並不直接寫回到磁盤文件中,每每在調用munmap後才執行該操做.能夠經過調用msync實現磁盤上文件內容與共享內存區的內容一致.該函數的原型以下:

 

#include<sys/mman.h>
int msync ( void * addr, size_t len, int flags);

addr:文件映射到進程空間的地址;
len:映射空間的大小;
flags:刷新的參數設置,能夠取值MS_ASYNC/MS_SYNC/MS_INVALIDATE

 

  • 取值爲MS_ASYNC(異步)時,調用會當即返回,不等到更新的完成;
  • 取值爲MS_SYNC(同步)時,調用會等到更新完成以後返回;
  • 取MS_INVALIDATE(通知使用該共享區域的進程,數據已經改變)時,在共享內容更改以後,使得文件的其餘映射失效,從而使得共享該文件的其餘進程去從新獲取最新值.

5.應用實例

該實例包含兩個子程序,這兩個子程序編譯爲mmap_read和mmap_write.兩個程序經過命令行參數指定痛一個文件來實現共享內存方式的進程間通訊.mmap_write試圖打開命令行參數指定的一個普通文件,把該文件映射到進程的地址空間,而後對映射後的地址空間進行寫操做.mmap_read把命令行參數指定的文件映射到進程的地址空間,而後對映射的地址空間執行讀操做.這樣,兩個進程經過命令行參數指定同一個文件來實現共享內存方式的進程間通訊.寫操做操做子程序源碼以下:

 

/**************************************************************************************/
/*簡介:mmap_write共享內存,寫操做子程序						 */
/*************************************************************************************/
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

typedef struct
{
	char name[4];
	int  age;
}people;

int main(int argc, char** argv)
{
	int fd,i;
	people *p_map;
	char name[4];
	if (argc != 2)
	{
	    perror("usage: mmap_write <mmap file>");
	    return 1;
	}

	fd=open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777);
	lseek(fd,sizeof(people)*5-1,SEEK_SET);
	write(fd,"",1);
	
	p_map = (people*) mmap( NULL,sizeof(people)*10,\
		PROT_READ|PROT_WRITE,MAP_SHARED,fd,0 );
	close( fd );
	name[0] = 'a';
	name[1] = '\0';
	for(i=0; i<10; i++)
	{
		name[0] ++;
		memcpy( ( *(p_map+i) ).name, &name,sizeof(name) );
		( *(p_map+i) ).age = 20+i;
	}
	printf(" initialize over \n ");
	sleep(10);

	munmap( p_map, sizeof(people)*10 );
	printf( "umap ok \n" );
	return 0;
}

在上面的程序中,首先定義了一個people的數據格式(共享內存區的數據每每有固定的格式,由通訊的各個進程決定,採用結構的方式具備廣泛表明性).mmap_write首先打開或建立一個文件,並把文件的長度設爲5個people結構大小.而後從mmap的返回地址開始,設置了10個people結構.而後進程睡眠10s,等待其餘進程映射同一個文件,最後解除映射.讀操做子程序的源代碼以下:

 

/**************************************************************************************/
/*簡介:mmap_read共享內存,讀操做子程序						 */
/*************************************************************************************/
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

typedef struct
{
	char name[4];
	int  age;
}people;

int main(int argc, char** argv)
{
	int fd,i;
	people *p_map;
	if (argc != 2)
	{
		perror("usage: mmap_read <mmap file>");
		return 1;
	}
	fd=open( argv[1],O_CREAT|O_RDWR,00777 );
	p_map = (people*)mmap(NULL,sizeof(people)*10,\
		PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
	close(fd);
	for(i = 0;i<10;i++)
	{
		printf( "name: %s age %d;\n",(*(p_map+i)).name, (*(p_map+i)).age );
	}
	munmap( p_map,sizeof(people)*10 );
	return 0;
}

mmap_read只是簡單的映射一個文件,並以people數據結構的格式從mmap返回的地址處讀取10個people結構,並輸出讀取的值,而後解除映射.下圖是運行結果.

 

 

文件被映射後,調用mmap的進程對返回地址的訪問實際上是對某一內存區域的訪問,暫時脫離了磁盤上文件的影響.全部對mmap返回地址空間的操做只是在內存中才有意義,只有在調用了munmap或或者msync時,才把內存中的相應內容寫回磁盤文件.

相關文章
相關標籤/搜索