Linux進程間通訊(六):共享內存 shmget()、shmat()、shmdt()、shmctl()

下面將講解進程間通訊的另外一種方式,使用共享內存。php

1、什麼是共享內存

顧名思義,共享內存就是容許兩個不相關的進程訪問同一個邏輯內存。共享內存是在兩個正在運行的進程之間共享和傳遞數據的一種很是有效的方式。不一樣進程之間共享的內存一般安排爲同一段物理內存。進程能夠將同一段共享內存鏈接到它們本身的地址空間中,全部進程均可以訪問共享內存中的地址,就好像它們是由用C語言函數malloc()分配的內存同樣。而若是某個進程向共享內存寫入數據,所作的改動將當即影響到能夠訪問同一段共享內存的任何其餘進程。html

特別提醒:共享內存並未提供同步機制,也就是說,在第一個進程結束對共享內存的寫操做以前,並沒有自動機制能夠阻止第二個進程開始對它進行讀取。因此咱們一般須要用其餘的機制來同步對共享內存的訪問,例如前面說到的信號量。有關信號量的更多內容,能夠查閱另外一篇文章:Linux進程間通訊 -- 使用信號量編程

2、共享內存的使得

與信號量同樣,在Linux中也提供了一組函數接口用於使用共享內存,並且使用共享共存的接口還與信號量的很是類似,並且比使用信號量的接口來得簡單。它們聲明在頭文件 sys/shm.h 中。安全

一、shmget()函數服務器

該函數用來建立共享內存,它的原型爲:網絡

int shmget(key_t key, size_t size, int shmflg);數據結構

第一個參數,與信號量的semget函數同樣,程序須要提供一個參數key(非0整數),它有效地爲共享內存段命名,shmget()函數成功時返回一個與key相關的共享內存標識符(非負整數),用於後續的共享內存函數。調用失敗返回-1.函數

不相關的進程能夠經過該函數的返回值訪問同一共享內存,它表明程序可能要使用的某個資源,程序對全部共享內存的訪問都是間接的,程序先經過調用shmget()函數並提供一個鍵,再由系統生成一個相應的共享內存標識符(shmget()函數的返回值),只有shmget()函數才直接使用信號量鍵,全部其餘的信號量函數使用由semget函數返回的信號量標識符。性能

第二個參數,size以字節爲單位指定須要共享的內存容量ui

第三個參數,shmflg是權限標誌,它的做用與open函數的mode參數同樣,若是要想在key標識的共享內存不存在時,建立它的話,能夠與IPC_CREAT作或操做。共享內存的權限標誌與文件的讀寫權限同樣,舉例來講,0644,它表示容許一個進程建立的共享內存被內存建立者所擁有的進程向共享內存讀取和寫入數據,同時其餘用戶建立的進程只能讀取共享內存。

二、shmat()函數    -- at:attach

第一次建立完共享內存時,它還不能被任何進程訪問,shmat()函數的做用就是用來啓動對該共享內存的訪問,並把共享內存鏈接到當前進程的地址空間。它的原型以下:

void *shmat(int shm_id, const void *shm_addr, int shmflg);

第一個參數,shm_id是由shmget()函數返回的共享內存標識。

第二個參數,shm_addr指定共享內存鏈接到當前進程中的地址位置,一般爲空,表示讓系統來選擇共享內存的地址。

第三個參數,shm_flg是一組標誌位,一般爲0。

調用成功時返回一個指向共享內存第一個字節的指針,若是調用失敗返回-1.

三、shmdt()函數    -- dt:detach

該函數用於將共享內存從當前進程中分離。注意,將共享內存分離並非刪除它,只是使該共享內存對當前進程再也不可用。它的原型以下:

int shmdt(const void *shmaddr);

參數shmaddr是shmat()函數返回的地址指針,調用成功時返回0,失敗時返回-1.

四、shmctl()函數    -- ctl:control

與信號量的semctl()函數同樣,用來控制共享內存,它的原型以下:

int shmctl(int shm_id, int command, struct shmid_ds *buf);

第一個參數,shm_id是shmget()函數返回的共享內存標識符。

第二個參數,command是要採起的操做,它能夠取下面的三個值 :

  • IPC_STAT:把shmid_ds結構中的數據設置爲共享內存的當前關聯值,即用共享內存的當前關聯值覆蓋shmid_ds的值。
  • IPC_SET:若是進程有足夠的權限,就把共享內存的當前關聯值設置爲shmid_ds結構中給出的值
  • IPC_RMID:刪除共享內存段

第三個參數,buf是一個結構指針,它指向共享內存模式和訪問權限的結構。

shmid_ds結構 至少包括如下成員:

struct shmid_ds
{
    uid_t shm_perm.uid;
    uid_t shm_perm.gid;
    mode_t shm_perm.mode;
};

3、使用共享內存進行進程間通訊

說了這麼多,又到了實戰的時候了。下面就以兩個不相關的進程來講明進程間如何經過共享內存來進行通訊。其中一個文件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 <stddef.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "shmdata.h"

int main(int argc, char **argv)
{
	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, "shmat 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; // 注意:shm有點相似經過 malloc() 獲取到的內存,因此這裏須要作個 類型強制轉換
	shared->written = 0;
	while (1) // 讀取共享內存中的數據
	{
		// 沒有進程向內存寫數據,有數據可讀取
		if (shared->written == 1)
		{
			printf("You wrote: %s", shared->text);
			sleep(1);

			// 讀取完數據,設置written使共享內存段可寫
			shared->written = 0;

			// 輸入了 end,退出循環(程序)
			if (strncmp(shared->text, "end", 3) == 0)
			{
				break;
			}
		}
		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 argc, char **argv)
{
	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 attched at %X\n", (int)shm);

	// 設置共享內存
	shared = (struct shared_use_st *)shm;
	while (1) // 向共享內存中寫數據
	{
		// 數據尚未被讀取,則等待數據被讀取,不能向共享內存中寫入文本
		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)
		{
			break;
		}
	}

	// 把共享內存從當前進程中分離
	if (shmdt(shm) == -1)
	{
		fprintf(stderr, "shmdt failed\n");
		exit(EXIT_FAILURE);
	}

	sleep(2);
	exit(EXIT_SUCCESS);
}

再來看看運行的結果:

分析:

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

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

4、關於前面的例子的安全性討論

這個程序是不安全的,當有多個程序同時向共享內存中讀寫數據時,問題就會出現。可能你會認爲,能夠改變一下written的使用方式,例如,只有當written爲0時進程才能夠向共享內存寫入數據,而當一個進程只有在written不爲0時才能對其進行讀取,同時把written進行加1操做,讀取完後進行減1操做。這就有點像文件鎖中的讀寫鎖的功能。咋看之下,它彷佛能行得通。可是這都不是原子操做,因此這種作法是行不能的。試想當written爲0時,若是有兩個進程同時訪問共享內存,它們就會發現written爲0,因而兩個進程都對其進行寫操做,顯然不行。當written爲1時,有兩個進程同時對共享內存進行讀操做時也是如些,當這兩個進程都讀取完是,written就變成了-1.

要想讓程序安全地執行,就要有一種進程同步的進制,保證在進入臨界區的操做是原子操做。例如,可使用前面所講的信號量來進行進程的同步。由於信號量的操做都是原子性的。

5、使用共享內存的優缺點

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

二、缺點:共享內存沒有提供同步的機制,這使得咱們在使用共享內存進行進程間通訊時,每每要藉助其餘的手段來進行進程間的同步工做。

 

 

參考:

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

《Linux高性能服務器編程》

《UNIX網絡編程_卷2_進程間通訊》

相關文章
相關標籤/搜索